├── .gitattributes ├── .gitignore ├── ShenzhenMod ├── Properties │ ├── Settings.settings │ ├── Settings.Designer.cs │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── packages.config ├── Program.cs ├── App.config ├── Patches │ ├── IncreaseMaxSpeed.cs │ ├── AdjustPlaybackSpeedSlider.cs │ ├── AddBiggerSandbox.cs │ └── IncreaseMaxBoardSize.cs ├── Content │ └── messages.en │ │ └── bigger-prototyping-area.txt ├── Form1.cs ├── CecilCilExtensions.cs ├── Installer.cs ├── CecilExtensions.cs ├── ShenzhenMod.csproj ├── Form1.resx ├── ShenzhenLocator.cs ├── Form1.Designer.cs └── ShenzhenTypes.cs ├── web └── v1 │ └── hashes.json ├── LICENSE.txt ├── ShenzhenMod.sln ├── ThirdParty └── Licenses │ ├── mono.cecil │ └── LICENSE.txt │ └── log4net │ └── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | Debug/ 3 | Release/ 4 | Bin/ 5 | Obj/ 6 | packages/ 7 | /temp/ 8 | /output/ -------------------------------------------------------------------------------- /ShenzhenMod/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ShenzhenMod/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /web/v1/hashes.json: -------------------------------------------------------------------------------- 1 | [ 2 | "1D-65-40-5A-63-82-77-4F-2E-99-F2-00-B0-59-9B-4D-B3-71-2B-1B-C1-01-8A-4B-D6-02-C4-7A-8B-11-DB-E8", 3 | "-- Steam version (old)", 4 | "02-DB-AC-A8-8C-27-4E-A2-8F-A2-7E-D5-92-72-08-9C-F7-5A-DB-B7-5A-88-64-B9-54-05-55-51-7F-6D-56-F6", 5 | "-- GOG version", 6 | "78-87-24-5C-9A-40-0A-F0-2B-48-F4-7D-3B-C4-33-3E-64-8B-54-73-20-55-8B-8D-F5-E8-DE-99-E6-43-94-54", 7 | "-- Steam version (new)", 8 | "14-B7-7D-28-6D-CB-5B-3B-98-1A-E9-C4-18-9D-23-0D-FF-39-00-6E-45-76-8B-EA-C1-39-F7-4A-FC-FF-9C-D4", 9 | "-- Steam version (Jan 2021)" 10 | ] 11 | -------------------------------------------------------------------------------- /ShenzhenMod/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | [assembly: log4net.Config.XmlConfigurator(Watch = false)] 5 | 6 | namespace ShenzhenMod 7 | { 8 | static class Program 9 | { 10 | private static readonly log4net.ILog sm_log = log4net.LogManager.GetLogger(typeof(Program)); 11 | 12 | /// 13 | /// The main entry point for the application. 14 | /// 15 | [STAThread] 16 | static void Main(string[] args) 17 | { 18 | sm_log.Info("---------------------------------------------------------------------------------------"); 19 | sm_log.Info("Starting up"); 20 | 21 | Application.EnableVisualStyles(); 22 | Application.SetCompatibleTextRenderingDefault(false); 23 | Application.Run(new Form1(args.Length > 0 ? args[0] : null)); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ShenzhenMod/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 gtw123 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /ShenzhenMod.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2005 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShenzhenMod", "ShenzhenMod\ShenzhenMod.csproj", "{F54C1C3C-07DC-44F4-B799-7B3DBA6A5290}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {F54C1C3C-07DC-44F4-B799-7B3DBA6A5290}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {F54C1C3C-07DC-44F4-B799-7B3DBA6A5290}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {F54C1C3C-07DC-44F4-B799-7B3DBA6A5290}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {F54C1C3C-07DC-44F4-B799-7B3DBA6A5290}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {A692A271-2674-495F-B6E7-3F829928D3B0} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /ThirdParty/Licenses/mono.cecil/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008 - 2015 Jb Evain 2 | Copyright (c) 2008 - 2011 Novell, Inc. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /ShenzhenMod/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace ShenzhenMod.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ShenzhenMod/Patches/IncreaseMaxSpeed.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Drawing.Drawing2D; 3 | using System.IO; 4 | using System.Linq; 5 | using Mono.Cecil; 6 | using Mono.Cecil.Cil; 7 | 8 | namespace ShenzhenMod.Patches 9 | { 10 | /// 11 | /// Patches the code to increase the maximum simulation speed. 12 | public class IncreaseMaxSpeed 13 | { 14 | private static readonly log4net.ILog sm_log = log4net.LogManager.GetLogger(typeof(IncreaseMaxSpeed)); 15 | 16 | private ShenzhenTypes m_types; 17 | 18 | public IncreaseMaxSpeed(ShenzhenTypes types) 19 | { 20 | m_types = types; 21 | } 22 | 23 | public void Apply() 24 | { 25 | sm_log.Info("Applying patch"); 26 | 27 | AdjustMaxSpeed(); 28 | } 29 | 30 | /// 31 | /// Increases the maximum simulation speed for the sandbox. This also affects the first three test runs 32 | /// of a normal puzzle. 33 | /// 34 | private void AdjustMaxSpeed() 35 | { 36 | m_types.GameLogic.CircuitEditorScreen.Update.FindInstruction(OpCodes.Ldc_R4, 30f).Operand = 100f; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ShenzhenMod/Content/messages.en/bigger-prototyping-area.txt: -------------------------------------------------------------------------------- 1 | Subject: Prototyping bigger ideas 2 | From: Joe (周海涛) 3 | 4 | So… last night I had this AWESOME idea for a new game! It’s the most AMAZING thing you’ve ever seen! 5 | 6 | Wanna take a look? 7 | 8 | ----------- 9 | 10 | From: Carl Tesky 11 | 12 | That looks… interesting. I *think* we could build it, but we’re probably going to need some bigger circuit boards. 13 | 14 | ----------- 15 | 16 | From: Joe (周海涛) 17 | 18 | Don’t worry, I’ve got you covered! I found a whole stash of these bigger boards just gathering dust in the warehouse. 19 | 20 | ----------- 21 | 22 | From: Carl Tesky 23 | 24 | These could come in handy… 25 | 26 | ----------- (Sandbox) 27 | 28 | From: Carl Tesky 29 | 30 | You know, even with these bigger boards it’s still a struggle trying to get everything to fit. 31 | 32 | ----------- 33 | 34 | From: 张杰 35 | 36 | I see you found our beginner boards. My daughter used these to design her first robot. 37 | 38 | Jie. 39 | 40 | ----------- 41 | 42 | From: Joe (周海涛) 43 | 44 | Hey, these boards are AWESOME! There’s even enough room to engrave my name on them. I’m going to be FAMOUS! 45 | -------------------------------------------------------------------------------- /ShenzhenMod/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ShenzhenMod")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ShenzhenMod")] 13 | [assembly: AssemblyCopyright("Copyright © 2021")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("f54c1c3c-07dc-44f4-b799-7b3dba6a5290")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.3.2.0")] 36 | [assembly: AssemblyFileVersion("1.3.2.0")] 37 | -------------------------------------------------------------------------------- /ShenzhenMod/Form1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace ShenzhenMod 5 | { 6 | public partial class Form1 : Form 7 | { 8 | private static readonly log4net.ILog sm_log = log4net.LogManager.GetLogger(typeof(Form1)); 9 | 10 | private ShenzhenLocator m_locator; 11 | 12 | public Form1(string platformName) 13 | { 14 | m_locator = new ShenzhenLocator(platformName); 15 | InitializeComponent(); 16 | 17 | this.label4.Text = m_locator.GetUIString("LocateShenzhenFolder"); 18 | this.label7.Text = m_locator.GetUIString("SaveFilesHint"); 19 | m_exeFolderField.Text = m_locator.FindShenzhenDirectory(); 20 | } 21 | 22 | private void BrowseButtonClick(object sender, System.EventArgs e) 23 | { 24 | using (var dialog = new FolderBrowserDialog()) 25 | { 26 | dialog.RootFolder = Environment.SpecialFolder.MyComputer; 27 | dialog.SelectedPath = m_exeFolderField.Text; 28 | dialog.Description = m_locator.GetUIString("LocateShenzhenFolderWithHint"); 29 | var result = dialog.ShowDialog(this); 30 | if (result == DialogResult.OK) 31 | { 32 | m_exeFolderField.Text = dialog.SelectedPath; 33 | } 34 | } 35 | } 36 | 37 | private void InstallButtonClick(object sender, System.EventArgs e) 38 | { 39 | try 40 | { 41 | string shenzhenDir = m_exeFolderField.Text; 42 | sm_log.InfoFormat("Installing to \"{0}\"", shenzhenDir); 43 | new Installer(shenzhenDir).Install(); 44 | 45 | sm_log.Info("Installation complete"); 46 | MessageBox.Show(this, "Installation complete!"); 47 | Application.Exit(); 48 | } 49 | catch (Exception ex) 50 | { 51 | string message = "Error patching SHENZHEN I/O." + Environment.NewLine + Environment.NewLine + ex.ToString(); 52 | sm_log.Error(message); 53 | MessageBox.Show(this, message, "Error"); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ShenzhenMod/CecilCilExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Mono.Cecil.Cil; 4 | using static System.FormattableString; 5 | 6 | namespace ShenzhenMod 7 | { 8 | public static class CecilCilExtensions 9 | { 10 | public static bool Matches(this Instruction instruction, OpCode opCode, object operand) 11 | { 12 | return instruction.OpCode == opCode && Object.Equals(instruction.Operand, operand); 13 | } 14 | 15 | public static Instruction FindNext(this Instruction instruction, OpCode opCode) 16 | { 17 | var instr = instruction; 18 | while (instr != null) 19 | { 20 | if (instr.OpCode == opCode) 21 | { 22 | return instr; 23 | } 24 | 25 | instr = instr.Next; 26 | } 27 | 28 | throw new Exception(Invariant($"Cannot find instruction with OpCode \"{opCode}\" anywhere after instruction \"{instruction}\"")); 29 | } 30 | 31 | public static void Set(this Instruction instruction, OpCode opCode, object operand) 32 | { 33 | instruction.OpCode = opCode; 34 | instruction.Operand = operand; 35 | } 36 | 37 | public static IEnumerable RemoveRange(this ILProcessor il, Instruction start, Instruction end) 38 | { 39 | var removed = new List(); 40 | var current = start; 41 | while (current != end) 42 | { 43 | var next = current.Next; 44 | il.Remove(current); 45 | removed.Add(current); 46 | current = next; 47 | } 48 | 49 | return removed; 50 | } 51 | 52 | public static void InsertRangeBefore(this ILProcessor il, Instruction target, IEnumerable instructions) 53 | { 54 | foreach (var instr in instructions) 55 | { 56 | il.InsertBefore(target, instr); 57 | } 58 | } 59 | 60 | public static void InsertBefore(this ILProcessor il, Instruction target, params Instruction[] instructions) 61 | { 62 | InsertRangeBefore(il, target, instructions); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /ShenzhenMod/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace ShenzhenMod.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ShenzhenMod.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ShenzhenMod/Installer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Configuration; 3 | using System.Globalization; 4 | using System.IO; 5 | using Mono.Cecil; 6 | using ShenzhenMod.Patches; 7 | using static System.FormattableString; 8 | 9 | namespace ShenzhenMod 10 | { 11 | public class Installer 12 | { 13 | private static readonly log4net.ILog sm_log = log4net.LogManager.GetLogger(typeof(Installer)); 14 | 15 | private string m_shenzhenDir; 16 | 17 | public Installer(string shenzhenDir) 18 | { 19 | if (String.IsNullOrEmpty(shenzhenDir)) 20 | { 21 | throw new Exception("No SHENZHEN I/O directory specified"); 22 | } 23 | 24 | m_shenzhenDir = shenzhenDir; 25 | } 26 | 27 | public void Install() 28 | { 29 | sm_log.Info("Finding unpatched SHENZHEN I/O executable"); 30 | string exePath = ShenzhenLocator.FindUnpatchedShenzhenExecutable(m_shenzhenDir); 31 | string exeDir = Path.GetDirectoryName(exePath); 32 | 33 | if (Path.GetFileName(exePath).Equals("Shenzhen.exe", StringComparison.OrdinalIgnoreCase)) 34 | { 35 | string backupPath = Path.Combine(exeDir, "Shenzhen.Unpatched.exe"); 36 | sm_log.InfoFormat("Backing up unpatched executable \"{0}\" to \"{1}\"", exePath, backupPath); 37 | File.Copy(exePath, backupPath, overwrite: true); 38 | } 39 | 40 | string patchedPath = Path.Combine(exeDir, "Shenzhen.Patched.exe"); 41 | if (File.Exists(patchedPath)) 42 | { 43 | sm_log.InfoFormat("Deleting existing patched file \"{0}\"", patchedPath); 44 | File.Delete(patchedPath); 45 | } 46 | 47 | ApplyPatches(exePath, patchedPath); 48 | 49 | string targetPath = Path.Combine(exeDir, "Shenzhen.exe"); 50 | if (File.Exists(targetPath)) 51 | { 52 | // Rename the existing Shenzhen.exe before overwriting it, in case the user wants to roll back 53 | string timestamp = DateTime.Now.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture); 54 | string backupPath = Path.Combine(exeDir, Invariant($"Shenzhen.{timestamp}.exe")); 55 | sm_log.InfoFormat("Moving \"{0}\" to \"{1}\"", targetPath, backupPath); 56 | File.Move(targetPath, backupPath); 57 | } 58 | 59 | sm_log.InfoFormat("Moving \"{0}\" to \"{1}\"", patchedPath, targetPath); 60 | File.Move(patchedPath, targetPath); 61 | } 62 | 63 | private void ApplyPatches(string unpatchedPath, string patchedPath) 64 | { 65 | sm_log.InfoFormat("Reading module \"{0}\"", unpatchedPath); 66 | using (var module = ModuleDefinition.ReadModule(unpatchedPath)) 67 | { 68 | sm_log.Info("Locating types"); 69 | var types = new ShenzhenTypes(module); 70 | 71 | sm_log.Info("Applying patches"); 72 | string exeDir = Path.GetDirectoryName(unpatchedPath); 73 | new IncreaseMaxBoardSize(types).Apply(); 74 | new AddBiggerSandbox(types, exeDir).Apply(); 75 | new AdjustPlaybackSpeedSlider(types, exeDir).Apply(); 76 | 77 | if (bool.TryParse(ConfigurationManager.AppSettings["IncreaseMaxSpeed"], out bool increaseMaxSpeed) && increaseMaxSpeed) 78 | { 79 | new IncreaseMaxSpeed(types).Apply(); 80 | } 81 | 82 | sm_log.InfoFormat("Saving patched file to \"{0}\"", patchedPath); 83 | module.Write(patchedPath); 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShenzhenMod 2 | 3 | A mod for [SHENZHEN I/O](http://www.zachtronics.com/shenzhen-io/). 4 | 5 | ## Features 6 | 7 | * Adds a new prototyping area (sandbox) which is four times the size of the regular one. 8 | * Makes the simulation speed slider a bit wider in the sandbox, for more fine-grained speed control. 9 | * (Experimental) Increases the maximum simulation speed for the first three test runs and in the sandbox. 10 | * Note: This feature sometimes makes the game crash, so it's not enabled by default. See below for more info. 11 | * Supports both the Steam and GoG versions of SHENZHEN I/O. 12 | * Supports Windows, Linux and macOS. 13 | 14 | ## Installing 15 | 16 | ### Windows 17 | 18 | * First, back up your SHENZHEN I/O save files. This is **strongly** recommended as there is a risk that using this mod may corrupt your save files. Make a copy of `My Documents\My Games\SHENZHEN IO` and put it somewhere safe. 19 | * Download and unzip the latest release from https://github.com/gtw123/ShenzhenMod/releases 20 | * Run ShenzhenMod.exe 21 | * Follow the steps to install the mod. 22 | 23 | ### Linux 24 | 25 | * First, back up your SHENZHEN I/O save files. This is **strongly** recommended as there is a risk that using this mod may corrupt your save files. Make a copy of `$HOME/.local/share/SHENZHEN IO/` and put it somewhere safe. 26 | * Download and unzip the latest ShenzhenMod release from https://github.com/gtw123/ShenzhenMod/releases. 27 | * Run `mono ShenzhenMod.exe` 28 | * Follow the steps to install the mod. 29 | 30 | ### macOS 31 | 32 | * First, back up your SHENZHEN I/O save files. This is **strongly** recommended as there is a risk that using this mod may corrupt your save files. Make a copy of `~/Library/Application Support/SHENZHEN IO/` and put it somewhere safe. 33 | * Download and install Mono from http://www.mono-project.com/download/stable/. Version 5.10.1 or later is recommended. 34 | * Download and unzip the latest ShenzhenMod release from https://github.com/gtw123/ShenzhenMod/releases. 35 | * Open a Terminal window and change into the unzipped folder. 36 | * Tip: Type `cd`, then a space, then drag the unzipped folder into the window then press enter. 37 | * Run `mono32 ShenzhenMod.exe macos` 38 | * Follow the steps to install the mod. 39 | 40 | ## Using the new features in SHENZHEN I/O 41 | 42 | * To use the bigger sandbox, look for "Prototyping bigger ideas" in the puzzle list, just below "Prototyping new ideas". 43 | * Use middle-click-drag or alt-drag to scroll around the circuit board. 44 | 45 | ## Upgrading from an earlier version of ShenzhenMod 46 | 47 | No need to uninstall or unpatch first: simply run the new installer! 48 | 49 | ## Enabling the "Increase max game speed" feature 50 | 51 | This feature increases the maximum simulation speed. Unfortunately it can cause the game to crash on certain puzzles, especially in the bigger prototyping area, so it's not enabled by default. 52 | 53 | To enable it: 54 | * Edit Shenzhen.exe.config and set "IncreaseMaxSpeed" to true". (If you're building from source, edit App.config instead.) 55 | * Run the installer again. 56 | 57 | ## Building from source 58 | 59 | ### Windows 60 | 61 | * Clone or download the repo from https://github.com/gtw123/ShenzhenMod 62 | * Install [Visual Studio 2017](https://www.visualstudio.com/downloads/). 63 | * Open ShenzhenMod.sln in Visual Studio. 64 | * Build. 65 | 66 | ### Linux 67 | 68 | * Clone or download the repo from https://github.com/gtw123/ShenzhenMod 69 | * Install `mono-devel`, version 5.0 or later. 70 | * Open the ShenzhenMod folder. 71 | * Run `nuget restore` to download the dependencies. 72 | * Build using `msbuild` 73 | * Launch it via `mono bin/Debug/ShenzhenMod.exe` 74 | 75 | ### macOS 76 | 77 | * Clone or download the repo from https://github.com/gtw123/ShenzhenMod 78 | * Download and install Mono from http://www.mono-project.com/download/stable/. Version 5.10.1 or later is recommended. 79 | * Open a Terminal Window and `cd` into the ShenzhenMod folder. 80 | * Run `nuget restore` to download the dependencies. 81 | * Build using `msbuild` 82 | * Launch it via `mono32 bin/Debug/ShenzhenMod.exe macos` 83 | 84 | ## Releasing a new version 85 | 86 | * Make sure you've built the solution in release. 87 | * Make sure you've updated the version number in `AssemblyInfo.cs`. 88 | * (Windows) Open a developer command prompt. 89 | * Run `msbuild build-release.targets`. 90 | * The zip file will be in the `output` directory. 91 | * Test the zip file. 92 | * Upload to GitHub. 93 | * Tag the release. 94 | 95 | ## Credits 96 | 97 | The following people have helped develop this mod, either directly or indirectly: 98 | * gtw123 99 | * 12345ieee 100 | * pseudonym404 101 | * csaboka 102 | * mathiscode 103 | -------------------------------------------------------------------------------- /ShenzhenMod/CecilExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Mono.Cecil; 5 | using Mono.Cecil.Cil; 6 | using Mono.Cecil.Rocks; 7 | using static System.FormattableString; 8 | 9 | namespace ShenzhenMod 10 | { 11 | public static class CecilExtensions 12 | { 13 | public static FieldDefinition FindField(this ModuleDefinition module, string typeName, string fieldName) 14 | { 15 | return module.FindType(typeName).FindField(fieldName); 16 | } 17 | 18 | public static FieldDefinition FindField(this TypeDefinition type, string fieldName) 19 | { 20 | return type.Fields.SingleOrDefault(m => m.Name == fieldName) ?? throw new Exception(Invariant($"Cannot find field \"{fieldName}\" in type \"{type.Name}\"")); 21 | } 22 | 23 | public static MethodDefinition FindMethod(this ModuleDefinition module, string typeName, string methodName) 24 | { 25 | return module.FindType(typeName).FindMethod(methodName); 26 | } 27 | 28 | public static MethodDefinition FindMethod(this TypeDefinition type, string methodName) 29 | { 30 | var method = type.Methods.Where(m => m.Name == methodName); 31 | if (method.Count() == 0) 32 | { 33 | throw new Exception(Invariant($"Cannot find method \"{methodName}\" in type \"{type.Name}\"")); 34 | } 35 | else if (method.Count() > 1) 36 | { 37 | throw new Exception(Invariant($"Found more than one method called \"{methodName}\" in type \"{type.Name}\"")); 38 | } 39 | 40 | return method.First(); 41 | } 42 | 43 | public static TypeDefinition FindType(this ModuleDefinition module, string name) 44 | { 45 | return module.GetType(name) ?? throw new Exception(Invariant($"Cannot find type \"{name}\"")); 46 | } 47 | 48 | public static Instruction FindInstructionAtOffset(this MethodDefinition method, int offset, OpCode opCode, object operand) 49 | { 50 | var instr = method.Body.Instructions.SingleOrDefault(i => i.Offset == offset) ?? throw new Exception(Invariant($"Cannot find instruction at offset IL_{offset:X4} in method \"{method.Name}\"")); 51 | if (!instr.Matches(opCode, operand)) 52 | { 53 | throw new Exception(Invariant($"Instruction at offset IL_{offset:X4} in method \"{method.Name}\" does not match OpCode \"{opCode}\" and operand \"{operand}\"")); 54 | } 55 | 56 | return instr; 57 | } 58 | 59 | public static IEnumerable FindInstructions(this MethodDefinition method, OpCode opCode, object operand, int numExpected) 60 | { 61 | var instr = FindInstructions(method, opCode, operand); 62 | if (instr.Count() != numExpected) 63 | { 64 | throw new Exception(Invariant($"Expected to find {numExpected} instructions with OpCode \"{opCode}\" and operand \"{operand}\" in method \"{method.Name}\", but found {instr.Count()}")); 65 | } 66 | 67 | return instr; 68 | } 69 | 70 | public static IEnumerable FindInstructions(this MethodDefinition method, OpCode opCode, object operand) 71 | { 72 | return method.Body.Instructions.Where(i => i.Matches(opCode, operand)); 73 | } 74 | 75 | public static Instruction FindInstruction(this MethodDefinition method, OpCode opCode, object operand) 76 | { 77 | var instr = FindInstructions(method, opCode, operand); 78 | if (instr.Count() == 0) 79 | { 80 | throw new Exception(Invariant($"Cannot find instruction with OpCode \"{opCode}\" and operand \"{operand}\" in method \"{method.Name}\"")); 81 | } 82 | else if (instr.Count() > 1) 83 | { 84 | throw new Exception(Invariant($"Found more than one instruction with OpCode \"{opCode}\" and operand \"{operand}\" in method \"{method.Name}\"")); 85 | } 86 | 87 | return instr.First(); 88 | } 89 | 90 | public static MethodReference MakeGeneric(this MethodReference method, params TypeReference[] arguments) 91 | { 92 | var reference = new MethodReference(method.Name, method.ReturnType, method.DeclaringType.MakeGenericInstanceType(arguments)) 93 | { 94 | HasThis = method.HasThis, 95 | ExplicitThis = method.ExplicitThis, 96 | CallingConvention = method.CallingConvention 97 | }; 98 | 99 | foreach (var parameter in method.Parameters) 100 | { 101 | reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType)); 102 | } 103 | 104 | foreach (var genericParameter in method.GenericParameters) 105 | { 106 | reference.GenericParameters.Add(new GenericParameter(genericParameter.Name, reference)); 107 | } 108 | 109 | return reference; 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /ShenzhenMod/Patches/AdjustPlaybackSpeedSlider.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Drawing.Drawing2D; 3 | using System.IO; 4 | using System.Linq; 5 | using Mono.Cecil.Cil; 6 | 7 | namespace ShenzhenMod.Patches 8 | { 9 | /// 10 | /// Adds a wider "playback speed" slider to the sandbox window so the user has more fine-grained control over 11 | /// the simulation speed. 12 | /// 13 | public class AdjustPlaybackSpeedSlider 14 | { 15 | private static readonly log4net.ILog sm_log = log4net.LogManager.GetLogger(typeof(AdjustPlaybackSpeedSlider)); 16 | 17 | private string m_shenzhenDir; 18 | private ShenzhenTypes m_types; 19 | 20 | private const int EXTRA_WIDTH = 90; 21 | 22 | public AdjustPlaybackSpeedSlider(ShenzhenTypes types, string shenzhenDir) 23 | { 24 | m_shenzhenDir = shenzhenDir; 25 | m_types = types; 26 | } 27 | 28 | public void Apply() 29 | { 30 | sm_log.Info("Applying patch"); 31 | 32 | AdjustControls(); 33 | AdjustSandboxPanelTexture(); 34 | } 35 | 36 | /// 37 | /// Adjusts the controls at the bottom of the screen to accomodate a larger slider. 38 | /// 39 | private void AdjustControls() 40 | { 41 | var updateMethod = m_types.GameLogic.CircuitEditorScreen.Update; 42 | 43 | // Increase the maximum allowed X position of the playback speed slider 44 | updateMethod.FindInstruction(OpCodes.Ldc_I4, 706).Operand = 706 + EXTRA_WIDTH; 45 | 46 | // Move the "Show Wires" and "Hide Signals" buttons to the right 47 | foreach (var instr in updateMethod.FindInstructions(OpCodes.Ldc_R4, 850f, 2)) 48 | { 49 | instr.Operand = 850f + EXTRA_WIDTH; 50 | } 51 | } 52 | 53 | /// 54 | /// Creates a new version of the sandbox panel texture, with a wider space for the playback 55 | /// speed slider. 56 | /// 57 | private void AdjustSandboxPanelTexture() 58 | { 59 | string newTextureName = "panel_sandbox_wide"; 60 | 61 | MakeNewTexture(); 62 | AdjustTextureName(); 63 | 64 | void MakeNewTexture() 65 | { 66 | string editorPath = Path.Combine(m_shenzhenDir, "Content", "textures", "editor"); 67 | string panelTexturePath = Path.Combine(editorPath, "panel_sandbox.png"); 68 | using (var image = Image.FromFile(panelTexturePath)) 69 | { 70 | using (var newImage = new Bitmap(image.Width + EXTRA_WIDTH, image.Height)) 71 | { 72 | using (var graphics = Graphics.FromImage(newImage)) 73 | { 74 | graphics.CompositingMode = CompositingMode.SourceCopy; 75 | graphics.DrawImageUnscaled(image, new Point(0, 0)); 76 | 77 | // Copy the part just to the right of the "-" sign 78 | int startX = 640; 79 | var sourceRect = new Rectangle(startX, 0, image.Width - startX, image.Height); 80 | graphics.DrawImage(image, startX + EXTRA_WIDTH, 0, sourceRect, GraphicsUnit.Pixel); 81 | } 82 | 83 | string newTexturePath = Path.Combine(editorPath, newTextureName + ".png"); 84 | newImage.Save(newTexturePath); 85 | } 86 | } 87 | } 88 | 89 | void AdjustTextureName() 90 | { 91 | // Since the names of the textures are obfuscated it's difficult to find the one we want. 92 | // For now we'll use a hard-coded index but we'll check that it matches the field used 93 | // where CircuitEditorScreen.Update() draws it. This could easily break if the game 94 | // is updated, but the chance of us getting the wrong texture is very low. 95 | var panelSandboxTextureField = m_types.TextureManager.Type.Fields[15].FieldType.Resolve().Fields[32]; 96 | m_types.GameLogic.CircuitEditorScreen.Update.FindInstructionAtOffset(0x2775, OpCodes.Ldfld, panelSandboxTextureField); // This will throw if it doesn't match our texture field 97 | 98 | // Find the method that loads all the textures 99 | var method = m_types.TextureManager.Type.Methods.Single(m => m.Parameters.Count == 1 && m.Parameters[0].ParameterType.ToString() == "System.Action`1"); 100 | var il = method.Body.GetILProcessor(); 101 | 102 | // Change it to use our new file 103 | // panelSandboxTextureField = LoadTexture(DeobfuscateString(-1809885311)) => panelSandboxTextureField = LoadTexture("textures/editor/newTextureName"); 104 | var instr = method.FindInstruction(OpCodes.Stfld, panelSandboxTextureField).Previous.Previous.Previous; 105 | instr.Set(OpCodes.Ldstr, "textures/editor/" + newTextureName); 106 | il.Remove(instr.Next); 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /ShenzhenMod/ShenzhenMod.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {F54C1C3C-07DC-44F4-B799-7B3DBA6A5290} 8 | WinExe 9 | ShenzhenMod 10 | ShenzhenMod 11 | v4.6.1 12 | 512 13 | true 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | ..\bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | ..\bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\packages\log4net.2.0.12\lib\net45\log4net.dll 37 | 38 | 39 | ..\packages\Mono.Cecil.0.10.0\lib\net40\Mono.Cecil.dll 40 | 41 | 42 | ..\packages\Mono.Cecil.0.10.0\lib\net40\Mono.Cecil.Mdb.dll 43 | 44 | 45 | ..\packages\Mono.Cecil.0.10.0\lib\net40\Mono.Cecil.Pdb.dll 46 | 47 | 48 | ..\packages\Mono.Cecil.0.10.0\lib\net40\Mono.Cecil.Rocks.dll 49 | 50 | 51 | ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | Form 68 | 69 | 70 | Form1.cs 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | Form1.cs 81 | 82 | 83 | ResXFileCodeGenerator 84 | Resources.Designer.cs 85 | Designer 86 | 87 | 88 | True 89 | Resources.resx 90 | 91 | 92 | 93 | SettingsSingleFileGenerator 94 | Settings.Designer.cs 95 | 96 | 97 | True 98 | Settings.settings 99 | True 100 | 101 | 102 | 103 | 104 | Designer 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /ShenzhenMod/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /ShenzhenMod/Form1.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /ShenzhenMod/ShenzhenLocator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Security.Cryptography; 6 | using System.IO; 7 | using Microsoft.Win32; 8 | using Newtonsoft.Json.Linq; 9 | using static System.FormattableString; 10 | 11 | namespace ShenzhenMod 12 | { 13 | public class ShenzhenLocator 14 | { 15 | private static readonly log4net.ILog sm_log = log4net.LogManager.GetLogger(typeof(ShenzhenLocator)); 16 | 17 | private Dictionary m_uiStrings; 18 | private Func> m_getShenzhenSearchPaths; 19 | 20 | public ShenzhenLocator(string platformName) 21 | { 22 | var platform = Environment.OSVersion.Platform; 23 | if (platformName == "macos") 24 | { 25 | // Unfortunately Mono's version of Environment.OSVersion.Platform returns Unix on macOS, and there's no easy 26 | // way to tell if we're on macOS. So we just rely on a command-line parameter for now. 27 | platform = PlatformID.MacOSX; 28 | } 29 | 30 | switch (platform) 31 | { 32 | case PlatformID.Win32NT: 33 | { 34 | m_uiStrings = new Dictionary 35 | { 36 | ["LocateShenzhenFolder"] = "Locate your SHENZHEN I/O installation folder:", 37 | ["LocateShenzhenFolderWithHint"] = @"Please locate your SHENZHEN I/O installation folder. This will usually be C:\Program Files (x86)\Steam\steamapps\common\SHENZHEN IO", 38 | ["SaveFilesHint"] = @"Your save files are normally located in: My Documents\My Games\SHENZHEN IO\", 39 | }; 40 | 41 | m_getShenzhenSearchPaths = GetWindowsSearchPaths; 42 | break; 43 | } 44 | case PlatformID.Unix: 45 | { 46 | m_uiStrings = new Dictionary 47 | { 48 | ["LocateShenzhenFolder"] = "Locate your SHENZHEN I/O installation directory:", 49 | ["LocateShenzhenFolderWithHint"] = @"Please locate your SHENZHEN I/O installation directory. This will usually be $HOME/.steam/steam/steamapps/common/SHENZHEN IO/", 50 | ["SaveFilesHint"] = @"Your save files are normally located in: $HOME/.local/share/SHENZHEN IO", 51 | }; 52 | 53 | m_getShenzhenSearchPaths = GetLinuxSearchPaths; 54 | break; 55 | } 56 | case PlatformID.MacOSX: 57 | { 58 | m_uiStrings = new Dictionary 59 | { 60 | ["LocateShenzhenFolder"] = "Locate the SHENZHEN I/O application:", 61 | ["LocateShenzhenFolderWithHint"] = @"Please locate your SHENZHEN I/O application. This will usually be in your Applications folder or in ~/Library/Application Support/Steam/SteamApps/common/", 62 | ["SaveFilesHint"] = @"Your save files are normally located in: ~/Library/Application Support/SHENZHEN IO/", 63 | }; 64 | 65 | m_getShenzhenSearchPaths = GetMacSearchPaths; 66 | break; 67 | } 68 | default: 69 | throw new Exception("Unsupported platform: " + Environment.OSVersion.Platform); 70 | } 71 | } 72 | 73 | public string GetUIString(string token) 74 | { 75 | return m_uiStrings.TryGetValue(token, out string uiString) ? uiString : $""; 76 | } 77 | 78 | public string FindShenzhenDirectory() 79 | { 80 | foreach (string dir in m_getShenzhenSearchPaths()) 81 | { 82 | if (Directory.Exists(dir)) 83 | { 84 | sm_log.InfoFormat("Found SHENZHEN I/O directory: \"{0}\"", dir); 85 | return dir; 86 | } 87 | else 88 | { 89 | sm_log.InfoFormat("Did not find SHENZHEN I/O directory: \"{0}\"", dir); 90 | } 91 | } 92 | 93 | sm_log.WarnFormat("Could not find SHENZHEN I/O directory"); 94 | return null; 95 | } 96 | 97 | private IEnumerable GetWindowsSearchPaths() 98 | { 99 | string steamPath = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Valve\Steam", "InstallPath", null) as string; 100 | sm_log.InfoFormat("Steam install path: \"{0}\"", steamPath); 101 | if (steamPath != null) 102 | { 103 | yield return Path.Combine(steamPath, "steamapps", "common", "SHENZHEN IO"); 104 | } 105 | 106 | string gogPath = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\GOG.com\Games\1640205738", "PATH", null) as string; 107 | sm_log.InfoFormat("GoG install path: \"{0}\"", gogPath); 108 | if (gogPath != null) 109 | { 110 | yield return gogPath; 111 | } 112 | } 113 | 114 | private IEnumerable GetLinuxSearchPaths() 115 | { 116 | yield return Path.Combine(Environment.GetEnvironmentVariable("HOME"), ".steam/steam/steamapps/common/SHENZHEN IO"); 117 | } 118 | 119 | private IEnumerable GetMacSearchPaths() 120 | { 121 | yield return Path.Combine(Environment.GetEnvironmentVariable("HOME"), "Library/Application Support/Steam/SteamApps/common/SHENZHEN IO/Shenzhen IO.app"); 122 | yield return $"/Applications/Shenzhen IO.app"; 123 | } 124 | 125 | public static string FindUnpatchedShenzhenExecutable(string shenzhenDir) 126 | { 127 | sm_log.InfoFormat("Fetching the latest list of unpatched hashes"); 128 | 129 | WebRequest webRequest = WebRequest.Create("https://raw.githubusercontent.com/gtw123/ShenzhenMod/master/web/v1/hashes.json"); 130 | WebResponse response; 131 | 132 | try { response = webRequest.GetResponse(); } 133 | catch(Exception e) { throw new AggregateException("\n\nAn error occurred fetching the hash list. Please try again.\n\n", e); } 134 | 135 | string[] unpatchedHashes; 136 | using (Stream data = response.GetResponseStream()) 137 | { 138 | StreamReader reader = new StreamReader(data); 139 | string json = reader.ReadToEnd(); 140 | unpatchedHashes = JArray.Parse(json).ToObject(); 141 | } 142 | 143 | response.Close(); 144 | 145 | sm_log.InfoFormat("Looking for unpatched SHENZHEN I/O executable in \"{0}\"", shenzhenDir); 146 | string path = FindExecutableWithHash(shenzhenDir, unpatchedHashes); 147 | if (path == null) 148 | { 149 | throw new Exception(Invariant($"Cannot locate unpatched SHENZHEN I/O executable in \"{shenzhenDir}\"")); 150 | } 151 | 152 | sm_log.InfoFormat("Found unpatched SHENZHEN I/O executable: \"{0}\"", path); 153 | return path; 154 | } 155 | 156 | private static string FindExecutableWithHash(string dir, string[] expectedHashes) 157 | { 158 | foreach (string file in Directory.GetFiles(dir, "*.exe", SearchOption.AllDirectories)) 159 | { 160 | try 161 | { 162 | string hash = CalculateHash(file); 163 | if (expectedHashes.Contains(hash)) 164 | { 165 | return file; 166 | } 167 | } 168 | catch (Exception e) 169 | { 170 | sm_log.ErrorFormat("Error calculating hash for file \"{0}\": {1}", file, e.Message); 171 | } 172 | } 173 | 174 | return null; 175 | } 176 | 177 | private static string CalculateHash(string file) 178 | { 179 | sm_log.InfoFormat("Calculating hash for \"{0}\"", file); 180 | using (var stream = File.OpenRead(file)) 181 | { 182 | var sha = SHA256.Create(); 183 | byte[] hash = sha.ComputeHash(stream); 184 | string hashString = BitConverter.ToString(hash); 185 | 186 | sm_log.InfoFormat("Calculated hash: \"{0}\"", hashString); 187 | return hashString; 188 | } 189 | } 190 | } 191 | } -------------------------------------------------------------------------------- /ShenzhenMod/Form1.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace ShenzhenMod 2 | { 3 | partial class Form1 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.m_exeFolderField = new System.Windows.Forms.TextBox(); 32 | this.m_browseButton = new System.Windows.Forms.Button(); 33 | this.m_installButton = new System.Windows.Forms.Button(); 34 | this.label1 = new System.Windows.Forms.Label(); 35 | this.label2 = new System.Windows.Forms.Label(); 36 | this.label3 = new System.Windows.Forms.Label(); 37 | this.label4 = new System.Windows.Forms.Label(); 38 | this.label5 = new System.Windows.Forms.Label(); 39 | this.label6 = new System.Windows.Forms.Label(); 40 | this.label7 = new System.Windows.Forms.Label(); 41 | this.SuspendLayout(); 42 | // 43 | // m_exeFolderField 44 | // 45 | this.m_exeFolderField.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 46 | | System.Windows.Forms.AnchorStyles.Right))); 47 | this.m_exeFolderField.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 48 | this.m_exeFolderField.Location = new System.Drawing.Point(15, 173); 49 | this.m_exeFolderField.Name = "m_exeFolderField"; 50 | this.m_exeFolderField.Size = new System.Drawing.Size(605, 23); 51 | this.m_exeFolderField.TabIndex = 0; 52 | // 53 | // m_browseButton 54 | // 55 | this.m_browseButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 56 | this.m_browseButton.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 57 | this.m_browseButton.Location = new System.Drawing.Point(626, 172); 58 | this.m_browseButton.Name = "m_browseButton"; 59 | this.m_browseButton.Size = new System.Drawing.Size(75, 25); 60 | this.m_browseButton.TabIndex = 1; 61 | this.m_browseButton.Text = "Browse..."; 62 | this.m_browseButton.UseVisualStyleBackColor = true; 63 | this.m_browseButton.Click += new System.EventHandler(this.BrowseButtonClick); 64 | // 65 | // m_installButton 66 | // 67 | this.m_installButton.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 68 | this.m_installButton.Location = new System.Drawing.Point(15, 284); 69 | this.m_installButton.Name = "m_installButton"; 70 | this.m_installButton.Size = new System.Drawing.Size(75, 23); 71 | this.m_installButton.TabIndex = 1; 72 | this.m_installButton.Text = "Install"; 73 | this.m_installButton.UseVisualStyleBackColor = true; 74 | this.m_installButton.Click += new System.EventHandler(this.InstallButtonClick); 75 | // 76 | // label1 77 | // 78 | this.label1.AutoSize = true; 79 | this.label1.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 80 | this.label1.Location = new System.Drawing.Point(12, 14); 81 | this.label1.Name = "label1"; 82 | this.label1.Size = new System.Drawing.Size(43, 15); 83 | this.label1.TabIndex = 2; 84 | this.label1.Text = "Step 1"; 85 | // 86 | // label2 87 | // 88 | this.label2.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 89 | this.label2.Location = new System.Drawing.Point(12, 36); 90 | this.label2.Name = "label2"; 91 | this.label2.Size = new System.Drawing.Size(679, 42); 92 | this.label2.TabIndex = 2; 93 | this.label2.Text = "Using this mod may corrupt your saved solutions. It is strongly recommend that yo" + 94 | "u back up your SHENZHEN I/O save files before continuing!"; 95 | // 96 | // label3 97 | // 98 | this.label3.AutoSize = true; 99 | this.label3.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 100 | this.label3.Location = new System.Drawing.Point(12, 130); 101 | this.label3.Name = "label3"; 102 | this.label3.Size = new System.Drawing.Size(43, 15); 103 | this.label3.TabIndex = 2; 104 | this.label3.Text = "Step 2"; 105 | // 106 | // label4 107 | // 108 | this.label4.AutoSize = true; 109 | this.label4.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 110 | this.label4.Location = new System.Drawing.Point(12, 152); 111 | this.label4.Name = "label4"; 112 | this.label4.Size = new System.Drawing.Size(251, 15); 113 | this.label4.TabIndex = 2; 114 | this.label4.Text = ""; 115 | // 116 | // label5 117 | // 118 | this.label5.AutoSize = true; 119 | this.label5.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 120 | this.label5.Location = new System.Drawing.Point(12, 236); 121 | this.label5.Name = "label5"; 122 | this.label5.Size = new System.Drawing.Size(43, 15); 123 | this.label5.TabIndex = 2; 124 | this.label5.Text = "Step 3"; 125 | // 126 | // label6 127 | // 128 | this.label6.AutoSize = true; 129 | this.label6.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 130 | this.label6.Location = new System.Drawing.Point(12, 258); 131 | this.label6.Name = "label6"; 132 | this.label6.Size = new System.Drawing.Size(70, 15); 133 | this.label6.TabIndex = 2; 134 | this.label6.Text = "Click Install."; 135 | // 136 | // label7 137 | // 138 | this.label7.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 139 | this.label7.Location = new System.Drawing.Point(12, 78); 140 | this.label7.Name = "label7"; 141 | this.label7.Size = new System.Drawing.Size(671, 26); 142 | this.label7.TabIndex = 2; 143 | this.label7.Text = ""; 144 | // 145 | // Form1 146 | // 147 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 148 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 149 | this.ClientSize = new System.Drawing.Size(713, 325); 150 | this.Controls.Add(this.label6); 151 | this.Controls.Add(this.label5); 152 | this.Controls.Add(this.label4); 153 | this.Controls.Add(this.label3); 154 | this.Controls.Add(this.label7); 155 | this.Controls.Add(this.label2); 156 | this.Controls.Add(this.label1); 157 | this.Controls.Add(this.m_installButton); 158 | this.Controls.Add(this.m_browseButton); 159 | this.Controls.Add(this.m_exeFolderField); 160 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; 161 | this.MaximizeBox = false; 162 | this.Name = "Form1"; 163 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 164 | this.Text = "SheznhenMod Installer"; 165 | this.ResumeLayout(false); 166 | this.PerformLayout(); 167 | 168 | } 169 | 170 | #endregion 171 | 172 | private System.Windows.Forms.TextBox m_exeFolderField; 173 | private System.Windows.Forms.Button m_browseButton; 174 | private System.Windows.Forms.Button m_installButton; 175 | private System.Windows.Forms.Label label1; 176 | private System.Windows.Forms.Label label2; 177 | private System.Windows.Forms.Label label3; 178 | private System.Windows.Forms.Label label4; 179 | private System.Windows.Forms.Label label5; 180 | private System.Windows.Forms.Label label6; 181 | private System.Windows.Forms.Label label7; 182 | } 183 | } 184 | 185 | -------------------------------------------------------------------------------- /ShenzhenMod/Patches/AddBiggerSandbox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using Mono.Cecil; 5 | using Mono.Cecil.Cil; 6 | 7 | namespace ShenzhenMod.Patches 8 | { 9 | /// 10 | /// Adds a bigger sandbox to the game. 11 | /// 12 | public class AddBiggerSandbox 13 | { 14 | private static readonly log4net.ILog sm_log = log4net.LogManager.GetLogger(typeof(AddBiggerSandbox)); 15 | 16 | private ShenzhenTypes m_types; 17 | private string m_shenzhenDir; 18 | 19 | public AddBiggerSandbox(ShenzhenTypes types, string shenzhenDir) 20 | { 21 | m_types = types; 22 | m_shenzhenDir = shenzhenDir; 23 | } 24 | 25 | public void Apply() 26 | { 27 | sm_log.Info("Applying patch"); 28 | 29 | AddSandbox2Puzzle(); 30 | AddSandbox2MessageThread(); 31 | CopyMessagesFile(); 32 | } 33 | 34 | /// 35 | /// Adds the Sandbox2 puzzle to the Puzzles class. 36 | /// 37 | private void AddSandbox2Puzzle() 38 | { 39 | const int WIDTH = 44; 40 | const int HEIGHT = 28; 41 | 42 | // Add the static field for the new puzzle 43 | var sandbox2Field = new FieldDefinition("Sandbox2", FieldAttributes.Static | FieldAttributes.InitOnly, m_types.Puzzle.Type); 44 | m_types.Puzzles.Type.Fields.Add(sandbox2Field); 45 | 46 | // Add the method that creates the puzzle 47 | var createSandbox2 = AddCreateSandbox2(); 48 | PatchPuzzlesConstructor(); 49 | 50 | MethodDefinition AddCreateSandbox2() 51 | { 52 | var method = new MethodDefinition("CreateSandbox2", MethodAttributes.Private | MethodAttributes.Static, m_types.BuiltIn.Void); 53 | m_types.Puzzles.Type.Methods.Add(method); 54 | var il = method.Body.GetILProcessor(); 55 | 56 | var puzzleType = m_types.Puzzle.Type; 57 | var puzzle = new VariableDefinition(puzzleType); 58 | method.Body.Variables.Add(puzzle); 59 | 60 | // Puzzle puzzle = new Puzzle(); 61 | il.Emit(OpCodes.Newobj, m_types.Puzzle.Constructor); 62 | il.Emit(OpCodes.Stloc_S, puzzle); 63 | 64 | // puzzle.Name = "SzSandbox2"; 65 | il.Emit(OpCodes.Ldloc_S, puzzle); 66 | il.Emit(OpCodes.Ldstr, "SzSandbox2"); 67 | il.Emit(OpCodes.Stfld, m_types.Puzzle.Name); 68 | 69 | // puzzle.IsSandbox = true; 70 | il.Emit(OpCodes.Ldloc_S, puzzle); 71 | il.Emit(OpCodes.Ldc_I4_1); 72 | il.Emit(OpCodes.Stfld, m_types.Puzzle.IsSandbox); 73 | 74 | // Find the built-in sandbox. It's the only puzzle with zero terminals. 75 | var instr = m_types.Puzzles.ClassConstructor.Body.Instructions.Single(i => i.Matches(OpCodes.Ldc_I4_0, null) && i.Next.Matches(OpCodes.Newarr, m_types.Terminal.Type)); 76 | instr = instr.FindNext(OpCodes.Stsfld); 77 | var sandboxField = (FieldDefinition)instr.Operand; 78 | 79 | // puzzle.GetTestRunOutputs = Puzzles.Sandbox.GetTestRunOutputs; 80 | il.Emit(OpCodes.Ldloc_S, puzzle); 81 | il.Emit(OpCodes.Ldsfld, sandboxField); 82 | il.Emit(OpCodes.Ldfld, m_types.Puzzle.GetTestRunOutputs); 83 | il.Emit(OpCodes.Stfld, m_types.Puzzle.GetTestRunOutputs); 84 | 85 | // puzzle.Terminals = new Terminal[0]; 86 | il.Emit(OpCodes.Ldloc_S, puzzle); 87 | il.Emit(OpCodes.Ldc_I4_0); 88 | il.Emit(OpCodes.Newarr, m_types.Terminal.Type); 89 | il.Emit(OpCodes.Stfld, m_types.Puzzle.Terminals); 90 | 91 | // puzzle.SetSize(new Index2(WIDTH, HEIGHT)); 92 | il.Emit(OpCodes.Ldloc_S, puzzle); 93 | il.Emit(OpCodes.Ldc_I4_S, (sbyte)WIDTH); 94 | il.Emit(OpCodes.Ldc_I4_S, (sbyte)HEIGHT); 95 | il.Emit(OpCodes.Newobj, m_types.Index2.Constructor); 96 | il.Emit(OpCodes.Call, puzzleType.FindMethod("SetSize")); 97 | 98 | // puzzle.Tiles = new int[WIDTH * HEIGHT * 3]; 99 | il.Emit(OpCodes.Ldloc_S, puzzle); 100 | il.Emit(OpCodes.Ldc_I4_S, (sbyte)WIDTH); 101 | il.Emit(OpCodes.Ldc_I4_S, (sbyte)HEIGHT); 102 | il.Emit(OpCodes.Mul); 103 | il.Emit(OpCodes.Ldc_I4_3); 104 | il.Emit(OpCodes.Mul); 105 | il.Emit(OpCodes.Newarr, m_types.BuiltIn.Int32); 106 | il.Emit(OpCodes.Stfld, m_types.Puzzle.Tiles); 107 | 108 | // It's not clear how to initialize arrays with Mono.Cecil so for now we'll do it 109 | // the brute force way... 110 | 111 | // Set top row tiles 112 | SetTile(0, 0, 8, 1); 113 | SetRowInterior(0, 4, 2); 114 | SetTile(0, WIDTH - 1, 8, 2); 115 | 116 | // Set middle rows tiles 117 | for (int row = 1; row < HEIGHT - 1; row++) 118 | { 119 | SetTile(row, 0, 4, 1); 120 | SetRowInterior(row, 1, 0); 121 | SetTile(row, WIDTH - 1, 4, 3); 122 | } 123 | 124 | // Set bottom row tiles 125 | SetTile(HEIGHT - 1, 0, 8, 0); 126 | SetRowInterior(HEIGHT - 1, 4, 0); 127 | SetTile(HEIGHT - 1, WIDTH - 1, 8, 3); 128 | 129 | // Puzzles.Sandbox2 = puzzle; 130 | il.Emit(OpCodes.Ldloc_S, puzzle); 131 | il.Emit(OpCodes.Stsfld, sandbox2Field); 132 | 133 | il.Emit(OpCodes.Ret); 134 | return method; 135 | 136 | void SetRowInterior(int row, int value1, int value2) 137 | { 138 | for (int col = 1; col < WIDTH - 1; col++) 139 | { 140 | SetTile(row, col, value1, value2); 141 | } 142 | } 143 | 144 | void SetTile(int row, int col, int value1, int value2) 145 | { 146 | int index = (row * WIDTH + col) * 3; 147 | 148 | // puzzle.Tiles[index] = value1; 149 | il.Emit(OpCodes.Ldloc_S, puzzle); 150 | il.Emit(OpCodes.Ldfld, m_types.Puzzle.Tiles); 151 | il.Emit(OpCodes.Ldc_I4, index); 152 | il.Emit(OpCodes.Ldc_I4_S, (sbyte)value1); 153 | il.Emit(OpCodes.Stelem_I4); 154 | 155 | // puzzle.Tiles[index + 1] = value2; 156 | il.Emit(OpCodes.Ldloc_S, puzzle); 157 | il.Emit(OpCodes.Ldfld, m_types.Puzzle.Tiles); 158 | il.Emit(OpCodes.Ldc_I4, index + 1); 159 | il.Emit(OpCodes.Ldc_I4_S, (sbyte)value2); 160 | il.Emit(OpCodes.Stelem_I4); 161 | } 162 | } 163 | 164 | // Patches the Puzzles static constructor to call CreateSandbox2. 165 | void PatchPuzzlesConstructor() 166 | { 167 | var method = m_types.Puzzles.ClassConstructor; 168 | var il = method.Body.GetILProcessor(); 169 | il.InsertBefore(method.Body.Instructions.Last(), il.Create(OpCodes.Call, createSandbox2)); 170 | } 171 | } 172 | 173 | /// 174 | /// Adds a new email thread to get to the Sandbox2 puzzle. 175 | /// 176 | private void AddSandbox2MessageThread() 177 | { 178 | var method = m_types.MessageThreads.CreateAllThreads; 179 | var il = method.Body.GetILProcessor(); 180 | 181 | // Increase the length of the messageThreads array by one 182 | const int NUM_THREADS = 76; 183 | method.FindInstruction(OpCodes.Ldc_I4_S, (sbyte)NUM_THREADS).Operand = (sbyte)(NUM_THREADS + 1); 184 | 185 | // We want to insert our new puzzle just after the existing sandbox, so shuffle the 186 | // later message threads forward by one. 187 | const int INSERT_INDEX = 18; 188 | var endThreads = method.FindInstruction(OpCodes.Stsfld, m_types.MessageThreads.AllThreads).Next; 189 | 190 | // Array.Copy(AllThreads, INSERT_INDEX, AllThreads, INSERT_INDEX + 1, NUM_THREADS - INSERT_INDEX); 191 | il.InsertBefore(endThreads, 192 | il.Create(OpCodes.Ldsfld, m_types.MessageThreads.AllThreads), 193 | il.Create(OpCodes.Ldc_I4_S, (sbyte)INSERT_INDEX), 194 | il.Create(OpCodes.Ldsfld, m_types.MessageThreads.AllThreads), 195 | il.Create(OpCodes.Ldc_I4_S, (sbyte)(INSERT_INDEX + 1)), 196 | il.Create(OpCodes.Ldc_I4_S, (sbyte)(NUM_THREADS - INSERT_INDEX)), 197 | il.Create(OpCodes.Call, m_types.Module.ImportReference(typeof(Array).GetMethod("Copy", new[] { typeof(Array), typeof(int), typeof(Array), typeof(int), typeof(int) })))); 198 | 199 | // Now create our new message thread 200 | // AllThreads[INSERT_INDEX] = CreateThread((Location)0 /* Longteng Co. Ltd. */, /* stage unlocked at */ 6, "bigger-prototyping-area", Puzzles.Sandbox2, L.GetString("Bigger Prototyping Area", ""), (Enum2)2, 4, null); 201 | il.InsertBefore(endThreads, 202 | il.Create(OpCodes.Ldsfld, m_types.MessageThreads.AllThreads), 203 | il.Create(OpCodes.Ldc_I4_S, (sbyte)INSERT_INDEX), 204 | il.Create(OpCodes.Ldc_I4_0), 205 | il.Create(OpCodes.Ldc_I4_6), 206 | il.Create(OpCodes.Ldstr, "bigger-prototyping-area"), // Name of the messages file containing the email thread (also used to name the solution files on disk) 207 | il.Create(OpCodes.Ldsfld, m_types.Puzzles.Type.FindField("Sandbox2")), 208 | il.Create(OpCodes.Ldstr, "Bigger Prototyping Area"), // Name of the puzzle shown in the game 209 | il.Create(OpCodes.Ldstr, ""), 210 | il.Create(OpCodes.Call, m_types.L.GetString), 211 | il.Create(OpCodes.Ldc_I4_2), 212 | il.Create(OpCodes.Ldc_I4_4), 213 | il.Create(OpCodes.Ldnull), 214 | il.Create(OpCodes.Call, m_types.MessageThreads.CreateThread), 215 | il.Create(OpCodes.Stelem_Ref)); 216 | } 217 | 218 | private void CopyMessagesFile() 219 | { 220 | using (var stream = System.Reflection.Assembly.GetCallingAssembly().GetManifestResourceStream("ShenzhenMod.Content.messages.en.bigger-prototyping-area.txt")) 221 | { 222 | string path = Path.Combine(m_shenzhenDir, "Content", "messages.en", "bigger-prototyping-area.txt"); 223 | using (var file = File.Create(path)) 224 | { 225 | sm_log.InfoFormat("Writing resource file to \"{0}\"", path); 226 | stream.CopyTo(file); 227 | } 228 | 229 | // Although we haven't got a Chinese version, we need to have a corresponding file in messages.zh to avoid a crash. 230 | string path2 = Path.Combine(m_shenzhenDir, "Content", "messages.zh", "bigger-prototyping-area.txt"); 231 | sm_log.InfoFormat("Copying \"{0}\" to \"{1}\"", path, path2); 232 | File.Copy(path, path2, overwrite: true); 233 | } 234 | } 235 | } 236 | } 237 | 238 | -------------------------------------------------------------------------------- /ThirdParty/Licenses/log4net/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /ShenzhenMod/ShenzhenTypes.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Mono.Cecil; 3 | using Mono.Cecil.Cil; 4 | 5 | namespace ShenzhenMod 6 | { 7 | public class ShenzhenTypes 8 | { 9 | public class CircuitEditorScreenType 10 | { 11 | public readonly TypeDefinition Type; 12 | public readonly MethodDefinition Update; 13 | 14 | public CircuitEditorScreenType(ModuleDefinition module) 15 | { 16 | Type = module.FindType("GameLogic/CircuitEditorScreen"); 17 | 18 | // The Update() method is also defined on the IScreen interface, so get the name from there 19 | var updateMethod = module.FindType("IScreen").Methods.Single(m => m.Parameters.Count == 1 && m.Parameters[0].ParameterType == module.TypeSystem.Single); 20 | Update = Type.FindMethod(updateMethod.Name); 21 | } 22 | } 23 | 24 | public class GameLogicType 25 | { 26 | public readonly TypeDefinition Type; 27 | public readonly CircuitEditorScreenType CircuitEditorScreen; 28 | 29 | public GameLogicType(ModuleDefinition module) 30 | { 31 | Type = module.FindType("GameLogic"); 32 | CircuitEditorScreen = new CircuitEditorScreenType(module); 33 | } 34 | } 35 | 36 | public class GlobalsType 37 | { 38 | public readonly TypeDefinition Type; 39 | public readonly MethodDefinition ClassConstructor; 40 | public readonly FieldDefinition MaxBoardSize; 41 | 42 | public GlobalsType(ModuleDefinition module) 43 | { 44 | // The Solution constructor has a line like this: 45 | // this.someField = new HashSet[Globals.MaxTestRuns]; 46 | // We use this to find the "Globals" type. 47 | var ctor = module.FindMethod("Solution", ".ctor"); 48 | var instr = ctor.Body.Instructions.Single(i => i.OpCode == OpCodes.Newarr && i.Operand?.ToString() == "System.Collections.Generic.HashSet`1"); 49 | Type = ((FieldDefinition)instr.Previous.Operand).DeclaringType; 50 | ClassConstructor = Type.FindMethod(".cctor"); 51 | 52 | // Find "MaxBoardSize = new Index2(22, 14)" and use that to get the MaxBoardSize field 53 | MaxBoardSize = (FieldDefinition)ClassConstructor.FindInstruction(OpCodes.Ldc_I4_S, (sbyte)14).FindNext(OpCodes.Stsfld).Operand; 54 | } 55 | } 56 | 57 | public class Index2Type 58 | { 59 | public readonly TypeDefinition Type; 60 | public readonly MethodDefinition Constructor; 61 | public readonly FieldDefinition X; 62 | public readonly FieldDefinition Y; 63 | 64 | public Index2Type(ModuleDefinition module) 65 | { 66 | Type = module.FindType("Index2"); 67 | Constructor = Type.FindMethod(".ctor"); 68 | X = Type.Fields[1]; 69 | Y = Type.Fields[2]; 70 | } 71 | } 72 | 73 | public class LType 74 | { 75 | public readonly TypeDefinition Type; 76 | public readonly MethodDefinition GetString; 77 | 78 | public LType(ModuleDefinition module) 79 | { 80 | Type = module.FindType("L"); 81 | GetString = Type.Methods.Single(m => m.IsPublic && m.Parameters.Count == 2 82 | && m.Parameters.All(p => p.ParameterType == module.TypeSystem.String) 83 | && m.ReturnType == module.FindType("LocString")); 84 | } 85 | } 86 | 87 | public class MessageThreadType 88 | { 89 | public readonly TypeDefinition Type; 90 | public readonly MethodDefinition Constructor; 91 | 92 | public MessageThreadType(ModuleDefinition module) 93 | { 94 | Type = module.FindType("MessageThread"); 95 | Constructor = Type.FindMethod(".ctor"); 96 | } 97 | } 98 | 99 | public class MessageThreadsType 100 | { 101 | public readonly TypeDefinition Type; 102 | public readonly MethodDefinition ClassConstructor; 103 | public readonly MethodDefinition CreateAllThreads; 104 | public readonly MethodDefinition CreateThread; 105 | public readonly FieldDefinition AllThreads; 106 | 107 | public MessageThreadsType(ModuleDefinition module) 108 | { 109 | Type = module.FindType("MessageThreads"); 110 | ClassConstructor = Type.FindMethod(".cctor"); 111 | CreateAllThreads = Type.Methods.Single(m => m.IsPublic && m.IsStatic && m.Body.Variables.Count == 2 112 | && m.Body.Variables[0].VariableType == module.FindType("MessageThread")); 113 | CreateThread = Type.Methods.Single(m => m.IsPrivate && m.IsStatic && m.Parameters.Count == 8); 114 | AllThreads = Type.Fields.Single(f => f.FieldType.IsArray && f.FieldType.GetElementType() == module.FindType("MessageThread")); 115 | } 116 | } 117 | 118 | public class OptionalType 119 | { 120 | public readonly TypeDefinition Type; 121 | public readonly MethodDefinition ClassConstructor; 122 | 123 | private readonly MethodDefinition m_hasValue; 124 | private readonly MethodDefinition m_getValue; 125 | 126 | public MethodReference HasValue(TypeReference typeParam) 127 | { 128 | return m_hasValue.MakeGeneric(typeParam); 129 | } 130 | 131 | public MethodReference GetValue(TypeReference typeParam) 132 | { 133 | return m_getValue.MakeGeneric(typeParam); 134 | } 135 | 136 | public OptionalType(ModuleDefinition module) 137 | { 138 | // The Optional type has two fields: a boolean and a T 139 | Type = module.Types.Single(t => t.IsValueType && t.HasGenericParameters && t.Fields.Count == 2 140 | && t.Fields[0].FieldType == module.TypeSystem.Boolean && t.Fields[1].FieldType == t.GenericParameters[0]); 141 | m_hasValue = Type.Methods.Single(m => m.IsPublic && m.Parameters.Count == 0 && m.ReturnType == module.TypeSystem.Boolean); 142 | m_getValue = Type.Methods.Single(m => m.IsPublic && m.Parameters.Count == 0 && m.ReturnType == Type.GenericParameters[0]); 143 | } 144 | } 145 | 146 | public class PuzzleType 147 | { 148 | public readonly TypeDefinition Type; 149 | public readonly MethodDefinition Constructor; 150 | public readonly FieldDefinition Name; 151 | public readonly FieldDefinition IsSandbox; 152 | public readonly FieldDefinition Terminals; 153 | public readonly FieldDefinition Tiles; 154 | public readonly FieldDefinition GetTestRunOutputs; 155 | 156 | public PuzzleType(ModuleDefinition module) 157 | { 158 | Type = module.FindType("Puzzle"); 159 | Constructor = Type.FindMethod(".ctor"); 160 | Name = Type.Fields.First(f => f.FieldType == module.TypeSystem.String); 161 | IsSandbox = Type.Fields.First(f => f.FieldType == module.TypeSystem.Boolean); 162 | Terminals = Type.Fields.Single(f => f.FieldType.ToString() == "Terminal[]"); 163 | Tiles = Type.Fields.Single(f => f.FieldType.ToString() == "System.Int32[]"); 164 | GetTestRunOutputs = Type.Fields.Single(f => f.FieldType.ToString().StartsWith("System.Func`3")); 165 | 166 | } 167 | } 168 | 169 | public class PuzzlesType 170 | { 171 | public readonly TypeDefinition Type; 172 | public readonly MethodDefinition ClassConstructor; 173 | public readonly MethodDefinition FindPuzzle; 174 | 175 | public PuzzlesType(ModuleDefinition module) 176 | { 177 | Type = module.FindType("Puzzles"); 178 | ClassConstructor = Type.FindMethod(".cctor"); 179 | FindPuzzle = Type.Methods.Single(m => m.Parameters.Count == 1 && m.Parameters[0].ParameterType == module.TypeSystem.String); 180 | } 181 | } 182 | 183 | public class SolutionType 184 | { 185 | public readonly TypeDefinition Type; 186 | public readonly MethodDefinition Constructor; 187 | public readonly FieldDefinition PuzzleName; 188 | public readonly FieldDefinition Traces; 189 | 190 | public SolutionType(ModuleDefinition module, TracesType traces) 191 | { 192 | Type = module.FindType("Solution"); 193 | Constructor = Type.FindMethod(".ctor"); 194 | PuzzleName = Type.Fields.Where(f => f.FieldType == module.TypeSystem.String).Skip(1).First(); 195 | Traces = Type.Fields.Single(f => f.FieldType == traces.Type); 196 | } 197 | } 198 | 199 | public class TerminalType 200 | { 201 | public readonly TypeDefinition Type; 202 | 203 | public TerminalType(ModuleDefinition module) 204 | { 205 | Type = module.FindType("Terminal"); 206 | } 207 | } 208 | 209 | public class TextureManagerType 210 | { 211 | public readonly TypeDefinition Type; 212 | 213 | public TextureManagerType(ModuleDefinition module) 214 | { 215 | var textureType = module.FindType("Texture"); 216 | Type = module.Types.Single(t => t.Fields.Count > 24 && t.Fields.Take(10).All(f => f.FieldType == textureType)); 217 | } 218 | } 219 | 220 | public class TracesType 221 | { 222 | public readonly TypeDefinition Type; 223 | public readonly MethodDefinition GetSize; 224 | 225 | public TracesType(ModuleDefinition module) 226 | { 227 | var structType = module.Types.Single(t => t.IsValueType && t.Fields.Count == 2 228 | && t.Fields[0].FieldType == module.FindType("Index2") 229 | && t.Fields[1].FieldType == module.FindType("Trace")); 230 | 231 | // Traces is an IEnumerable of the above struct type 232 | Type = module.Types.Single(t => t.Interfaces.Count > 0 && t.Interfaces.Any(i => i.InterfaceType.ToString() == $"System.Collections.Generic.IEnumerable`1<{structType.Name}>")); 233 | GetSize = Type.Methods.Single(m => m.IsPublic && m.Parameters.Count == 0 && m.ReturnType == module.FindType("Index2")); 234 | } 235 | } 236 | 237 | public readonly ModuleDefinition Module; 238 | public readonly TypeSystem BuiltIn; 239 | 240 | public readonly GameLogicType GameLogic; 241 | public readonly GlobalsType Globals; 242 | public readonly Index2Type Index2; 243 | public readonly LType L; 244 | public readonly MessageThreadType MessageThread; 245 | public readonly MessageThreadsType MessageThreads; 246 | public readonly OptionalType Optional; 247 | public readonly PuzzleType Puzzle; 248 | public readonly PuzzlesType Puzzles; 249 | public readonly SolutionType Solution; 250 | public readonly TerminalType Terminal; 251 | public readonly TextureManagerType TextureManager; 252 | public readonly TracesType Traces; 253 | 254 | public ShenzhenTypes(ModuleDefinition module) 255 | { 256 | Module = module; 257 | BuiltIn = module.TypeSystem; 258 | 259 | GameLogic = new GameLogicType(module); 260 | Globals = new GlobalsType(module); 261 | Index2 = new Index2Type(module); 262 | L = new LType(module); 263 | MessageThread = new MessageThreadType(module); 264 | MessageThreads = new MessageThreadsType(module); 265 | Optional = new OptionalType(module); 266 | Puzzle = new PuzzleType(module); 267 | Puzzles = new PuzzlesType(module); 268 | Terminal = new TerminalType(module); 269 | TextureManager = new TextureManagerType(module); 270 | Traces = new TracesType(module); 271 | 272 | Solution = new SolutionType(module, Traces); 273 | } 274 | } 275 | } -------------------------------------------------------------------------------- /ShenzhenMod/Patches/IncreaseMaxBoardSize.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Mono.Cecil; 3 | using Mono.Cecil.Cil; 4 | using Mono.Cecil.Rocks; 5 | 6 | namespace ShenzhenMod.Patches 7 | { 8 | /// 9 | /// Patches the code to increase the maximum circuit board size. 10 | /// 11 | public class IncreaseMaxBoardSize 12 | { 13 | private static readonly log4net.ILog sm_log = log4net.LogManager.GetLogger(typeof(IncreaseMaxBoardSize)); 14 | 15 | private ShenzhenTypes m_types; 16 | 17 | public IncreaseMaxBoardSize(ShenzhenTypes types) 18 | { 19 | m_types = types; 20 | } 21 | 22 | public void Apply() 23 | { 24 | sm_log.Info("Applying patch"); 25 | 26 | ChangeMaxBoardSize(); 27 | ChangeScrollSize(); 28 | AddSizeToPuzzle(); 29 | PatchTileCreation(); 30 | PatchTraceReading(); 31 | PatchTraceWriting(); 32 | PatchCustomPuzzleReading(); 33 | } 34 | 35 | /// 36 | /// Changes the maximum circuit board size to a bigger size, to allow bigger puzzles. 37 | /// 38 | private void ChangeMaxBoardSize() 39 | { 40 | // Find "MaxBoardSize = new Index2(22, 14)" and change the values. 41 | var instr = m_types.Globals.ClassConstructor.FindInstruction(OpCodes.Ldc_I4_S, (sbyte)22); 42 | instr.Operand = (sbyte)44; 43 | instr.Next.Operand = (sbyte)28; 44 | } 45 | 46 | /// 47 | /// Changes the window size at which scrolling will be disabled. By default this 48 | /// is 1920x1080 because that's big enough for the largest built-in puzzles. We 49 | /// need to increase so we can have bigger puzzles. (Ideally we could find a way 50 | /// to make this be dynamically adjusted based on the current puzzle and the 51 | /// window size, but this is good enough for now.) 52 | /// 53 | private void ChangeScrollSize() 54 | { 55 | // Find the method which returns a bool and uses the constant 1920f 56 | var method = m_types.Globals.Type.Methods.Where(m => m.IsPublic && m.IsStatic && m.ReturnType == m_types.BuiltIn.Boolean) 57 | .Single(m => m.Body.Instructions.Any(i => i.Matches(OpCodes.Ldc_R4, 1920f))); 58 | method.FindInstruction(OpCodes.Ldc_R4, 1920f).Operand = 3840f; 59 | method.FindInstruction(OpCodes.Ldc_R4, 1080f).Operand = 2160f; 60 | } 61 | 62 | /// 63 | /// Adds fields to the Puzzle class to store its actual board size, and accessors for the board size. 64 | /// 65 | private void AddSizeToPuzzle() 66 | { 67 | var puzzleType = m_types.Puzzle.Type; 68 | var sizeField = new FieldDefinition("size", FieldAttributes.Private, m_types.Index2.Type); 69 | puzzleType.Fields.Add(sizeField); 70 | var isSizeSetField = new FieldDefinition("isSizeSet", FieldAttributes.Private, m_types.BuiltIn.Boolean); 71 | puzzleType.Fields.Add(isSizeSetField); 72 | 73 | AddSetSize(); 74 | AddGetSize(); 75 | 76 | void AddSetSize() 77 | { 78 | var method = new MethodDefinition("SetSize", MethodAttributes.Public, m_types.BuiltIn.Void); 79 | method.Parameters.Add(new ParameterDefinition("size", ParameterAttributes.None, m_types.Index2.Type)); 80 | puzzleType.Methods.Add(method); 81 | var il = method.Body.GetILProcessor(); 82 | 83 | // this.size = size 84 | il.Emit(OpCodes.Ldarg_0); 85 | il.Emit(OpCodes.Ldarg_1); 86 | il.Emit(OpCodes.Stfld, sizeField); 87 | 88 | // isSizeSet = true 89 | il.Emit(OpCodes.Ldarg_0); 90 | il.Emit(OpCodes.Ldc_I4_1); 91 | il.Emit(OpCodes.Stfld, isSizeSetField); 92 | 93 | il.Emit(OpCodes.Ret); 94 | } 95 | 96 | // Adds GetSize() to the Puzzle class. 97 | // This returns the size of the board if it has been set via SetSize(), and 98 | // defaults to (22, 14) if not. This is to avoid having to update all the existing 99 | // puzzles to set their size. 100 | void AddGetSize() 101 | { 102 | var method = new MethodDefinition("GetSize", MethodAttributes.Public, m_types.Index2.Type); 103 | puzzleType.Methods.Add(method); 104 | var il = method.Body.GetILProcessor(); 105 | var label1 = il.Create(OpCodes.Nop); 106 | 107 | // if (isSizeSet) 108 | il.Emit(OpCodes.Ldarg_0); 109 | il.Emit(OpCodes.Ldfld, isSizeSetField); 110 | il.Emit(OpCodes.Brfalse_S, label1); 111 | 112 | // return size 113 | il.Emit(OpCodes.Ldarg_0); 114 | il.Emit(OpCodes.Ldfld, sizeField); 115 | il.Emit(OpCodes.Ret); 116 | 117 | // return new Index2(22, 14) 118 | il.Append(label1); 119 | label1.Set(OpCodes.Ldc_I4_S, (sbyte)22); 120 | il.Emit(OpCodes.Ldc_I4_S, (sbyte)14); 121 | il.Emit(OpCodes.Newobj, m_types.Index2.Constructor); 122 | 123 | il.Emit(OpCodes.Ret); 124 | } 125 | } 126 | 127 | /// 128 | /// Patches the code that populates the tiles of the circuit board from the puzzle definition 129 | /// so that it uses the actual board size of the puzzle rather than the maximum board size. 130 | /// This allows us to add puzzles with custom board sizes without needing to change the size of 131 | /// existing puzzles. 132 | /// 133 | private void PatchTileCreation() 134 | { 135 | // This isn't a particularly intuitive way to find this class, but it's good enough for now 136 | var type = m_types.Module.Types.Single(t => t.Fields.Count == 0 && t.Methods.Any(m => m.ReturnType.ToString() == "Texture[]")); 137 | 138 | var method = type.Methods.Single(m => m.IsPublic && m.IsStatic && m.Parameters.Count == 1 && m.Parameters[0].ParameterType == m_types.Puzzle.Type); 139 | var il = method.Body.GetILProcessor(); 140 | 141 | var instructionsToReplace = method.FindInstructions(OpCodes.Ldsfld, m_types.Globals.MaxBoardSize); 142 | foreach (var instr in instructionsToReplace.ToList()) 143 | { 144 | il.InsertBefore(instr, il.Create(OpCodes.Ldarg_0)); 145 | il.InsertBefore(instr, il.Create(OpCodes.Call, m_types.Puzzle.Type.FindMethod("GetSize"))); 146 | il.Remove(instr); 147 | } 148 | } 149 | 150 | /// 151 | /// Patches the code that reads traces from a solution file so that it works if the 152 | /// puzzle has a smaller board size than the maximum board size. 153 | /// 154 | private void PatchTraceReading() 155 | { 156 | var method = m_types.Solution.Type.Methods.Single(m => m.IsPrivate && m.Parameters.Count == 2 157 | && m.Parameters[0].ParameterType.ToString().EndsWith("&")); 158 | var il = method.Body.GetILProcessor(); 159 | 160 | FixRowShuffling(); 161 | FixRowReading(); 162 | 163 | // Fix up branches instructions that can no longer be "short" branches 164 | method.Body.SimplifyMacros(); 165 | method.Body.OptimizeMacros(); 166 | 167 | // In memory, the traces are stored in reverse order to the solution file - i.e. row 0 168 | // is actually at the bottom of the screen. This means that when the traces are read in 169 | // from the solution file, they are inserted into the traces "grid" in reverse row order. 170 | // If there are less rows in the solution file than the max puzzle height, the rows need 171 | // to be shuffled down so that the last row is at index 0. By default, the code only 172 | // handles this for 22x11 and 22x14. We patch it so that it works for any number of rows. 173 | void FixRowShuffling() 174 | { 175 | /* Replace this: 176 | 177 | if (index == new Index2(0, 2)) 178 | { 179 | for (int j = 0; j < 22; j++) 180 | { 181 | for (int k = 0; k < 11; k++) 182 | { 183 | this.Traces.AddTrace(new Index2(j, k), this.Traces.GetTrace(new Index2(j, k + 3))); 184 | } 185 | for (int l = 11; l < 14; l++) 186 | { 187 | this.Traces.AddTrace(new Index2(j, l), this.Trace.None); 188 | } 189 | } 190 | } 191 | 192 | with this: 193 | 194 | int lastY = index.Y; 195 | if (lastY > 0) 196 | { 197 | int maxY = this.Traces.GetSize().Y; 198 | for (int j = 0; j < this.Traces.GetSize().X; j++) 199 | { 200 | for (int k = 0; k < maxY - lastY; k++) 201 | { 202 | this.Traces.AddTrace(new Index2(j, k), this.Traces.GetTrace(new Index2(j, k + lastY))); 203 | } 204 | for (int l = maxY - lastY; l < maxY; l++) 205 | { 206 | this.Traces.AddTrace(new Index2(j, l), Trace.None); 207 | } 208 | } 209 | } 210 | 211 | */ 212 | 213 | // Remove the "if (index == new Index2(0, 2))" check, but leave the first instruction so we don't break branches 214 | var ifIndexEquals02 = method.FindInstructionAtOffset(0x04be, OpCodes.Ldloc_S, method.Body.Variables[13]); 215 | var jLoopStart = method.FindInstructionAtOffset(0x04ce, OpCodes.Ldc_I4_0, null); 216 | il.RemoveRange(ifIndexEquals02.Next, jLoopStart); 217 | 218 | // Add two new variables 219 | var lastY = new VariableDefinition(m_types.BuiltIn.Int32); 220 | method.Body.Variables.Add(lastY); 221 | var maxY = new VariableDefinition(m_types.BuiltIn.Int32); 222 | method.Body.Variables.Add(maxY); 223 | 224 | // Initialize the two new variables and add the "if (lastY > 0)" check 225 | ifIndexEquals02.Set(OpCodes.Ldloca_S, method.Body.Variables[13]); 226 | il.InsertBefore(jLoopStart, 227 | il.Create(OpCodes.Ldfld, m_types.Index2.Y), 228 | il.Create(OpCodes.Stloc_S, lastY), 229 | il.Create(OpCodes.Ldloc_S, lastY), 230 | il.Create(OpCodes.Ldc_I4_0), 231 | il.Create(OpCodes.Ble, method.Body.Instructions.Last()), 232 | il.Create(OpCodes.Ldarg_0), 233 | il.Create(OpCodes.Ldfld, m_types.Solution.Traces), 234 | il.Create(OpCodes.Callvirt, m_types.Traces.GetSize), 235 | il.Create(OpCodes.Ldfld, m_types.Index2.Y), 236 | il.Create(OpCodes.Stloc_S, maxY)); 237 | 238 | // Replace "k + 3" with "k + lastY" 239 | method.FindInstruction(OpCodes.Ldc_I4_3, null).Set(OpCodes.Ldloc_S, lastY); 240 | 241 | // Replace "l < 14" with "l < maxY" 242 | method.FindInstruction(OpCodes.Ldc_I4_S, (sbyte)14).Set(OpCodes.Ldloc_S, maxY); 243 | 244 | // Replace "11" with "maxY - lastY" 245 | foreach (var instr in method.FindInstructions(OpCodes.Ldc_I4_S, (sbyte)11, 2).ToList()) 246 | { 247 | instr.Set(OpCodes.Ldloc_S, maxY); 248 | il.InsertBefore(instr.Next, 249 | il.Create(OpCodes.Ldloc_S, lastY), 250 | il.Create(OpCodes.Sub)); 251 | } 252 | 253 | // Replace "22" with "this.Traces.GetSize().X" 254 | var instr2 = method.FindInstruction(OpCodes.Ldc_I4_S, (sbyte)22); 255 | instr2.Set(OpCodes.Ldarg_0, null); 256 | il.InsertBefore(instr2.Next, 257 | il.Create(OpCodes.Ldfld, m_types.Solution.Traces), 258 | il.Create(OpCodes.Callvirt, m_types.Traces.GetSize), 259 | il.Create(OpCodes.Ldfld, m_types.Index2.X)); 260 | } 261 | 262 | // By default, the trace reading code assumes the width of the traces in the solution file 263 | // is the same as the max board width. We change it so that it uses the width of the rows 264 | // in the solution file instead. This allows it to correctly read (say) a 22x14 solution 265 | // if the max board size is bigger than 22x14. 266 | void FixRowReading() 267 | { 268 | /* Replace this: 269 | 270 | if (flag) 271 | { 272 | index2.x++; 273 | if (index2.x >= this.Traces.GetSize().x) 274 | { 275 | index2.x = 0; 276 | index2.y--; 277 | if (index2.y < 0) 278 | { 279 | break; 280 | } 281 | } 282 | } 283 | 284 | with this: 285 | 286 | else if (chr == '\n') 287 | { 288 | index2.x = 0; 289 | index2.y--; 290 | if (index2.y < 0) 291 | { 292 | break; 293 | } 294 | } 295 | if (flag) 296 | { 297 | index2.x++; 298 | } 299 | */ 300 | 301 | var ifFlag = method.FindInstructionAtOffset(0x0463, OpCodes.Ldloc_S, method.Body.Variables[16]); 302 | var ifXGreater = method.FindInstructionAtOffset(0x0473, OpCodes.Ldloc_S, method.Body.Variables[13]); 303 | var handleEOL = method.FindInstructionAtOffset(0x048c, OpCodes.Ldloca_S, method.Body.Variables[13]); 304 | var loopBodyEnd = method.FindInstructionAtOffset(0x04aa, OpCodes.Ldloc_S, method.Body.Variables[4]); 305 | 306 | // If the if before "if (flag)" is true, jump to the "if (flag" rather than falling through 307 | il.InsertBefore(ifFlag, il.Create(OpCodes.Br, ifFlag)); 308 | 309 | // Remove some of the instructions from the "if (flag)" block and shift some of them towards the bottom of the loop 310 | var removed = il.RemoveRange(ifFlag, handleEOL); 311 | il.InsertRangeBefore(loopBodyEnd, removed.TakeWhile(i => i != ifXGreater)); 312 | 313 | // Insert the '\n' check 314 | var checkNewLine = il.Create(OpCodes.Ldloc_S, method.Body.Variables[15]); 315 | il.InsertBefore(handleEOL, 316 | checkNewLine, 317 | il.Create(OpCodes.Ldc_I4_S, (sbyte)10), 318 | il.Create(OpCodes.Bne_Un, ifFlag)); 319 | 320 | // If the if before "if (flag)" is false, jump to our new '\n' check 321 | method.FindInstructionAtOffset(0x044b, OpCodes.Brfalse_S, ifFlag).Operand = checkNewLine; 322 | } 323 | } 324 | 325 | /// 326 | /// Patches the code that writes traces to a solution file so that it uses the board size of 327 | /// the puzzle rather than the maximum board size. This ensures that solutions for 328 | /// default puzzles are written out the same way even after we've increased the maximum 329 | /// board size. 330 | /// 331 | private void PatchTraceWriting() 332 | { 333 | // Look for the private method with a single StringBuilder parameter 334 | var method = m_types.Solution.Type.Methods.Single(m => m.IsPrivate && m.Parameters.Count == 1 && m.Parameters[0].ParameterType.ToString() == "System.Text.StringBuilder"); 335 | var il = method.Body.GetILProcessor(); 336 | 337 | /* Replace this: 338 | 339 | for (int i = this.Traces.GetSize().Y - 1; i >= 0; i--) 340 | { 341 | for (int j = 0; j < this.Traces.GetSize().X; j++) 342 | 343 | with this: 344 | 345 | Index2 size = this.Traces.GetSize(); 346 | Optional puzzle = Puzzles.FindPuzzle(this.PuzzleName); 347 | if (puzzle.HasValue()) 348 | { 349 | size = puzzle.Value().GetSize(); 350 | } 351 | for (int i = size.int_1 - 1; i >= 0; i--) 352 | { 353 | for (int j = 0; j < size.int_0; j++) 354 | 355 | */ 356 | 357 | var puzzleType = m_types.Puzzle.Type; 358 | var solutionType = m_types.Solution.Type; 359 | 360 | // Add two new variables 361 | var size = new VariableDefinition(m_types.Index2.Type); 362 | method.Body.Variables.Add(size); 363 | var puzzle = new VariableDefinition(m_types.Optional.Type.MakeGenericInstanceType(puzzleType)); 364 | method.Body.Variables.Add(puzzle); 365 | 366 | var loopStart = method.FindInstructionAtOffset(0x0010, OpCodes.Ldarg_0, null); 367 | 368 | il.InsertBefore(loopStart, 369 | // size = this.Traces.GetSize(); 370 | il.Create(OpCodes.Ldarg_0), 371 | il.Create(OpCodes.Ldfld, m_types.Solution.Traces), 372 | il.Create(OpCodes.Callvirt, m_types.Traces.GetSize), 373 | il.Create(OpCodes.Stloc_S, size), 374 | 375 | // Optional puzzle = Puzzles.FindPuzzle(this.PuzzleName); 376 | il.Create(OpCodes.Ldarg_0), 377 | il.Create(OpCodes.Ldfld, m_types.Solution.PuzzleName), 378 | il.Create(OpCodes.Call, m_types.Puzzles.FindPuzzle), 379 | il.Create(OpCodes.Stloc_S, puzzle), 380 | 381 | // if (puzzle.HasValue()) 382 | il.Create(OpCodes.Ldloca_S, puzzle), 383 | il.Create(OpCodes.Call, m_types.Optional.HasValue(puzzleType)), 384 | il.Create(OpCodes.Brfalse_S, loopStart), 385 | 386 | // size = puzzle.GetValue().GetSize(); 387 | il.Create(OpCodes.Ldloca_S, puzzle), 388 | il.Create(OpCodes.Call, m_types.Optional.GetValue(puzzleType)), 389 | il.Create(OpCodes.Call, puzzleType.FindMethod("GetSize")), 390 | il.Create(OpCodes.Stloc_S, size)); 391 | 392 | // Replace "this.Traces.GetSize()" with "size" in the first loop 393 | loopStart.Set(OpCodes.Ldloc_S, size); 394 | il.Remove(loopStart.Next); 395 | il.Remove(loopStart.Next); 396 | 397 | // Replace "this.Traces.GetSize()" with "size" in the second loop 398 | var loop2Condition = method.FindInstructionAtOffset(0x005C, OpCodes.Ldarg_0, null); 399 | loop2Condition.Set(OpCodes.Ldloc_S, size); 400 | il.Remove(loop2Condition.Next); 401 | il.Remove(loop2Condition.Next); 402 | } 403 | 404 | /// 405 | /// Patches the code that reads a custom puzzle definition so that it uses the correct board size. 406 | /// 407 | private void PatchCustomPuzzleReading() 408 | { 409 | var method = m_types.Module.FindType("CustomLevelCompiler").Methods.Single(m => m.IsPrivate && m.Parameters.Count == 2 410 | && m.Parameters[0].ParameterType == m_types.BuiltIn.String && m.Parameters[1].ParameterType.ToString() == "System.Collections.Generic.Dictionary`2"); 411 | var il = method.Body.GetILProcessor(); 412 | 413 | // Change the code that gets the max board size to instead use the default board size of 22x14. 414 | // Technically custom puzzles are always 18x7, but to avoid changing the file format of existing 415 | // solutions we use the old default of 22x14 instead. 416 | var instrs = method.FindInstructions(OpCodes.Ldsfld, m_types.Globals.MaxBoardSize, 2).ToList(); 417 | instrs[0].Set(OpCodes.Ldc_I4_S, (sbyte)22); 418 | il.Remove(instrs[0].Next); 419 | 420 | instrs[1].Set(OpCodes.Ldc_I4_S, (sbyte)14); 421 | il.Remove(instrs[1].Next); 422 | } 423 | } 424 | } 425 | 426 | --------------------------------------------------------------------------------