using System; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using System.IO; using System.Drawing; using System.Diagnostics; using System.ComponentModel; using Microsoft.Win32; using System.Xml; using System.Xml.Serialization; using System.Security.Cryptography; namespace Rebuilder { public class Rebuilder { MusicInjector music_injector = new MusicInjector(); string jukebox_output; List processes = new List(); private enum Version { kUnknown = -1, kEnglish10, kEnglish11, kGerman11, kDanish11, kSpanish11 } // These must correspond to the `Version` enum above private static string[] VersionHashes = { "58FCF0F6500614E9F743712D1DD4D340088123DE", "BBE289E89E5A39949D272174162711EA5CFF522C", "96A6BAE8345AA04C21F1B319A632CAECFEE22443", "8DFD3E5FDDE8C95C61013069795171163C9A4821", "47EE50FC1EC5F6C54F465EB296D2F1B7CA25D5D2", }; public static string[] standard_hdd_dirs = { //TODO: Find these installation directory strings programmatically "C:/Program Files (x86)/LEGO Island", "C:/Program Files/LEGO Island", "/Program Files (x86)/LEGO Island", "/Program Files/LEGO Island", "C:/Program Files (x86)/LEGO Insel", "C:/Program Files/LEGO Insel", "/Program Files (x86)/LEGO Insel", "/Program Files/LEGO Insel", "C:/Program Files (x86)/LEGO Øen", "C:/Program Files/LEGO Øen", "/Program Files (x86)/LEGO Øen", "/Program Files/LEGO Øen", "C:/Program Files (x86)/レゴアイランド", "C:/Program Files/レゴアイランド", "/Program Files (x86)/レゴアイランド", "/Program Files/レゴアイランド", "C:/Program Files (x86)/LEGO Isola", "C:/Program Files/LEGO Isola", "/Program Files (x86)/LEGO Isola", "/Program Files/LEGO Isola", "C:/Program Files (x86)/A Ilha Lego", "C:/Program Files/A Ilha Lego", "/Program Files (x86)/A Ilha Lego", "/Program Files/A Ilha Lego", "C:/Program Files (x86)/Aventures sur l'Ile LEGO", "C:/Program Files/Aventures sur l'Ile LEGO", "/Program Files (x86)/Aventures sur l'Ile LEGO", "/Program Files/Aventures sur l'Ile LEGO", "C:/Program Files (x86)/La Isla LEGO", "C:/Program Files/La Isla LEGO", "/Program Files (x86)/La Isla LEGO", "/Program Files/La Isla LEGO" }; public enum FPSLimitType { Default, Uncapped, Limited }; public enum ModelQualityType { High, Medium, Low } public class PatchList { float turn_max_speed = 20.0F; [Category("Controls")] [DisplayName("Turning: Max Speed")] [Description("Set the maximum turning speed. (Default = 20.0)")] [DefaultValue(20.0F)] public float TurnMaxSpeed { get { return turn_max_speed; } set { turn_max_speed = value; } } float turn_max_acceleration = 30.0F; [Category("Controls")] [DisplayName("Turning: Max Acceleration")] [Description("Set the speed at which turning accelerates (requires 'Turning: Enable Velocity') (Default = 30.0)")] [DefaultValue(30.0F)] public float TurnMaxAcceleration { get { return turn_max_acceleration; } set { turn_max_acceleration = value; } } float turn_min_acceleration = 15.0F; [Category("Controls")] [DisplayName("Turning: Min Acceleration")] [Description("Set the speed at which turning accelerates (requires 'Turning: Enable Velocity') (Default = 15.0)")] [DefaultValue(15.0F)] public float TurnMinAcceleration { get { return turn_min_acceleration; } set { turn_min_acceleration = value; } } float turn_deceleration = 50.0F; [Category("Controls")] [DisplayName("Turning: Deceleration")] [Description("Set the speed at which turning decelerates (requires 'Turning: Enable Velocity') (Default = 50.0)")] [DefaultValue(50.0F)] public float TurnDeceleration { get { return turn_deceleration; } set { turn_deceleration = value; } } bool turn_use_velocity = false; [Category("Controls")] [DisplayName("Turning: Enable Velocity")] [Description("By default, LEGO Island ignores the turning acceleration/deceleration values. Set this to TRUE to utilize them (Default = FALSE)")] [DefaultValue(false)] public bool TurnUseVelocity { get { return turn_use_velocity; } set { turn_use_velocity = value; } } float movement_max_speed = 40.0F; [Category("Controls")] [DisplayName("Movement: Max Speed")] [Description("Set the movement maximum speed. (Default = 40.0)")] [DefaultValue(40.0F)] public float MovementMaxSpeed { get { return movement_max_speed; } set { movement_max_speed = value; } } float movement_max_acceleration = 15.0F; [Category("Controls")] [DisplayName("Movement: Max Acceleration")] [Description("Set the movement acceleration speed (i.e. how long it takes to go from not moving to top speed) (Default = 15.0)")] [DefaultValue(15.0F)] public float MovementMaxAcceleration { get { return movement_max_acceleration; } set { movement_max_acceleration = value; } } float movement_min_acceleration = 4.0F; [Category("Controls")] [DisplayName("Movement: Min Acceleration")] [Description("Set the movement acceleration speed (i.e. how long it takes to go from not moving to top speed) (Default = 4.0)")] [DefaultValue(4.0F)] public float MovementMinAcceleration { get { return movement_min_acceleration; } set { movement_min_acceleration = value; } } float movement_deceleration = 50.0F; [Category("Controls")] [DisplayName("Movement: Deceleration")] [Description("Set the movement deceleration speed (i.e. how long it takes to slow to a stop after releasing the controls). " + "Increase this value to stop faster, decrease it to stop slower. " + "Usually this is set to a very high value making deceleration almost instant. (Default = 50.0)")] [DefaultValue(50.0F)] public float MovementDeceleration { get { return movement_deceleration; } set { movement_deceleration = value; } } int mouse_deadzone = 40; [Category("Controls")] [DisplayName("Mouse Deadzone")] [Description("Sets the radius from the center of the screen where the mouse will do nothing (40 = default).")] [DefaultValue(40)] public int MouseDeadzone { get { return mouse_deadzone; } set { mouse_deadzone = value; } } bool unhook_turn_speed = false; [Category("Controls")] [DisplayName("Turning: Unhook From Frame Rate")] [Description("LEGO Island contains a bug where the turning speed is influenced by the frame rate. Enable this to make the turn speed independent of the frame rate.")] [DefaultValue(false)] public bool UnhookTurnSpeed { get { return unhook_turn_speed; } set { unhook_turn_speed = value; } } bool use_joystick = false; [Category("Controls")] [DisplayName("Use Joystick")] [Description("Enables Joystick functionality.")] [DefaultValue(false)] public bool UseJoystick { get { return use_joystick; } set { use_joystick = value; } } bool full_screen = true; [Category("Graphics")] [DisplayName("Run in Full Screen")] [Description("Allows you to change modes without administrator privileges and registry editing.")] [DefaultValue(true)] public bool FullScreen { get { return full_screen; } set { full_screen = value; } } bool draw_cursor = false; [Category("Graphics")] [DisplayName("Draw Cursor")] [Description("Renders an in-game cursor, rather than a standard Windows pointer.")] [DefaultValue(false)] public bool DrawCursor { get { return draw_cursor; } set { draw_cursor = value; } } bool multiple_instances = false; [Category("System")] [DisplayName("Allow Multiple Instances")] [Description("By default, LEGO Island will allow only one instance of itself to run. " + "This patch allows infinite instances of LEGO Island to run.")] [DefaultValue(false)] public bool MultipleInstances { get { return multiple_instances; } set { multiple_instances = value; } } bool stay_active_when_defocused = false; [Category("System")] [DisplayName("Stay Active When Defocused")] [Description("By default, LEGO Island pauses when it's not the active window. " + "This patch prevents that behavior.")] [DefaultValue(false)] public bool StayActiveWhenDefocused { get { return stay_active_when_defocused; } set { stay_active_when_defocused = value; } } bool redirect_save_data = true; [Category("System")] [DisplayName("Redirect Save Files to %APPDATA%")] [Description("By default LEGO Island saves its game data in its Program Files folder. In newer versions of " + "Windows, this folder is considered privileged access, necessitating running LEGO Island as administrator " + "to save here. This patch sets LEGO Island's save location to %APPDATA% instead, which is an accessible and " + "standard location that most modern games and apps save to.")] [DefaultValue(true)] public bool RedirectSaveData { get { return redirect_save_data; } set { redirect_save_data = value; } } FPSLimitType fps_limit_type = FPSLimitType.Default; [Category("Graphics")] [DisplayName("FPS Cap")] [Description("Modify LEGO Island's frame rate cap")] [DefaultValue(FPSLimitType.Default)] public FPSLimitType FPSLimit { get { return fps_limit_type; } set { fps_limit_type = value; } } ModelQualityType model_quality = ModelQualityType.Medium; [Category("Graphics")] [DisplayName("Model Quality")] [Description("Change LEGO Island's default model quality")] [DefaultValue(ModelQualityType.Medium)] public ModelQualityType ModelQuality { get { return model_quality; } set { model_quality = value; } } float custom_fps_limit = 24.0F; [Category("Graphics")] [DisplayName("FPS Cap - Custom Limit")] [Description("Is 'FPS Cap' is set to 'Limited', this will be the frame rate used.")] [DefaultValue(24.0F)] public float CustomFPS { get { return custom_fps_limit; } set { custom_fps_limit = value; } } bool override_resolution = false; [Category("Experimental (Use at your own risk)")] [DisplayName("Override Resolution")] [Description("Override LEGO Island's hardcoded 640x480 resolution with a custom resolution. " + "NOTE: This patch is currently incomplete and buggy.")] [DefaultValue(false)] public bool OverrideResolution { get { return override_resolution; } set { override_resolution = value; } } int resolution_width = 640; [Category("Experimental (Use at your own risk)")] [DisplayName("Override Resolution - Width:")] [Description("If 'Override Resolution' is enabled, this is the screen resolution width to use instead.")] [DefaultValue(640)] public int ResolutionWidth { get { return resolution_width; } set { resolution_width = value; } } int resolution_height = 480; [Category("Experimental (Use at your own risk)")] [DisplayName("Override Resolution - Height:")] [Description("If 'Override Resolution' is enabled, this is the screen resolution height to use instead.")] [DefaultValue(480)] public int ResolutionHeight { get { return resolution_height; } set { resolution_height = value; } } bool upscale_bitmaps = false; [Category("Experimental (Use at your own risk)")] [DisplayName("Override Resolution - Bitmap Upscale")] [Description("WARNING: This doesn't upscale the bitmaps' hitboxes yet and can make 2D areas like the Information Center difficult to navigate.")] [DefaultValue(false)] public bool UpscaleBitmaps { get { return upscale_bitmaps; } set { upscale_bitmaps = value; } } bool disable_autofinish_building = false; [Category("Gameplay")] [DisplayName("Disable Auto-Finish Building Section")] [Description("In LEGO Island v1.1, placing the last block when building will automatically end the building section. While convenient, " + "this prevents players from making any further changes after placing the last brick. It also notably defies what Bill Ding says - you " + "don't hit the triangle when you're finished building.\n\nThis patch restores the functionality in v1.0 where placing the last block " + "will not automatically finish the build section.")] [DefaultValue(false)] public bool DisableAutoFinishBuilding { get { return disable_autofinish_building; } set { disable_autofinish_building = value; } } bool music_toggle = true; [Category("Gameplay")] [DisplayName("Play Music")] [Description("Turns in-game music on or off.")] [DefaultValue(true)] public bool MusicToggle { get { return music_toggle; } set { music_toggle = value; } } float fov_multiplier = 0.1F; [Category("Graphics")] [DisplayName("Field of View Adjustment")] [Description("Globally adjusts the field of view by a multiplier\n\n" + "0.1 = Default (smaller than 0.1 is more zoomed in, larger than 0.1 is more zoomed out")] [DefaultValue(0.1F)] public float FOVMultiplier { get { return fov_multiplier; } set { fov_multiplier = value; } } } private PatchList patch_config; public class ConfigForm : Form { private LinkLabel update; private TabControl tabs; private PropertyGrid patch_view; public Button run_button; public Button run_additional_button; public const string run_button_run = "Run"; public const string run_button_kill = "Kill"; public TabPage music_page; public ConfigForm(PatchList working_config, MusicInjector music_injector) { Size = new Size(420, 420); Text = "LEGO Island Rebuilder"; Icon = Icon.ExtractAssociatedIcon(System.Reflection.Assembly.GetExecutingAssembly().Location); TableLayoutPanel grid = new TableLayoutPanel(); grid.Dock = DockStyle.Fill; // Build standard layout grid.SuspendLayout(); Label title = new Label(); title.Anchor = AnchorStyles.Left | AnchorStyles.Right; title.Text = "LEGO Island Rebuilder"; title.Font = new Font(title.Font, FontStyle.Bold); title.TextAlign = ContentAlignment.MiddleCenter; grid.Controls.Add(title, 0, 0); LinkLabel subtitle = new LinkLabel(); subtitle.Anchor = AnchorStyles.Left | AnchorStyles.Right; subtitle.Text = "by MattKC (www.legoisland.org)"; subtitle.TextAlign = ContentAlignment.MiddleCenter; subtitle.LinkClicked += new LinkLabelLinkClickedEventHandler(AuthorLinkClick); grid.Controls.Add(subtitle, 0, 1); update = new LinkLabel(); update.Visible = false; update.Anchor = AnchorStyles.Left | AnchorStyles.Right; update.Text = "An update is available!"; update.TextAlign = ContentAlignment.MiddleCenter; update.LinkClicked += new LinkLabelLinkClickedEventHandler(AuthorLinkClick); grid.Controls.Add(update, 0, 2); // Set up patch view patch_view = new PropertyGrid(); patch_view.Dock = DockStyle.Fill; patch_view.SelectedObject = working_config; // Set up tabs tabs = new TabControl(); tabs.Dock = DockStyle.Fill; TabPage patches_page = new TabPage("Patches"); patches_page.Controls.Add(patch_view); tabs.Controls.Add(patches_page); music_page = new TabPage("Music"); music_page.Controls.Add(music_injector); tabs.Controls.Add(music_page); grid.Controls.Add(tabs, 0, 3); TableLayoutPanel run_btns = new TableLayoutPanel(); run_btns.Dock = DockStyle.Fill; run_btns.Padding = new Padding(0); run_btns.Margin = new Padding(0); run_btns.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 0.5F)); run_btns.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 0.5F)); run_button = new Button(); run_button.Text = run_button_run; run_button.Anchor = AnchorStyles.Left | AnchorStyles.Right; run_button.Font = new Font(run_button.Font, FontStyle.Bold); run_btns.Controls.Add(run_button, 0, 0); run_additional_button = new Button(); run_additional_button.Visible = false; run_additional_button.Text = "Run Additional"; run_additional_button.Anchor = AnchorStyles.Left | AnchorStyles.Right; run_btns.Controls.Add(run_additional_button, 1, 0); grid.Controls.Add(run_btns, 0, 4); grid.RowStyles.Clear(); grid.RowStyles.Add(new RowStyle(SizeType.Absolute, title.Height)); grid.RowStyles.Add(new RowStyle(SizeType.Absolute, subtitle.Height)); grid.RowStyles.Add(new RowStyle(SizeType.Absolute, update.Height)); grid.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); grid.RowStyles.Add(new RowStyle(SizeType.Absolute, run_button.Height + run_button.Margin.Top + run_button.Margin.Bottom)); grid.ResumeLayout(true); Controls.Add(grid); ResumeLayout(true); CenterToScreen(); } private void AuthorLinkClick(object sender, LinkLabelLinkClickedEventArgs e) { Process.Start("https://www.legoisland.org/"); } private void UpdateLinkClick(object sender, LinkLabelLinkClickedEventArgs e) { Process.Start("https://www.legoisland.org/rebuilder"); } } public ConfigForm form; Rebuilder(bool enable_ui) { LoadConfig(); if (enable_ui) { form = new ConfigForm(patch_config, music_injector); form.run_button.Click += new System.EventHandler(this.Run); form.run_additional_button.Click += new System.EventHandler(this.RunAdditional); form.music_page.Enter += new EventHandler(this.ShowMusicInjectorForm); form.FormClosing += new FormClosingEventHandler(this.OnClosing); } } private void Write(FileStream fs, byte[] bytes, long pos = -1) { if (pos > -1) { fs.Position = pos; } fs.Write(bytes, 0, bytes.Length); } private void WriteByte(FileStream fs, byte b, long pos = -1) { if (pos > -1) { fs.Position = pos; } fs.WriteByte(b); } private void WriteManyBytes(FileStream fs, byte b, int count, long pos = -1) { if (pos > -1) { fs.Position = pos; } for (int i=0;i< count;i++) fs.WriteByte(b); } private void WriteInt32(FileStream fs, Int32 integer, long pos = -1) { byte[] int_bytes = BitConverter.GetBytes(integer); Write(fs, int_bytes, pos); } private void WriteFloat(FileStream fs, float f, long pos = -1) { byte[] f_bytes = BitConverter.GetBytes(f); Write(fs, f_bytes, pos); } private void WriteString(FileStream fs, string s, long pos = -1) { byte[] str_bytes = System.Text.Encoding.ASCII.GetBytes(s); Write(fs, str_bytes, pos); } private bool ApproxEqual(float a, float b) { return Math.Abs(a - b) < 0.0001; } private bool IncompatibleBuildMessage(string incompatibilities) { return (MessageBox.Show("The following patches you've chosen are not compatible with this version of LEGO Island:\n\n" + incompatibilities + "\nContinue without them?", "Compatibility", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes); } private static string GetDisplayNameOfProperty(string property) { return ((DisplayNameAttribute)typeof(PatchList).GetProperty(property).GetCustomAttributes(typeof(DisplayNameAttribute), true)[0]).DisplayName; } public static RegistryKey GetGameRegistryKey() { RegistryKey src = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Mindscape\\LEGO Island", false); if (src == null) { src = Registry.LocalMachine.OpenSubKey("SOFTWARE\\WOW6432Node\\Mindscape\\LEGO Island", false); } return src; } public static string GetRegistryEntry(string key) { using (RegistryKey reg = GetGameRegistryKey()) { if (reg != null) { object o = reg.GetValue(key); if (o != null) { return o.ToString(); } } } return null; } private static Version DetermineVersion(string lego1dll_url) { using (FileStream fs = new FileStream(lego1dll_url, FileMode.Open, FileAccess.Read)) using (BufferedStream bs = new BufferedStream(fs)) using (SHA1Managed sha1 = new SHA1Managed()) { byte[] hash = sha1.ComputeHash(bs); StringBuilder formatted = new StringBuilder(2 * hash.Length); foreach (byte b in hash) { formatted.AppendFormat("{0:X2}", b); } string final_hash = formatted.ToString(); Version v = (Version) Array.IndexOf(VersionHashes, final_hash); if (v == Version.kUnknown) { Log("Unknown version: " + final_hash); if (MessageBox.Show("The version of LEGO Island you have installed is unknown to Rebuilder. This may result in unpredictable behavior. Would you like to continue?\n\n" + "Your version is: " + final_hash, "Unknown Version", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes) { return Version.kEnglish11; } } return v; } } private bool Patch(string source_dir, string dir) { string incompatibilities = ""; string isleexe_url = Path.Combine(dir, "ISLE.EXE"); string lego1dll_url = Path.Combine(dir, "LEGO1.DLL"); Version version = DetermineVersion(lego1dll_url); Log("Found version: " + version); if (version == Version.kUnknown) { return false; } using (FileStream lego1dll = File.Open(lego1dll_url, FileMode.Open, FileAccess.ReadWrite)) using (FileStream isleexe = File.Open(isleexe_url, FileMode.Open, FileAccess.ReadWrite)) { long nav_offset, fov_offset_1, fov_offset_2, turn_speed_routine_loc, dsoundoffs1, dsoundoffs2, dsoundoffs3, remove_fps_limit, jukebox_path_offset, model_quality_offset; switch (version) { case Version.kEnglish10: nav_offset = 0xF2C28; fov_offset_1 = 0xA1D67; fov_offset_2 = 0xA1D32; turn_speed_routine_loc = 0x54258; dsoundoffs1 = 0xB48FB; dsoundoffs2 = 0xB48F1; dsoundoffs3 = 0xAD7D3; remove_fps_limit = 0x7A68B; jukebox_path_offset = 0xD28F6; model_quality_offset = 0xFF028; break; case Version.kEnglish11: default: nav_offset = 0xF3228; fov_offset_1 = 0xA22D7; fov_offset_2 = 0xA22A2; turn_speed_routine_loc = 0x544F8; dsoundoffs1 = 0xB120B; dsoundoffs2 = 0xB1201; dsoundoffs3 = 0xADD43; remove_fps_limit = 0x7ABAB; jukebox_path_offset = 0xD2E66; model_quality_offset = 0xFF648; break; case Version.kGerman11: nav_offset = 0xF3428; fov_offset_1 = 0xA2517; fov_offset_2 = 0xA24E2; turn_speed_routine_loc = 0x544F8; dsoundoffs1 = 0xB144B; dsoundoffs2 = 0xB1441; dsoundoffs3 = 0xADF83; remove_fps_limit = 0x7AD9B; jukebox_path_offset = 0xD30A6; model_quality_offset = 0xFF878; break; case Version.kDanish11: nav_offset = 0xF3428; fov_offset_1 = 0xA24C7; fov_offset_2 = 0xA2492; turn_speed_routine_loc = 0x544F8; dsoundoffs1 = 0xB13FB; dsoundoffs2 = 0xB13F1; dsoundoffs3 = 0xADF33; remove_fps_limit = 0x7AD5B; jukebox_path_offset = 0xD3056; model_quality_offset = 0xFF868; break; case Version.kSpanish11: nav_offset = 0xF3228; fov_offset_1 = 0xA2407; fov_offset_2 = 0xA23D2; turn_speed_routine_loc = 0x544F8; dsoundoffs1 = 0xB133B; dsoundoffs2 = 0xB1331; dsoundoffs3 = 0xADE73; remove_fps_limit = 0x7ACBB; jukebox_path_offset = 0xD2F96; model_quality_offset = 0xFF658; break; } WriteInt32(lego1dll, (Int32)patch_config.MouseDeadzone, nav_offset); // Skip zero threshold lego1dll.Position += 4; WriteFloat(lego1dll, (float)patch_config.MovementMaxSpeed); WriteFloat(lego1dll, (float)patch_config.TurnMaxSpeed); WriteFloat(lego1dll, (float)patch_config.MovementMaxAcceleration); WriteFloat(lego1dll, (float)patch_config.TurnMaxAcceleration); WriteFloat(lego1dll, (float)patch_config.MovementMinAcceleration); WriteFloat(lego1dll, (float)patch_config.TurnMinAcceleration); WriteFloat(lego1dll, (float)patch_config.MovementDeceleration); WriteFloat(lego1dll, (float)patch_config.TurnDeceleration); // Skip 0.4 value that we don't know yet lego1dll.Position += 4; WriteInt32(lego1dll, Convert.ToInt32(patch_config.TurnUseVelocity)); // Patch EXE to read from HKCU instead of HKLM WriteByte(isleexe, 0x01, 0x1B5F); if (patch_config.UnhookTurnSpeed) { // Write routine to use frame delta time to adjust the turn speed Write(lego1dll, new byte[] { 0xD9, 0x46, 0x24, 0xD8, 0x4C, 0x24, 0x14, 0xD8, 0x4E, 0x34 }, turn_speed_routine_loc); // Frees up 26 bytes WriteManyBytes(lego1dll, 0x90, 26); } if (patch_config.StayActiveWhenDefocused) { // Remove code that writes focus value to memory, effectively keeping it always true - frees up 3 bytes Write(isleexe, new byte[] { 0x90, 0x90, 0x90 }, 0x1363); // Write DirectSound flags to allow audio to play while the window is defocused WriteByte(lego1dll, 0x80, dsoundoffs1); WriteByte(lego1dll, 0x80, 0x5B96); WriteByte(lego1dll, 0x80, dsoundoffs2); WriteByte(lego1dll, 0x80, dsoundoffs3); } if (patch_config.MultipleInstances) { // LEGO Island uses FindWindowA in user32.dll to determine if it's already running, here we replace the call with moving 0x0 into EAX, simulating a NULL response from FindWindowA WriteByte(isleexe, 0xEB, 0x10B5); } // Redirect JUKEBOX.SI if we're inserting music if (music_injector.ReplaceCount() > 0) { Uri uri1 = new Uri(jukebox_output.Substring(0, jukebox_output.LastIndexOf("."))); Uri uri2 = new Uri(Path.Combine(source_dir, "ISLE.EXE")); Uri relative = uri2.MakeRelativeUri(uri1); string jukebox_path = "\\" + Uri.UnescapeDataString(relative.ToString()).Replace("/", "\\"); switch (version) { case Version.kEnglish10: WriteByte(lego1dll, 0xF6, 0x51EF5); WriteByte(lego1dll, 0x34); WriteByte(lego1dll, 0x0D); WriteByte(lego1dll, 0x10); break; case Version.kEnglish11: default: WriteByte(lego1dll, 0x66, 0x52195); WriteByte(lego1dll, 0x3A); WriteByte(lego1dll, 0x0D); WriteByte(lego1dll, 0x10); break; case Version.kGerman11: WriteByte(lego1dll, 0xA6, 0x52195); WriteByte(lego1dll, 0x3C); WriteByte(lego1dll, 0x0D); WriteByte(lego1dll, 0x10); break; case Version.kDanish11: WriteByte(lego1dll, 0x56, 0x52195); WriteByte(lego1dll, 0x3C); WriteByte(lego1dll, 0x0D); WriteByte(lego1dll, 0x10); break; case Version.kSpanish11: WriteByte(lego1dll, 0x96, 0x52195); WriteByte(lego1dll, 0x3B); WriteByte(lego1dll, 0x0D); WriteByte(lego1dll, 0x10); break; } WriteString(lego1dll, jukebox_path, jukebox_path_offset); } // FOV Patch WriteByte(lego1dll, 0xEB, fov_offset_1); WriteByte(lego1dll, 0xC9); //WriteByte(lego1dll, 0x90); //WriteByte(lego1dll, 0x90); WriteByte(lego1dll, 0x68, fov_offset_2); WriteFloat(lego1dll, (float)patch_config.FOVMultiplier); WriteByte(lego1dll, 0xD8); WriteByte(lego1dll, 0x0C); WriteByte(lego1dll, 0x24); WriteByte(lego1dll, 0x5E); WriteByte(lego1dll, 0xEB); WriteByte(lego1dll, 0x2E); // FPS Patch if (patch_config.FPSLimit == FPSLimitType.Uncapped) { // Write zero frame delay resulting in uncapped frame rate WriteInt32(isleexe, 0, 0x4B4); } else if (patch_config.FPSLimit == FPSLimitType.Limited) { // Calculate frame delay and write new limit Int32 delay = (Int32) Math.Round(1000.0F / patch_config.CustomFPS); WriteInt32(isleexe, delay, 0x4B4); } if (patch_config.FPSLimit != FPSLimitType.Default) { // Disables 30 FPS limit in Information Center when using software mode WriteManyBytes(lego1dll, 0x90, 8, remove_fps_limit); } switch (patch_config.ModelQuality) { case ModelQualityType.Low: WriteFloat(lego1dll, 0.0f, model_quality_offset); break; case ModelQualityType.Medium: WriteFloat(lego1dll, 3.6f, model_quality_offset); break; case ModelQualityType.High: WriteFloat(lego1dll, 5.0f, model_quality_offset); break; } // INCOMPLETE: Resolution hack: if (patch_config.OverrideResolution) { // Changes window size WriteInt32(isleexe, (Int32)patch_config.ResolutionWidth, 0xE848); WriteInt32(isleexe, (Int32)patch_config.ResolutionHeight, 0xE84C); // Changes D3D render size WriteInt32(isleexe, (Int32)patch_config.ResolutionWidth - 1, 0x4D0); WriteInt32(isleexe, (Int32)patch_config.ResolutionHeight - 1, 0x4D7); // Write code to upscale the bitmaps if (patch_config.UpscaleBitmaps) { Write(lego1dll, new byte[] { 0xE9, 0x2D, 0x01, 0x00, 0x00, 0x8B, 0x56, 0x1C, 0x6A, 0x00, 0x8D, 0x45, 0xE4, 0xF6, 0x42, 0x30, 0x08, 0x74, 0x07, 0x68, 0x00, 0x80, 0x00, 0x00, 0xEB, 0x02, 0x6A, 0x00, 0x8B, 0x3B, 0x50, 0x51, 0x8D, 0x4D, 0xD4, 0x51, 0x53, 0x53, 0x50, 0x68 }, 0xB20E9); WriteFloat(lego1dll, (float)patch_config.ResolutionHeight / 480.0f); Int32 x_offset = (Int32)Math.Round((patch_config.ResolutionWidth - (patch_config.ResolutionHeight / 3.0 * 4.0))/2.0); Write(lego1dll, new byte[] { 0xDB, 0x45, 0xD4, 0xD8, 0x0C, 0x24, 0xDB, 0x5D, 0xD4, 0xDB, 0x45, 0xD8, 0xD8, 0x0C, 0x24, 0xDB, 0x5D, 0xD8, 0xDB, 0x45, 0xDC, 0xD8, 0x0C, 0x24, 0xDB, 0x5D, 0xDC, 0xDB, 0x45, 0xE0, 0xD8, 0x0C, 0x24, 0xDB, 0x5D, 0xE0, 0x58, 0x8B, 0x45, 0xD4, 0x05 }); WriteInt32(lego1dll, x_offset); Write(lego1dll, new byte[] { 0x89, 0x45, 0xD4, 0x8B, 0x45, 0xDC, 0x05 }); WriteInt32(lego1dll, x_offset); Write(lego1dll, new byte[] { 0x89, 0x45, 0xDC }); // Frees up 143 bytes of NOPs WriteManyBytes(lego1dll, 0x90, 143); Write(lego1dll, new byte[] { 0x58, 0x5B }); Write(lego1dll, new byte[] { 0xE9, 0xF6, 0xFD, 0xFF, 0xFF }, 0xB22F3); // Frees up 19 bytes of NOPs WriteManyBytes(lego1dll, 0x90, 19); } } if (version == Version.kEnglish10 && patch_config.RedirectSaveData) { incompatibilities += "- " + GetDisplayNameOfProperty("RedirectSaveData") + "\n"; patch_config.RedirectSaveData = false; } if (patch_config.DisableAutoFinishBuilding) { if (version == Version.kEnglish10) { incompatibilities += "- " + GetDisplayNameOfProperty("DisableAutoFinishBuilding") + "\n"; } else { // Disables cutscene/exit code WriteManyBytes(lego1dll, 0x90, 5, 0x22C0B); // Disables flag that freezes the UI on completion WriteManyBytes(lego1dll, 0x90, 7, 0x22C6A); } } } using (RegistryKey src = GetGameRegistryKey()) { if (src == null) { if (MessageBox.Show("Failed to find LEGO Island's registry entries. Some patches may fail. Do you wish to continue?", "Failed to find registry keys", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No) { return false; } } else { using (RegistryKey dst = Registry.CurrentUser.CreateSubKey("Software\\Mindscape\\LEGO Island")) { // Copy config data from HKLM to HKCU CopyRegistryKey(src, dst); // Set full screen value dst.SetValue("Full Screen", patch_config.FullScreen ? "YES" : "NO"); // Set draw cursor value dst.SetValue("Draw Cursor", patch_config.DrawCursor ? "YES" : "NO"); // Set Joystick toggle dst.SetValue("UseJoystick", patch_config.UseJoystick ? "YES" : "NO"); // Toggle music on or off dst.SetValue("Music", patch_config.MusicToggle ? "YES" : "NO"); // Redirect save path if (patch_config.RedirectSaveData) { string new_save_dir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\LEGO Island\\"; Directory.CreateDirectory(new_save_dir); dst.SetValue("savepath", new_save_dir); } } } } return string.IsNullOrEmpty(incompatibilities) || IncompatibleBuildMessage(incompatibilities); } private bool IsValidDir(string dir) { return (File.Exists(Path.Combine(dir, "ISLE.EXE")) && File.Exists(Path.Combine(dir, "LEGO1.DLL"))); } private void ShowMusicInjectorForm(object sender, EventArgs e) { if (!music_injector.Prepare()) { } } private void CopyRegistryKey(RegistryKey src, RegistryKey dst) { // copy the values foreach (var name in src.GetValueNames()) { dst.SetValue(name, src.GetValue(name), src.GetValueKind(name)); } // copy the subkeys foreach (var name in src.GetSubKeyNames()) { using (var srcSubKey = src.OpenSubKey(name, false)) { var dstSubKey = dst.CreateSubKey(name); CopyRegistryKey(srcSubKey, dstSubKey); } } } private void RunAdditional(object sender, EventArgs e) { Process p = Process.Start(processes[0].StartInfo); p.EnableRaisingEvents = true; p.Exited += new EventHandler(ProcessExit); processes.Add(p); } static public void Log(string text) { string log_path = Path.Combine(Path.GetTempPath(), "legoislandrebuilder.log"); using (StreamWriter log = new StreamWriter(log_path, true)) { log.WriteLine("[" + DateTime.Now + "] " + text); log.Close(); } } public void LaunchGame() { // If the game is already running, we assume this button is a "Kill" button if (processes.Count > 0) { foreach (Process p in processes) { p.Kill(); } processes.Clear(); return; } // Create temp path string temp_path = Path.Combine(Path.GetTempPath(), "LEGOIslandRebuilder"); Log("Using working directory: " + temp_path); if (Directory.Exists(temp_path)) { Log("Working directory already exists, no need to create"); } else { try { Directory.CreateDirectory(temp_path); Log("Working directory created successfully"); } catch (Exception e) { Log("Failed to create working directory: " + e.ToString()); MessageBox.Show( "Failed to create temporary path: " + e.ToString(), "Failed to patch files", MessageBoxButtons.OK, MessageBoxIcon.Error ); return; } } // Search for game directory Log("Searching for game directory..."); // Check our list of "standard paths" string dir = ""; for (int i=0;i 0) { jukebox_output = Path.Combine(dir, "LEGO/Scripts/REJUKEBOX.SI"); Log("Attempting to create injected jukebox file: " + jukebox_output); try { using (FileStream test_fs = new FileStream(jukebox_output, FileMode.Create, FileAccess.Write)) { test_fs.Close(); } } catch { jukebox_output = Path.Combine(Path.GetTempPath(), "REJUKEBOX.SI"); Log("Unable to use previous jukebox path, using alternative: " + jukebox_output); } music_injector.Insert(jukebox_output); } Log("Patching files"); if (!Patch(dir, temp_path)) return; // Set new EXE's compatibility mode to 256-colors Log("Creating compatibility registry keys"); using (RegistryKey key = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers", true)) { if (key != null) { string compat_string = "DWM8And16BitMitigation"; if (!patch_config.FullScreen) { if (System.Environment.OSVersion.Version.Major < 8) { // Use 16-bit on Windows 8+ compat_string += " 16BITCOLOR"; } else { // Older versions only have 256 color support compat_string += " 256COLOR"; } } if (!patch_config.RedirectSaveData) { compat_string += " RUNASADMIN"; } key.SetValue(temp_path + "\\ISLE.EXE", compat_string); } } Log("Launching game..."); ProcessStartInfo start_info = new ProcessStartInfo(Path.Combine(temp_path, "ISLE.EXE")); start_info.WorkingDirectory = dir; try { Process p = Process.Start(start_info); p.EnableRaisingEvents = true; p.Exited += new EventHandler(ProcessExit); processes.Add(p); form.run_button.Text = ConfigForm.run_button_kill; if (patch_config.MultipleInstances) { form.run_additional_button.Visible = true; } Log("Game launched successfully"); } catch (Exception e) { Log("Failed to launch LEGO Island: " + e.ToString()); MessageBox.Show( "Failed to launch LEGO Island: " + e.ToString(), "Failed to launch", MessageBoxButtons.OK, MessageBoxIcon.Error ); return; } } private void Run(object sender, EventArgs e) { LaunchGame(); } private void ProcessExit(object sender, EventArgs e) { form.run_button.BeginInvoke((MethodInvoker)delegate () { form.run_button.Text = ConfigForm.run_button_run; form.run_additional_button.Visible = false; }); for (int i=0;i