├── .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 |
--------------------------------------------------------------------------------