Skip to content

Instantly share code, notes, and snippets.

@boformer
Last active July 15, 2021 07:22
Show Gist options
  • Save boformer/fa0ad18e843452d1e30448c4cdcf8e27 to your computer and use it in GitHub Desktop.
Save boformer/fa0ad18e843452d1e30448c4cdcf8e27 to your computer and use it in GitHub Desktop.
Example Harmony 2.x Cities: Skylines Mod (Harmony provided by CitiesHarmony mod)

To use Harmony 2.0.0.8 in your mod, add the HarmonyLib nuget package to your project. Make sure that the 0Harmony.dll is not copied to the output directory.

Also make sure that there are no references to HarmonyLib in your IUserMod implementation. Otherwise the mod could not be loaded if CitiesHarmony is not subscribed.

Before calling harmony methods, check if the CitiesHarmony assembly is present. If it is not installed, you can either auto-subscribe it or show a warning to the user (depending on your preference).

When you are certain that CitiesHarmony is available, call CitiesHarmony.Provider.CreateHarmony(yourHarmonyID) by reflection to get your Harmony instance. This will ensure that old Harmony versions get patched before Harmony 2.x patches are applied.

Add this mod as a dependency to your workshop item.

You can use this mod as a reference. By using auto-subscription, it is possible to migrate existing mods to Harmony 2.x without causing disruptions for users!

using ICities;
using System;
using ColossalFramework.PlatformServices;
using ColossalFramework.Plugins;
namespace HarmonyMod {
public class Mod : IUserMod {
// You can add Harmony 1.2.0.8 as a dependency, but make sure that 0Harmony.dll is not copied to the output directory!
// (0Harmony.dll is provided by CitiesHarmony workshop item)
// Also make sure that HarmonyLib is not referenced in any way in your IUserMod implementation!
// (otherwise it will fail to instantiate the type when CitiesHarmony is not installed)
public string Name => "Harmony Test Mod";
public string Description => "Patches SimulationManager.CreateRelay";
private const ulong CitiesHarmonyWorkshopId = 2040656402uL;
public void OnEnabled() {
if (SteamWorkshopAvailable) {
PlatformService.workshop.eventWorkshopItemInstalled += OnWorkshopItemInstalled;
}
if (HarmonyModInstalled) {
Patcher.PatchAll();
} else {
UnityEngine.Debug.LogWarning("CitiesHarmony not installed!");
InstallHarmonyMod();
}
}
public void OnWorkshopItemInstalled(PublishedFileId id) {
if (id.AsUInt64 == CitiesHarmonyWorkshopId) Patcher.PatchAll();
}
public void OnDisabled() {
if (SteamWorkshopAvailable) {
PlatformService.workshop.eventWorkshopItemInstalled -= OnWorkshopItemInstalled;
}
if (HarmonyModInstalled) {
Patcher.UnpatchAll();
}
}
private static bool SteamWorkshopAvailable => PlatformService.platformType == PlatformType.Steam && !PluginManager.noWorkshop;
private static bool HarmonyModInstalled => Type.GetType("CitiesHarmony.Provider, CitiesHarmony", false) != null;
private static void InstallHarmonyMod() {
if (PlatformService.platformType != PlatformType.Steam) {
UnityEngine.Debug.LogError("Cannot auto-subscribe CitiesHarmony on platforms other than Steam!");
return;
}
if (PluginManager.noWorkshop) {
UnityEngine.Debug.LogError("Cannot auto-subscribe CitiesHarmony in noWorkshop mode!");
return;
}
UnityEngine.Debug.Log("Subscribing to CitiesHarmony workshop item!");
PlatformService.workshop.Subscribe(new PublishedFileId(CitiesHarmonyWorkshopId));
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Lib.Harmony" version="2.0.0.8" targetFramework="net35" />
</packages>
using HarmonyLib;
using System;
using System.Reflection;
namespace HarmonyMod {
public static class Patcher {
private const string HarmonyId = "boformer.HarmonyTest";
private static bool patched = false;
public static void PatchAll() {
if (!patched) {
patched = true;
UnityEngine.Debug.Log("HarmonyTest Patching...");
var harmony = CreateHarmony();
// Apply your patches here!
harmony.PatchAll(typeof(Mod).GetType().Assembly);
}
}
public static void UnpatchAll() {
if (patched) {
var harmony = CreateHarmony();
harmony.UnpatchAll(HarmonyId);
UnityEngine.Debug.Log("HarmonyTest Reverted...");
patched = false;
}
}
private static Harmony CreateHarmony() {
// Use CitiesHarmony.Provider to obtain your Harmony instance!
// (CitiesHarmony needs to self-patch all Harmony 1.2.0.1 assemblies before any Harmony 2.x patches are applied)
Type providerType = Type.GetType("CitiesHarmony.Provider, CitiesHarmony", true);
MethodInfo createHarmonyMethod = providerType.GetMethod("CreateHarmony", BindingFlags.Static | BindingFlags.Public);
return (Harmony)createHarmonyMethod.Invoke(null, new[] { HarmonyId });
}
}
// Random example patch
[HarmonyPatch(typeof(SimulationManager), "CreateRelay")]
public static class SimulationManagerCreateRelayPatch {
public static void Prefix() {
UnityEngine.Debug.Log("Prefix");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment