├── TODO ├── API ├── package_icon.png ├── Properties │ ├── AssemblyInfo.cs │ └── Versioning.cs ├── API.csproj ├── SubscriptionWarning.cs └── HarmonyHelper.cs ├── HarmonyMod ├── Resources │ └── HarmonyLogo.png ├── Properties │ ├── AssemblyInfo.cs │ └── Versioning.cs ├── Patches │ ├── PackageEntry_SetEntryPatch.cs │ ├── PluginManager_OnDestroyPatch.cs │ ├── UnityEngineDebug_LogException.cs │ ├── UIView_ForwardExceptionPatch.cs │ └── PluginManager_userModInstancePatch.cs ├── Source │ ├── HarmonyModACLException.cs │ ├── LoadingExtension.cs │ ├── HarmonyModSupportException.cs │ ├── AssemblyNameCompare.cs │ ├── Installer.cs │ ├── TextureResources.cs │ ├── TypeExtensions.cs │ ├── Harmony1StateTransfer.cs │ ├── Handover.cs │ └── Patcher.cs └── HarmonyMod.csproj ├── nuget-repo ├── citiesharmony.api.1.0.3.nupkg ├── citiesharmony.api.1.0.4.nupkg ├── citiesharmony.api.1.0.5.nupkg ├── citiesharmony.api.1.0.6.nupkg ├── citiesharmony.api.2.0.0.nupkg └── citiesharmony.api.2.0.0-rc.nupkg ├── PatchTooEarly_1_0_6 ├── Class1.cs └── PatchTooEarly_1_0_6.csproj ├── Compat-CitiesHarmony-Mod ├── Properties │ ├── StrongName.pfx │ └── AssemblyInfo.cs ├── Installer.cs └── Compat-CitiesHarmony-Mod.csproj ├── BadMods ├── API-1.0.6 │ └── PatchTooEarly_1_0_6 │ │ ├── PatchTooEarly_1_0_6.csproj │ │ └── PatchTooEarlyMod.cs └── PatchTooEarly │ ├── PatchTooEarly.csproj │ └── PatchTooEarly.cs ├── .gitmodules ├── IAmAware ├── ModReportBase.cs ├── IAwareness0.csproj ├── Properties │ └── AssemblyInfo.cs └── IAmAware.cs ├── Test.Harmony ├── Test.Harmony.csproj ├── TesterMod.cs └── HarmonyTests │ ├── Specials.cs │ ├── AttributePatchTest.cs │ ├── ReturningStructs.cs │ ├── ReturningStructMethods.cs │ └── ACLTest.cs ├── ExampleMod ├── ExampleMod.csproj ├── ExampleMod.cs └── Patcher.cs ├── .gitignore ├── README.md └── MonoMod.Common.props /TODO: -------------------------------------------------------------------------------- 1 | * Remove Hardcoded References paths C:\References 2 | 3 | -------------------------------------------------------------------------------- /API/package_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drok/Harmony-CitiesSkylines/HEAD/API/package_icon.png -------------------------------------------------------------------------------- /HarmonyMod/Resources/HarmonyLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drok/Harmony-CitiesSkylines/HEAD/HarmonyMod/Resources/HarmonyLogo.png -------------------------------------------------------------------------------- /nuget-repo/citiesharmony.api.1.0.3.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drok/Harmony-CitiesSkylines/HEAD/nuget-repo/citiesharmony.api.1.0.3.nupkg -------------------------------------------------------------------------------- /nuget-repo/citiesharmony.api.1.0.4.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drok/Harmony-CitiesSkylines/HEAD/nuget-repo/citiesharmony.api.1.0.4.nupkg -------------------------------------------------------------------------------- /nuget-repo/citiesharmony.api.1.0.5.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drok/Harmony-CitiesSkylines/HEAD/nuget-repo/citiesharmony.api.1.0.5.nupkg -------------------------------------------------------------------------------- /nuget-repo/citiesharmony.api.1.0.6.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drok/Harmony-CitiesSkylines/HEAD/nuget-repo/citiesharmony.api.1.0.6.nupkg -------------------------------------------------------------------------------- /nuget-repo/citiesharmony.api.2.0.0.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drok/Harmony-CitiesSkylines/HEAD/nuget-repo/citiesharmony.api.2.0.0.nupkg -------------------------------------------------------------------------------- /PatchTooEarly_1_0_6/Class1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PatchTooEarly_1_0_6 4 | { 5 | public class Class1 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /nuget-repo/citiesharmony.api.2.0.0-rc.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drok/Harmony-CitiesSkylines/HEAD/nuget-repo/citiesharmony.api.2.0.0-rc.nupkg -------------------------------------------------------------------------------- /Compat-CitiesHarmony-Mod/Properties/StrongName.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drok/Harmony-CitiesSkylines/HEAD/Compat-CitiesHarmony-Mod/Properties/StrongName.pfx -------------------------------------------------------------------------------- /PatchTooEarly_1_0_6/PatchTooEarly_1_0_6.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BadMods/API-1.0.6/PatchTooEarly_1_0_6/PatchTooEarly_1_0_6.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /API/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | [assembly: System.Reflection.AssemblyCompanyAttribute("Radu Hociung")] 2 | [assembly: System.Reflection.AssemblyCopyrightAttribute("Copyright 2021 Radu Hociung")] 3 | [assembly: System.Reflection.AssemblyDescriptionAttribute("Auto-installs and gives access to the Harmony Mod from the Steam Workshop")] 4 | [assembly: System.Reflection.AssemblyProductAttribute("Harmony.API")] 5 | [assembly: System.Reflection.AssemblyTitleAttribute("Lib.Harmony Helper for Cities: Skylines")] 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Harmony-v2"] 2 | path = Harmony-v2 3 | url = https://github.com/drok/Harmony 4 | branch = maintenance-v2 5 | [submodule "Compat-Harmony-2.0.0.9"] 6 | path = Compat-Harmony-2.0.0.9 7 | url = https://github.com/drok/Harmony 8 | branch = maintenance-2.0.0.9 9 | [submodule "Compat-Harmony-2.0.1.0"] 10 | path = Compat-Harmony-2.0.1.0 11 | url = https://github.com/drok/Harmony 12 | branch = maintenance-2.0.1.0 13 | [submodule "Compat-CitiesHarmony.Harmony-2.0.4.0"] 14 | path = Compat-CitiesHarmony.Harmony-2.0.4.0 15 | url = https://github.com/drok/Harmony 16 | branch = maintenance-CitiesHarmony.Harmony-2.0.4.0 17 | [submodule "MonoMod.Common"] 18 | path = MonoMod.Common 19 | url = https://github.com/drok/MonoMod.Common 20 | branch = maintenance-Harmony-CitiesSkylines 21 | -------------------------------------------------------------------------------- /HarmonyMod/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: System.Reflection.AssemblyCompanyAttribute("Radu Hociung")] 4 | [assembly: System.Reflection.AssemblyProductAttribute("Modding Infrastructure")] 5 | [assembly: System.Reflection.AssemblyTitleAttribute("Harmony Mod for Cities Skylines")] 6 | 7 | /* Allow integration tests unrestricted access for testing */ 8 | [assembly: InternalsVisibleTo("Test.Harmony, PublicKey=" + 9 | "00240000048000009400000006020000002400005253413100040000010001009d0f13cde5b126" + 10 | "c67d0c94873430cc171f8919863c6218a5bc1788a91caf6c197a851fdd4e5df5fe68726b5ca92a" + 11 | "cd2a47770cde3eb1538693a427a6c7591878b59dacc8fd24339f0e77f923ada3f80133f3a5b182" + 12 | "d7d04b16fb7bd02abff840b4b4ed9114463fef35c3437385205ebed7906a29ce6bd16a84e50129" + 13 | "8c8224ba")] 14 | -------------------------------------------------------------------------------- /HarmonyMod/Patches/PackageEntry_SetEntryPatch.cs: -------------------------------------------------------------------------------- 1 | extern alias Harmony2; 2 | using Harmony2::HarmonyLib; 3 | using JetBrains.Annotations; 4 | using System; 5 | 6 | namespace HarmonyMod.MyPatches 7 | { 8 | 9 | [HarmonyPatch(typeof(PackageEntry), "SetEntry", new Type[] { typeof(EntryData), })] 10 | internal class PackageEntry_SetEntryPatch 11 | { 12 | /// 13 | /// when loading asset from a file, IAssetData.OnAssetLoaded() is called for all assets but the one that is loaded from the file. 14 | /// this postfix calls IAssetData.OnAssetLoaded() for asset loaded from file. 15 | /// 16 | [HarmonyPostfix] 17 | [UsedImplicitly] 18 | public static void Postfix(PackageEntry __instance, EntryData data) 19 | { 20 | (Mod.mainMod.userModInstance as Mod).report.OnContentManagerSetEntry(__instance, data.pluginInfo); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /HarmonyMod/Patches/PluginManager_OnDestroyPatch.cs: -------------------------------------------------------------------------------- 1 | extern alias Harmony2; 2 | using Harmony2::HarmonyLib; 3 | using JetBrains.Annotations; 4 | using System; 5 | using ColossalFramework.Plugins; 6 | 7 | namespace HarmonyMod.MyPatches 8 | { 9 | 10 | [HarmonyPatch(typeof(PluginManager), "OnDestroy", new Type[] {})] 11 | internal class PluginManager_OnDestroyPatch 12 | { 13 | [HarmonyPrefix] 14 | [UsedImplicitly] 15 | public static void Prefix(PluginManager __instance) 16 | { 17 | Mod.mainModInstance.OnPluginManagerDestroyStart(); 18 | } 19 | 20 | [HarmonyFinalizer] 21 | [UsedImplicitly] 22 | public static Exception Finalizer(Exception __exception) 23 | { 24 | if (__exception is Exception) 25 | { 26 | UnityEngine.Debug.LogError($"[{Versioning.FULL_PACKAGE_NAME}] PluginManager.OnDestroy() threw {__exception.GetType().FullName}: {__exception.Message}\n{__exception.StackTrace}"); 27 | } 28 | Mod.mainModInstance.OnPluginManagerDestroyDone(); 29 | return null; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /IAmAware/ModReportBase.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Harmony for Cities Skylines 3 | * Copyright (C) 2021 Radu Hociung 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | using System.Collections.Generic; 21 | using System.Reflection; 22 | 23 | namespace IAwareness 24 | { 25 | internal abstract class ModReportBase 26 | { 27 | public abstract HashSet missingAssemblies { get; } 28 | public abstract uint numProblems { get; } 29 | public abstract uint numProblemsCaused { get; } 30 | public abstract bool usesHarmony { get; } 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /HarmonyMod/Source/HarmonyModACLException.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Harmony for Cities Skylines 3 | * Copyright (C) 2021 Radu Hociung 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | using System; 21 | using System.Collections.Generic; 22 | using System.Linq; 23 | using System.Text; 24 | using System.Reflection; 25 | 26 | namespace HarmonyMod 27 | 28 | { 29 | /// An exception caused by a user call to the library. 30 | /// 31 | [Serializable] 32 | public class HarmonyModACLException : Exception 33 | { 34 | public HarmonyModACLException(string message) 35 | : base(message) 36 | { 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Compat-CitiesHarmony-Mod/Installer.cs: -------------------------------------------------------------------------------- 1 | namespace CitiesHarmony 2 | { 3 | using HarmonyLib; 4 | using System.Diagnostics; 5 | using System.Reflection; 6 | 7 | public static class Installer { 8 | 9 | /* Installer.Run() is a legacy static method called by boformer's CitiesHarmony.API.HarmonyHelper, 10 | * various versions, so it must not be removed, for backward compatibility 11 | * If this method exists, it understands that the CitiesHarmony Mod is installed 12 | * */ 13 | public static void Run() { 14 | var stack = new StackTrace(); 15 | var lastCaller = stack.GetFrame(0).GetMethod(); 16 | MethodBase caller = lastCaller; 17 | int assemblyDepth = 0; 18 | /* Search in the stack for the assembly that called 19 | * my caller(CitiesHarmony.API) 20 | */ 21 | for (int i = 1; i < stack.FrameCount && assemblyDepth < 3; ++i) 22 | { 23 | caller = stack.GetFrame(i).GetMethod(); 24 | if (lastCaller.DeclaringType.Assembly.FullName != caller.DeclaringType.Assembly.FullName) 25 | { 26 | lastCaller = caller; 27 | ++assemblyDepth; 28 | } 29 | } 30 | 31 | if (!Harmony.harmonyUsers.TryGetValue(caller.DeclaringType.Assembly.FullName, out var userStatus)) { 32 | Harmony.harmonyUsers[caller.DeclaringType.Assembly.FullName] = new Harmony.HarmonyUser() { instancesCreated = 0, checkBeforeUse = true, }; 33 | } 34 | } 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /HarmonyMod/Source/LoadingExtension.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Harmony for Cities Skylines 3 | * Copyright (C) 2021 Radu Hociung 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | using ICities; 21 | 22 | namespace HarmonyMod 23 | { 24 | class LoadingExtension : LoadingExtensionBase 25 | { 26 | public override void OnCreated(ILoading loading) 27 | { 28 | base.OnCreated(loading); 29 | #if TRACE 30 | UnityEngine.Debug.LogWarning($"[{Versioning.FULL_PACKAGE_NAME}] INFO: LoadingExtension.OnCreated(mode={loading.currentMode}, complete={loading.loadingComplete}"); 31 | #endif 32 | 33 | } 34 | 35 | public override void OnReleased() 36 | { 37 | base.OnReleased(); 38 | #if TRACE 39 | UnityEngine.Debug.LogWarning($"[{Versioning.FULL_PACKAGE_NAME}] INFO: LoadingExtension.OnReleased()"); 40 | #endif 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Compat-CitiesHarmony-Mod/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("Compat-CitiesHarmony-Mod")] 9 | [assembly: AssemblyDescription("Stub for Backward compatibility with boformer's CitiesHarmony mod")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Radu Hociung")] 12 | [assembly: AssemblyProduct("Compat-CitiesHarmony-Mod")] 13 | [assembly: AssemblyCopyright("Copyright Radu Hociung © 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("d80afd7c-cb94-4696-b1f8-6255a5b07fb0")] 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("0.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /BadMods/PatchTooEarly/PatchTooEarly.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | {EBC32B21-119F-4D23-9B31-61B4DEFFC16F} 4 | net35 5 | false 6 | ExampleMod 7 | 1.0.* 8 | false 9 | 10 | 11 | 12 | C:\References\Assembly-CSharp.dll 13 | False 14 | 15 | 16 | C:\References\ColossalManaged.dll 17 | False 18 | 19 | 20 | C:\References\ICities.dll 21 | False 22 | 23 | 24 | C:\References\UnityEngine.dll 25 | False 26 | 27 | 28 | 29 | 30 | 31 | 32 | none 33 | false 34 | 35 | 36 | 37 | $(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(ProjectName)\ 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /API/Properties/Versioning.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | #if LABS || DEBUG 6 | [assembly: AssemblyVersion("0.0.0.0")] 7 | #else 8 | [assembly: AssemblyVersion("0.0.0.0")] 9 | [assembly: AssemblyFileVersion(CitiesHarmony.API.Versioning.MyFileVersion)] 10 | [assembly: AssemblyInformationalVersionAttribute(CitiesHarmony.API.Versioning.MyInformationalVersion)] 11 | #endif 12 | 13 | namespace CitiesHarmony.API 14 | { 15 | public static class Versioning 16 | { 17 | public const string PACKAGE_NAME = "Harmony.API"; 18 | public const string MyFileVersion = "0.9.0"; 19 | public const string MyInformationalVersion = MyFileVersion + POSTFIX; 20 | public const string FULL_PACKAGE_NAME = PACKAGE_NAME + " " + MyInformationalVersion; 21 | public const uint MyVersionNumber = 0x00090000; 22 | 23 | public struct Obsolescence 24 | { 25 | /* Versions at which various features will be disabled. 26 | * They should not be removed from the source code, because 27 | * they serve as documentation for how older versions worked. 28 | * The compiler with automatically remove the dead code. 29 | */ 30 | } 31 | 32 | public static string VersionString(uint number) 33 | { 34 | return new System.Version( 35 | (int)((number >> 24) & 0xff), 36 | (int)((number >> 16) & 0xff), 37 | (int)((number >> 8) & 0xff), 38 | (int)(number & 0xff)).ToString(); 39 | } 40 | 41 | public static bool IsObsolete(uint ver, string explanation) 42 | { 43 | bool obsolete = MyVersionNumber >= ver; 44 | if (!obsolete) 45 | { 46 | UnityEngine.Debug.LogWarning($"[{Versioning.FULL_PACKAGE_NAME}] WARNING '{explanation}' will be removed at release {VersionString(ver)}"); 47 | } 48 | return obsolete; 49 | } 50 | 51 | #if DEBUG 52 | const string POSTFIX = "-DEBUG"; 53 | #elif BETA 54 | const string POSTFIX = "-BETA"; 55 | #else 56 | const string POSTFIX = ""; 57 | #endif 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /HarmonyMod/Patches/UnityEngineDebug_LogException.cs: -------------------------------------------------------------------------------- 1 | extern alias Harmony2; 2 | using Harmony2::HarmonyLib; 3 | using JetBrains.Annotations; 4 | using ColossalFramework.UI; 5 | using System; 6 | 7 | namespace HarmonyMod.MyPatches 8 | { 9 | 10 | [HarmonyPatch(typeof(UnityEngine.Debug), "LogException", new Type[] { typeof(Exception), })] 11 | [HarmonyPriority(Priority.First)] 12 | internal class UnityEngineDebug_LogException 13 | { 14 | /// 15 | /// when loading asset from a file, IAssetData.OnAssetLoaded() is called for all assets but the one that is loaded from the file. 16 | /// this postfix calls IAssetData.OnAssetLoaded() for asset loaded from file. 17 | /// 18 | [HarmonyPrefix] 19 | [UsedImplicitly] 20 | public static bool Prefix(Exception exception) 21 | { 22 | /* Return false if the Plugin report was successful 23 | * Allows up to 3 (MAX_EXCEPTION_PROMPTS_PER_MOD) ExceptionPanel popups per mod, 24 | * suppressing the rest. 25 | * 26 | * All exceptions are logged and reported in the Harmony report. 27 | */ 28 | Mod.mainModInstance?.report.TryReportPlugin(exception); 29 | return true; 30 | } 31 | } 32 | 33 | [HarmonyPatch(typeof(UnityEngine.Debug), "LogException", new Type[] { typeof(Exception), typeof(UnityEngine.Object),})] 34 | [HarmonyPriority(Priority.First)] 35 | internal class UnityEngineDebug_LogException_withObject 36 | { 37 | /// 38 | /// when loading asset from a file, IAssetData.OnAssetLoaded() is called for all assets but the one that is loaded from the file. 39 | /// this postfix calls IAssetData.OnAssetLoaded() for asset loaded from file. 40 | /// 41 | [HarmonyPrefix] 42 | [UsedImplicitly] 43 | public static bool Prefix(Exception exception, UnityEngine.Object context) 44 | { 45 | /* Return false if the Plugin report was successful 46 | * Allows up to 3 (MAX_EXCEPTION_PROMPTS_PER_MOD) ExceptionPanel popups per mod, 47 | * suppressing the rest. 48 | * 49 | * All exceptions are logged and reported in the Harmony report. 50 | */ 51 | Mod.mainModInstance?.report.TryReportPlugin(exception); 52 | return true; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Test.Harmony/Test.Harmony.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | {47363338-1B57-467C-93B9-DD9FB293AF27} 4 | net35 5 | false 6 | Test.Harmony 7 | 1.0.* 8 | false 9 | 0.9.0;1.0.3;1.0.4;1.0.5;1.0.6;2.0.0 10 | 11 | 12 | false 13 | true 14 | Properties\StrongName.pfx 15 | 16 | 17 | false 18 | full 19 | true 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | C:\References\Assembly-CSharp.dll 32 | False 33 | 34 | 35 | C:\References\ColossalManaged.dll 36 | False 37 | 38 | 39 | C:\References\IAmAware-0.0.1.0.dll 40 | 41 | 42 | C:\References\ICities.dll 43 | False 44 | 45 | 46 | C:\References\UnityEngine.dll 47 | False 48 | 49 | 50 | 51 | 52 | $(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(ProjectName)-$(Configuration)\ 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /BadMods/PatchTooEarly/PatchTooEarly.cs: -------------------------------------------------------------------------------- 1 | using CitiesHarmony.API; 2 | using HarmonyLib; 3 | using ICities; 4 | using System.Reflection; 5 | 6 | namespace ExampleMod { 7 | public class Mod : IUserMod { 8 | // You can add Harmony 2.0.0.9 as a dependency, but make sure that 0Harmony.dll is not copied to the output directory! 9 | // (0Harmony.dll is provided by CitiesHarmony workshop item) 10 | 11 | // Also make sure that HarmonyLib is not referenced in any way in your IUserMod implementation! 12 | // Instead, apply your patches from a separate static patcher class! 13 | // (otherwise it will fail to instantiate the type when CitiesHarmony is not installed) 14 | 15 | public string Name => "Harmony 2 Example"; 16 | public string Description => "Patches SimulationManager.CreateRelay and LoadingManager.MetaDataLoaded"; 17 | 18 | public void OnEnabled() { 19 | HarmonyHelper.DoOnHarmonyReady(() => Patcher.PatchAll()); 20 | } 21 | 22 | public void OnDisabled() { 23 | if (HarmonyHelper.IsHarmonyInstalled) Patcher.UnpatchAll(); 24 | } 25 | } 26 | 27 | public static class Patcher { 28 | private const string HarmonyId = "boformer.Harmony2Example"; 29 | 30 | private static bool patched = false; 31 | 32 | public static void PatchAll() { 33 | if (patched) return; 34 | 35 | UnityEngine.Debug.Log("Harmony 2 Example: Patching..."); 36 | 37 | patched = true; 38 | 39 | // Apply your patches here! 40 | // Harmony.DEBUG = true; 41 | var harmony = new Harmony("boformer.Harmony2Example"); 42 | harmony.PatchAll(Assembly.GetExecutingAssembly()); 43 | } 44 | 45 | public static void UnpatchAll() { 46 | if (!patched) return; 47 | 48 | var harmony = new Harmony(HarmonyId); 49 | harmony.UnpatchAll(HarmonyId); 50 | 51 | patched = false; 52 | 53 | UnityEngine.Debug.Log("Harmony 2 Example: Reverted..."); 54 | } 55 | } 56 | 57 | // Random example patch 58 | [HarmonyPatch(typeof(SimulationManager), "CreateRelay")] 59 | public static class SimulationManagerCreateRelayPatch { 60 | public static void Prefix() { 61 | UnityEngine.Debug.Log("CreateRelay Prefix"); 62 | } 63 | } 64 | 65 | // Random example patch 66 | [HarmonyPatch(typeof(LoadingManager), "MetaDataLoaded")] 67 | public static class LoadingManagerMetaDataLoadedPatch { 68 | public static void Prefix() { 69 | UnityEngine.Debug.Log("MetaDataLoaded Prefix"); 70 | } 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /BadMods/API-1.0.6/PatchTooEarly_1_0_6/PatchTooEarlyMod.cs: -------------------------------------------------------------------------------- 1 | using CitiesHarmony.API; 2 | using HarmonyLib; 3 | using ICities; 4 | using System.Reflection; 5 | 6 | namespace PatchTooEarly_1_0_6 7 | { 8 | public class PatchTooEarlyMod : IUserMod 9 | { 10 | // You can add Harmony 2.0.0.9 as a dependency, but make sure that 0Harmony.dll is not copied to the output directory! 11 | // (0Harmony.dll is provided by CitiesHarmony workshop item) 12 | 13 | // Also make sure that HarmonyLib is not referenced in any way in your IUserMod implementation! 14 | // Instead, apply your patches from a separate static patcher class! 15 | // (otherwise it will fail to instantiate the type when CitiesHarmony is not installed) 16 | 17 | public string Name => "Harmony 2 Example"; 18 | public string Description => "Patches SimulationManager.CreateRelay and LoadingManager.MetaDataLoaded"; 19 | 20 | public void OnEnabled() 21 | { 22 | HarmonyHelper.DoOnHarmonyReady(() => Patcher.PatchAll()); 23 | } 24 | 25 | public void OnDisabled() 26 | { 27 | if (HarmonyHelper.IsHarmonyInstalled) Patcher.UnpatchAll(); 28 | } 29 | } 30 | 31 | public static class Patcher 32 | { 33 | private const string HarmonyId = "boformer.Harmony2Example"; 34 | 35 | private static bool patched = false; 36 | 37 | public static void PatchAll() 38 | { 39 | if (patched) return; 40 | 41 | UnityEngine.Debug.Log("Harmony 2 Example: Patching..."); 42 | 43 | patched = true; 44 | 45 | // Apply your patches here! 46 | // Harmony.DEBUG = true; 47 | var harmony = new Harmony("boformer.Harmony2Example"); 48 | harmony.PatchAll(Assembly.GetExecutingAssembly()); 49 | } 50 | 51 | public static void UnpatchAll() 52 | { 53 | if (!patched) return; 54 | 55 | var harmony = new Harmony(HarmonyId); 56 | harmony.UnpatchAll(HarmonyId); 57 | 58 | patched = false; 59 | 60 | UnityEngine.Debug.Log("Harmony 2 Example: Reverted..."); 61 | } 62 | } 63 | 64 | // Random example patch 65 | [HarmonyPatch(typeof(SimulationManager), "CreateRelay")] 66 | public static class SimulationManagerCreateRelayPatch 67 | { 68 | public static void Prefix() 69 | { 70 | UnityEngine.Debug.Log("CreateRelay Prefix"); 71 | } 72 | } 73 | 74 | // Random example patch 75 | [HarmonyPatch(typeof(LoadingManager), "MetaDataLoaded")] 76 | public static class LoadingManagerMetaDataLoadedPatch 77 | { 78 | public static void Prefix() 79 | { 80 | UnityEngine.Debug.Log("MetaDataLoaded Prefix"); 81 | } 82 | } 83 | } 84 | 85 | -------------------------------------------------------------------------------- /HarmonyMod/Source/HarmonyModSupportException.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Harmony for Cities Skylines 3 | * Copyright (C) 2021 Radu Hociung 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | using System; 21 | using System.Collections.Generic; 22 | using System.Linq; 23 | using System.Text; 24 | using System.Reflection; 25 | 26 | namespace HarmonyMod 27 | 28 | { 29 | /// An exception caused by a user call to the library. 30 | /// 31 | [Serializable] 32 | public class HarmonyModSupportException : Exception 33 | { 34 | public class UnsupportedAssembly 35 | { 36 | public UnsupportedAssembly(Assembly assembly, bool pullRequired) 37 | { 38 | this.assembly = assembly; 39 | isPullRequestRequired = pullRequired; 40 | } 41 | 42 | public bool isPullRequestRequired { get; private set; } 43 | public string Message 44 | { 45 | get 46 | { 47 | if (isPullRequestRequired) 48 | { 49 | return $"HarmonyLib version {assembly.GetName().Version} requires" + 50 | $" a compatibility patch. Submit it at https://github.com/drok/Harmony-CitiesSkylines/issues"; 51 | } else 52 | { 53 | return $"HarmonyLib version {assembly.GetName().Version} is not supported by HarmonyMod yet. Please enter" + 54 | $" a feature request at https://github.com/drok/Harmony-CitiesSkylines/issues"; 55 | } 56 | } 57 | } 58 | public Assembly assembly { get; private set; } 59 | } 60 | public List unsupportedAssemblies { get; private set; } 61 | 62 | /// The Harmony Instance that was in use when the exception occured 63 | /// 64 | internal HarmonyModSupportException(List assemblies) 65 | : base() 66 | { 67 | unsupportedAssemblies = assemblies; 68 | } 69 | public override string Message { 70 | get { 71 | string list = null; 72 | 73 | unsupportedAssemblies.ForEach((a) => 74 | { 75 | list += (list is null ? "" : ", ") + a.assembly.GetName().Name + "[" + a.assembly.GetName().Version + "]"; 76 | }); 77 | 78 | return "Unsupported HarmonyLib implementations: " + list; 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Compat-CitiesHarmony-Mod/Compat-CitiesHarmony-Mod.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {D80AFD7C-CB94-4696-B1F8-6255A5B07FB0} 8 | Library 9 | Properties 10 | Compat_CitiesHarmony_Mod 11 | CitiesHarmony 12 | v3.5 13 | 512 14 | true 15 | 7.3 16 | bin\$(Configuration)\ 17 | 18 | 19 | false 20 | true 21 | Properties\StrongName.pfx 22 | 23 | 24 | false 25 | full 26 | true 27 | 28 | 29 | true 30 | none 31 | false 32 | 33 | 34 | bin\Beta\ 35 | true 36 | AnyCPU 37 | 7.3 38 | 39 | 40 | false 41 | true 42 | Properties\StrongName.pfx 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {608c45f0-e72f-4436-b9ea-4d108b42313b} 59 | Harmony-v2 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /HarmonyMod/Source/AssemblyNameCompare.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Harmony for Cities Skylines 3 | * Copyright (C) 2021 Radu Hociung 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | using System.Reflection; 21 | using System.Collections.Generic; 22 | using System.Linq; 23 | 24 | namespace HarmonyMod 25 | { 26 | 27 | internal class SameAssemblyName : IEqualityComparer 28 | { 29 | 30 | readonly bool m_compareVersion; 31 | readonly bool m_compareToken; 32 | readonly bool m_compareName; 33 | readonly bool m_compareCulture; 34 | readonly bool m_satisfyStrongName; 35 | 36 | public SameAssemblyName() 37 | { 38 | m_compareName = true; 39 | m_compareToken = true; 40 | m_compareVersion = true; 41 | m_satisfyStrongName = false; 42 | } 43 | public SameAssemblyName(bool compareVersion, bool compareToken, bool compareName, bool compareCulture) 44 | { 45 | m_compareVersion = compareVersion; 46 | m_compareToken = compareToken; 47 | m_compareName = compareName; 48 | m_compareCulture = compareCulture; 49 | m_satisfyStrongName = true; 50 | } 51 | 52 | public int GetHashCode(AssemblyName n) 53 | { 54 | int hashcode = n.Name.GetHashCode(); 55 | int keyhash = n.GetPublicKeyToken() == null ? 0 : n.GetPublicKeyToken().ToString().GetHashCode(); 56 | hashcode += keyhash; 57 | hashcode += n.Version.GetHashCode(); 58 | return hashcode; 59 | } 60 | public bool Equals(AssemblyName a, AssemblyName b) 61 | { 62 | bool result = (!m_compareName || a.Name.Equals(b.Name)) && 63 | (!m_compareVersion || a.Version.Equals(b.Version)) && 64 | (!m_compareToken || (a.GetPublicKeyToken().Length == 0 && (!m_satisfyStrongName || b.GetPublicKeyToken().Length == 0)) || 65 | (a.GetPublicKeyToken().SequenceEqual(b.GetPublicKeyToken()))) && 66 | (!m_compareCulture || a.CultureInfo.Equals(b.CultureInfo)); 67 | return result; 68 | } 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /HarmonyMod/Patches/UIView_ForwardExceptionPatch.cs: -------------------------------------------------------------------------------- 1 | extern alias Harmony2; 2 | using Harmony2::HarmonyLib; 3 | using JetBrains.Annotations; 4 | using ColossalFramework.UI; 5 | using System; 6 | 7 | namespace HarmonyMod.MyPatches 8 | { 9 | 10 | [HarmonyPatch(typeof(UIView), "ForwardException", new Type[] { typeof(Exception), })] 11 | [HarmonyPriority(Priority.First)] 12 | internal class UIView_ForwardExceptionPatch 13 | { 14 | /// 15 | /// when loading asset from a file, IAssetData.OnAssetLoaded() is called for all assets but the one that is loaded from the file. 16 | /// this postfix calls IAssetData.OnAssetLoaded() for asset loaded from file. 17 | /// 18 | [HarmonyPrefix] 19 | [UsedImplicitly] 20 | public static bool Prefix(Exception e) 21 | { 22 | /* Return false if the Plugin report was successful 23 | * Allows up to 3 (MAX_EXCEPTION_PROMPTS_PER_MOD) ExceptionPanel popups per mod, 24 | * suppressing the rest. 25 | * 26 | * All exceptions are logged and reported in the Harmony report. 27 | */ 28 | return (Mod.mainMod.userModInstance as Mod).report.TryReportPlugin(e) <= Report.MAX_EXCEPTION_PROMPTS_PER_MOD; 29 | } 30 | } 31 | 32 | /* TODO: Hook UnityEngine.DebugLogHandler.LogException(), but it gets the same exceptions as UIView. 33 | * Also possibly hook Starter.OnDestroy (not declared) to capture logs to the last breath of the app. 34 | * 35 | * public void LogException(Exception exception, Object context) 36 | * { 37 | * DebugLogHandler.Internal_LogException(exception, context); 38 | * } 39 | * 40 | * NullReferenceException: Object reference not set to an instance of an object 41 | * at HarmonyMod.Mod.get_assemblyLocations () [0x00001] in U:\proj\skylines\CitiesHarmony\HarmonyMod\Source\Mod.cs:81 42 | * at HarmonyMod.Report.GetModList (Boolean showOnlyProblems) [0x001d3] in U:\proj\skylines\CitiesHarmony\HarmonyMod\Source\Report.cs:181 43 | * at HarmonyMod.Report.OutputReport (HarmonyMod.Mod self, Boolean final, System.String stepName) [0x00001] in U:\proj\skylines\CitiesHarmony\HarmonyMod\Source\Report.cs:277 44 | * at HarmonyMod.Mod.ICities.ILoadingExtension.OnReleased () [0x00001] in U:\proj\skylines\CitiesHarmony\HarmonyMod\Source\Mod.cs:275 45 | * at LoadingWrapper.OnLoadingExtensionsReleased () [0x00000] in :0 46 | * UnityEngine.DebugLogHandler:Internal_LogException(Exception, Object) 47 | * UnityEngine.DebugLogHandler:LogException(Exception, Object) 48 | * UnityEngine.Logger:LogException(Exception, Object) 49 | * UnityEngine.Debug:LogException(Exception) 50 | * LoadingWrapper:OnLoadingExtensionsReleased() 51 | * LoadingWrapper:Release() 52 | * LoadingManager:ReleaseRelay() 53 | * LoadingManager:OnDestroy() 54 | * 55 | */ 56 | 57 | } 58 | -------------------------------------------------------------------------------- /ExampleMod/ExampleMod.csproj: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | {961A70CE-8B6C-4950-A845-E215F07D0CC2} 27 | net35 28 | false 29 | ExampleMod 30 | 1.0.* 31 | false 32 | Debug;Release 33 | 34 | 35 | 36 | C:\References\Assembly-CSharp.dll 37 | False 38 | 39 | 40 | C:\References\ColossalManaged.dll 41 | False 42 | 43 | 44 | C:\References\ICities.dll 45 | False 46 | 47 | 48 | C:\References\UnityEngine.dll 49 | False 50 | 51 | 52 | 53 | 54 | 55 | 56 | none 57 | false 58 | 59 | 60 | 61 | $(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(ProjectName)\ 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /HarmonyMod/Patches/PluginManager_userModInstancePatch.cs: -------------------------------------------------------------------------------- 1 | extern alias Harmony2; 2 | using Harmony2::HarmonyLib; 3 | using JetBrains.Annotations; 4 | using System; 5 | using System.Linq; 6 | using System.Collections.Generic; 7 | using System.Reflection; 8 | using ColossalFramework.Plugins; 9 | using static ColossalFramework.Plugins.PluginManager; 10 | 11 | 12 | namespace HarmonyMod.MyPatches 13 | { 14 | 15 | [HarmonyPatch(typeof(PluginInfo), "get_userModInstance", new Type[] { })] 16 | internal class PluginManager_userModInstancePatch 17 | { 18 | [HarmonyPrefix] 19 | [UsedImplicitly] 20 | public static bool Prefix(PluginInfo __instance, ref object __result) 21 | { 22 | var inst = Traverse.Create(__instance); 23 | var userModInstance = inst.Field("m_UserModInstance"); 24 | var m_UserModInstance = userModInstance.Value; 25 | List m_Assemblies = inst.Field>("m_Assemblies").Value; 26 | 27 | #if HEAVY_TRACE 28 | UnityEngine.Debug.LogError($"[{Versioning.FULL_PACKAGE_NAME}] PluginInfo.get_userModInstance() for {__instance.name} called from \n{new System.Diagnostics.StackTrace(true)}"); // : {__exception.Message}\n{__exception.StackTrace}"); 29 | #endif 30 | 31 | __result = null; 32 | if (m_UserModInstance == null) 33 | { 34 | for (int i = 0; i < m_Assemblies.Count; i++) 35 | { 36 | foreach (Type type in m_Assemblies[i].GetExportedTypes()) 37 | { 38 | if (type.IsClass && !type.IsAbstract) 39 | { 40 | Type[] interfaces = type.GetInterfaces(); 41 | if (interfaces.Contains(PluginManager.userModType)) 42 | { 43 | ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes); 44 | m_UserModInstance = constructor.Invoke(null); 45 | userModInstance.Value = m_UserModInstance; 46 | } 47 | } 48 | } 49 | } 50 | } 51 | __result = m_UserModInstance; 52 | 53 | return false; 54 | } 55 | 56 | [HarmonyFinalizer] 57 | [UsedImplicitly] 58 | public static Exception Finalizer(Exception __exception, PluginInfo __instance) 59 | { 60 | if (__exception is not null) 61 | { 62 | if (__exception is ReflectionTypeLoadException) 63 | { 64 | (__exception as ReflectionTypeLoadException).LoaderExceptions.Do( 65 | (e) => 66 | { 67 | e.HelpLink = "https://github.com/drok/Harmony-CitiesSkylines/issues/9"; 68 | Mod.mainModInstance.report.ReportPlugin(__instance, ModReport.ProblemType.ExceptionThrown, e, $"{e.GetType().Name}: {e.Message}"); 69 | UnityEngine.Debug.LogError($"[{Versioning.FULL_PACKAGE_NAME}] {__instance.name} threw: {e.GetType().Name}: {e.Message}"); 70 | }); 71 | } 72 | else 73 | { 74 | Mod.mainModInstance.report.ReportPlugin(__instance, ModReport.ProblemType.ExceptionThrown, __exception, $"while instatiating: {__exception.Message}"); 75 | #if TRACE 76 | UnityEngine.Debug.LogError($"[{Versioning.FULL_PACKAGE_NAME}] PluginInfo.get_userModInstance() for {__instance.name} threw {__exception.GetType().FullName}"); 77 | #endif 78 | } 79 | } 80 | 81 | return null; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /IAmAware/IAwareness0.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2A8BFCFE-DC32-4227-870C-ADDB18858608} 8 | Library 9 | Properties 10 | IAmAware 11 | IAmAware 12 | v3.5 13 | 512 14 | true 15 | 16 | 17 | false 18 | true 19 | Properties\StrongName.pfx 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | C:\References\ColossalManaged.dll 41 | False 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | none 60 | false 61 | false 62 | ;NU1605 63 | 64 | 65 | Full 66 | true 67 | TRACE 68 | 69 | 70 | Full 71 | true 72 | DEBUG;TRACE 73 | 74 | 75 | -------------------------------------------------------------------------------- /API/API.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | {A3462C5B-B9A4-4154-9855-F3025E943946} 4 | false 5 | net35 6 | Lib.Harmony Helper for Cities: Skylines 7 | Radu Hociung 8 | Auto-installs and gives access to the Harmony Mod from the Steam Workshop 9 | Copyright 2021 Radu Hociung 10 | 0.9.0 11 | Radu Hociung 12 | Harmony-CitiesSkylines 13 | https://github.com/drok/Harmony-CitiesSkylines 14 | This is a drop-in replacement for CitiesHarmony.API by boformer, which subscribes the new Harmony Mod instead of boformer's 15 | Games Modding Patching 16 | package_icon.png 17 | MIT 18 | true 19 | false 20 | false 21 | false 22 | false 23 | false 24 | false 25 | false 26 | false 27 | Harmony-CitiesSkylines 28 | 29 | 30 | false 31 | true 32 | Properties\StrongName.pfx 33 | false 34 | 631fdc66-d970-44a9-9df3-895a0745b50e 35 | 36 | 37 | 38 | 39 | 40 | 41 | C:\References\Assembly-CSharp.dll 42 | False 43 | 44 | 45 | C:\References\ColossalManaged.dll 46 | False 47 | 48 | 49 | C:\References\IAmAware-0.0.0.0.dll 50 | false 51 | 52 | 53 | C:\References\ICities.dll 54 | False 55 | 56 | 57 | C:\References\UnityEngine.dll 58 | False 59 | 60 | 61 | 62 | 63 | runtime 64 | 65 | 66 | 67 | none 68 | false 69 | 70 | 71 | -------------------------------------------------------------------------------- /HarmonyMod/Source/Installer.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Harmony for Cities Skylines 3 | * Copyright (C) 2021 Radu Hociung 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | /* 20 | MIT License 21 | 22 | Copyright (c) 2017 Felix Schmidt 23 | 24 | Permission is hereby granted, free of charge, to any person obtaining a copy 25 | of this software and associated documentation files (the "Software"), to deal 26 | in the Software without restriction, including without limitation the rights 27 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | copies of the Software, and to permit persons to whom the Software is 29 | furnished to do so, subject to the following conditions: 30 | 31 | The above copyright notice and this permission notice shall be included in all 32 | copies or substantial portions of the Software. 33 | 34 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 35 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 36 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 37 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 38 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 39 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 40 | SOFTWARE. 41 | */ 42 | 43 | /* ************************************************************************** 44 | * 45 | * 46 | * IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT 47 | * 48 | * This file contains leftover code from the initial fork from Felix Schmidt's 49 | * repository https://github.com/boformer/CitiesHarmony 50 | * 51 | * It contains known bad code, which is either not used at all in my implementation, 52 | * or it is in the course of being re-written. If I am rewriting it, I only included 53 | * it because an initial release of my project was needed urgently to address 54 | * a broken modding eco-system in Cities Skylines, and I considered it will do no 55 | * further harm over what has already been done by Felix Schmidt's code. 56 | * 57 | * I would recommend you do not copy or rely on this code. A near future update will 58 | * remove this and replace it with proper code I would be proud to put my name on. 59 | * 60 | * Until then, the copyright notice above was expressely requested by Felix Schmidt, 61 | * by means of a DMCA complaint at GitHub and Steam. 62 | * 63 | * There is no code between the end of this comment and he "END-OF-Felix Schmidt-COPYRIGHT" 64 | * line if there is one, or the end of the file, that I, Radu Hociung, claim any copyright 65 | * on. The rest of the content, outside of these delimiters, is my copyright, and 66 | * you may copy it in accordance to the modified GPL license at the root or the repo 67 | * (LICENSE file) 68 | * 69 | * FUTHER, if you base your code on a copy of the example mod from Felix Schmidt's 70 | * repository, which does not include his copyright notice, you will likely also 71 | * be a victim of DMCA complaint from him. 72 | */ 73 | namespace HarmonyMod { 74 | #if false 75 | public static class Installer { 76 | 77 | /* Installer.Run() is a legacy static method called by boformer's CitiesHarmony.API.HarmonyHelper, 78 | * various versions, so it must not be removed, for backward compatibility 79 | * */ 80 | public static void Run() { } 81 | 82 | } 83 | #endif 84 | } 85 | -------------------------------------------------------------------------------- /ExampleMod/ExampleMod.cs: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017 Felix Schmidt 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ************************************************************************** 26 | * 27 | * 28 | * IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT 29 | * 30 | * This file contains leftover code from the initial fork from Felix Schmidt's 31 | * repository https://github.com/boformer/CitiesHarmony 32 | * 33 | * It contains known bad code, which is either not used at all in my implementation, 34 | * or it is in the course of being re-written. If I am rewriting it, I only included 35 | * it because an initial release of my project was needed urgently to address 36 | * a broken modding eco-system in Cities Skylines, and I considered it will do no 37 | * further harm over what has already been done by Felix Schmidt's code. 38 | * 39 | * I would recommend you do not copy or rely on this code. A near future update will 40 | * remove this and replace it with proper code I would be proud to put my name on. 41 | * 42 | * Until then, the copyright notice above was expressely requested by Felix Schmidt, 43 | * by means of a DMCA complaint at GitHub and Steam. 44 | * 45 | * There is no code between the end of this comment and he "END-OF-Felix Schmidt-COPYRIGHT" 46 | * line if there is one, or the end of the file, that I, Radu Hociung, claim any copyright 47 | * on. The rest of the content, outside of these delimiters, is my copyright, and 48 | * you may copy it in accordance to the modified GPL license at the root or the repo 49 | * (LICENSE file) 50 | * 51 | * FUTHER, if you base your code on a copy of the example mod from Felix Schmidt's 52 | * repository, which does not include his copyright notice, you will likely also 53 | * be a victim of DMCA complaint from him. 54 | */ 55 | 56 | using CitiesHarmony.API; 57 | using ICities; 58 | // Make sure that "using HarmonyLib;" does not appear here! 59 | // Only reference HarmonyLib in code that runs when Harmony is ready (DoOnHarmonyReady, IsHarmonyInstalled) 60 | 61 | namespace BadMods { 62 | public class PatchTooEarlyMod : IUserMod { 63 | // You can add Harmony 2.0.0.9 as a dependency, but make sure that 0Harmony.dll is not copied to the output directory! 64 | // (0Harmony.dll is provided by CitiesHarmony workshop item) 65 | 66 | // Also make sure that HarmonyLib is not referenced in any way in your IUserMod implementation! 67 | // Instead, apply your patches from a separate static patcher class! 68 | // (otherwise it will fail to instantiate the type when CitiesHarmony is not installed) 69 | 70 | public string Name => $"{GetType().Namespace}: {GetType().Name}"; 71 | public string Description => "Patches SimulationManager.CreateRelay and LoadingManager.MetaDataLoaded"; 72 | 73 | Patcher patcher = new Patcher(); 74 | public void OnEnabled() { 75 | HarmonyHelper.DoOnHarmonyReady(() => patcher.PatchAll()); 76 | } 77 | 78 | public void OnDisabled() { 79 | if (HarmonyHelper.IsHarmonyInstalled) patcher.UnpatchAll(); 80 | } 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /HarmonyMod/Source/TextureResources.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Harmony for Cities Skylines 3 | * Copyright (C) 2021 Radu Hociung 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | /* Borrowed from Traffic Manager - thank you! */ 21 | namespace HarmonyMod 22 | { 23 | using System.IO; 24 | using System.Reflection; 25 | using System; 26 | using UnityEngine; 27 | 28 | public static class TextureResources { 29 | static TextureResources() { 30 | } 31 | 32 | internal static Texture2D LoadDllResource(string resourceName, int width, int height) 33 | { 34 | try { 35 | #if DEBUG 36 | UnityEngine.Debug.LogWarning($"[{Versioning.FULL_PACKAGE_NAME}] Loading DllResource {resourceName})"); 37 | #endif 38 | var myAssembly = Assembly.GetExecutingAssembly(); 39 | var fullname = typeof(TextureResources).Namespace + ".Resources." + resourceName; 40 | var myStream = myAssembly.GetManifestResourceStream(fullname); 41 | if (myStream == null) 42 | throw new Exception($"{fullname} not found!"); 43 | 44 | var texture = new Texture2D(width, height, TextureFormat.ARGB32, false); 45 | 46 | texture.LoadImage(ReadToEnd(myStream)); 47 | 48 | return texture; 49 | } catch (Exception e) { 50 | UnityEngine.Debug.LogWarning($"[{Versioning.FULL_PACKAGE_NAME}] Failed Loading DllResource: {Report.ExMessage(e, true)}"); 51 | return null; 52 | } 53 | } 54 | 55 | static byte[] ReadToEnd(Stream stream) 56 | { 57 | var originalPosition = stream.Position; 58 | stream.Position = 0; 59 | 60 | try 61 | { 62 | var readBuffer = new byte[4096]; 63 | 64 | var totalBytesRead = 0; 65 | int bytesRead; 66 | 67 | while ((bytesRead = stream.Read(readBuffer, totalBytesRead, readBuffer.Length - totalBytesRead)) > 0) 68 | { 69 | totalBytesRead += bytesRead; 70 | 71 | if (totalBytesRead != readBuffer.Length) 72 | continue; 73 | 74 | var nextByte = stream.ReadByte(); 75 | if (nextByte == -1) 76 | continue; 77 | 78 | var temp = new byte[readBuffer.Length * 2]; 79 | Buffer.BlockCopy(readBuffer, 0, temp, 0, readBuffer.Length); 80 | Buffer.SetByte(temp, totalBytesRead, (byte)nextByte); 81 | readBuffer = temp; 82 | totalBytesRead++; 83 | } 84 | 85 | var buffer = readBuffer; 86 | if (readBuffer.Length == totalBytesRead) 87 | return buffer; 88 | 89 | buffer = new byte[totalBytesRead]; 90 | Buffer.BlockCopy(readBuffer, 0, buffer, 0, totalBytesRead); 91 | return buffer; 92 | } 93 | catch (Exception e) { 94 | UnityEngine.Debug.LogWarning($"[{Versioning.FULL_PACKAGE_NAME}] Failed Reading stream: {Report.ExMessage(e, true)}"); 95 | return null; 96 | } 97 | finally 98 | { 99 | stream.Position = originalPosition; 100 | } 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /HarmonyMod/Properties/Versioning.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyVersion(HarmonyMod.Versioning.MyAssemblyVersion)] 6 | [assembly: AssemblyFileVersion(HarmonyMod.Versioning.MyFileVersion)] 7 | [assembly: AssemblyInformationalVersionAttribute(HarmonyMod.Versioning.MyInformationalVersion)] 8 | 9 | namespace HarmonyMod 10 | { 11 | public static class Versioning 12 | { 13 | public const string ReleaseStr = "2"; 14 | public const uint ImplementationVersion = 0x00090002; 15 | public const string MyAssemblyVersion = "1.0.0." + ReleaseStr + ReleaseTypeStr; 16 | public const string MyFileVersion = "0.9"; 17 | public const string PACKAGE_NAME = "Harmony"; 18 | 19 | public struct Obsolescence 20 | { 21 | /* Versions at which various features will be disabled. 22 | * They should not be removed from the source code, because 23 | * they serve as documentation for how older versions worked. 24 | * The compiler with automatically remove the dead code. 25 | */ 26 | public const uint AUTO_MOD_ENABLE = 0x02000000; 27 | 28 | /* Auto-helper-install (one time) is actually a good feature, 29 | * should probably never be obsoleted 30 | */ 31 | public const uint AUTO_INSTALL_HELPER = 0x03000000; 32 | 33 | /* PatchACL will deny Harmony using mods that don't use the 34 | * CitiesHarmony.API (IsHarmonyInstalled, DoOnHarmonyReady, etc) 35 | * after this version 36 | */ 37 | public const uint PROHIBIT_API_MISUSE_AFTER = 0x01010000; 38 | } 39 | 40 | public static string VersionString(uint number) 41 | { 42 | return new System.Version( 43 | (int)((number >> 24) & 0xff), 44 | (int)((number >> 16) & 0xff), 45 | (int)((number >> 8) & 0xff), 46 | (int)(number & 0xff)).ToString(); 47 | } 48 | 49 | public static bool IsObsolete(uint ver, string explanation) 50 | { 51 | bool obsolete = ImplementationVersion >= ver; 52 | if (!obsolete) 53 | { 54 | UnityEngine.Debug.LogWarning($"[{Versioning.FULL_PACKAGE_NAME}] WARNING '{explanation}' is deprecated; Will be obsolete at {PACKAGE_NAME} {VersionString(ver)}"); 55 | } 56 | return obsolete; 57 | } 58 | 59 | public const string MyInformationalVersion = MyFileVersion + POSTFIX; 60 | public const string FULL_PACKAGE_NAME = PACKAGE_NAME + " " + MyInformationalVersion; 61 | 62 | #if DEBUG 63 | public const string ReleaseTypeStr = "5"; 64 | #elif DEVELOPER_UPDATER 65 | /* Note the DEVELOPER_UPDATER is also a DEVELOPER 66 | * so its conditional must be first 67 | */ 68 | public const string ReleaseTypeStr = "3"; 69 | #elif DEVELOPER 70 | public const string ReleaseTypeStr = "4"; 71 | #elif BETA 72 | public const string ReleaseTypeStr = "2"; 73 | #elif RELEASE 74 | public const string ReleaseTypeStr = "1"; 75 | #else 76 | public const string ReleaseTypeStr = "0"; 77 | #endif 78 | 79 | #if DEBUG 80 | const string POSTFIX = "-DEBUG"; 81 | #elif DEVELOPER 82 | /* The Developer edition includes auto-installing the HELPER, and 83 | * tracking if it loads first. This is needed to install the 84 | * Exception handler early to capture *all* errors. 85 | * It also includes more exception analysis than the other editions. 86 | */ 87 | const string POSTFIX = "-DEV"; 88 | #elif DEVELOPER_UPDATER 89 | /* The Developer Updater is the same as Developer, 90 | * but it has a lower version number. 91 | * When the Workshop Developer is updated, this 92 | * edition will be the active one instead of the local 93 | * Developer, and will update the local Developer. 94 | * Then, the local Developer will be higher versioned and 95 | * have precedence. 96 | */ 97 | const string POSTFIX = "-DEV"; 98 | #elif BETA 99 | const string POSTFIX = "-BETA"; 100 | #else 101 | const string POSTFIX = ""; 102 | #endif 103 | } 104 | } 105 | 106 | -------------------------------------------------------------------------------- /ExampleMod/Patcher.cs: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017 Felix Schmidt 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ************************************************************************** 26 | * 27 | * 28 | * IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT 29 | * 30 | * This file contains leftover code from the initial fork from Felix Schmidt's 31 | * repository https://github.com/boformer/CitiesHarmony 32 | * 33 | * It contains known bad code, which is either not used at all in my implementation, 34 | * or it is in the course of being re-written. If I am rewriting it, I only included 35 | * it because an initial release of my project was needed urgently to address 36 | * a broken modding eco-system in Cities Skylines, and I considered it will do no 37 | * further harm over what has already been done by Felix Schmidt's code. 38 | * 39 | * I would recommend you do not copy or rely on this code. A near future update will 40 | * remove this and replace it with proper code I would be proud to put my name on. 41 | * 42 | * Until then, the copyright notice above was expressely requested by Felix Schmidt, 43 | * by means of a DMCA complaint at GitHub and Steam. 44 | * 45 | * There is no code between the end of this comment and he "END-OF-Felix Schmidt-COPYRIGHT" 46 | * line if there is one, or the end of the file, that I, Radu Hociung, claim any copyright 47 | * on. The rest of the content, outside of these delimiters, is my copyright, and 48 | * you may copy it in accordance to the modified GPL license at the root or the repo 49 | * (LICENSE file) 50 | * 51 | * FUTHER, if you base your code on a copy of the example mod from Felix Schmidt's 52 | * repository, which does not include his copyright notice, you will likely also 53 | * be a victim of DMCA complaint from him. 54 | */ 55 | using System.Reflection; 56 | using HarmonyLib; 57 | 58 | namespace ExampleMod { 59 | public static class Patcher { 60 | private const string HarmonyId = "boformer.Harmony2Example"; 61 | 62 | private static bool patched = false; 63 | 64 | public static void PatchAll() { 65 | if (patched) return; 66 | 67 | UnityEngine.Debug.Log("Harmony 2 Example: Patching..."); 68 | 69 | patched = true; 70 | 71 | // Apply your patches here! 72 | // Harmony.DEBUG = true; 73 | var harmony = new Harmony("boformer.Harmony2Example"); 74 | harmony.PatchAll(Assembly.GetExecutingAssembly()); 75 | } 76 | 77 | public static void UnpatchAll() { 78 | if (!patched) return; 79 | 80 | var harmony = new Harmony(HarmonyId); 81 | harmony.UnpatchAll(HarmonyId); 82 | 83 | patched = false; 84 | 85 | UnityEngine.Debug.Log("Harmony 2 Example: Reverted..."); 86 | } 87 | } 88 | 89 | // Random example patch 90 | [HarmonyPatch(typeof(SimulationManager), "CreateRelay")] 91 | public static class SimulationManagerCreateRelayPatch { 92 | public static void Prefix() { 93 | UnityEngine.Debug.Log("CreateRelay Prefix"); 94 | } 95 | } 96 | 97 | // Random example patch 98 | [HarmonyPatch(typeof(LoadingManager), "MetaDataLoaded")] 99 | public static class LoadingManagerMetaDataLoadedPatch { 100 | public static void Prefix() { 101 | UnityEngine.Debug.Log("MetaDataLoaded Prefix"); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /IAmAware/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("IAmAware")] 9 | [assembly: AssemblyDescription("Internal Awareness Interface for the Harmony Mod")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Radu Hociung")] 12 | [assembly: AssemblyProduct("IAmAware")] 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("2a8bfcfe-dc32-4227-870c-addb18858608")] 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("0.0.1.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | 38 | /* Allow access to previous versions for interface upgrades */ 39 | [assembly: InternalsVisibleTo("IAmAware, PublicKey=" + 40 | "0024000004800000940000000602000000240000525341310004000001000100" + 41 | "e9f6f326593be181e1d4fea8ba7d991fc9ff3e7adf8ee659550cd00e34673409d5e177bab53f08" + 42 | "4410455066e2a05864973a0b91b4fd6f827f6d0c70db0299db5f7d95429418e0e58a519838ceda" + 43 | "4ad16caf832a9da9feac59c8ea78a37f8e22c85058e544801972d98c1ad999e6aa09374cb69606" + 44 | "7a66ae7b154d0e616ca0b0")] 45 | 46 | /* Allow integration tests unrestricted access for testing */ 47 | [assembly: InternalsVisibleTo("Test.Harmony, PublicKey=" + 48 | "00240000048000009400000006020000002400005253413100040000010001009d0f13cde5b126" + 49 | "c67d0c94873430cc171f8919863c6218a5bc1788a91caf6c197a851fdd4e5df5fe68726b5ca92a" + 50 | "cd2a47770cde3eb1538693a427a6c7591878b59dacc8fd24339f0e77f923ada3f80133f3a5b182" + 51 | "d7d04b16fb7bd02abff840b4b4ed9114463fef35c3437385205ebed7906a29ce6bd16a84e50129" + 52 | "8c8224ba")] 53 | 54 | /* Allow HarmonyMods to communicate with each other 55 | */ 56 | [assembly: InternalsVisibleTo("HarmonyMod, PublicKey=" + 57 | "0024000004800000940000000602000000240000525341310004000001000100e9f6f326593be1" + 58 | "81e1d4fea8ba7d991fc9ff3e7adf8ee659550cd00e34673409d5e177bab53f084410455066e2a0" + 59 | "5864973a0b91b4fd6f827f6d0c70db0299db5f7d95429418e0e58a519838ceda4ad16caf832a9d" + 60 | "a9feac59c8ea78a37f8e22c85058e544801972d98c1ad999e6aa09374cb696067a66ae7b154d0e" + 61 | "616ca0b0")] 62 | 63 | /* Allow HarmonyLib to query the Awareness interface (to find if it's first run) 64 | */ 65 | [assembly: InternalsVisibleTo("0Harmony, PublicKey=" + 66 | "0024000004800000940000000602000000240000525341310004000001000100e9f6f326593be1" + 67 | "81e1d4fea8ba7d991fc9ff3e7adf8ee659550cd00e34673409d5e177bab53f084410455066e2a0" + 68 | "5864973a0b91b4fd6f827f6d0c70db0299db5f7d95429418e0e58a519838ceda4ad16caf832a9d" + 69 | "a9feac59c8ea78a37f8e22c85058e544801972d98c1ad999e6aa09374cb696067a66ae7b154d0e" + 70 | "616ca0b0")] 71 | 72 | /* Allow CitiesHarmony.Harmony to query the Awareness interface (to find if it's first run) 73 | */ 74 | [assembly: InternalsVisibleTo("CitiesHarmony.Harmony, PublicKey=" + 75 | "0024000004800000940000000602000000240000525341310004000001000100e9f6f326593be1" + 76 | "81e1d4fea8ba7d991fc9ff3e7adf8ee659550cd00e34673409d5e177bab53f084410455066e2a0" + 77 | "5864973a0b91b4fd6f827f6d0c70db0299db5f7d95429418e0e58a519838ceda4ad16caf832a9d" + 78 | "a9feac59c8ea78a37f8e22c85058e544801972d98c1ad999e6aa09374cb696067a66ae7b154d0e" + 79 | "616ca0b0")] 80 | 81 | /* Allow the API to query the Awareness interface, to send the mod callbacks */ 82 | [assembly: InternalsVisibleTo("Harmony-CitiesSkylines, PublicKey=" + 83 | "0024000004800000940000000602000000240000525341310004000001000100" + 84 | "e9f6f326593be181e1d4fea8ba7d991fc9ff3e7adf8ee659550cd00e34673409d5e177bab53f08" + 85 | "4410455066e2a05864973a0b91b4fd6f827f6d0c70db0299db5f7d95429418e0e58a519838ceda" + 86 | "4ad16caf832a9da9feac59c8ea78a37f8e22c85058e544801972d98c1ad999e6aa09374cb69606" + 87 | "7a66ae7b154d0e616ca0b0")] 88 | -------------------------------------------------------------------------------- /IAmAware/IAmAware.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Harmony for Cities Skylines 3 | * Copyright (C) 2021 Radu Hociung 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | using System; 21 | using System.Collections.Generic; 22 | using System.Reflection; 23 | using static ColossalFramework.Plugins.PluginManager; 24 | 25 | namespace IAwareness 26 | { 27 | enum SelfProblemType 28 | { 29 | HarmonyLibNotFunctional = 0, 30 | WrongHarmonyLib, 31 | ManifestMismatch, 32 | FailedToInitialize, 33 | CompatibilityPatchesFailed, 34 | NotLoadedFirst, 35 | FailedToUninstall, 36 | FailedToYieldToMain, 37 | FailedToReceiveReports, 38 | FailedToGetAssemblyLocations, 39 | OwnPatchInstallation, 40 | OwnPatchRemoval, 41 | HelperInstallationFailed, 42 | FailedToReport, 43 | Last, 44 | } 45 | 46 | internal interface IAmAware 47 | { 48 | /* On Enabled is used by one instance to tell the others 49 | * of its presence 50 | */ 51 | void OnMainModChanged(IAmAware mainMod, bool enabled); 52 | 53 | /* Used to transfer state from a helper instance when 54 | * another instance becomes main mod. 55 | */ 56 | void PutModReports(Dictionary reports); 57 | 58 | /* Used to report various well-defined conditions */ 59 | void SelfReport(SelfProblemType problem, Exception e); 60 | 61 | /* Used to report whether the Harmony Mod is present and 62 | * not just installed. When first subscribed, the Harmony Mod 63 | * is enabled dead last, but the mods that require patching 64 | * must not get the "Harmony is disabled" error. 65 | * HarmonyLib will query this to learn if the Mod has been 66 | * enabled from the beginning. 67 | */ 68 | bool IsFullyAware(); 69 | 70 | /* Used to signal that the first Harmony2 access has started 71 | * while the Harmony2 was neigther enabled nor disabled 72 | * (ie, before awareness) 73 | */ 74 | void OnHarmonyAccessBeforeAwareness(bool needHarmon1StateTransfer); 75 | 76 | /* The API calls this to queue callbacks to its callers 77 | */ 78 | bool DoOnHarmonyReady(List callbacks); 79 | 80 | /* The API calls this to cancel callbacks to its callers 81 | */ 82 | void CancelHarmonyReadyCallback(List callbacks); 83 | 84 | /// Check permission to Unpatch. Non-patch manager can be prohibited 85 | /// from removing a manager installed patch. 86 | /// 87 | /// Target method which is to be patched 88 | /// Method originating the unpatch request 89 | /// Patch to be removed 90 | /// true if unpatch is allowed, false if it should be skipped 91 | /// Added at 0.0.1.0 92 | /// Thrown if access is blocked 93 | bool UnpatchACL(MethodBase original, MethodBase caller, MethodInfo patchMethod); 94 | 95 | /// 96 | /// Check permission to install a patch. User mods can be prohibited 97 | /// from patching the manager or other game methods 98 | /// 99 | /// Target method which is to be patched 100 | /// Method originating the unpatch request 101 | /// Patch to be removed 102 | /// Prefix, Postfix, etc 103 | /// true if patch is allowed, false if it should be skipped 104 | /// Added at 0.0.1.0 105 | /// Thrown if access is blocked 106 | bool PatchACL(MethodBase original, MethodBase caller, object patchMethod, Enum patchType); 107 | } 108 | 109 | internal struct ClientCallback 110 | { 111 | public Action action; 112 | public System.Diagnostics.StackTrace callStack; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /Test.Harmony/TesterMod.cs: -------------------------------------------------------------------------------- 1 | using IAwareness; 2 | using System; 3 | using CitiesHarmony.API; 4 | using HarmonyLib; 5 | using ICities; 6 | using UnityEngine; 7 | using System.Reflection; 8 | using ColossalFramework; 9 | using ColossalFramework.Plugins; 10 | 11 | namespace HarmonyMod.Tests 12 | { 13 | 14 | public class TestFailed : Exception 15 | { 16 | public TestFailed(string message) 17 | : base(message) { } 18 | 19 | public TestFailed(string message, Exception ex) 20 | : base(message, ex) { } 21 | } 22 | 23 | public class TesterMod : IUserMod { 24 | public string Name => "_Test.Harmony"; 25 | public string Description => $"Test Harmony API \"{typeof(HarmonyHelper).Assembly.GetName().Name} {typeof(HarmonyHelper).Assembly.GetName().Version}\""; 26 | 27 | public void OnEnabled() { 28 | Debug.LogError($"Running TesterMod.OnEnabled() {Assembly.GetExecutingAssembly().GetName().Version}"); 29 | 30 | TestRunner.Run(); 31 | } 32 | 33 | public void OnDisabled() 34 | { 35 | Debug.LogError("Running TesterMod.OnDisabled() {Assembly.GetExecutingAssembly().GetName().Version}"); 36 | } 37 | } 38 | 39 | public static class TestRunner { 40 | public static void Run() { 41 | Harmony.DEBUG = true; 42 | 43 | ReturnColorTest.Run(); 44 | 45 | 46 | int[] values = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; 47 | foreach (var n in values) Specials.Test_Patch_Returning_Structs(n, "S"); 48 | foreach (var n in values) Specials.Test_Patch_Returning_Structs(n, "I"); 49 | 50 | try 51 | { 52 | new AttributePatchTest().Run(); 53 | new ACLTest().Run(); 54 | 55 | // #if USE_ISAWARE_TYPE 56 | bool hamonyModTestsDone = false; 57 | Singleton.instance.GetPluginsInfo().DoIf( 58 | (p) => p.isEnabled && p.userModInstance is IAmAware, 59 | (p) => { 60 | hamonyModTestsDone = true; 61 | new ACLTest().RunAfterHarmony(); 62 | }); 63 | if (!hamonyModTestsDone) 64 | { 65 | Singleton.instance.eventPluginsChanged += OnModEnabled; 66 | } 67 | // #endif 68 | } 69 | catch (TestFailed ex) 70 | { 71 | throw new TestFailed($"Test API-{typeof(HarmonyHelper).Assembly.GetName().Version} failed {ex.Message}", ex); 72 | } 73 | } 74 | 75 | //#if USE_ISAWARE_TYPE 76 | static void OnModEnabled() 77 | { 78 | bool hamonyModTestsDone = false; 79 | Singleton.instance.GetPluginsInfo().DoIf( 80 | (p) => p.isEnabled && p.userModInstance is IAmAware, 81 | (p) => { 82 | try 83 | { 84 | new ACLTest().RunAfterHarmony(); 85 | } 86 | catch (TestFailed ex) 87 | { 88 | throw new TestFailed($"Test API-{typeof(HarmonyHelper).Assembly.GetName().Version} failed {ex.Message}", ex); 89 | } 90 | hamonyModTestsDone = true; 91 | }); 92 | 93 | if (hamonyModTestsDone) 94 | { 95 | Singleton.instance.eventPluginsChanged -= OnModEnabled; 96 | } 97 | } 98 | //#endif 99 | 100 | } 101 | 102 | public static class ReturnColorTest { 103 | public static void Run() { 104 | var harmony = new Harmony("boformer.Harmony2Example"); 105 | harmony.Patch(typeof(MyClass).GetMethod("GetColorStatic"), null, new HarmonyMethod(typeof(ReturnColorTest).GetMethod("GetColor_Postfix"))); 106 | harmony.Patch(typeof(MyClass).GetMethod("GetColor"), null, new HarmonyMethod(typeof(ReturnColorTest).GetMethod("GetColor_Postfix"))); 107 | 108 | 109 | var colorStatic = MyClass.GetColorStatic(); 110 | UnityEngine.Debug.Log("colorStatic.g: " + colorStatic.g); 111 | UnityEngine.Debug.Log(colorStatic.ToString()); 112 | 113 | var myClass = new MyClass(); 114 | 115 | var color = myClass.GetColor(); 116 | UnityEngine.Debug.Log("color.g: " + color.g); 117 | UnityEngine.Debug.Log(color.ToString()); 118 | } 119 | 120 | public static void GetColor_Postfix(ref UnityEngine.Color __result) { 121 | UnityEngine.Debug.Log("GetColor__Postfix: __result.g: " + __result.g); 122 | } 123 | 124 | public class MyClass { 125 | public static UnityEngine.Color GetColorStatic() { 126 | UnityEngine.Debug.Log("GetColorStatic"); 127 | return new UnityEngine.Color(1f, 0.75f, 0.5f, 0.25f); 128 | } 129 | 130 | public UnityEngine.Color GetColor() { 131 | UnityEngine.Debug.Log("GetColor"); 132 | return new UnityEngine.Color(1f, 0.75f, 0.5f, 0.25f); 133 | } 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /HarmonyMod/Source/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Harmony for Cities Skylines 3 | * Copyright (C) 2021 Radu Hociung 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | /* 21 | MIT License 22 | 23 | Copyright (c) 2017 Felix Schmidt 24 | 25 | Permission is hereby granted, free of charge, to any person obtaining a copy 26 | of this software and associated documentation files (the "Software"), to deal 27 | in the Software without restriction, including without limitation the rights 28 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 29 | copies of the Software, and to permit persons to whom the Software is 30 | furnished to do so, subject to the following conditions: 31 | 32 | The above copyright notice and this permission notice shall be included in all 33 | copies or substantial portions of the Software. 34 | 35 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 36 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 37 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 38 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 39 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 40 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 41 | SOFTWARE. 42 | */ 43 | 44 | /* ************************************************************************** 45 | * 46 | * 47 | * IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT 48 | * 49 | * This file contains leftover code from the initial fork from Felix Schmidt's 50 | * repository https://github.com/boformer/CitiesHarmony 51 | * 52 | * It contains known bad code, which is either not used at all in my implementation, 53 | * or it is in the course of being re-written. If I am rewriting it, I only included 54 | * it because an initial release of my project was needed urgently to address 55 | * a broken modding eco-system in Cities Skylines, and I considered it will do no 56 | * further harm over what has already been done by Felix Schmidt's code. 57 | * 58 | * I would recommend you do not copy or rely on this code. A near future update will 59 | * remove this and replace it with proper code I would be proud to put my name on. 60 | * 61 | * Until then, the copyright notice above was expressely requested by Felix Schmidt, 62 | * by means of a DMCA complaint at GitHub and Steam. 63 | * 64 | * There is no code between the end of this comment and he "END-OF-Felix Schmidt-COPYRIGHT" 65 | * line if there is one, or the end of the file, that I, Radu Hociung, claim any copyright 66 | * on. The rest of the content, outside of these delimiters, is my copyright, and 67 | * you may copy it in accordance to the modified GPL license at the root or the repo 68 | * (LICENSE file) 69 | * 70 | * FUTHER, if you base your code on a copy of the example mod from Felix Schmidt's 71 | * repository, which does not include his copyright notice, you will likely also 72 | * be a victim of DMCA complaint from him. 73 | */ 74 | using System; 75 | using System.Reflection; 76 | 77 | namespace HarmonyMod { 78 | public static class TypeExtensions { 79 | public static FieldInfo GetFieldOrThrow(this Type type, string name) { 80 | return type?.GetField(name) ?? throw new Exception($"{name} field not found"); 81 | } 82 | 83 | public static FieldInfo GetFieldOrThrow(this Type type, string name, BindingFlags flags) { 84 | return type?.GetField(name, flags) ?? throw new Exception($"{name} field not found"); 85 | } 86 | 87 | public static MethodInfo GetMethodOrThrow(this Type type, string name) { 88 | return type?.GetMethod(name) ?? throw new Exception($"{name} method not found"); 89 | } 90 | 91 | public static MethodInfo GetMethodOrThrow(this Type type, string name, BindingFlags flags) { 92 | return type?.GetMethod(name, flags) ?? throw new Exception($"{name} method not found"); 93 | } 94 | 95 | public static MethodInfo GetMethodOrThrow(this Type type, string name, Type[] types) { 96 | return type?.GetMethod(name, types) ?? throw new Exception($"{name} method not found"); 97 | } 98 | 99 | public static Version GetAssemblyVersion(this Type type) { 100 | return type.Assembly.GetName().Version; 101 | } 102 | 103 | public static string Max(this string value, int maxLength) 104 | { 105 | if (string.IsNullOrEmpty(value)) return value; 106 | return value.Length <= maxLength ? value : value.Substring(0, maxLength-1) + 'Û'; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Test.Harmony/HarmonyTests/Specials.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Harmony for Cities Skylines 3 | * Copyright (C) 2021 Radu Hociung 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | /* 20 | MIT License 21 | 22 | Copyright (c) 2017 Felix Schmidt 23 | 24 | Permission is hereby granted, free of charge, to any person obtaining a copy 25 | of this software and associated documentation files (the "Software"), to deal 26 | in the Software without restriction, including without limitation the rights 27 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | copies of the Software, and to permit persons to whom the Software is 29 | furnished to do so, subject to the following conditions: 30 | 31 | The above copyright notice and this permission notice shall be included in all 32 | copies or substantial portions of the Software. 33 | 34 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 35 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 36 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 37 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 38 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 39 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 40 | SOFTWARE. 41 | */ 42 | 43 | /* ************************************************************************** 44 | * 45 | * 46 | * IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT 47 | * 48 | * This file contains leftover code from the initial fork from Felix Schmidt's 49 | * repository https://github.com/boformer/CitiesHarmony 50 | * 51 | * It contains known bad code, which is either not used at all in my implementation, 52 | * or it is in the course of being re-written. If I am rewriting it, I only included 53 | * it because an initial release of my project was needed urgently to address 54 | * a broken modding eco-system in Cities Skylines, and I considered it will do no 55 | * further harm over what has already been done by Felix Schmidt's code. 56 | * 57 | * I would recommend you do not copy or rely on this code. A near future update will 58 | * remove this and replace it with proper code I would be proud to put my name on. 59 | * 60 | * Until then, the copyright notice above was expressely requested by Felix Schmidt, 61 | * by means of a DMCA complaint at GitHub and Steam. 62 | * 63 | * There is no code between the end of this comment and he "END-OF-Felix Schmidt-COPYRIGHT" 64 | * line if there is one, or the end of the file, that I, Radu Hociung, claim any copyright 65 | * on. The rest of the content, outside of these delimiters, is my copyright, and 66 | * you may copy it in accordance to the modified GPL license at the root or the repo 67 | * (LICENSE file) 68 | * 69 | * FUTHER, if you base your code on a copy of the example mod from Felix Schmidt's 70 | * repository, which does not include his copyright notice, you will likely also 71 | * be a victim of DMCA complaint from him. 72 | */ 73 | using HarmonyLib; 74 | using HarmonyLibTests.Assets.Methods; 75 | using System; 76 | 77 | namespace HarmonyMod.Tests 78 | { 79 | public static class Specials { 80 | // Based on HarmonyLib test "Test_Patch_Returning_Structs", adjusted to be run ingame 81 | public static void Test_Patch_Returning_Structs(int n, string type) { 82 | var name = $"{type}M{n:D2}"; 83 | 84 | var patchClass = typeof(ReturningStructs_Patch); 85 | 86 | var prefix = SymbolExtensions.GetMethodInfo(() => ReturningStructs_Patch.Prefix(null)); 87 | 88 | var instance = new Harmony("returning-structs"); 89 | 90 | var cls = typeof(ReturningStructs); 91 | var method = AccessTools.DeclaredMethod(cls, name); 92 | if (method == null) throw new Exception("method == null"); 93 | 94 | UnityEngine.Debug.Log($"Test_Returning_Structs: patching {name} start"); 95 | try { 96 | var replacement = instance.Patch(method, new HarmonyMethod(prefix)); 97 | if (replacement == null) throw new Exception("replacement == null"); 98 | } catch (Exception ex) { 99 | UnityEngine.Debug.Log($"Test_Returning_Structs: patching {name} exception: {ex}"); 100 | } 101 | UnityEngine.Debug.Log($"Test_Returning_Structs: patching {name} done"); 102 | 103 | var clsInstance = new ReturningStructs(); 104 | try { 105 | UnityEngine.Debug.Log($"Test_Returning_Structs: running patched {name}"); 106 | 107 | var original = AccessTools.DeclaredMethod(cls, name); 108 | if (original == null) throw new Exception("original == null"); 109 | var result = original.Invoke(type == "S" ? null : clsInstance, new object[] { "test" }); 110 | if (result == null) throw new Exception("result == null"); 111 | if ($"St{n:D2}" != result.GetType().Name) throw new Exception($"invalid result type name: {result.GetType().Name}"); 112 | 113 | var field = result.GetType().GetField("b1"); 114 | var fieldValue = (byte)field.GetValue(result); 115 | UnityEngine.Debug.Log(fieldValue); 116 | if (fieldValue != 42) throw new Exception($"result scrambled!"); 117 | 118 | UnityEngine.Debug.Log($"Test_Returning_Structs: running patched {name} done"); 119 | } catch (Exception ex) { 120 | UnityEngine.Debug.Log($"Test_Returning_Structs: running {name} exception: {ex}"); 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /API/SubscriptionWarning.cs: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017 Felix Schmidt 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ************************************************************************** 26 | * 27 | * 28 | * IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT 29 | * 30 | * This file contains leftover code from the initial fork from Felix Schmidt's 31 | * repository https://github.com/boformer/CitiesHarmony 32 | * 33 | * It contains known bad code, which is either not used at all in my implementation, 34 | * or it is in the course of being re-written. If I am rewriting it, I only included 35 | * it because an initial release of my project was needed urgently to address 36 | * a broken modding eco-system in Cities Skylines, and I considered it will do no 37 | * further harm over what has already been done by Felix Schmidt's code. 38 | * 39 | * I would recommend you do not copy or rely on this code. A near future update will 40 | * remove this and replace it with proper code I would be proud to put my name on. 41 | * 42 | * Until then, the copyright notice above was expressely requested by Felix Schmidt, 43 | * by means of a DMCA complaint at GitHub and Steam. 44 | * 45 | * There is no code between the end of this comment and he "END-OF-Felix Schmidt-COPYRIGHT" 46 | * line if there is one, or the end of the file, that I, Radu Hociung, claim any copyright 47 | * on. The rest of the content, outside of these delimiters, is my copyright, and 48 | * you may copy it in accordance to the modified GPL license at the root or the repo 49 | * (LICENSE file) 50 | * 51 | * FUTHER, if you base your code on a copy of the example mod from Felix Schmidt's 52 | * repository, which does not include his copyright notice, you will likely also 53 | * be a victim of DMCA complaint from him. 54 | */ 55 | 56 | using ColossalFramework.PlatformServices; 57 | using ColossalFramework.Plugins; 58 | using static ColossalFramework.Plugins.PluginManager; 59 | using ColossalFramework.UI; 60 | using ColossalFramework; 61 | using System; 62 | using System.Reflection; 63 | using System.Text; 64 | using ICities; 65 | 66 | namespace CitiesHarmony.API 67 | { 68 | internal static class SubscriptionWarning { 69 | private const string Marker = "Harmony2SubscriptionWarning"; 70 | 71 | public static void ShowOnce() { 72 | if (UnityEngine.GameObject.Find(Marker)) return; 73 | 74 | var go = new UnityEngine.GameObject(Marker); 75 | UnityEngine.Object.DontDestroyOnLoad(go); 76 | 77 | if(LoadingManager.instance.m_currentlyLoading || UIView.library == null) { 78 | LoadingManager.instance.m_introLoaded += OnIntroLoaded; 79 | LoadingManager.instance.m_levelLoaded += OnLevelLoaded; 80 | } else { 81 | Show(); 82 | } 83 | } 84 | 85 | private static void OnIntroLoaded() { 86 | LoadingManager.instance.m_introLoaded -= OnIntroLoaded; 87 | Show(); 88 | } 89 | 90 | private static void OnLevelLoaded(SimulationManager.UpdateMode updateMode) { 91 | LoadingManager.instance.m_levelLoaded -= OnLevelLoaded; 92 | Show(); 93 | } 94 | 95 | public static void Show() { 96 | string reason = "An error occured while attempting to automatically subscribe to Harmony (no network connection?)"; 97 | string solution = "You can manually download the Harmony mod from github.com/boformer/CitiesHarmony/releases"; 98 | if (PlatformService.platformType != PlatformType.Steam) { 99 | reason = "Harmony could not be installed automatically because you are using a platform other than Steam."; 100 | } else if (PluginManager.noWorkshop) { 101 | reason = "Harmony could not be installed automatically because you are playing in --noWorkshop mode!"; 102 | solution = "Restart without --noWorkshop or manually download the Harmony mod from github.com/boformer/CitiesHarmony/releases"; 103 | } else if(!PlatformService.workshop.IsAvailable()) { 104 | reason = "Harmony could not be installed automatically because the Steam workshop is not available (no network connection?)"; 105 | } else if(HarmonyHelper.IsHarmonyWorkshopItemSubscribed) { 106 | reason = "It seems that Harmony has already been subscribed, but Steam failed to download the files correctly or they were deleted."; 107 | solution = $"Close the game, then unsubscribe and resubscribe the Harmony workshop item from steamcommunity.com/sharedfiles/filedetails/?id={HarmonyHelper.HarmonyModWorkshopId}"; 108 | } 109 | 110 | /* FIXME: 111 | */ 112 | var affectedAssemblyNames = new StringBuilder(); 113 | 114 | Singleton.instance.GetImplementations() 115 | .ForEach((i) => 116 | { 117 | if (Array.Exists(i.GetType().Assembly.GetReferencedAssemblies(), 118 | (a) => a.Name == "CitiesHarmony.API" || a.Name == "Harmony.API")) 119 | { 120 | affectedAssemblyNames.Append("• ").Append(i.Name).Append('\n'); 121 | } 122 | }); 123 | 124 | var message = $"The mod(s):\n{affectedAssemblyNames} require the dependency 'Harmony' to work correctly!\n\n{reason}\n\n{solution}"; 125 | 126 | UIView.library.ShowModal("ExceptionPanel").SetMessage("Missing dependency: Harmony", message, false); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /API/HarmonyHelper.cs: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017 Felix Schmidt 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ************************************************************************** 26 | * 27 | * 28 | * IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT 29 | * 30 | * This file contains leftover code from the initial fork from Felix Schmidt's 31 | * repository https://github.com/boformer/CitiesHarmony 32 | * 33 | * It contains known bad code, which is either not used at all in my implementation, 34 | * or it is in the course of being re-written. If I am rewriting it, I only included 35 | * it because an initial release of my project was needed urgently to address 36 | * a broken modding eco-system in Cities Skylines, and I considered it will do no 37 | * further harm over what has already been done by Felix Schmidt's code. 38 | * 39 | * I would recommend you do not copy or rely on this code. A near future update will 40 | * remove this and replace it with proper code I would be proud to put my name on. 41 | * 42 | * Until then, the copyright notice above was expressely requested by Felix Schmidt, 43 | * by means of a DMCA complaint at GitHub and Steam. 44 | * 45 | * There is no code between the end of this comment and he "END-OF-Felix Schmidt-COPYRIGHT" 46 | * line if there is one, or the end of the file, that I, Radu Hociung, claim any copyright 47 | * on. The rest of the content, outside of these delimiters, is my copyright, and 48 | * you may copy it in accordance to the modified GPL license at the root or the repo 49 | * (LICENSE file) 50 | * 51 | * FUTHER, if you base your code on a copy of the example mod from Felix Schmidt's 52 | * repository, which does not include his copyright notice, you will likely also 53 | * be a victim of DMCA complaint from him. 54 | */ 55 | 56 | using IAwareness; 57 | using ColossalFramework.PlatformServices; 58 | using ColossalFramework.Plugins; 59 | using ColossalFramework; 60 | using System; 61 | using System.Collections.Generic; 62 | 63 | namespace CitiesHarmony.API 64 | { 65 | public static class HarmonyHelper { 66 | internal const ulong HarmonyModWorkshopId = 2399343344uL; 67 | 68 | private static bool _workshopItemInstalledSubscribed = false; 69 | private static List _harmonyReadyActions = new List(); 70 | 71 | public static bool IsHarmonyInstalled { 72 | get { 73 | var empty = new List(); 74 | 75 | return Singleton.instance.GetImplementations() 76 | .Exists((i) => i.DoOnHarmonyReady(empty)); 77 | } 78 | } 79 | 80 | public static void EnsureHarmonyInstalled() { 81 | if (!IsHarmonyInstalled) { 82 | InstallHarmonyWorkshopItem(); 83 | } 84 | } 85 | 86 | public static void DoOnHarmonyReady(Action action) { 87 | if (IsHarmonyInstalled) { 88 | action(); 89 | } else { 90 | _harmonyReadyActions.Add(new ClientCallback() { action = action, callStack = new System.Diagnostics.StackTrace(1, false), }); 91 | 92 | if (!_workshopItemInstalledSubscribed && SteamWorkshopAvailable) { 93 | _workshopItemInstalledSubscribed = true; 94 | PlatformService.workshop.eventWorkshopItemInstalled += OnWorkshopItemInstalled; 95 | } 96 | 97 | InstallHarmonyWorkshopItem(); 98 | } 99 | } 100 | 101 | private static bool SteamWorkshopAvailable => PlatformService.platformType == PlatformType.Steam && !PluginManager.noWorkshop; 102 | 103 | private static void InstallHarmonyWorkshopItem() { 104 | // TODO show error message to the user 105 | 106 | if (PlatformService.platformType != PlatformType.Steam) { 107 | UnityEngine.Debug.LogError($"[{Versioning.FULL_PACKAGE_NAME}] Cannot auto-subscribe Harmony on platforms other than Steam!"); 108 | SubscriptionWarning.ShowOnce(); 109 | return; 110 | } 111 | 112 | if (PluginManager.noWorkshop) { 113 | UnityEngine.Debug.LogError($"[{Versioning.FULL_PACKAGE_NAME}] Cannot auto-subscribe Harmony in --noWorkshop mode!"); 114 | SubscriptionWarning.ShowOnce(); 115 | return; 116 | } 117 | 118 | if(!PlatformService.workshop.IsAvailable()) { 119 | UnityEngine.Debug.LogError($"[{Versioning.FULL_PACKAGE_NAME}] Cannot auto-subscribe Harmony while workshop is not available"); 120 | SubscriptionWarning.ShowOnce(); 121 | return; 122 | } 123 | 124 | 125 | if(IsHarmonyWorkshopItemSubscribed) { 126 | UnityEngine.Debug.LogError($"[{Versioning.FULL_PACKAGE_NAME}] Harmony workshop item is subscribed, but assembly is not loaded!"); 127 | SubscriptionWarning.ShowOnce(); 128 | return; 129 | } 130 | 131 | UnityEngine.Debug.Log($"[{Versioning.FULL_PACKAGE_NAME}] Subscribing to Harmony workshop item!"); 132 | if (!PlatformService.workshop.Subscribe(new PublishedFileId(HarmonyModWorkshopId))) { 133 | SubscriptionWarning.ShowOnce(); 134 | } 135 | } 136 | 137 | private static void OnWorkshopItemInstalled(PublishedFileId id) { 138 | if (Singleton.instance.GetImplementations() 139 | .Exists((i) => i.DoOnHarmonyReady(_harmonyReadyActions))) 140 | { 141 | UnityEngine.Debug.Log($"[{Versioning.FULL_PACKAGE_NAME}] Harmony workshop item subscribed and {_harmonyReadyActions.Count} OnReady tasks dispatched!"); 142 | } 143 | } 144 | 145 | internal static bool IsHarmonyWorkshopItemSubscribed { 146 | get { 147 | var subscribedIds = PlatformService.workshop.GetSubscribedItems(); 148 | if (subscribedIds == null) return false; 149 | 150 | foreach (var id in subscribedIds) { 151 | if (id.AsUInt64 == HarmonyModWorkshopId) return true; 152 | } 153 | 154 | return false; 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /Test.Harmony/HarmonyTests/AttributePatchTest.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Harmony for Cities Skylines 3 | * Copyright (C) 2021 Radu Hociung 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Reflection; 22 | using System.Reflection.Emit; 23 | using System.Linq; 24 | using HarmonyLib; 25 | using CitiesHarmony.API; 26 | 27 | namespace HarmonyMod.Tests 28 | { 29 | internal class AttributePatchTest 30 | { 31 | public void Run() 32 | { 33 | Harmony h = null; 34 | string hid = $"AttributePatchTest-API-{typeof(HarmonyHelper).Assembly.GetName().Version}"; 35 | try 36 | { 37 | h = new Harmony(hid); 38 | h.PatchAll(); 39 | } catch (Exception ex) 40 | { 41 | throw new TestFailed("Patching with Attributes", ex); 42 | } 43 | if (h != null) 44 | { 45 | Run("PatchAll()"); 46 | try 47 | { 48 | h.UnpatchAll(hid); 49 | } 50 | catch (Exception ex) 51 | { 52 | throw new TestFailed("Unpatching with Attributes", ex); 53 | } 54 | } 55 | } 56 | 57 | const int UNTOUCHED = 0x55aa; 58 | internal static string testName; 59 | public void Run(string subtest) 60 | { 61 | testName = "TEST/" + 62 | "API/" + 63 | typeof(HarmonyHelper).Assembly.GetName().Name + " " + 64 | typeof(HarmonyHelper).Assembly.GetName().Version; 65 | 66 | lastArg = UNTOUCHED; 67 | for (int i = 0; i < 30; i+=5) 68 | { 69 | int expected_result = 70 | /* prefix patch active */ 71 | i < 10 ? 100 * i : 72 | 73 | /* postfix patch active */ 74 | i < 20 ? 200 * i : 75 | 76 | /* transpiler patch active */ 77 | i < 30 ? i : 78 | 79 | /* No patch active */ 80 | i * 1000; 81 | 82 | int result = PatchTarget(i); 83 | if (expected_result != result) 84 | { 85 | throw new TestFailed($"Failed {subtest} Test. Expected {expected_result} got {result} for input={i}"); 86 | } 87 | 88 | if (i < 10) 89 | { 90 | if (lastArg != UNTOUCHED) 91 | { 92 | throw new TestFailed($"Failed {subtest} Test. LastArg set wrong. Expected {UNTOUCHED} got {lastArg}"); 93 | } 94 | 95 | } else if (i < 20) 96 | { 97 | if (lastArg != i) 98 | { 99 | throw new TestFailed($"Failed {subtest} Test. LastArg set wrong. Expected {i} got {lastArg}"); 100 | } 101 | } 102 | else if (i < 30) 103 | { 104 | #if true 105 | expected_result = 1000 + i; 106 | if (lastArg != expected_result) 107 | { 108 | throw new TestFailed($"Failed {subtest} Test. LastArg set wrong. Expected {expected_result} got {lastArg}"); 109 | } 110 | #endif 111 | } 112 | } 113 | } 114 | 115 | public int lastArg; 116 | int PatchTarget(int myArg) 117 | { 118 | lastArg = myArg; 119 | // PatchDefinitions.MyExtraMethod(this, myArg); 120 | 121 | UnityEngine.Debug.Log($"[{testName}] INFO - PatchTarget({myArg}) => {myArg} ; lastArg = {lastArg}"); 122 | return myArg; 123 | } 124 | } 125 | 126 | [HarmonyPatch(typeof(AttributePatchTest), "PatchTarget")] 127 | internal static class PatchDefinitions 128 | { 129 | 130 | [HarmonyPrefix] 131 | static bool Target_Prefix(AttributePatchTest __instance, int myArg, ref int __result) 132 | { 133 | if (myArg < 10) 134 | { 135 | __result = myArg * 100; 136 | UnityEngine.Debug.Log($"[{AttributePatchTest.testName}] INFO - Target_Prefix({myArg}) => {__result}"); 137 | return false; 138 | } 139 | UnityEngine.Debug.Log($"[{AttributePatchTest.testName}] INFO - Target_Prefix({myArg}) ... nop"); 140 | return true; 141 | } 142 | 143 | [HarmonyPostfix] 144 | static void Target_Postfix(AttributePatchTest __instance, int myArg, ref int __result) 145 | { 146 | if (myArg >= 10 && myArg < 20) 147 | { 148 | UnityEngine.Debug.Log($"[{AttributePatchTest.testName}] INFO - Target_Postfix({myArg}) => {__result} -> {myArg * 200}"); 149 | __result = myArg * 200; 150 | } else 151 | { 152 | UnityEngine.Debug.Log($"[{AttributePatchTest.testName}] INFO - Target_Postfix({myArg}) ... nop"); 153 | } 154 | } 155 | 156 | #if true 157 | [HarmonyTranspiler] 158 | static IEnumerable Target_Transpiler(IEnumerable instructions) 159 | { 160 | FieldInfo f_lastArg = AccessTools.Field(typeof(AttributePatchTest), "lastArg") 161 | ?? throw new TestFailed("Could not find lastArg field"); 162 | 163 | //MethodInfo m_MyExtraMethod = typeof(PatchDefinitions).GetMethod("MyExtraMethod", 164 | // BindingFlags.NonPublic | 165 | // BindingFlags.Static | 166 | // BindingFlags.Instance); 167 | 168 | MethodInfo m_MyExtraMethod = AccessTools.DeclaredMethod( 169 | typeof(PatchDefinitions), nameof(MyExtraMethod)) 170 | ?? throw new TestFailed("cound not find MyExtraMethod()"); 171 | 172 | if (m_MyExtraMethod == null) 173 | { 174 | throw new TestFailed("Could not find MyExtraMethod()"); 175 | } 176 | 177 | UnityEngine.Debug.Log($"[{AttributePatchTest.testName}] INFO - Target_Transpiler({instructions.Count()} instructions)"); 178 | 179 | var found = false; 180 | foreach (var instruction in instructions) 181 | { 182 | if (instruction.StoresField(f_lastArg)) 183 | { 184 | yield return instruction; 185 | yield return new CodeInstruction(OpCodes.Ldarg_0); // load instance ref 186 | yield return new CodeInstruction(OpCodes.Ldarg_1); // load argument info 187 | yield return new CodeInstruction(OpCodes.Call, m_MyExtraMethod); // Call MyExtraMethod. 188 | yield return new CodeInstruction(OpCodes.Nop); // not sure why this is needed 189 | 190 | found = true; 191 | } else 192 | { 193 | yield return instruction; 194 | } 195 | } 196 | if (found is false) 197 | throw new TestFailed($"Transpiler did not find taget instruction."); 198 | } 199 | #endif 200 | 201 | public static void MyExtraMethod(AttributePatchTest inst, int theArg) 202 | { 203 | if (theArg >= 20 && theArg < 30) 204 | { 205 | inst.lastArg = 1000 + theArg; 206 | } 207 | } 208 | 209 | } 210 | 211 | } 212 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Harmony lib for Cities Skylines 2 | 3 | /* 4 | MIT License 5 | 6 | Copyright (c) 2017 Felix Schmidt 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /* ************************************************************************** 28 | * 29 | * 30 | * IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT 31 | * 32 | * This file contains leftover code from the initial fork from Felix Schmidt's 33 | * repository https://github.com/boformer/CitiesHarmony 34 | * 35 | * It contains known bad code, which is either not used at all in my implementation, 36 | * or it is in the course of being re-written. If I am rewriting it, I only included 37 | * it because an initial release of my project was needed urgently to address 38 | * a broken modding eco-system in Cities Skylines, and I considered it will do no 39 | * further harm over what has already been done by Felix Schmidt's code. 40 | * 41 | * I would recommend you do not copy or rely on this code. A near future update will 42 | * remove this and replace it with proper code I would be proud to put my name on. 43 | * The examples given in this leftover README are very bad, and they are main causes 44 | * for the malaise in the Cities Skylines modding environment. It is because of this 45 | * bad code that I got involved to try to clean up the mess for the player community. 46 | * 47 | * Until then, the copyright notice above was expressely requested by Felix Schmidt, 48 | * by means of a DMCA complaint at GitHub and Steam. 49 | * 50 | * There is no code between the end of this comment and he "END-OF-Felix Schmidt-COPYRIGHT" 51 | * line if there is one, or the end of the file, that I, Radu Hociung, claim any copyright 52 | * on. The rest of the content, outside of these delimiters, is my copyright, and 53 | * you may copy it in accordance to the modified GPL license at the root or the repo 54 | * (LICENSE file) 55 | * 56 | * FUTHER, if you base your code on a copy of the example mod from Felix Schmidt's 57 | * repository, which does not include his copyright notice, you will likely also 58 | * be a victim of DMCA complaint from him. 59 | */ 60 | 61 | 62 | 63 | [![NuGet Badge](https://buildstats.info/nuget/Harmony-CitiesSkylines)](https://www.nuget.org/packages/Harmony-CitiesSkylines/) 64 | 65 | This C:SL mod provides Andreas Pardeike's [Harmony patching library](https://github.com/pardeike/Harmony) (version 2.0.4) to all mods that require it. 66 | 67 | It hotpatches older Harmony versions (1.2.0.1 and 1.1.0.0) and adds limited cross-compatibility for Harmony 1.0.9.1. All of those versions are still used by various mods. The patching is necessary because Harmony 1.x is incompatible with 2.x. 68 | 69 | All API versions previously released by boformer for his CitiesHarmony mod are supported. This mod is a **drop-in replacement**. 70 | 71 | **By using auto-subscription, it is possible to migrate existing mods to Harmony 2.x without causing disruptions for users!** 72 | 73 | ## Documentation for Mod Developers 74 | 75 | ### API Package Installation 76 | 77 | To use Harmony 2.x in your mod, add the [CitiesHarmony.API](https://www.nuget.org/packages/CitiesHarmony.API/) nuget package to your project. The package includes the latest version of Harmony as well as the `HarmonyHelper` that is used to access it. 78 | 79 | Make sure that when you build your mod: 80 | 81 | * The `CitiesHarmony.API.dll` is copied to the AppData mod directory 82 | * The `CitiesHarmony.Harmony.dll` is **not** copied to the AppData mod directory (it is provided by the central Harmony mod) 83 | 84 | Depending on the version of Visual Studio, the project style and the post-build script you are using, there are different ways to achieve that: 85 | 86 | * If you are manually copying the assemblies to AppData mod directory, make sure to copy only the DLL of your mod and the `CitiesHarmony.API.dll`, **not** the `CitiesHarmony.Harmony.dll`. 87 | * If you are using the old Visual Studio project style (packages.config) with a post-build script, set "Local Copy" to true for `CitiesHarmony.API` reference and to **false** for `CitiesHarmony.Harmony` reference. 88 | * If you are using the new Visual Studio project style (like the example mod in this repository) with a post-build target, no action is required after installing the Nuget package! It is configured so that `CitiesHarmony.Harmony` is not copied to the target directory. 89 | 90 | ### API Usage 91 | 92 | **Make sure that there are no references to `HarmonyLib` in your `IUserMod` implementation.** 93 | Otherwise the mod could not be loaded if CitiesHarmony is not subscribed. Instead, it is recommended to keep `HarmonyLib`-related code (such as calls to `PatchAll` and `UnpatchAll`) in a separate static `Patcher` class. 94 | 95 | Before making calls to harmony in your code, you need to query `CitiesHarmony.API.HarmonyHelper` to see if it is available. There are 3 different hooks for that purpose: 96 | 97 | * `void HarmonyHelper.DoOnHarmonyReady(Action)`: Will invoke the passed action when Harmony 2.x is ready to use. This hook should be called from `IUserMod.OnEnabled`. If the Harmony mod is not installed, this hook will attempt to auto-subscribe to it. 98 | * `void HarmonyHelper.EnsureHarmonyInstalled()`: If you don't want to apply your patches in `IUserMod.OnEnabled`, call this method instead. It will perform the same auto-subscription as `DoOnHarmonyReady` 99 | * `bool HarmonyHelper.IsHarmonyInstalled`: Returns `true` is Harmony is ready to be used. When queried, this hook will *not* attempt to auto-subscribe to the Harmony workshop item. Use this hook for all kinds of unpatching, applying patches in the `LoadingExtension` or while the simulation is running. 100 | 101 | Take a look at the example mod in this repository for further inspiration! 102 | 103 | It is recommened to add this mod as a dependency to your workshop item for transparency reasons. 104 | 105 | #### Alternative A: Applying your patches in `OnEnabled`/`OnDisabled` 106 | 107 | ```c# 108 | public class Mod : IUserMod { 109 | // ... 110 | 111 | public void OnEnabled() { 112 | HarmonyHelper.DoOnHarmonyReady(() => Patcher.PatchAll()); 113 | } 114 | 115 | public void OnDisabled() { 116 | if (HarmonyHelper.IsHarmonyInstalled) Patcher.UnpatchAll(); 117 | } 118 | } 119 | ``` 120 | 121 | #### Alternative B: Applying your patches in `LoadingExtensionBase` 122 | 123 | ```c# 124 | public class Mod : LoadingExtensionBase, IUserMod { 125 | // ... 126 | 127 | public void OnEnabled() { 128 | HarmonyHelper.EnsureHarmonyInstalled(); 129 | } 130 | 131 | public override void OnCreated(ILoading loading) { 132 | if (HarmonyHelper.IsHarmonyInstalled) Patcher.PatchAll(); 133 | } 134 | 135 | public override void OnReleased() { 136 | if (HarmonyHelper.IsHarmonyInstalled) Patcher.UnpatchAll(); 137 | } 138 | } 139 | ``` 140 | 141 | #### Example Patcher Class 142 | 143 | ```c# 144 | public static class Patcher { 145 | private const string HarmonyId = "yourname.YourModName"; 146 | private static bool patched = false; 147 | 148 | public static void PatchAll() { 149 | if (patched) return; 150 | 151 | patched = true; 152 | var harmony = new Harmony(HarmonyId); 153 | harmony.PatchAll(typeof(Patcher).GetType().Assembly); // you can also do manual patching here! 154 | } 155 | 156 | public static void UnpatchAll() { 157 | if (!patched) return; 158 | 159 | var harmony = new Harmony(HarmonyId); 160 | harmony.UnpatchAll(HarmonyId); 161 | patched = false; 162 | } 163 | } 164 | ``` 165 | -------------------------------------------------------------------------------- /Test.Harmony/HarmonyTests/ReturningStructs.cs: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017 Felix Schmidt 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ************************************************************************** 26 | * 27 | * 28 | * IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT 29 | * 30 | * This file contains leftover code from the initial fork from Felix Schmidt's 31 | * repository https://github.com/boformer/CitiesHarmony 32 | * 33 | * It contains known bad code, which is either not used at all in my implementation, 34 | * or it is in the course of being re-written. If I am rewriting it, I only included 35 | * it because an initial release of my project was needed urgently to address 36 | * a broken modding eco-system in Cities Skylines, and I considered it will do no 37 | * further harm over what has already been done by Felix Schmidt's code. 38 | * 39 | * I would recommend you do not copy or rely on this code. A near future update will 40 | * remove this and replace it with proper code I would be proud to put my name on. 41 | * 42 | * Until then, the copyright notice above was expressely requested by Felix Schmidt, 43 | * by means of a DMCA complaint at GitHub and Steam. 44 | * 45 | * There is no code between the end of this comment and he "END-OF-Felix Schmidt-COPYRIGHT" 46 | * line if there is one, or the end of the file, that I, Radu Hociung, claim any copyright 47 | * on. The rest of the content, outside of these delimiters, is my copyright, and 48 | * you may copy it in accordance to the modified GPL license at the root or the repo 49 | * (LICENSE file) 50 | * 51 | * FUTHER, if you base your code on a copy of the example mod from Felix Schmidt's 52 | * repository, which does not include his copyright notice, you will likely also 53 | * be a victim of DMCA complaint from him. 54 | */ 55 | namespace HarmonyLibTests.Assets.Structs { 56 | public struct St01 { 57 | public byte b1; 58 | } 59 | 60 | public struct St02 { 61 | public byte b1; 62 | public byte b2; 63 | } 64 | 65 | public struct St03 { 66 | public byte b1; 67 | public byte b2; 68 | public byte b3; 69 | } 70 | 71 | public struct St04 { 72 | public byte b1; 73 | public byte b2; 74 | public byte b3; 75 | public byte b4; 76 | } 77 | 78 | public struct St05 { 79 | public byte b1; 80 | public byte b2; 81 | public byte b3; 82 | public byte b4; 83 | public byte b5; 84 | } 85 | 86 | public struct St06 { 87 | public byte b1; 88 | public byte b2; 89 | public byte b3; 90 | public byte b4; 91 | public byte b5; 92 | public byte b6; 93 | } 94 | 95 | public struct St07 { 96 | public byte b1; 97 | public byte b2; 98 | public byte b3; 99 | public byte b4; 100 | public byte b5; 101 | public byte b6; 102 | public byte b7; 103 | } 104 | 105 | public struct St08 { 106 | public byte b1; 107 | public byte b2; 108 | public byte b3; 109 | public byte b4; 110 | public byte b5; 111 | public byte b6; 112 | public byte b7; 113 | public byte b8; 114 | } 115 | 116 | public struct St09 { 117 | public byte b1; 118 | public byte b2; 119 | public byte b3; 120 | public byte b4; 121 | public byte b5; 122 | public byte b6; 123 | public byte b7; 124 | public byte b8; 125 | public byte b9; 126 | } 127 | 128 | public struct St10 { 129 | public byte b1; 130 | public byte b2; 131 | public byte b3; 132 | public byte b4; 133 | public byte b5; 134 | public byte b6; 135 | public byte b7; 136 | public byte b8; 137 | public byte b9; 138 | public byte b10; 139 | } 140 | 141 | public struct St11 { 142 | public byte b1; 143 | public byte b2; 144 | public byte b3; 145 | public byte b4; 146 | public byte b5; 147 | public byte b6; 148 | public byte b7; 149 | public byte b8; 150 | public byte b9; 151 | public byte b10; 152 | public byte b11; 153 | } 154 | 155 | public struct St12 { 156 | public byte b1; 157 | public byte b2; 158 | public byte b3; 159 | public byte b4; 160 | public byte b5; 161 | public byte b6; 162 | public byte b7; 163 | public byte b8; 164 | public byte b9; 165 | public byte b10; 166 | public byte b11; 167 | public byte b12; 168 | } 169 | 170 | public struct St13 { 171 | public byte b1; 172 | public byte b2; 173 | public byte b3; 174 | public byte b4; 175 | public byte b5; 176 | public byte b6; 177 | public byte b7; 178 | public byte b8; 179 | public byte b9; 180 | public byte b10; 181 | public byte b11; 182 | public byte b12; 183 | public byte b13; 184 | } 185 | 186 | public struct St14 { 187 | public byte b1; 188 | public byte b2; 189 | public byte b3; 190 | public byte b4; 191 | public byte b5; 192 | public byte b6; 193 | public byte b7; 194 | public byte b8; 195 | public byte b9; 196 | public byte b10; 197 | public byte b11; 198 | public byte b12; 199 | public byte b13; 200 | public byte b14; 201 | } 202 | 203 | public struct St15 { 204 | public byte b1; 205 | public byte b2; 206 | public byte b3; 207 | public byte b4; 208 | public byte b5; 209 | public byte b6; 210 | public byte b7; 211 | public byte b8; 212 | public byte b9; 213 | public byte b10; 214 | public byte b11; 215 | public byte b12; 216 | public byte b13; 217 | public byte b14; 218 | public byte b15; 219 | } 220 | 221 | public struct St16 { 222 | public byte b1; 223 | public byte b2; 224 | public byte b3; 225 | public byte b4; 226 | public byte b5; 227 | public byte b6; 228 | public byte b7; 229 | public byte b8; 230 | public byte b9; 231 | public byte b10; 232 | public byte b11; 233 | public byte b12; 234 | public byte b13; 235 | public byte b14; 236 | public byte b15; 237 | public byte b16; 238 | } 239 | 240 | public struct St17 { 241 | public byte b1; 242 | public byte b2; 243 | public byte b3; 244 | public byte b4; 245 | public byte b5; 246 | public byte b6; 247 | public byte b7; 248 | public byte b8; 249 | public byte b9; 250 | public byte b10; 251 | public byte b11; 252 | public byte b12; 253 | public byte b13; 254 | public byte b14; 255 | public byte b15; 256 | public byte b16; 257 | public byte b17; 258 | } 259 | 260 | public struct St18 { 261 | public byte b1; 262 | public byte b2; 263 | public byte b3; 264 | public byte b4; 265 | public byte b5; 266 | public byte b6; 267 | public byte b7; 268 | public byte b8; 269 | public byte b9; 270 | public byte b10; 271 | public byte b11; 272 | public byte b12; 273 | public byte b13; 274 | public byte b14; 275 | public byte b15; 276 | public byte b16; 277 | public byte b17; 278 | public byte b18; 279 | } 280 | 281 | public struct St19 { 282 | public byte b1; 283 | public byte b2; 284 | public byte b3; 285 | public byte b4; 286 | public byte b5; 287 | public byte b6; 288 | public byte b7; 289 | public byte b8; 290 | public byte b9; 291 | public byte b10; 292 | public byte b11; 293 | public byte b12; 294 | public byte b13; 295 | public byte b14; 296 | public byte b15; 297 | public byte b16; 298 | public byte b17; 299 | public byte b18; 300 | public byte b19; 301 | } 302 | 303 | public struct St20 { 304 | public byte b1; 305 | public byte b2; 306 | public byte b3; 307 | public byte b4; 308 | public byte b5; 309 | public byte b6; 310 | public byte b7; 311 | public byte b8; 312 | public byte b9; 313 | public byte b10; 314 | public byte b11; 315 | public byte b12; 316 | public byte b13; 317 | public byte b14; 318 | public byte b15; 319 | public byte b16; 320 | public byte b17; 321 | public byte b18; 322 | public byte b19; 323 | public byte b20; 324 | } 325 | } -------------------------------------------------------------------------------- /MonoMod.Common.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Library 6 | $(AssemblyName) 7 | 8 | Debug;Release;DebugLegacy;ReleaseLegacy;DebugTrace;ReleaseTrace 9 | Debug 10 | Any CPU 11 | portable 12 | bin\$(Configuration) 13 | 14 | 7.3 15 | true 16 | 17 | prompt 18 | 4 19 | true 20 | 21 | true 22 | false 23 | true 24 | true 25 | $(OutputPath)\$(AssemblyName).xml 26 | 27 | false 28 | 29 | false 30 | false 31 | 32 | 33 | true 34 | false 35 | 36 | true 37 | 38 | $(AssemblyName.ToUpper().Replace('.', '_'));$(DefineConstants) 39 | 40 | ..\MonoMod.Common\MonoMod.snk 41 | true 42 | false 43 | 44 | CS1591,$(NoWarn) 45 | 46 | 47 | 48 | 49 | true 50 | true 51 | true 52 | snupkg 53 | 54 | 55 | 56 | 57 | 0x0ade 58 | $([System.DateTime]::Now.ToString(yy.MM.dd)).00 59 | $(BUILD_BUILDNUMBER) 60 | Copyright $([System.DateTime]::Now.ToString(yyyy)) Maik Macho 61 | MonoMod;assembly;assemblies;module;modules;il;cil;msil;bytecode;reflection;injection;cecil;mono;$(PackageTags) 62 | MIT 63 | https://github.com/MonoMod/MonoMod 64 | https://github.com/MonoMod 65 | https://user-images.githubusercontent.com/1200380/47308180-28242880-d631-11e8-89d2-14d86f3db113.png 66 | 67 | 68 | 69 | 70 | true 71 | false 72 | DEBUG;TRACE;$(DefineConstants) 73 | 74 | 75 | 76 | 77 | true 78 | TRACE;$(DefineConstants) 79 | 80 | 81 | 82 | 83 | MONOMOD_DBGLOG;$(DefineConstants) 84 | 85 | 86 | 87 | 88 | true 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | true 97 | NETSTANDARD;$(DefineConstants) 98 | CECIL0_10;$(DefineConstants) 99 | CECIL0_11;$(DefineConstants) 100 | 0.10.0 101 | 0.11.* 102 | 4.* 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | true 122 | NETFRAMEWORK;NETFRAMEWORK4;$(DefineConstants) 123 | CECIL0_10;$(DefineConstants) 124 | CECIL0_11;$(DefineConstants) 125 | 0.10.0 126 | 0.11.* 127 | 128 | 129 | 130 | 131 | 132 | 133 | all 134 | runtime; build; native; contentfiles; analyzers; buildtransitive 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | true 143 | NETFRAMEWORK;NETFRAMEWORK3;$(DefineConstants) 144 | CECIL0_9;$(DefineConstants) 145 | CECIL0_10;$(DefineConstants) 146 | 0.9.6 147 | 0.10.* 148 | 149 | 150 | 151 | 152 | 153 | 154 | all 155 | runtime; build; native; contentfiles; analyzers; buildtransitive 156 | 157 | 158 | 159 | 160 | 161 | 162 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /Test.Harmony/HarmonyTests/ReturningStructMethods.cs: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017 Felix Schmidt 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | /* ************************************************************************** 26 | * 27 | * 28 | * IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT 29 | * 30 | * This file contains leftover code from the initial fork from Felix Schmidt's 31 | * repository https://github.com/boformer/CitiesHarmony 32 | * 33 | * It contains known bad code, which is either not used at all in my implementation, 34 | * or it is in the course of being re-written. If I am rewriting it, I only included 35 | * it because an initial release of my project was needed urgently to address 36 | * a broken modding eco-system in Cities Skylines, and I considered it will do no 37 | * further harm over what has already been done by Felix Schmidt's code. 38 | * 39 | * I would recommend you do not copy or rely on this code. A near future update will 40 | * remove this and replace it with proper code I would be proud to put my name on. 41 | * 42 | * Until then, the copyright notice above was expressely requested by Felix Schmidt, 43 | * by means of a DMCA complaint at GitHub and Steam. 44 | * 45 | * There is no code between the end of this comment and he "END-OF-Felix Schmidt-COPYRIGHT" 46 | * line if there is one, or the end of the file, that I, Radu Hociung, claim any copyright 47 | * on. The rest of the content, outside of these delimiters, is my copyright, and 48 | * you may copy it in accordance to the modified GPL license at the root or the repo 49 | * (LICENSE file) 50 | * 51 | * FUTHER, if you base your code on a copy of the example mod from Felix Schmidt's 52 | * repository, which does not include his copyright notice, you will likely also 53 | * be a victim of DMCA complaint from him. 54 | */ 55 | using HarmonyLibTests.Assets.Structs; 56 | using System; 57 | 58 | namespace HarmonyLibTests.Assets.Methods { 59 | public static class ReturningStructs_Patch { 60 | public static void Prefix(string s) { 61 | if (s != "test") throw new ArgumentException("First argument corrupt"); 62 | } 63 | } 64 | 65 | public class ReturningStructs { 66 | public St01 IM01(string s) { 67 | if (s != "test") throw new ArgumentException("First argument corrupt"); 68 | return new St01 { b1 = 42 }; 69 | } 70 | 71 | public St02 IM02(string s) { 72 | if (s != "test") throw new ArgumentException("First argument corrupt"); 73 | return new St02 { b1 = 42 }; 74 | } 75 | 76 | public St03 IM03(string s) { 77 | if (s != "test") throw new ArgumentException("First argument corrupt"); 78 | return new St03 { b1 = 42 }; 79 | } 80 | 81 | public St04 IM04(string s) { 82 | if (s != "test") throw new ArgumentException("First argument corrupt"); 83 | return new St04 { b1 = 42 }; 84 | } 85 | 86 | public St05 IM05(string s) { 87 | if (s != "test") throw new ArgumentException("First argument corrupt"); 88 | return new St05 { b1 = 42 }; 89 | } 90 | 91 | public St06 IM06(string s) { 92 | if (s != "test") throw new ArgumentException("First argument corrupt"); 93 | return new St06 { b1 = 42 }; 94 | } 95 | 96 | public St07 IM07(string s) { 97 | if (s != "test") throw new ArgumentException("First argument corrupt"); 98 | return new St07 { b1 = 42 }; 99 | } 100 | 101 | public St08 IM08(string s) { 102 | if (s != "test") throw new ArgumentException("First argument corrupt"); 103 | return new St08 { b1 = 42 }; 104 | } 105 | 106 | public St09 IM09(string s) { 107 | if (s != "test") throw new ArgumentException("First argument corrupt"); 108 | return new St09 { b1 = 42 }; 109 | } 110 | 111 | public St10 IM10(string s) { 112 | if (s != "test") throw new ArgumentException("First argument corrupt"); 113 | return new St10 { b1 = 42 }; 114 | } 115 | 116 | public St11 IM11(string s) { 117 | if (s != "test") throw new ArgumentException("First argument corrupt"); 118 | return new St11 { b1 = 42 }; 119 | } 120 | 121 | public St12 IM12(string s) { 122 | if (s != "test") throw new ArgumentException("First argument corrupt"); 123 | return new St12 { b1 = 42 }; 124 | } 125 | 126 | public St13 IM13(string s) { 127 | if (s != "test") throw new ArgumentException("First argument corrupt"); 128 | return new St13 { b1 = 42 }; 129 | } 130 | 131 | public St14 IM14(string s) { 132 | if (s != "test") throw new ArgumentException("First argument corrupt"); 133 | return new St14 { b1 = 42 }; 134 | } 135 | 136 | public St15 IM15(string s) { 137 | if (s != "test") throw new ArgumentException("First argument corrupt"); 138 | return new St15 { b1 = 42 }; 139 | } 140 | 141 | public St16 IM16(string s) { 142 | if (s != "test") throw new ArgumentException("First argument corrupt"); 143 | return new St16 { b1 = 42 }; 144 | } 145 | 146 | public St17 IM17(string s) { 147 | if (s != "test") throw new ArgumentException("First argument corrupt"); 148 | return new St17 { b1 = 42 }; 149 | } 150 | 151 | public St18 IM18(string s) { 152 | if (s != "test") throw new ArgumentException("First argument corrupt"); 153 | return new St18 { b1 = 42 }; 154 | } 155 | 156 | public St19 IM19(string s) { 157 | if (s != "test") throw new ArgumentException("First argument corrupt"); 158 | return new St19 { b1 = 42 }; 159 | } 160 | 161 | public St20 IM20(string s) { 162 | if (s != "test") throw new ArgumentException("First argument corrupt"); 163 | return new St20 { b1 = 42 }; 164 | } 165 | 166 | // 167 | 168 | public static St01 SM01(string s) { 169 | if (s != "test") throw new ArgumentException("First argument corrupt"); 170 | return new St01 { b1 = 42 }; 171 | } 172 | 173 | public static St02 SM02(string s) { 174 | if (s != "test") throw new ArgumentException("First argument corrupt"); 175 | return new St02 { b1 = 42 }; 176 | } 177 | 178 | public static St03 SM03(string s) { 179 | if (s != "test") throw new ArgumentException("First argument corrupt"); 180 | return new St03 { b1 = 42 }; 181 | } 182 | 183 | public static St04 SM04(string s) { 184 | if (s != "test") throw new ArgumentException("First argument corrupt"); 185 | return new St04 { b1 = 42 }; 186 | } 187 | 188 | public static St05 SM05(string s) { 189 | if (s != "test") throw new ArgumentException("First argument corrupt"); 190 | return new St05 { b1 = 42 }; 191 | } 192 | 193 | public static St06 SM06(string s) { 194 | if (s != "test") throw new ArgumentException("First argument corrupt"); 195 | return new St06 { b1 = 42 }; 196 | } 197 | 198 | public static St07 SM07(string s) { 199 | if (s != "test") throw new ArgumentException("First argument corrupt"); 200 | return new St07 { b1 = 42 }; 201 | } 202 | 203 | public static St08 SM08(string s) { 204 | if (s != "test") throw new ArgumentException("First argument corrupt"); 205 | return new St08 { b1 = 42 }; 206 | } 207 | 208 | public static St09 SM09(string s) { 209 | if (s != "test") throw new ArgumentException("First argument corrupt"); 210 | return new St09 { b1 = 42 }; 211 | } 212 | 213 | public static St10 SM10(string s) { 214 | if (s != "test") throw new ArgumentException("First argument corrupt"); 215 | return new St10 { b1 = 42 }; 216 | } 217 | 218 | public static St11 SM11(string s) { 219 | if (s != "test") throw new ArgumentException("First argument corrupt"); 220 | return new St11 { b1 = 42 }; 221 | } 222 | 223 | public static St12 SM12(string s) { 224 | if (s != "test") throw new ArgumentException("First argument corrupt"); 225 | return new St12 { b1 = 42 }; 226 | } 227 | 228 | public static St13 SM13(string s) { 229 | if (s != "test") throw new ArgumentException("First argument corrupt"); 230 | return new St13 { b1 = 42 }; 231 | } 232 | 233 | public static St14 SM14(string s) { 234 | if (s != "test") throw new ArgumentException("First argument corrupt"); 235 | return new St14 { b1 = 42 }; 236 | } 237 | 238 | public static St15 SM15(string s) { 239 | if (s != "test") throw new ArgumentException("First argument corrupt"); 240 | return new St15 { b1 = 42 }; 241 | } 242 | 243 | public static St16 SM16(string s) { 244 | if (s != "test") throw new ArgumentException("First argument corrupt"); 245 | return new St16 { b1 = 42 }; 246 | } 247 | 248 | public static St17 SM17(string s) { 249 | if (s != "test") throw new ArgumentException("First argument corrupt"); 250 | return new St17 { b1 = 42 }; 251 | } 252 | 253 | public static St18 SM18(string s) { 254 | if (s != "test") throw new ArgumentException("First argument corrupt"); 255 | return new St18 { b1 = 42 }; 256 | } 257 | 258 | public static St19 SM19(string s) { 259 | if (s != "test") throw new ArgumentException("First argument corrupt"); 260 | return new St19 { b1 = 42 }; 261 | } 262 | 263 | public static St20 SM20(string s) { 264 | if (s != "test") throw new ArgumentException("First argument corrupt"); 265 | return new St20 { b1 = 42 }; 266 | } 267 | } 268 | } -------------------------------------------------------------------------------- /HarmonyMod/HarmonyMod.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | {1821BC25-9494-40B7-8229-945C60C16BC2} 4 | net35 5 | false 6 | HarmonyMod 7 | Debug;Release;Beta;Developer-Updater;Developer 8 | 22 | false 23 | false 24 | false 25 | false 26 | false 27 | false 28 | HarmonyMod 29 | Harmony-CitiesSkylines 30 | Radu Hociung 31 | This is a mod for Cities: Skylines that provides common infrastructure like patching (using Andreas Pardeike' Harmony), 32 | compatibility management, exception management, etc 33 | 2021 34 | https://github.com/drok/Harmony-CitiesSkylines 35 | latest 36 | 37 | 38 | false 39 | true 40 | Properties\StrongName.pfx 41 | false 42 | 631fdc66-d970-44a9-9df3-895a0745b50e 43 | 44 | 45 | 46 | 47 | 48 | 49 | true 50 | true 51 | false 52 | false 53 | 54 | 55 | HarmonyCHH2040 56 | false 57 | true 58 | 59 | 60 | Harmony2009 61 | false 62 | true 63 | 64 | 65 | Harmony2010 66 | false 67 | true 68 | 69 | 70 | Harmony2 71 | true 72 | true 73 | false 74 | false 75 | 76 | 77 | C:\References\Assembly-CSharp.dll 78 | False 79 | 80 | 81 | C:\References\ColossalManaged.dll 82 | False 83 | 84 | 85 | C:\References\IAmAware-0.0.1.0.dll 86 | 87 | true 88 | false 89 | false 90 | 91 | 92 | C:\References\ICities.dll 93 | False 94 | 95 | 96 | C:\References\UnityEngine.dll 97 | False 98 | 99 | 100 | 101 | 102 | Never 103 | 104 | 105 | 106 | false 107 | ;NU1605 108 | 109 | 110 | 111 | false 112 | full 113 | true 114 | 115 | 116 | 117 | true 118 | none 119 | false 120 | 121 | 122 | 123 | TRACE 124 | 125 | 126 | 127 | TRACE;DEVELOPER 128 | 129 | 130 | 131 | TRACE;DEVELOPER;DEVELOPER_UPDATER 132 | 133 | 134 | 135 | DEBUG;TRACE;HEAVY_TRACE 136 | 137 | 138 | 139 | 140 | PreserveNewest 141 | HarmonyMod_helper_dll 142 | 143 | 144 | PreserveNewest 145 | HarmonyMod_helper_pdb 146 | 147 | 148 | 149 | 150 | PreserveNewest 151 | CitiesHarmony.Harmony-2.0.4.0.dll 152 | 153 | 154 | PreserveNewest 155 | 0Harmony-2.0.0.9.dll 156 | 157 | 158 | PreserveNewest 159 | 0Harmony-2.0.1.0.dll 160 | 161 | 162 | 163 | 164 | PreserveNewest 165 | CitiesHarmony.Harmony-2.0.4.0.pdb 166 | 167 | 168 | PreserveNewest 169 | 0Harmony-2.0.0.9.pdb 170 | 171 | 172 | PreserveNewest 173 | 0Harmony-2.0.1.0.pdb 174 | 175 | 176 | 177 | 178 | 179 | $(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)-$(Configuration)\ 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /Test.Harmony/HarmonyTests/ACLTest.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Harmony for Cities Skylines 3 | * Copyright (C) 2021 Radu Hociung 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Reflection; 22 | using System.Reflection.Emit; 23 | using System.Linq; 24 | using HarmonyLib; 25 | using CitiesHarmony.API; 26 | 27 | namespace HarmonyMod.Tests 28 | { 29 | internal class ACLTest 30 | { 31 | public void Run() 32 | { 33 | Harmony h = null; 34 | string hid = $"ACL-API-{typeof(HarmonyHelper).Assembly.GetName().Version}"; 35 | try 36 | { 37 | h = new Harmony(hid); 38 | if (h != null) 39 | { 40 | AttemptProhibitedUnpatchAll(h); 41 | AttemptProhibitedPatch(h); 42 | } 43 | } 44 | catch (Exception ex) 45 | { 46 | throw new TestFailed("Test ACL", ex); 47 | } 48 | } 49 | public void RunAfterHarmony() 50 | { 51 | Harmony h = null; 52 | string hid = $"ACL-API-{typeof(HarmonyHelper).Assembly.GetName().Version}"; 53 | try 54 | { 55 | h = new Harmony(hid); 56 | if (h != null) 57 | { 58 | AttemptRemoveHarmonyModPatch(h); 59 | } 60 | } 61 | catch (Exception ex) 62 | { 63 | throw new TestFailed("Test ACL", ex); 64 | } 65 | } 66 | public void AttemptProhibitedUnpatchAll(Harmony h) 67 | { 68 | testName = "TEST/" + 69 | "API/ACL1/" + 70 | typeof(HarmonyHelper).Assembly.GetName().Name + " " + 71 | typeof(HarmonyHelper).Assembly.GetName().Version; 72 | 73 | try 74 | { 75 | h.UnpatchAll(); 76 | throw new TestFailed("Global UnpatchAll() should throw"); 77 | } 78 | catch (HarmonyException ex) 79 | { 80 | if (ex.GetType().Name != "HarmonyUserException" || ex.Message != "Prohibited global UnpatchAll()") 81 | { 82 | throw new TestFailed("Global UnpatchAll() failed but is not prohibited"); 83 | } 84 | UnityEngine.Debug.Log($"[{testName}] INFO - Prohibited UnpatchAll() works. OK"); 85 | } 86 | catch (Exception ex) 87 | { 88 | throw new TestFailed("Global Unpatch", ex); 89 | } 90 | 91 | } 92 | public void AttemptProhibitedPatch(Harmony h) 93 | { 94 | testName = "TEST/" + 95 | "API/ACL2/" + 96 | typeof(HarmonyHelper).Assembly.GetName().Name + " " + 97 | typeof(HarmonyHelper).Assembly.GetName().Version; 98 | 99 | PatchProcessor processor = null; 100 | MethodInfo prohibitedPatch = null; 101 | try 102 | { 103 | var targetName = "CompareTo"; 104 | MethodInfo target = typeof(Patch).GetMethod(targetName, 105 | BindingFlags.Instance | 106 | BindingFlags.Static | 107 | BindingFlags.Public | 108 | BindingFlags.NonPublic); 109 | if (target == null) 110 | { 111 | throw new TestFailed($"Target fn ({targetName}) was not found."); 112 | } 113 | processor = h.CreateProcessor(target); 114 | if (processor == null) 115 | { 116 | UnityEngine.Debug.Log($"[{testName}] INFO - Processor=null. BAD"); 117 | throw new TestFailed($"Failed to create processor."); 118 | } 119 | 120 | prohibitedPatch = typeof(ACLPatchDefinitions).GetMethod("ProhibitedPatch", 121 | BindingFlags.Instance | 122 | BindingFlags.Static | 123 | BindingFlags.Public | 124 | BindingFlags.NonPublic); 125 | if (prohibitedPatch == null) 126 | { 127 | throw new TestFailed("Patch fn (ProhibitedPatch) was not found."); 128 | } 129 | 130 | processor.AddPostfix(prohibitedPatch); 131 | processor.Patch(); 132 | throw new TestFailed("Installing a prohibited patch did not throw HarmonyModACLException"); 133 | } 134 | catch (TestFailed ex) 135 | { 136 | UnityEngine.Debug.Log($"[{testName}] ERROR : {ex.Message}. BAD"); 137 | throw ex; 138 | } 139 | catch (Exception ex) 140 | { 141 | if (ex.GetType().Name != "HarmonyModACLException") 142 | { 143 | throw new TestFailed($"Installing a prohibited patch failed but not because of ACL: {ex.Message}"); 144 | } 145 | UnityEngine.Debug.Log($"[{testName}] INFO - Attempting to install a prohibited patch was blocked ({ex.Message}). OK."); 146 | 147 | } 148 | finally 149 | { 150 | if (processor != null && prohibitedPatch != null) 151 | { 152 | try 153 | { 154 | processor.Unpatch(prohibitedPatch); 155 | } 156 | catch (Exception ex) 157 | { 158 | if (ex.GetType().Name != "HarmonyModACLException") 159 | { 160 | throw new TestFailed($"Uninstalling a prohibited patch failed but not because of ACL: {ex.Message}"); 161 | } 162 | UnityEngine.Debug.Log($"[{testName}] INFO - Attempting to remove a prohibited patch was blocked ({ex.Message}). OK."); 163 | } 164 | 165 | } 166 | else 167 | { 168 | throw new TestFailed($"Prohibited patch test is broken. processor={processor != null} patch={prohibitedPatch != null}"); 169 | } 170 | } 171 | } 172 | 173 | public void AttemptRemoveHarmonyModPatch(Harmony h) 174 | { 175 | /* Needs to run after Harmony is OnEnabled() */ 176 | 177 | testName = "TEST/" + 178 | "API/ACL3/" + 179 | typeof(HarmonyHelper).Assembly.GetName().Name + " " + 180 | typeof(HarmonyHelper).Assembly.GetName().Version; 181 | 182 | PatchProcessor processor = null; 183 | MethodInfo prohibitedPatch = null; 184 | try 185 | { 186 | var targetName = "SetEntry"; 187 | MethodInfo target = typeof(PackageEntry).GetMethod(targetName, 188 | BindingFlags.Instance | 189 | BindingFlags.Static | 190 | BindingFlags.Public | 191 | BindingFlags.NonPublic); 192 | if (target == null) 193 | { 194 | throw new TestFailed($"Target fn ({targetName}) was not found."); 195 | } 196 | processor = h.CreateProcessor(target); 197 | if (processor == null) 198 | { 199 | UnityEngine.Debug.Log($"[{testName}] INFO - Processor=null. BAD"); 200 | throw new TestFailed($"Failed to create processor."); 201 | } 202 | 203 | var patches = Harmony.GetPatchInfo(target); 204 | 205 | if (patches == null) 206 | { 207 | throw new TestFailed($"Failed to get '{target}' patches."); 208 | } 209 | 210 | if (patches.Prefixes != null) 211 | UnityEngine.Debug.Log($"[{testName}] INFO - found {patches.Prefixes.Count} prefix patches to {targetName}"); 212 | if (patches.Postfixes != null) 213 | UnityEngine.Debug.Log($"[{testName}] INFO - found {patches.Postfixes.Count} postfix patches to {targetName}"); 214 | if (patches.Transpilers!= null) 215 | UnityEngine.Debug.Log($"[{testName}] INFO - found {patches.Transpilers.Count} transpiler patches to {targetName}"); 216 | if (patches.Finalizers != null) 217 | UnityEngine.Debug.Log($"[{testName}] INFO - found {patches.Finalizers.Count} finalizer patches to {targetName}"); 218 | 219 | patches.Postfixes.Do((p) => UnityEngine.Debug.Log($"[{testName}] INFO - found patch to {targetName} = {p.PatchMethod.Name} by {p.owner}")); 220 | 221 | bool found = false; 222 | patches.Postfixes.DoIf((p) => p.owner.Contains("org.ohmi.harmony"), 223 | (p) => { 224 | found = true; 225 | processor.Unpatch(p.PatchMethod); 226 | }); 227 | 228 | if (!found) 229 | { 230 | throw new TestFailed($"Mod's patch to {targetName} not found."); 231 | } 232 | 233 | throw new TestFailed("Removing a Mod's patch did not throw HarmonyModACLException"); 234 | } 235 | catch (TestFailed ex) 236 | { 237 | UnityEngine.Debug.Log($"[{testName}] ERROR : {ex.Message}. BAD"); 238 | throw ex; 239 | } 240 | catch (Exception ex) 241 | { 242 | if (ex.GetType().Name != "HarmonyModACLException") 243 | { 244 | throw new TestFailed($"Unpatching the mod failed but not because of ACL: {ex.Message}", ex); 245 | } 246 | UnityEngine.Debug.Log($"[{testName}] INFO - Attempting to unpatch the mod was blocked ({ex.Message}). OK."); 247 | 248 | } 249 | } 250 | 251 | internal static string testName; 252 | 253 | public int lastArg; 254 | } 255 | 256 | internal static class ACLPatchDefinitions 257 | { 258 | public static void ProhibitedPatch() 259 | { 260 | } 261 | 262 | } 263 | 264 | } 265 | -------------------------------------------------------------------------------- /HarmonyMod/Source/Harmony1StateTransfer.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Harmony for Cities Skylines 3 | * Copyright (C) 2021 Radu Hociung 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | /* 20 | MIT License 21 | 22 | Copyright (c) 2017 Felix Schmidt 23 | 24 | Permission is hereby granted, free of charge, to any person obtaining a copy 25 | of this software and associated documentation files (the "Software"), to deal 26 | in the Software without restriction, including without limitation the rights 27 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | copies of the Software, and to permit persons to whom the Software is 29 | furnished to do so, subject to the following conditions: 30 | 31 | The above copyright notice and this permission notice shall be included in all 32 | copies or substantial portions of the Software. 33 | 34 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 35 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 36 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 37 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 38 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 39 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 40 | SOFTWARE. 41 | */ 42 | 43 | /* ************************************************************************** 44 | * 45 | * 46 | * IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT 47 | * 48 | * This file contains leftover code from the initial fork from Felix Schmidt's 49 | * repository https://github.com/boformer/CitiesHarmony 50 | * 51 | * It contains known bad code, which is either not used at all in my implementation, 52 | * or it is in the course of being re-written. If I am rewriting it, I only included 53 | * it because an initial release of my project was needed urgently to address 54 | * a broken modding eco-system in Cities Skylines, and I considered it will do no 55 | * further harm over what has already been done by Felix Schmidt's code. 56 | * 57 | * I would recommend you do not copy or rely on this code. A near future update will 58 | * remove this and replace it with proper code I would be proud to put my name on. 59 | * 60 | * Until then, the copyright notice above was expressely requested by Felix Schmidt, 61 | * by means of a DMCA complaint at GitHub and Steam. 62 | * 63 | * There is no code between the end of this comment and he "END-OF-Felix Schmidt-COPYRIGHT" 64 | * line if there is one, or the end of the file, that I, Radu Hociung, claim any copyright 65 | * on. The rest of the content, outside of these delimiters, is my copyright, and 66 | * you may copy it in accordance to the modified GPL license at the root or the repo 67 | * (LICENSE file) 68 | * 69 | * FUTHER, if you base your code on a copy of the example mod from Felix Schmidt's 70 | * repository, which does not include his copyright notice, you will likely also 71 | * be a victim of DMCA complaint from him. 72 | */ 73 | extern alias Harmony2; 74 | using Harmony2::HarmonyLib; 75 | using System; 76 | using System.Collections.Generic; 77 | using System.Linq; 78 | using System.Reflection; 79 | 80 | namespace HarmonyMod { 81 | /// 82 | /// 1. Reverts Harmony 1.x patches that were applied before this mod was loaded.
83 | /// 2. Resets the Harmony shared state so that Harmony 2.x runs without exceptions.
84 | /// 3. Self-patches the Harmony 1.2 assembly so that it redirects all calls to Harmony 2.x.
85 | /// 4. Re-applies the patches using Harmony 2.x 86 | ///
87 | class Harmony1StateTransfer { 88 | private MethodInfo HarmonySharedState_GetPatchedMethods; 89 | private MethodInfo HarmonySharedState_GetPatchInfo; 90 | 91 | private FieldInfo PatchInfo_prefixed; 92 | private FieldInfo PatchInfo_postfixes; 93 | private FieldInfo PatchInfo_transpilers; 94 | 95 | private FieldInfo Patch_owner; 96 | private FieldInfo Patch_priority; 97 | private FieldInfo Patch_before; 98 | private FieldInfo Patch_after; 99 | private FieldInfo Patch_patch; 100 | 101 | private Type harmonyInstanceType; 102 | private MethodInfo HarmonyInstance_Create; 103 | private MethodInfo HarmonyInstance_Unpatch; 104 | 105 | private object HarmonyPatchType_All; 106 | 107 | public Harmony1StateTransfer(Assembly assembly) { 108 | 109 | UnityEngine.Debug.Log($"[{Versioning.FULL_PACKAGE_NAME}] Transferring Harmony {assembly.GetName().Version} state ({assembly.FullName})"); 110 | 111 | var sharedStateType = assembly.GetType("Harmony.HarmonySharedState"); 112 | HarmonySharedState_GetPatchedMethods = sharedStateType.GetMethodOrThrow("GetPatchedMethods", BindingFlags.NonPublic | BindingFlags.Static); 113 | HarmonySharedState_GetPatchInfo = sharedStateType.GetMethodOrThrow("GetPatchInfo", BindingFlags.NonPublic | BindingFlags.Static); 114 | 115 | var patchInfoType = assembly.GetType("Harmony.PatchInfo"); 116 | PatchInfo_prefixed = patchInfoType.GetFieldOrThrow("prefixes"); 117 | PatchInfo_postfixes = patchInfoType.GetFieldOrThrow("postfixes"); 118 | PatchInfo_transpilers = patchInfoType.GetFieldOrThrow("transpilers"); 119 | 120 | var patchType = assembly.GetType("Harmony.Patch"); 121 | Patch_owner = patchType.GetFieldOrThrow("owner"); 122 | Patch_priority = patchType.GetFieldOrThrow("priority"); 123 | Patch_before = patchType.GetFieldOrThrow("before"); 124 | Patch_after = patchType.GetFieldOrThrow("after"); 125 | Patch_patch = patchType.GetFieldOrThrow("patch"); 126 | 127 | harmonyInstanceType = assembly.GetType("Harmony.HarmonyInstance") ?? throw new Exception("HarmonyInstance type not found"); 128 | HarmonyInstance_Create = harmonyInstanceType.GetMethodOrThrow("Create", BindingFlags.Public | BindingFlags.Static); 129 | 130 | var harmonyPatchTypeType = assembly.GetType("Harmony.HarmonyPatchType") ?? throw new Exception("HarmonyPatchType type not found"); 131 | 132 | var unpatchArgTypes = new Type[] { typeof(MethodBase), harmonyPatchTypeType, typeof(string) }; 133 | HarmonyInstance_Unpatch = HarmonyInstance_Unpatch = harmonyInstanceType.GetMethod("RemovePatch", unpatchArgTypes) // Harmony 1.1.0.0 134 | ?? harmonyInstanceType.GetMethodOrThrow("Unpatch", unpatchArgTypes); // Harmony 1.2.0.1 135 | 136 | HarmonyPatchType_All = Enum.ToObject(harmonyPatchTypeType, 0); 137 | } 138 | 139 | public void Patch() { 140 | var patchedMethods = new List((HarmonySharedState_GetPatchedMethods.Invoke(null, new object[0]) as IEnumerable)); 141 | 142 | UnityEngine.Debug.Log($"[{Versioning.FULL_PACKAGE_NAME}] {patchedMethods.Count} patched methods found."); 143 | 144 | if (patchedMethods.Count != 0) 145 | { 146 | var processors = new List(); 147 | 148 | foreach (var method in patchedMethods) { 149 | var patchInfo = HarmonySharedState_GetPatchInfo.Invoke(null, new object[] { method }); 150 | if (patchInfo == null) continue; 151 | 152 | var prefixes = (object[])PatchInfo_prefixed.GetValue(patchInfo); 153 | foreach (var patch in prefixes) { 154 | processors.Add(CreateHarmony(patch) 155 | .CreateProcessor(method) 156 | .AddPrefix(CreateHarmonyMethod(patch))); 157 | } 158 | 159 | var postfixes = (object[])PatchInfo_postfixes.GetValue(patchInfo); 160 | foreach (var patch in postfixes) { 161 | processors.Add(CreateHarmony(patch) 162 | .CreateProcessor(method) 163 | .AddPostfix(CreateHarmonyMethod(patch))); 164 | } 165 | 166 | var transpilers = (object[])PatchInfo_transpilers.GetValue(patchInfo); 167 | foreach (var patch in transpilers) { 168 | processors.Add(CreateHarmony(patch) 169 | .CreateProcessor(method) 170 | .AddTranspiler(CreateHarmonyMethod(patch))); 171 | } 172 | } 173 | 174 | UnityEngine.Debug.Log($"[{Versioning.FULL_PACKAGE_NAME}] Reverting patches..."); 175 | var oldInstance = HarmonyInstance_Create.Invoke(null, new object[] { "HarmonyMod" }); 176 | foreach (var method in patchedMethods.ToList()) 177 | { 178 | HarmonyInstance_Unpatch.Invoke(oldInstance, new object[] { method, HarmonyPatchType_All, null }); 179 | } 180 | 181 | // Reset shared state 182 | var sharedStateAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name.Contains("HarmonySharedState")); 183 | if (sharedStateAssembly != null) { 184 | var stateField = sharedStateAssembly.GetType("HarmonySharedState")?.GetField("state"); 185 | if (stateField != null) { 186 | UnityEngine.Debug.Log("Resetting HarmonySharedState..."); 187 | stateField.SetValue(null, null); 188 | } 189 | } 190 | 191 | // Apply patches using Harmony 2.x 192 | foreach (var processor in processors) { 193 | processor.Patch(); 194 | } 195 | } 196 | } 197 | 198 | private Harmony CreateHarmony(object patch) { 199 | var owner = (string)Patch_owner.GetValue(patch); 200 | return new Harmony(owner); 201 | } 202 | 203 | private HarmonyMethod CreateHarmonyMethod(object patch) { 204 | var priority = (int)Patch_priority.GetValue(patch); 205 | var before = (string[])Patch_before.GetValue(patch); 206 | var after = (string[])Patch_after.GetValue(patch); 207 | var method = (MethodInfo)Patch_patch.GetValue(patch); 208 | return new HarmonyMethod(method, priority, before, after); 209 | } 210 | } 211 | } -------------------------------------------------------------------------------- /HarmonyMod/Source/Handover.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Harmony for Cities Skylines 3 | * Copyright (C) 2021 Radu Hociung 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | extern alias Harmony2; 21 | using IAwareness; 22 | using System; 23 | using System.Reflection; 24 | using System.Collections.Generic; 25 | using System.Linq; 26 | using System.Text; 27 | using ColossalFramework; 28 | using ColossalFramework.PlatformServices; 29 | using ColossalFramework.Plugins; 30 | using static ColossalFramework.Plugins.PluginManager; 31 | using Harmony2::HarmonyLib; 32 | using UnityEngine.Assertions; 33 | 34 | namespace HarmonyMod 35 | { 36 | class Handover 37 | { 38 | PluginInfo m_self; 39 | PluginInfo m_mainMod; 40 | 41 | internal Handover (IAmAware selfMod) 42 | { 43 | m_self = Singleton.instance.GetPluginsInfo().First((x) => { 44 | try 45 | { 46 | return x.isEnabledNoOverride && x.userModInstance == selfMod; 47 | } 48 | catch (ReflectionTypeLoadException ex) 49 | { 50 | ex.LoaderExceptions.Do((e) => (selfMod as Mod).report.ReportPlugin(x, ModReport.ProblemType.ExceptionThrown, $"LoaderException: {e}")); 51 | } 52 | catch (Exception ex) 53 | { 54 | (selfMod as Mod).report.ReportPlugin(x, ModReport.ProblemType.ExceptionThrown, ex.Message); 55 | UnityEngine.Debug.LogWarning($"[{Versioning.FULL_PACKAGE_NAME}] While Scanning plugins, {x.name} caused exception {ex.GetType().Name}: {ex.Message}"); 56 | } 57 | return false; 58 | }); 59 | 60 | DisableHarmonyFromBoformer(); 61 | } 62 | 63 | internal PluginInfo mainMod(bool? mainEnabled = null) 64 | { 65 | /* When the main mode gets OnDisable(), its userModInstance is still valid, it gets set to null 66 | * *after* OnDisable returns. So, I must ignore a main that appears enabled, but in fact 67 | * is telling me it's being disabled 68 | */ 69 | 70 | Assert.IsTrue( 71 | (m_mainMod == null && mainEnabled == null) || /* First Scan */ 72 | (m_mainMod != null && mainEnabled == null) || /* get existing value */ 73 | (m_mainMod != null && mainEnabled != null)); /* scan for update, ignoring m_mainMod when mainEnabled=false */ 74 | 75 | var prevMain = m_mainMod; 76 | PluginInfo ignoreThis = null; 77 | if (m_mainMod != null) 78 | { 79 | if (mainEnabled != null) 80 | { 81 | if (mainEnabled == false) 82 | { 83 | ignoreThis = m_mainMod; 84 | } 85 | m_mainMod = null; 86 | } 87 | } 88 | 89 | if (m_mainMod == null) 90 | { 91 | 92 | /* This instance is a Helper to a main mod if: 93 | * 1. There is a higher version, enabled mod 94 | * 95 | * This instance is the main mod if: 96 | * 1. It is the highest version, enabled mod. 97 | * 98 | * Also sets appropriately: 99 | * - isMainMod 100 | * - isLocal 101 | * - isFirst 102 | */ 103 | var myName = Assembly.GetExecutingAssembly().GetName(); 104 | 105 | PluginInfo firstInstance = null; 106 | 107 | 108 | isLocal = m_self.publishedFileID == PublishedFileId.invalid; 109 | #if DEVELOPER 110 | bool isFirstInstanceLocal = false; 111 | #endif 112 | isFirst = true; 113 | bool isFirstInstanceFirst = false; 114 | 115 | /* GetPlugingsInfo is no good. When the 1st mod is OnEnable, it is the only mod on this list. 116 | * So helper cannot detect another instance, and assumes it is main. 117 | * Also a main cannot detect it should be inactive if a later main is avail. 118 | */ 119 | foreach (var info in Singleton.instance.GetPluginsInfo()) 120 | { 121 | if (info.isBuiltin) 122 | { 123 | continue; 124 | } 125 | 126 | if (info == ignoreThis) 127 | { 128 | continue; 129 | } 130 | bool isAnotherInstance = false; 131 | foreach (var assembly in info.GetAssemblies()) 132 | { 133 | var name = assembly.GetName(); 134 | 135 | if (name.Name == myName.Name && 136 | ((name.GetPublicKeyToken() == null && myName.GetPublicKeyToken() == null) || 137 | name.GetPublicKeyToken().SequenceEqual(myName.GetPublicKeyToken()))) 138 | { 139 | isAnotherInstance = true; 140 | if (info.isEnabledNoOverride) 141 | { 142 | /* Query if it is firstRun (not fully aware). If any instance is first run, all are, but only one can detect. */ 143 | if (!Mod.firstRun && assembly != Assembly.GetExecutingAssembly()) 144 | { 145 | Mod.firstRun = !assembly.GetTypes() 146 | .Where(p => typeof(IAmAware).IsAssignableFrom(p) && p.IsClass && !p.IsAbstract) 147 | .Any((p) => (Activator.CreateInstance(p) as IAmAware).IsFullyAware()); 148 | #if TRACE 149 | UnityEngine.Debug.LogError($"[{Versioning.FULL_PACKAGE_NAME}] Other instance is firstRun={Mod.firstRun}"); 150 | #endif 151 | } 152 | if (m_mainMod == null || name.Version >= mainModVersion) 153 | { 154 | m_mainMod = info; 155 | mainModVersion = name.Version; 156 | isMainMod = info == m_self; 157 | #if TRACE 158 | UnityEngine.Debug.LogError($"[{Versioning.FULL_PACKAGE_NAME}] In Handover..get_mainMod() current best {mainModVersion}"); 159 | #endif 160 | } 161 | } else 162 | { 163 | break; 164 | } 165 | } 166 | } 167 | if (isAnotherInstance && !info.isEnabledNoOverride) 168 | { 169 | continue; 170 | } 171 | if (isAnotherInstance && firstInstance == null) 172 | { 173 | firstInstance = info; 174 | isFirstInstanceFirst = isFirst; 175 | #if DEVELOPER 176 | isFirstInstanceLocal = info.publishedFileID == PublishedFileId.invalid; 177 | #endif 178 | } 179 | 180 | if (isFirst) 181 | { 182 | isFirst = m_mainMod != null; 183 | } 184 | } 185 | 186 | isHelper = !isMainMod && (m_self == firstInstance); 187 | if (m_mainMod != firstInstance) 188 | { 189 | helperMod = firstInstance; 190 | isHelperFirst = isFirstInstanceFirst; 191 | #if DEVELOPER 192 | isHelperLocal = isFirstInstanceLocal; 193 | #endif 194 | } 195 | } 196 | #if TRACE 197 | if (m_mainMod != prevMain) 198 | { 199 | var helperDescription = (helperMod != null) ? $" Helper={helperMod.userModInstance.GetType().FullName}, {helperMod.userModInstance.GetType().Assembly.GetName().Version}" : string.Empty; 200 | #if TRACE 201 | #if DEVELOPER 202 | UnityEngine.Debug.LogError($"[{Versioning.FULL_PACKAGE_NAME}] In Handover..get_mainMod() Result: isFirst={isFirst} isMainMod={isMainMod} isLocal={isLocal} isHelper={isHelper} isHelperFirst={isHelperFirst} isHelperLocal={isHelperLocal} mainModVersion={mainModVersion}{helperDescription}. I am {m_self.name}"); 203 | #else 204 | UnityEngine.Debug.LogError($"[{Versioning.FULL_PACKAGE_NAME}] In Handover..get_mainMod() Result: isFirst={isFirst} isMainMod={isMainMod} isLocal={isLocal} isHelper={isHelper} isHelperFirst={isHelperFirst} mainModVersion={mainModVersion}{helperDescription}. I am {m_self.name}"); 205 | #endif 206 | #endif 207 | } 208 | #endif 209 | return m_mainMod; 210 | } 211 | 212 | internal PluginInfo self { get { return m_self; } } 213 | 214 | internal PluginInfo helperMod { get; private set; } 215 | internal Version mainModVersion { get; private set; } 216 | 217 | /* Is THIS instance the main mod ? */ 218 | internal bool isMainMod { get; private set; } 219 | 220 | /* Is THIS instance installed locally ? */ 221 | internal bool isLocal { get; private set; } 222 | 223 | /* Is THIS instance the designated helper? */ 224 | internal bool isHelper { get; private set; } 225 | 226 | /* Is THIS instance First ? */ 227 | internal bool isFirst { get; private set; } 228 | 229 | internal bool isHelperFirst { get; private set; } 230 | #if DEVELOPER 231 | internal bool isHelperLocal { get; private set; } 232 | #endif 233 | internal bool BootStrapMainMod(bool? mainEnabled = null) 234 | { 235 | return mainMod(mainEnabled) != null && !isMainMod; 236 | } 237 | 238 | /* Signal the other instances which have lost mainMod status 239 | */ 240 | internal void NotifyStandbys(bool mainEnabled) 241 | { 242 | var selfAware = m_self.userModInstance as IAmAware; 243 | Assert.IsTrue(m_self == m_mainMod, 244 | "Only the main mod should notify others"); 245 | Assert.IsNotNull(helperMod, 246 | "Others should only be notified if a helper exists"); 247 | Assert.IsNotNull(selfAware, 248 | $"Self should be an {typeof(IAmAware).Name} instance"); 249 | 250 | foreach (var mod in Singleton.instance.GetPluginsInfo()) 251 | { 252 | if (mod != m_mainMod && mod != m_self && mod.isEnabledNoOverride) 253 | { 254 | try 255 | { 256 | IAmAware awareInst = mod.userModInstance as IAmAware; 257 | if (awareInst != null) 258 | { 259 | awareInst.OnMainModChanged(selfAware, mainEnabled); 260 | } 261 | } 262 | catch (ReflectionTypeLoadException ex) 263 | { 264 | ex.LoaderExceptions.Do((e) => (m_self.userModInstance as Mod).report.ReportPlugin(mod, ModReport.ProblemType.ExceptionThrown, $"LoaderException: {e}")); 265 | } 266 | catch (Exception ex) 267 | { 268 | (m_self.userModInstance as Mod).report.ReportPlugin(mod, ModReport.ProblemType.ExceptionThrown, ex.Message); 269 | UnityEngine.Debug.LogWarning($"[{Versioning.FULL_PACKAGE_NAME}] While notifying standys, {mod.name} caused exception {ex.GetType().Name}: {ex.Message}"); 270 | } 271 | } 272 | } 273 | } 274 | 275 | void DisableHarmonyFromBoformer() 276 | { 277 | /* TODO: Implement re-enabling it if this mod is disabled? 278 | * It is possible, however, it will work flawlessly after this mod, 279 | * because all the harmony assemblies (1.2.0.1, 2.0.1, 2.0.4) are already 280 | * patched and loaded. Boformer's mod would have nothing to do. 281 | * 282 | * I think it's more fair that if this mod Captured the Flag by getting 283 | * installed first, it gets to keep the flag. 284 | */ 285 | const string PatchedMarker = "Harmony1_PatchedMarker"; 286 | 287 | var go = UnityEngine.GameObject.Find(PatchedMarker); 288 | if (go == null) 289 | { 290 | UnityEngine.Object.DontDestroyOnLoad(new UnityEngine.GameObject(PatchedMarker)); 291 | } 292 | 293 | } 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /HarmonyMod/Source/Patcher.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Harmony for Cities Skylines 3 | * Copyright (C) 2021 Radu Hociung 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, write to the Free Software Foundation, Inc., 17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | */ 19 | 20 | extern alias Harmony2; 21 | extern alias Harmony2009; 22 | extern alias Harmony2010; 23 | extern alias HarmonyCHH2040; 24 | 25 | 26 | namespace HarmonyMod 27 | { 28 | using IAwareness; 29 | using Harmony2::HarmonyLib; 30 | using System; 31 | using System.Collections.Generic; 32 | using System.Reflection; 33 | using UnityEngine.Assertions; 34 | 35 | public class Patcher 36 | { 37 | const string HARMONY_ID = "org.ohmi.harmony"; 38 | 39 | bool initialized_ = false; 40 | 41 | readonly string id; 42 | readonly Harmony harmony; 43 | /* separate instance for patching harmony1; these patches will not be 44 | * removed when the HarmonyMod unloads, because it doesn't unload last yet 45 | * and other mods still need their 2.x access though 1.2.0.1 api 46 | */ 47 | readonly string compat_id; 48 | readonly Harmony compatHarmony; 49 | readonly Harmony1SelfPatcher harmony1SelfPatcher; 50 | readonly bool foundUnsupportedHarmonyLib = false; 51 | 52 | IAmAware self; 53 | 54 | internal Patcher(IAmAware selfMod, string name, bool onAwarenessCallback = false) 55 | { 56 | id = HARMONY_ID + (name != null ? "+" + name : string.Empty); 57 | compat_id = id + "+Compat"; 58 | #if DEBUG 59 | Harmony.DEBUG = true; 60 | #endif 61 | 62 | self = selfMod; 63 | harmony1SelfPatcher = new Harmony1SelfPatcher(); 64 | 65 | EnableHarmony(); 66 | harmony = new Harmony(id); 67 | compatHarmony = new Harmony(compat_id); 68 | 69 | 70 | if (!Harmony.Harmony1Patched) 71 | { 72 | Harmony.Harmony1Patched = true; 73 | try 74 | { 75 | ImplementAdditionalVersionSupport(true); 76 | } 77 | catch (HarmonyModSupportException ex) 78 | { 79 | foundUnsupportedHarmonyLib = true; 80 | DisableHarmony(); 81 | (self as Mod).report.ReportUnsupportedHarmony(ex); 82 | } 83 | } 84 | } 85 | 86 | internal void PatchHarmony1(Assembly assembly) 87 | { 88 | harmony1SelfPatcher.Apply(compatHarmony, assembly); 89 | } 90 | bool EnableHarmony() 91 | { 92 | bool wasInitialized = Harmony.m_enabled.HasValue; 93 | Harmony.isEnabled = true; 94 | if (self != null) 95 | { 96 | Harmony.awarenessInstance = self; 97 | Harmony2010::HarmonyLib.Harmony.awarenessInstance = self; 98 | Harmony2009::HarmonyLib.Harmony.awarenessInstance = self; 99 | HarmonyCHH2040::HarmonyLib.Harmony.awarenessInstance = self; 100 | } 101 | Harmony2009::HarmonyLib.Harmony.isEnabled = true; 102 | Harmony2010::HarmonyLib.Harmony.isEnabled = true; 103 | HarmonyCHH2040::HarmonyLib.Harmony.isEnabled = true; 104 | if (!Harmony.harmonyUsers.ContainsKey(Assembly.GetExecutingAssembly().FullName)) { 105 | Harmony.harmonyUsers[Assembly.GetExecutingAssembly().FullName] = new Harmony.HarmonyUser() { checkBeforeUse = true, legacyCaller = false, instancesCreated = 0, }; 106 | } 107 | 108 | return wasInitialized; 109 | } 110 | 111 | void DisableHarmony() 112 | { 113 | Harmony.isEnabled = false; 114 | Harmony2009::HarmonyLib.Harmony.isEnabled = false; 115 | Harmony2010::HarmonyLib.Harmony.isEnabled = false; 116 | HarmonyCHH2040::HarmonyLib.Harmony.isEnabled = false; 117 | } 118 | 119 | internal bool Install() 120 | { 121 | Assert.IsFalse(initialized_, 122 | "Should not call Patcher.Install() more than once"); 123 | #if TRACE 124 | UnityEngine.Debug.Log($"[{Versioning.FULL_PACKAGE_NAME}] INFO: Installing patches as {id}"); 125 | #endif 126 | 127 | try 128 | { 129 | EnableHarmony(); 130 | } 131 | catch (Exception ex) 132 | { 133 | /* This happens if another mod has a copy of 0Harmony.dll with version=2.0.4.0 (same Version as my own used), which gets loaded instead of mine. */ 134 | self.SelfReport(SelfProblemType.WrongHarmonyLib, ex); 135 | } 136 | 137 | try 138 | { 139 | harmony.PatchAll(Assembly.GetExecutingAssembly()); 140 | 141 | if (foundUnsupportedHarmonyLib) 142 | DisableHarmony(); 143 | 144 | initialized_ = true; 145 | } 146 | catch (Exception e) 147 | { 148 | Mod.SelfReport(SelfProblemType.OwnPatchInstallation, e); 149 | } 150 | 151 | return initialized_; 152 | } 153 | 154 | internal void Uninstall() 155 | { 156 | Assert.IsTrue(initialized_, 157 | "Should not call Patcher.Uninstall() more than once"); 158 | 159 | #if TRACE 160 | UnityEngine.Debug.Log($"[{Versioning.FULL_PACKAGE_NAME}] INFO: Removing patches as {id}"); 161 | #endif 162 | 163 | Assert.IsNotNull(harmony, "HarmonyInst != null"); 164 | try 165 | { 166 | if (foundUnsupportedHarmonyLib) 167 | EnableHarmony(); 168 | 169 | harmony.UnpatchAll(id); 170 | 171 | /* FIXME: When harmonymod is removed last, it's 172 | * safe to also remove Harmon1 patches. 173 | * 174 | * compatHarmony.UnpatchAll(compat_id); 175 | */ 176 | if (foundUnsupportedHarmonyLib) 177 | DisableHarmony(); 178 | } 179 | catch (Exception e) 180 | { 181 | Mod.SelfReport(SelfProblemType.OwnPatchRemoval, e); 182 | } 183 | 184 | initialized_ = false; 185 | } 186 | 187 | internal void UninstallAll() 188 | { 189 | Assert.IsTrue(Harmony.isEnabled, 190 | "Harmony should be enabled before UninstallAll"); 191 | 192 | /* FIXME - implement disabling Harmony should remove all dependent mods' patches */ 193 | // harmony.UnpatchAll(); 194 | } 195 | 196 | internal void ImplementAdditionalVersionSupport(bool needHarmon1StateTransfer) 197 | { 198 | /* FIXME: Move this table to the API, so mods 199 | * can query for support at runtime. 200 | */ 201 | Version[] harmonyVersionSupport = new Version[] 202 | { 203 | new Version(1, 0, 9, 1), 204 | new Version(1, 1, 0, 0), 205 | new Version(1, 2, 0, 1), 206 | new Version(2, 0, 0, 9), 207 | new Version(2, 0, 1, 0), 208 | new Version(2, 0, 4, 0), 209 | }; 210 | 211 | /* Official support cut-off. Above this version, I will implement support. 212 | * Below this version, you need to implement the support and submit a 213 | * pull request; see below 214 | */ 215 | Version minSupportedVersion = new Version(2, 0, 1, 0); 216 | 217 | List unsupportedAssemblies = new List(); 218 | 219 | /* Enable the main Library to enable state transition, but 220 | * disable it again if transition failed or unsupported harmony libs exist 221 | * 222 | */ 223 | EnableHarmony(); 224 | int failures = 0; 225 | 226 | /* FIXME: This should be done in order of decreasing Version */ 227 | AppDomain.CurrentDomain.GetAssemblies() 228 | .DoIf((assembly) => 229 | { 230 | var your = assembly.GetName(); 231 | if (your.Name == "0Harmony") 232 | { 233 | if (Array.Exists(harmonyVersionSupport, (supported) => supported == your.Version)) 234 | { 235 | return your.Version < new Version(2,0); 236 | } 237 | else if (your.Version < minSupportedVersion) 238 | { 239 | /* If you are a mod developer and came here due to the exception below, 240 | * it is likely because you're trying to use a version older than 2.0.1.0 241 | * which is not yet supported. You need to submit a pull-request to 242 | * the HarmonyMod which implements the necesary State Transfer and 243 | * runtime compatibility patch, or alternately a stub for your version. 244 | */ 245 | unsupportedAssemblies.Add(new HarmonyModSupportException.UnsupportedAssembly(assembly, true)); 246 | } 247 | else 248 | { 249 | /* If you are a mod developer and came here due to the exception below, 250 | * you're trying to use a version of 0Harmony which should be supported, 251 | * but is not currently. 252 | * Enter a feature request at https://github.com/drok/Harmony-CitiesSkylines/issues 253 | * For quicker service, include a pull request, implementing the appropriate 254 | * stub branch (you can use the branch stub-v2.0.1.0-to-current as example) 255 | * for your desired version 256 | * 257 | * Note if the version you're looking for is a new release from the HarmonyLib 258 | * developer, the HarmonyMod author will wait for some time for any bugs to shake 259 | * out of the new release before adding support to HarmonyMod. You can make this 260 | * happen quicker by reviewing pardeike's new release, report any bugs, and help 261 | * get them fixed. Mention that you've reviewed pardeike's release notes and 262 | * commits with your pull request. It'll speed up its implementation. 263 | * 264 | * Note that your pull request must include test cases for the new features 265 | * implemented in the new Harmony version, and the test cases should be particularly 266 | * thorough about the features you're interested in using. 267 | * 268 | * The goal is to add new feature support without compromising stability of the 269 | * existing ecosystem. 270 | */ 271 | unsupportedAssemblies.Add(new HarmonyModSupportException.UnsupportedAssembly(assembly, false)); 272 | } 273 | } 274 | return false; 275 | }, (a) => { 276 | try 277 | { 278 | if (needHarmon1StateTransfer) 279 | new Harmony1StateTransfer(a).Patch(); 280 | PatchHarmony1(a); 281 | } 282 | catch (Exception e) 283 | { 284 | Mod.SelfReport(SelfProblemType.CompatibilityPatchesFailed, e); 285 | failures++; 286 | } 287 | }); 288 | 289 | if (failures != 0 || unsupportedAssemblies.Count != 0) 290 | { 291 | DisableHarmony(); 292 | } 293 | 294 | if (unsupportedAssemblies.Count != 0) 295 | { 296 | Harmony.unsupportedException = new HarmonyModSupportException(unsupportedAssemblies); 297 | throw Harmony.unsupportedException; 298 | } 299 | } 300 | internal static bool isHarmonyUserException(Exception e) 301 | { 302 | return e is Harmony2.HarmonyLib.HarmonyUserException || 303 | e is Harmony2009::HarmonyLib.HarmonyUserException || 304 | e is Harmony2010::HarmonyLib.HarmonyUserException || 305 | e is HarmonyCHH2040::HarmonyLib.HarmonyUserException; 306 | } 307 | 308 | } 309 | } 310 | --------------------------------------------------------------------------------