diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ffe818a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,28 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = crlf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{cs,vb}] +# Suggestion-only so existing code does not break the build +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion +dotnet_style_readonly_field = true:suggestion +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = true:suggestion +csharp_style_expression_bodied_methods = when_on_single_line:suggestion +csharp_style_expression_bodied_properties = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..7850bc0 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,30 @@ +name: Build + +on: + push: + branches: [main, master, refactor/*] + pull_request: + branches: [main, master] + +jobs: + build: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Restore + run: dotnet restore DSP_Mods.sln + + - name: Build Release + run: dotnet build DSP_Mods.sln -c Release --no-restore + + - name: Package main mods + run: | + dotnet build UXAssist/UXAssist.csproj -t:ZipMod -c Release --no-restore + dotnet build CheatEnabler/CheatEnabler.csproj -t:ZipMod -c Release --no-restore + dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -t:ZipMod -c Release --no-restore diff --git a/AGENTS.md b/AGENTS.md index 97a028e..11063a0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -145,5 +145,7 @@ The sync is implemented as an inline PowerShell `Exec` step inside the `ZipMod` - **Preloader pattern:** `DustbinPreloader` and `LabOptPreloader` use Mono.Cecil to inject new fields into game assemblies at BepInEx preload time, enabling their corresponding main mods to read/write those fields via normal C# without reflection. - **Internationalization:** `UXAssist/Common/I18N.cs` provides bilingual (EN + ZH) string lookup used across UXAssist and CheatEnabler. Localization keys are declared as `public const string` in per-project registration classes (`UXAssist/Common/I18NKeys.cs`, `CheatEnabler/Localization.cs`, `UniverseGenTweaks/Localization.cs`) and registered through a single `Register()` call from each mod's `Awake()`. Do not pass Chinese string literals to `.Translate()` at call sites. - **Centralized game constants:** Hard-coded item IDs, tech IDs, logistics capacities, and Dyson sphere geometry defaults live in `UXAssist/Common/GameConstants` (`ItemIds`, `TechIds`, `LogisticsConstants`, `DysonSphereConstants`). Prefer these constants over inline literals in UXAssist patches. -- **Transpiler patches:** Performance-critical mods (LabOpt, MechaDronesTweaks) use `[HarmonyTranspiler]` to rewrite IL instructions directly for maximum efficiency. +- **Transpiler patches:** Performance-critical mods (LabOpt, MechaDronesTweaks) use `[HarmonyTranspiler]` to rewrite IL instructions directly for maximum efficiency. All transpilers in UXAssist, CheatEnabler, and UniverseGenTweaks carry a standard header comment (`// Harmony transpiler:`, `// Target:`, `// Fallback:`) documenting the target method and fallback behavior. `UXAssist.Common.Patching.TranspilerGuard` provides a reusable `CodeMatcher.Finish` helper that returns original instructions when a matcher becomes invalid. +- **Mod-compatibility reflection:** Use `UXAssist.Common.ModCompat.ModCompatHelper` for BepInEx plugin detection, external mod type/method/field resolution, and property-setter lookup. Use `UXAssist.Common.Utils.DysonSphereReflection` for the DSPOptimizations-compatible `DysonSphereLayer` private fields (`totalNodeSP`, `totalFrameSP`, `totalCP`) instead of resolving them locally in each consumer. +- **Build quality gates:** Root `.editorconfig` defines suggestion-only C# style conventions. `Directory.Build.props` enables `TreatWarningsAsErrors` with `NoWarn>0618` for the expected obsolete-API usage, so any new warning fails the build. `.github/workflows/build.yml` runs a Release build and packages the three main mods on every push/PR. - **Save persistence:** Mods that need to persist data use the `IModCanSave` interface from DSPModSave. diff --git a/CheatEnabler/Functions/DysonSphere/FrameRemovalFunctions.cs b/CheatEnabler/Functions/DysonSphere/FrameRemovalFunctions.cs index cfe6dff..18ce27b 100644 --- a/CheatEnabler/Functions/DysonSphere/FrameRemovalFunctions.cs +++ b/CheatEnabler/Functions/DysonSphere/FrameRemovalFunctions.cs @@ -4,6 +4,7 @@ using UnityEngine; using UXAssist.Common; using UXAssist.Common.GameConstants; using UXAssist.Common.ModFeatures; +using UXAssist.Common.Utils; using CheatEnabler; namespace CheatEnabler.Functions.DysonSphere; @@ -19,8 +20,6 @@ public static class FrameRemovalFunctions UIMessageBox.Show(Localization.CheatEnabler.Translate(), string.Format(Localization.ThisWillRemoveAllFramesOn0AreYouSure.Translate(), star.displayName), Localization.Cancel.Translate(), Localization.OK.Translate(), UIMessageBox.QUESTION, null, () => { - var totalFrameSpInfo = AccessTools.Field(typeof(DysonSphereLayer), "totalFrameSP"); - foreach (var dysonSphereLayer in dysonSphere.layersIdBased) { if (dysonSphereLayer == null) continue; @@ -49,7 +48,7 @@ public static class FrameRemovalFunctions dysonSphereLayer.frameCursor = 1; dysonSphereLayer.frameRecycleCursor = 0; dysonSphereLayer.SetFrameCapacity(DysonSphereConstants.DefaultLayerPoolCapacity); - totalFrameSpInfo?.SetValue(dysonSphereLayer, 0); + DysonSphereReflection.SetTotalFrameSP(dysonSphereLayer, 0); } dysonSphere.CheckAutoNodes(); dysonSphere.PickAutoNode(); diff --git a/CheatEnabler/Functions/DysonSphere/ShellCompletionFunctions.cs b/CheatEnabler/Functions/DysonSphere/ShellCompletionFunctions.cs index 3351c7a..32c60f6 100644 --- a/CheatEnabler/Functions/DysonSphere/ShellCompletionFunctions.cs +++ b/CheatEnabler/Functions/DysonSphere/ShellCompletionFunctions.cs @@ -4,6 +4,7 @@ using HarmonyLib; using UnityEngine; using UXAssist.Common; using UXAssist.Common.ModFeatures; +using UXAssist.Common.Utils; using CheatEnabler; namespace CheatEnabler.Functions.DysonSphere; @@ -19,10 +20,6 @@ public static class ShellCompletionFunctions UIMessageBox.Show(Localization.CheatEnabler.Translate(), string.Format(Localization.ThisWillCompleteAllDysonSphereShellsOn0InstantlyAreYouSure.Translate(), star.displayName), Localization.Cancel.Translate(), Localization.OK.Translate(), UIMessageBox.QUESTION, null, () => { - var totalNodeSpInfo = AccessTools.Field(typeof(DysonSphereLayer), "totalNodeSP"); - var totalFrameSpInfo = AccessTools.Field(typeof(DysonSphereLayer), "totalFrameSP"); - var totalCpInfo = AccessTools.Field(typeof(DysonSphereLayer), "totalCP"); - var rocketCount = 0L; var solarSailCount = 0L; foreach (var dysonSphereLayer in dysonSphere.layersIdBased) @@ -75,16 +72,16 @@ public static class ShellCompletionFunctions shell.nodecps[nodeIndex] = cpMax; shell.nodecps[shell.nodecps.Length - 1] += diff; solarSailCount += diff; - if (totalCpInfo != null) + if (DysonSphereReflection.HasTotalCP) { shell.SetMaterialDynamicVars(); } } } - totalNodeSpInfo?.SetValue(dysonSphereLayer, totalNodeSp); - totalFrameSpInfo?.SetValue(dysonSphereLayer, totalFrameSp); - totalCpInfo?.SetValue(dysonSphereLayer, totalCp); + DysonSphereReflection.SetTotalNodeSP(dysonSphereLayer, totalNodeSp); + DysonSphereReflection.SetTotalFrameSP(dysonSphereLayer, totalFrameSp); + DysonSphereReflection.SetTotalCP(dysonSphereLayer, totalCp); } dysonSphere.CheckAutoNodes(); diff --git a/CheatEnabler/Patches/CombatPatch.cs b/CheatEnabler/Patches/CombatPatch.cs index 2288466..3d1767e 100644 --- a/CheatEnabler/Patches/CombatPatch.cs +++ b/CheatEnabler/Patches/CombatPatch.cs @@ -34,6 +34,9 @@ public static class CombatPatch private class MechaInvincible : PatchImpl { + // Harmony transpiler: Player_get_invincible_Transpiler + // Target: Player.invincible (getter) + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(Player), nameof(Player.invincible), MethodType.Getter)] private static IEnumerable Player_get_invincible_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -45,7 +48,9 @@ public static class CombatPatch ); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: SkillSystem_DamageObject_Transpiler + // Target: SkillSystem.DamageGroundObjectByLocalCaster, SkillSystem.DamageGroundObjectByRemoteCaster, SkillSystem.DamageObject + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(SkillSystem), nameof(SkillSystem.DamageGroundObjectByLocalCaster))] [HarmonyPatch(typeof(SkillSystem), nameof(SkillSystem.DamageGroundObjectByRemoteCaster))] @@ -69,6 +74,9 @@ public static class CombatPatch private class BuildingsInvincible : PatchImpl { + // Harmony transpiler: SkillSystem_DamageObject_Transpiler + // Target: SkillSystem.DamageGroundObjectByLocalCaster, SkillSystem.DamageGroundObjectByRemoteCaster + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(SkillSystem), nameof(SkillSystem.DamageGroundObjectByLocalCaster))] [HarmonyPatch(typeof(SkillSystem), nameof(SkillSystem.DamageGroundObjectByRemoteCaster))] diff --git a/CheatEnabler/Patches/DysonSpherePatch.cs b/CheatEnabler/Patches/DysonSpherePatch.cs index b2f24d3..33249cc 100644 --- a/CheatEnabler/Patches/DysonSpherePatch.cs +++ b/CheatEnabler/Patches/DysonSpherePatch.cs @@ -67,7 +67,9 @@ public class DysonSpherePatch : PatchImpl // { // CheatEnabler.Logger.LogDebug($"[DysonShell.ImportFromBlueprint] vertCount={__instance.vertexCount}, cpPerVertex={__instance.cpPerVertex}, cpMax={__instance.cellPointMax}"); // } - + // Harmony transpiler: DysonNode_OrderConstructCp_Transpiler + // Target: DysonNode.OrderConstructCp + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(DysonNode), nameof(DysonNode.OrderConstructCp))] private static IEnumerable DysonNode_OrderConstructCp_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -82,7 +84,9 @@ public class DysonSpherePatch : PatchImpl ); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: DysonSwarm_AbsorbSail_Transpiler + // Target: DysonSwarm.AbsorbSail + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(DysonSwarm), nameof(DysonSwarm.AbsorbSail))] private static IEnumerable DysonSwarm_AbsorbSail_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -189,7 +193,9 @@ public class DysonSpherePatch : PatchImpl UpdateSailLifeTime(); } } - + // Harmony transpiler: EjectorComponent_InternalUpdate_Transpiler + // Target: EjectorComponent.InternalUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(EjectorComponent), nameof(EjectorComponent.InternalUpdate))] private static IEnumerable EjectorComponent_InternalUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -374,7 +380,9 @@ public class DysonSpherePatch : PatchImpl { _instantAbsorb = QuickAbsorbEnabled.Value && QuickAbsorbPatch.GetHarmony() != null; } - + // Harmony transpiler: DysonSwarm_AbsorbSail_Transpiler2 + // Target: DysonSwarm.AbsorbSail + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(DysonSwarm), nameof(DysonSwarm.AbsorbSail))] private static IEnumerable DysonSwarm_AbsorbSail_Transpiler2(IEnumerable instructions, ILGenerator generator) @@ -416,7 +424,9 @@ public class DysonSpherePatch : PatchImpl { _instantAbsorb = SkipAbsorbEnabled.Value && SkipAbsorbPatch.GetHarmony() != null; } - + // Harmony transpiler: DysonSphereLayer_GameTick_Transpiler + // Target: DysonSphereLayer.GameTick + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(DysonSphereLayer), nameof(DysonSphereLayer.GameTick))] private static IEnumerable DysonSphereLayer_GameTick_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -458,6 +468,9 @@ public class DysonSpherePatch : PatchImpl private class EjectAnywayPatch : PatchImpl { + // Harmony transpiler: EjectorComponent_InternalUpdate_Transpiler + // Target: EjectorComponent.InternalUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(EjectorComponent), nameof(EjectorComponent.InternalUpdate))] private static IEnumerable EjectorComponent_InternalUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -488,6 +501,9 @@ public class DysonSpherePatch : PatchImpl private class OverclockEjector : PatchImpl { + // Harmony transpiler: EjectComponent_InternalUpdate_Transpiler + // Target: EjectorComponent.InternalUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(EjectorComponent), nameof(EjectorComponent.InternalUpdate))] private static IEnumerable EjectComponent_InternalUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -510,7 +526,9 @@ public class DysonSpherePatch : PatchImpl matcher.Start().Advance(pos).RemoveInstructions(end - pos); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: UIEjectAndSiloWindow__OnUpdate_Transpiler + // Target: UIEjectorWindow._OnUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIEjectorWindow), nameof(UIEjectorWindow._OnUpdate))] private static IEnumerable UIEjectAndSiloWindow__OnUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -541,6 +559,9 @@ public class DysonSpherePatch : PatchImpl private class OverclockSilo : PatchImpl { + // Harmony transpiler: SiloComponent_InternalUpdate_Transpiler + // Target: SiloComponent.InternalUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(SiloComponent), nameof(SiloComponent.InternalUpdate))] private static IEnumerable SiloComponent_InternalUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -563,7 +584,9 @@ public class DysonSpherePatch : PatchImpl matcher.Start().Advance(pos).RemoveInstructions(end - pos); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: UIEjectAndSiloWindow__OnUpdate_Transpiler + // Target: UISiloWindow._OnUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UISiloWindow), nameof(UISiloWindow._OnUpdate))] private static IEnumerable UIEjectAndSiloWindow__OnUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -612,7 +635,9 @@ public class DysonSpherePatch : PatchImpl if (dysonEditor == null || !dysonEditor.gameObject.activeSelf) return; dysonEditor.selection?.onViewStarChange?.Invoke(); } - + // Harmony transpiler: MaxOrbitRadiusPatch_Transpiler + // Target: DysonSphere.CheckLayerRadius, DysonSphere.CheckSwarmRadius, DysonSphere.QueryLayerRadius, DysonSphere.QuerySwarmRadius, UIDEAddLayerDialogue.OnViewStarChange, UIDEAddSwarmDialogue.OnViewStarChange, UIDysonEditor.OnViewStarChange, UIDESwarmOrbitInfo._OnInit, UIDysonOrbitPreview.UpdateAscNodeGizmos + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(DysonSphere), nameof(DysonSphere.CheckLayerRadius))] [HarmonyPatch(typeof(DysonSphere), nameof(DysonSphere.CheckSwarmRadius))] diff --git a/CheatEnabler/Patches/Factory/FactoryPatch.cs b/CheatEnabler/Patches/Factory/FactoryPatch.cs index c4cd009..95cfac5 100644 --- a/CheatEnabler/Patches/Factory/FactoryPatch.cs +++ b/CheatEnabler/Patches/Factory/FactoryPatch.cs @@ -202,7 +202,9 @@ public class FactoryPatch : PatchImpl } GameMain.data?.warningSystem?.UpdateCriticalWarningText(); } - + // Harmony transpiler: WarningSystem_hasCriticalWarning_Transpiler + // Target: WarningSystem.hasCriticalWarning (getter) + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(WarningSystem), nameof(WarningSystem.hasCriticalWarning), MethodType.Getter)] private static IEnumerable WarningSystem_hasCriticalWarning_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -228,7 +230,9 @@ public class FactoryPatch : PatchImpl ); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: WarningSystem_UpdateCriticalWarningText_Transpiler + // Target: WarningSystem.UpdateCriticalWarningText + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(WarningSystem), nameof(WarningSystem.UpdateCriticalWarningText))] private static IEnumerable WarningSystem_UpdateCriticalWarningText_Transpiler(IEnumerable instructions, ILGenerator generator) diff --git a/CheatEnabler/Patches/Factory/ImmediateBuildPatch.cs b/CheatEnabler/Patches/Factory/ImmediateBuildPatch.cs index de3e403..c6d96c5 100644 --- a/CheatEnabler/Patches/Factory/ImmediateBuildPatch.cs +++ b/CheatEnabler/Patches/Factory/ImmediateBuildPatch.cs @@ -124,7 +124,9 @@ internal class ImmediateBuild : PatchImpl FactoryPatch.ArrivePlanet(factory); } } - + // Harmony transpiler: Transpiler + // Target: BuildTool_Addon.CreatePrebuilds, BuildTool_BlueprintPaste.CreatePrebuilds, BuildTool_Click.CreatePrebuilds, BuildTool_Inserter.CreatePrebuilds, BuildTool_Path.CreatePrebuilds + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Addon), nameof(BuildTool_Addon.CreatePrebuilds))] [HarmonyPatch(typeof(BuildTool_BlueprintPaste), nameof(BuildTool_BlueprintPaste.CreatePrebuilds))] diff --git a/CheatEnabler/Patches/Factory/LogisticsControlPatch.cs b/CheatEnabler/Patches/Factory/LogisticsControlPatch.cs index 9dad879..8c5940a 100644 --- a/CheatEnabler/Patches/Factory/LogisticsControlPatch.cs +++ b/CheatEnabler/Patches/Factory/LogisticsControlPatch.cs @@ -7,6 +7,9 @@ namespace CheatEnabler.Patches.Factory; internal class ControlPanelRemoteLogistics : PatchImpl { + // Harmony transpiler: UIControlPanelDispenserInspector_OnItemIconMouseDown_Transpiler + // Target: UIControlPanelDispenserInspector.OnItemIconMouseDown, UIControlPanelDispenserInspector.OnHoldupItemClick, UIControlPanelDispenserInspector.OnCourierIconClick + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIControlPanelDispenserInspector), nameof(UIControlPanelDispenserInspector.OnItemIconMouseDown))] [HarmonyPatch(typeof(UIControlPanelDispenserInspector), nameof(UIControlPanelDispenserInspector.OnHoldupItemClick))] @@ -35,7 +38,9 @@ internal class ControlPanelRemoteLogistics : PatchImpl UIControlPanelStationStorage_OnItemIconMouseDown_Transpiler(IEnumerable instructions) @@ -91,7 +98,9 @@ internal class ControlPanelRemoteLogistics : PatchImpl UIControlPanelStationStorage_OnTakeBackButtonClick_Transpiler(IEnumerable instructions) @@ -110,7 +119,9 @@ internal class ControlPanelRemoteLogistics : PatchImpl UIControlPanelVeinCollectorPanel_OnProductIconClick_Transpiler(IEnumerable instructions) diff --git a/CheatEnabler/Patches/Factory/NoConditionBuildPatch.cs b/CheatEnabler/Patches/Factory/NoConditionBuildPatch.cs index b3d02b8..9f28dac 100644 --- a/CheatEnabler/Patches/Factory/NoConditionBuildPatch.cs +++ b/CheatEnabler/Patches/Factory/NoConditionBuildPatch.cs @@ -16,7 +16,9 @@ internal class NoConditionBuild : PatchImpl { GameMain.data?.warningSystem?.UpdateCriticalWarningText(); } - + // Harmony transpiler: BuildTool_CheckBuildConditions_Transpiler + // Target: BuildTool_Addon.CheckBuildConditions, BuildTool_Inserter.CheckBuildConditions + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler, HarmonyPriority(Priority.Last)] [HarmonyPatch(typeof(BuildTool_Addon), nameof(BuildTool_Addon.CheckBuildConditions))] [HarmonyPatch(typeof(BuildTool_Inserter), nameof(BuildTool_Inserter.CheckBuildConditions))] @@ -25,7 +27,9 @@ internal class NoConditionBuild : PatchImpl yield return new CodeInstruction(OpCodes.Ldc_I4_1); yield return new CodeInstruction(OpCodes.Ret); } - + // Harmony transpiler: BuildTool_Path_CheckBuildConditions_Transpiler + // Target: BuildTool_Path.CheckBuildConditions + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler, HarmonyPriority(Priority.First)] [HarmonyPatch(typeof(BuildTool_Path), nameof(BuildTool_Path.CheckBuildConditions))] private static IEnumerable BuildTool_Path_CheckBuildConditions_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -60,7 +64,9 @@ internal class NoConditionBuild : PatchImpl ); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: BuildTool_Click_CheckBuildConditions_Transpiler + // Target: BuildTool_BlueprintPaste.CheckBuildConditions, BuildTool_Click.CheckBuildConditions + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler, HarmonyPriority(Priority.Last)] [HarmonyPatch(typeof(BuildTool_BlueprintPaste), nameof(BuildTool_BlueprintPaste.CheckBuildConditions))] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click.CheckBuildConditions))] diff --git a/CheatEnabler/Patches/Factory/PowerBoostPatch.cs b/CheatEnabler/Patches/Factory/PowerBoostPatch.cs index 3c1fec6..8b2972e 100644 --- a/CheatEnabler/Patches/Factory/PowerBoostPatch.cs +++ b/CheatEnabler/Patches/Factory/PowerBoostPatch.cs @@ -9,6 +9,9 @@ namespace CheatEnabler.Patches.Factory; internal class RemovePowerSpaceLimit : PatchImpl { + // Harmony transpiler: BuildTool_CheckBuildConditions_Transpiler + // Target: BuildTool_Click.CheckBuildConditions, BuildTool_BlueprintPaste.CheckBuildConditions + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click.CheckBuildConditions))] [HarmonyPatch(typeof(BuildTool_BlueprintPaste), nameof(BuildTool_BlueprintPaste.CheckBuildConditions))] @@ -39,6 +42,9 @@ internal class RemovePowerSpaceLimit : PatchImpl internal class BoostWindPower : PatchImpl { + // Harmony transpiler: PowerGeneratorComponent_EnergyCap_Wind_Transpiler + // Target: PowerGeneratorComponent.EnergyCap_Wind + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(PowerGeneratorComponent), nameof(PowerGeneratorComponent.EnergyCap_Wind))] private static IEnumerable PowerGeneratorComponent_EnergyCap_Wind_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -64,6 +70,9 @@ internal class BoostWindPower : PatchImpl internal class BoostSolarPower : PatchImpl { + // Harmony transpiler: PowerGeneratorComponent_EnergyCap_PV_Transpiler + // Target: PowerGeneratorComponent.EnergyCap_PV + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(PowerGeneratorComponent), nameof(PowerGeneratorComponent.EnergyCap_PV))] private static IEnumerable PowerGeneratorComponent_EnergyCap_PV_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -88,6 +97,9 @@ internal class BoostSolarPower : PatchImpl internal class BoostFuelPower : PatchImpl { + // Harmony transpiler: PowerGeneratorComponent_EnergyCap_Fuel_Transpiler + // Target: PowerGeneratorComponent.EnergyCap_Fuel + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(PowerGeneratorComponent), nameof(PowerGeneratorComponent.EnergyCap_Fuel))] private static IEnumerable PowerGeneratorComponent_EnergyCap_Fuel_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -129,6 +141,9 @@ internal class BoostFuelPower : PatchImpl internal class BoostGeothermalPower : PatchImpl { + // Harmony transpiler: PowerGeneratorComponent_EnergyCap_GTH_Transpiler + // Target: PowerGeneratorComponent.EnergyCap_GTH + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(PowerGeneratorComponent), nameof(PowerGeneratorComponent.EnergyCap_GTH))] private static IEnumerable PowerGeneratorComponent_EnergyCap_GTH_Transpiler(IEnumerable instructions, ILGenerator generator) diff --git a/CheatEnabler/Patches/GamePatch.cs b/CheatEnabler/Patches/GamePatch.cs index 65b9452..97ddacd 100644 --- a/CheatEnabler/Patches/GamePatch.cs +++ b/CheatEnabler/Patches/GamePatch.cs @@ -149,7 +149,9 @@ public static class GamePatch { __instance.Update(); } - + // Harmony transpiler: PlayerAction_Test_Update_Transpiler + // Target: PlayerAction_Test.Update + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(PlayerAction_Test), nameof(PlayerAction_Test.Update))] private static IEnumerable PlayerAction_Test_Update_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -182,7 +184,9 @@ public static class GamePatch matcher.Labels = labels; return matcher.InstructionEnumeration(); } - + // Harmony transpiler: GameCamera_Logic_Transpiler + // Target: GameCamera.FrameLogic + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(GameCamera), nameof(GameCamera.FrameLogic))] private static IEnumerable GameCamera_Logic_Transpiler(IEnumerable instructions) @@ -310,7 +314,9 @@ public static class GamePatch history.currentTech = history.techQueue[0]; } } - + // Harmony transpiler: UITechNode_OnPointerDown_Transpiler + // Target: UITechNode.OnPointerDown + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UITechNode), nameof(UITechNode.OnPointerDown))] private static IEnumerable UITechNode_OnPointerDown_Transpiler(IEnumerable instructions, ILGenerator generator) diff --git a/CheatEnabler/Patches/PlanetPatch.cs b/CheatEnabler/Patches/PlanetPatch.cs index 5d3a4e8..a434d7b 100644 --- a/CheatEnabler/Patches/PlanetPatch.cs +++ b/CheatEnabler/Patches/PlanetPatch.cs @@ -34,6 +34,9 @@ public static class PlanetPatch private class WaterPumperPatch : PatchImpl { + // Harmony transpiler: BuildTool_CheckBuildConditions_Transpiler + // Target: BuildTool_BlueprintPaste.CheckBuildConditions, BuildTool_Click.CheckBuildConditions + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_BlueprintPaste), nameof(BuildTool_BlueprintPaste.CheckBuildConditions))] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click.CheckBuildConditions))] @@ -56,6 +59,9 @@ public static class PlanetPatch private class TerraformAnyway : PatchImpl { + // Harmony transpiler: BuildTool_BlueprintPaste_DetermineReforms_Patch + // Target: BuildTool_BlueprintPaste.DetermineReforms + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_BlueprintPaste), nameof(BuildTool_BlueprintPaste.DetermineReforms))] private static IEnumerable BuildTool_BlueprintPaste_DetermineReforms_Patch(IEnumerable instructions, ILGenerator generator) @@ -79,7 +85,9 @@ public static class PlanetPatch matcher.Opcode = OpCodes.Br; return matcher.InstructionEnumeration(); } - + // Harmony transpiler: BuildTool_Reform_RemoveBasePit_Patch + // Target: BuildTool_Reform.RemoveBasePit + // Fallback: Checks CodeMatcher.IsInvalid/IsValid and returns original instructions on mismatch. [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Reform), nameof(BuildTool_Reform.RemoveBasePit))] private static IEnumerable BuildTool_Reform_RemoveBasePit_Patch(IEnumerable instructions, ILGenerator generator) @@ -104,7 +112,9 @@ public static class PlanetPatch ); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: UIRemoveBasePitButton_OnRemoveButtonClick_Patch + // Target: UIRemoveBasePitButton.OnRemoveButtonClick, UIRemoveBasePitButton._OnUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIRemoveBasePitButton), nameof(UIRemoveBasePitButton.OnRemoveButtonClick))] [HarmonyPatch(typeof(UIRemoveBasePitButton), nameof(UIRemoveBasePitButton._OnUpdate))] @@ -141,7 +151,9 @@ public static class PlanetPatch ); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: BuildTool_Reform_ReformAction_Patch + // Target: BuildTool_Reform.ReformAction + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Reform), nameof(BuildTool_Reform.ReformAction))] private static IEnumerable BuildTool_Reform_ReformAction_Patch(IEnumerable instructions, ILGenerator generator) diff --git a/CheatEnabler/Patches/PlayerPatch.cs b/CheatEnabler/Patches/PlayerPatch.cs index 3dd7525..e67e8cd 100644 --- a/CheatEnabler/Patches/PlayerPatch.cs +++ b/CheatEnabler/Patches/PlayerPatch.cs @@ -47,6 +47,9 @@ public static class PlayerPatch private class InstantTeleport : PatchImpl { + // Harmony transpiler: UIGlobemap__OnUpdate_Transpiler + // Target: UIGlobemap._OnUpdate, UIStarmap.DoRightClickFastTravel, UIStarmap.OnFastTravelButtonClick, UIStarmap.OnScreenClick, UIStarmap.SandboxRightClickFastTravelLogic, UIStarmap.StartFastTravelToPlanet, UIStarmap.StartFastTravelToUPosition, UIStarmap.UpdateCursorView, UIStarmap._OnUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIGlobemap), nameof(UIGlobemap._OnUpdate))] [HarmonyPatch(typeof(UIStarmap), nameof(UIStarmap.DoRightClickFastTravel))] diff --git a/CheatEnabler/Patches/ResourcePatch.cs b/CheatEnabler/Patches/ResourcePatch.cs index 9608118..e7a00ee 100644 --- a/CheatEnabler/Patches/ResourcePatch.cs +++ b/CheatEnabler/Patches/ResourcePatch.cs @@ -34,6 +34,9 @@ public static class ResourcePatch private class InfiniteResource : PatchImpl { static private readonly float InfiniteResourceRate = 0f; + // Harmony transpiler: Transpiler + // Target: FactorySystem.GameTick, GameLogic._miner_parallel, PlanetTransport.GameTick, UIChartAstroResource.CalculateMaxAmount, UIChartVeinGroup.CalculateMaxAmount, UIControlPanelAdvancedMinerEntry._OnUpdate, UIControlPanelVeinCollectorPanel._OnUpdate, UIMinerWindow._OnUpdate, UIMiningUpgradeLabel.Update, UIVeinCollectorPanel._OnUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(FactorySystem), nameof(FactorySystem.GameTick), typeof(long), typeof(bool))] [HarmonyPatch(typeof(GameLogic), nameof(GameLogic._miner_parallel))] @@ -75,7 +78,9 @@ public static class ResourcePatch private class FastMining : PatchImpl { static private readonly float FastMiningSpeed = 2400f; - + // Harmony transpiler: Transpiler + // Target: AstroResourceStatPlan.AddPlanetResources, BuildingGizmo.Update, FactorySystem.GameTick, GameLogic._miner_parallel, ItemProto.GetPropValue, PlanetTransport.GameTick, ProductionExtraInfoCalculator.CalculateFactory, UIChartAstroResource.CalculateMaxAmount, UIChartVeinGroup.CalculateMaxAmount, UIControlPanelStationStorage.RefreshValues, UIControlPanelVeinCollectorPanel._OnUpdate, UIMinerWindow._OnUpdate, UIMiningUpgradeLabel.Update, UIPlanetDetail.OnPlanetDataSet, UIPlanetDetail.RefreshDynamicProperties, UIReferenceSpeedTip.AddEntryDataWithFactory, UIStarDetail.OnStarDataSet, UIStarDetail.RefreshDynamicProperties, UIStationStorage.RefreshValues, UIVeinCollectorPanel._OnUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(AstroResourceStatPlan), nameof(AstroResourceStatPlan.AddPlanetResources))] [HarmonyPatch(typeof(BuildingGizmo), nameof(BuildingGizmo.Update))] diff --git a/Directory.Build.props b/Directory.Build.props index dfe413d..b4cc6d3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,6 +5,12 @@ net472 true latest + disable + true + + 0618 + none + false https://nuget.bepinex.dev/v3/index.json false diff --git a/UXAssist/Common/ModCompat/ModCompatHelper.cs b/UXAssist/Common/ModCompat/ModCompatHelper.cs new file mode 100644 index 0000000..3e5cb0a --- /dev/null +++ b/UXAssist/Common/ModCompat/ModCompatHelper.cs @@ -0,0 +1,82 @@ +using System; +using System.Reflection; +using BepInEx; +using BepInEx.Bootstrap; +using HarmonyLib; + +namespace UXAssist.Common.ModCompat; + +public static class ModCompatHelper +{ + public static bool TryGetLoadedPluginInfo(string guid, out BepInEx.PluginInfo pluginInfo) + { + return Chainloader.PluginInfos.TryGetValue(guid, out pluginInfo) && pluginInfo != null; + } + + public static bool TryGetPluginType(BepInEx.PluginInfo pluginInfo, string typeName, out Type type) + { + type = null; + if (pluginInfo?.Instance == null) return false; + try + { + type = pluginInfo.Instance.GetType().Assembly.GetType(typeName, throwOnError: false); + } + catch + { + // ignored + } + return type != null; + } + + public static bool TryGetPluginType(string guid, string typeName, out Type type) + { + type = null; + return TryGetLoadedPluginInfo(guid, out var pluginInfo) && TryGetPluginType(pluginInfo, typeName, out type); + } + + public static bool TryGetField(Type type, string fieldName, out FieldInfo field) + { + field = null; + if (type == null) return false; + field = AccessTools.Field(type, fieldName); + return field != null; + } + + public static bool TryGetFieldValue(Type type, string fieldName, object instance, out T value) + { + value = default; + if (!TryGetField(type, fieldName, out var field)) return false; + try + { + var result = field.GetValue(instance); + if (result is T t) + { + value = t; + return true; + } + } + catch + { + // ignored + } + return false; + } + + public static bool TryGetMethod(Type type, string methodName, out MethodInfo method) + { + method = null; + if (type == null) return false; + method = AccessTools.Method(type, methodName); + return method != null; + } + + public static bool TryGetPropertySetter(Type type, string propertyName, out MethodInfo setter) + { + setter = null; + if (type == null) return false; + var property = AccessTools.Property(type, propertyName); + if (property == null) return false; + setter = property.GetSetMethod(nonPublic: true); + return setter != null; + } +} diff --git a/UXAssist/Common/Patching/TranspilerGuard.cs b/UXAssist/Common/Patching/TranspilerGuard.cs new file mode 100644 index 0000000..9c2f33d --- /dev/null +++ b/UXAssist/Common/Patching/TranspilerGuard.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Reflection.Emit; +using BepInEx.Logging; +using HarmonyLib; + +namespace UXAssist.Common.Patching; + +/// +/// Helper for Harmony transpilers. Provides a standardized way to bail out and return the original +/// instructions when a fails to match, which makes version-fragile patches +/// easier to diagnose at runtime. +/// +public static class TranspilerGuard +{ + public static IEnumerable Finish( + this CodeMatcher matcher, + IEnumerable originalInstructions, + ManualLogSource logger, + string transpilerName) + { + if (matcher.IsInvalid) + { + logger?.LogWarning($"Transpiler '{transpilerName}' failed to match; returning original instructions."); + return originalInstructions; + } + return matcher.InstructionEnumeration(); + } +} diff --git a/UXAssist/Common/Utils/DysonSphereReflection.cs b/UXAssist/Common/Utils/DysonSphereReflection.cs new file mode 100644 index 0000000..6b137bb --- /dev/null +++ b/UXAssist/Common/Utils/DysonSphereReflection.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using HarmonyLib; + +namespace UXAssist.Common.Utils; + +public static class DysonSphereReflection +{ + private static readonly FieldInfo TotalNodeSpField = AccessTools.Field(typeof(DysonSphereLayer), "totalNodeSP"); + private static readonly FieldInfo TotalFrameSpField = AccessTools.Field(typeof(DysonSphereLayer), "totalFrameSP"); + private static readonly FieldInfo TotalCpField = AccessTools.Field(typeof(DysonSphereLayer), "totalCP"); + + public static bool IsAvailable => TotalNodeSpField != null && TotalFrameSpField != null && TotalCpField != null; + + public static bool HasTotalNodeSP => TotalNodeSpField != null; + + public static bool HasTotalFrameSP => TotalFrameSpField != null; + + public static bool HasTotalCP => TotalCpField != null; + + public static long? GetTotalNodeSP(DysonSphereLayer layer) + => layer != null && TotalNodeSpField != null ? (long?)TotalNodeSpField.GetValue(layer) : null; + + public static long? GetTotalFrameSP(DysonSphereLayer layer) + => layer != null && TotalFrameSpField != null ? (long?)TotalFrameSpField.GetValue(layer) : null; + + public static long? GetTotalCP(DysonSphereLayer layer) + => layer != null && TotalCpField != null ? (long?)TotalCpField.GetValue(layer) : null; + + public static void SetTotalNodeSP(DysonSphereLayer layer, long value) + => TotalNodeSpField?.SetValue(layer, value); + + public static void SetTotalFrameSP(DysonSphereLayer layer, long value) + => TotalFrameSpField?.SetValue(layer, value); + + public static void SetTotalCP(DysonSphereLayer layer, long value) + => TotalCpField?.SetValue(layer, value); +} diff --git a/UXAssist/ModsCompat/AuxilaryfunctionWrapper.cs b/UXAssist/ModsCompat/AuxilaryfunctionWrapper.cs index d9b35c5..9080066 100644 --- a/UXAssist/ModsCompat/AuxilaryfunctionWrapper.cs +++ b/UXAssist/ModsCompat/AuxilaryfunctionWrapper.cs @@ -1,7 +1,7 @@ using System; -using BepInEx.Bootstrap; using BepInEx.Configuration; using HarmonyLib; +using UXAssist.Common.ModCompat; using UXAssist.Patches; namespace UXAssist.ModsCompat; @@ -13,27 +13,28 @@ public static class AuxilaryfunctionWrapper public static void Start(Harmony harmony) { - if (!Chainloader.PluginInfos.TryGetValue(AuxilaryfunctionGuid, out var pluginInfo)) return; - var assembly = pluginInfo.Instance.GetType().Assembly; - try + if (!ModCompatHelper.TryGetLoadedPluginInfo(AuxilaryfunctionGuid, out var pluginInfo)) return; + if (!ModCompatHelper.TryGetPluginType(pluginInfo, "Auxilaryfunction.Auxilaryfunction", out var classType)) { - var classType = assembly.GetType("Auxilaryfunction.Auxilaryfunction"); - ShowStationInfo = (ConfigEntry)AccessTools.Field(classType, "ShowStationInfo").GetValue(pluginInfo.Instance); + UXAssist.Logger.LogWarning("Failed to locate Auxilaryfunction main type"); + return; } - catch + if (!ModCompatHelper.TryGetFieldValue>(classType, "ShowStationInfo", pluginInfo.Instance, out ShowStationInfo)) { UXAssist.Logger.LogWarning("Failed to get ShowStationInfo from Auxilaryfunction"); } - try + if (!ModCompatHelper.TryGetPluginType(pluginInfo, "Auxilaryfunction.Patch.SpeedUpPatch", out var speedUpPatchType)) { - var classType = assembly.GetType("Auxilaryfunction.Patch.SpeedUpPatch"); - harmony.Patch(AccessTools.PropertySetter(classType, "Enable"), - new HarmonyMethod(AccessTools.Method(typeof(AuxilaryfunctionWrapper), nameof(PatchSpeedUpPatchEnable)))); + UXAssist.Logger.LogWarning("Failed to locate Auxilaryfunction SpeedUpPatch"); + return; } - catch + if (!ModCompatHelper.TryGetPropertySetter(speedUpPatchType, "Enable", out var setter)) { - UXAssist.Logger.LogWarning("Failed to patch SpeedUpPatch.set_Enable() from Auxilaryfunction"); + UXAssist.Logger.LogWarning("Failed to resolve SpeedUpPatch.set_Enable() from Auxilaryfunction"); + return; } + harmony.Patch(setter, + new HarmonyMethod(AccessTools.Method(typeof(AuxilaryfunctionWrapper), nameof(PatchSpeedUpPatchEnable)))); } public static void PatchSpeedUpPatchEnable(bool value) diff --git a/UXAssist/ModsCompat/BlueprintTweaks.cs b/UXAssist/ModsCompat/BlueprintTweaks.cs index c460372..8a09530 100644 --- a/UXAssist/ModsCompat/BlueprintTweaks.cs +++ b/UXAssist/ModsCompat/BlueprintTweaks.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using UnityEngine; +using UXAssist.Common.ModCompat; using UXAssist.Functions; namespace UXAssist.ModsCompat; @@ -17,13 +18,12 @@ class BlueprintTweaks public static bool Run(Harmony harmony) { - if (!BepInEx.Bootstrap.Chainloader.PluginInfos.TryGetValue(BlueprintTweaksGuid, out var pluginInfo)) return false; - var assembly = pluginInfo.Instance.GetType().Assembly; - var classTypeDragRemoveBuildTool = assembly.GetType("BlueprintTweaks.DragRemoveBuildTool"); - if (classTypeDragRemoveBuildTool == null) return false; - if (AccessTools.Method(classTypeDragRemoveBuildTool, "DetermineMorePreviews") != null) return true; - classTypeBlueprintTweaksPlugin = assembly.GetType("BlueprintTweaks.BlueprintTweaksPlugin"); - classTypeUIBuildingGridPatch2 = assembly.GetType("BlueprintTweaks.UIBuildingGridPatch2"); + if (!ModCompatHelper.TryGetLoadedPluginInfo(BlueprintTweaksGuid, out var pluginInfo)) return false; + if (!ModCompatHelper.TryGetPluginType(pluginInfo, "BlueprintTweaks.DragRemoveBuildTool", out var classTypeDragRemoveBuildTool)) return false; + if (ModCompatHelper.TryGetMethod(classTypeDragRemoveBuildTool, "DetermineMorePreviews", out _)) return true; + ModCompatHelper.TryGetPluginType(pluginInfo, "BlueprintTweaks.BlueprintTweaksPlugin", out classTypeBlueprintTweaksPlugin); + ModCompatHelper.TryGetPluginType(pluginInfo, "BlueprintTweaks.UIBuildingGridPatch2", out classTypeUIBuildingGridPatch2); + if (classTypeBlueprintTweaksPlugin == null || classTypeUIBuildingGridPatch2 == null) return false; var UIBuildingGrid_Update = AccessTools.Method(typeof(UIBuildingGrid), nameof(UIBuildingGrid.Update)); harmony.Patch(AccessTools.Method(classTypeUIBuildingGridPatch2, "UpdateGrid"), null, null, new HarmonyMethod(AccessTools.Method(typeof(BlueprintTweaks), nameof(PatchUpdateGrid)))); selectObjIdsField = AccessTools.Field(classTypeDragRemoveBuildTool, "selectObjIds"); diff --git a/UXAssist/ModsCompat/BulletTimeWrapper.cs b/UXAssist/ModsCompat/BulletTimeWrapper.cs index de1df1c..3d3216d 100644 --- a/UXAssist/ModsCompat/BulletTimeWrapper.cs +++ b/UXAssist/ModsCompat/BulletTimeWrapper.cs @@ -1,5 +1,5 @@ -using BepInEx.Bootstrap; -using HarmonyLib; +using HarmonyLib; +using UXAssist.Common.ModCompat; namespace UXAssist.ModsCompat; @@ -10,6 +10,6 @@ public static class BulletTimeWrapper public static void Start(Harmony _) { - HasBulletTime = Chainloader.PluginInfos.TryGetValue(BulletTimeGuid, out var _); + HasBulletTime = ModCompatHelper.TryGetLoadedPluginInfo(BulletTimeGuid, out var _); } } diff --git a/UXAssist/ModsCompat/CommonAPIWrapper.cs b/UXAssist/ModsCompat/CommonAPIWrapper.cs index b1fe04f..3e9b8f2 100644 --- a/UXAssist/ModsCompat/CommonAPIWrapper.cs +++ b/UXAssist/ModsCompat/CommonAPIWrapper.cs @@ -1,6 +1,6 @@ -using BepInEx.Bootstrap; using CommonAPI; using HarmonyLib; +using UXAssist.Common.ModCompat; namespace UXAssist.ModsCompat; @@ -8,7 +8,7 @@ public static class CommonAPIWrapper { public static void Run(Harmony harmony) { - if (!Chainloader.PluginInfos.TryGetValue(CommonAPIPlugin.GUID, out var commonAPIPlugin) || + if (!ModCompatHelper.TryGetLoadedPluginInfo(CommonAPIPlugin.GUID, out var commonAPIPlugin) || commonAPIPlugin.Metadata.Version > new System.Version(1, 6, 7, 0)) return; harmony.Patch(AccessTools.Method(typeof(GameOption), nameof(GameOption.InitKeys)), new HarmonyMethod(AccessTools.Method(typeof(CommonAPIWrapper), nameof(PatchInitKeys)), Priority.First)); } diff --git a/UXAssist/ModsCompat/PlanetVeinUtilization.cs b/UXAssist/ModsCompat/PlanetVeinUtilization.cs index d8cf9c5..8b3d0e0 100644 --- a/UXAssist/ModsCompat/PlanetVeinUtilization.cs +++ b/UXAssist/ModsCompat/PlanetVeinUtilization.cs @@ -1,4 +1,5 @@ using HarmonyLib; +using UXAssist.Common.ModCompat; namespace UXAssist.ModsCompat; @@ -8,9 +9,8 @@ class PlanetVeinUtilization public static bool Run(Harmony harmony) { - if (!BepInEx.Bootstrap.Chainloader.PluginInfos.TryGetValue(PlanetVeinUtilizationGuid, out var pluginInfo)) return false; - var assembly = pluginInfo.Instance.GetType().Assembly; - var classType = assembly.GetType("PlanetVeinUtilization.PlanetVeinUtilization"); + if (!ModCompatHelper.TryGetLoadedPluginInfo(PlanetVeinUtilizationGuid, out var pluginInfo)) return false; + if (!ModCompatHelper.TryGetPluginType(pluginInfo, "PlanetVeinUtilization.PlanetVeinUtilization", out var classType)) return false; harmony.Patch(AccessTools.Method(classType, "Awake"), new HarmonyMethod(typeof(PlanetVeinUtilization).GetMethod("PatchPlanetVeinUtilizationAwake"))); return true; diff --git a/UXAssist/Patches/DysonSpherePatch.cs b/UXAssist/Patches/DysonSpherePatch.cs index 58ea54f..56a2d8c 100644 --- a/UXAssist/Patches/DysonSpherePatch.cs +++ b/UXAssist/Patches/DysonSpherePatch.cs @@ -1,10 +1,10 @@ using System.Collections.Generic; -using System.Reflection; using System.Reflection.Emit; using BepInEx.Configuration; using HarmonyLib; using UnityEngine.UI; using UXAssist.Common; +using UXAssist.Common.Utils; using GameLogicProc = UXAssist.Common.GameLogic; namespace UXAssist.Patches; @@ -15,16 +15,11 @@ public class DysonSpherePatch : PatchImpl public static ConfigEntry OnlyConstructNodesEnabled; public static ConfigEntry AutoConstructMultiplier; - private static FieldInfo _totalNodeSpInfo, _totalFrameSpInfo, _totalCpInfo; - public static void Init() { Enable(true); StopEjectOnNodeCompleteEnabled.SettingChanged += (_, _) => StopEjectOnNodeComplete.Enable(StopEjectOnNodeCompleteEnabled.Value); OnlyConstructNodesEnabled.SettingChanged += (_, _) => OnlyConstructNodes.Enable(OnlyConstructNodesEnabled.Value); - _totalNodeSpInfo = AccessTools.Field(typeof(DysonSphereLayer), "totalNodeSP"); - _totalFrameSpInfo = AccessTools.Field(typeof(DysonSphereLayer), "totalFrameSP"); - _totalCpInfo = AccessTools.Field(typeof(DysonSphereLayer), "totalCP"); GameLogicProc.OnGameEnd += StopEjectOnNodeComplete.ResetState; } @@ -93,8 +88,9 @@ public class DysonSpherePatch : PatchImpl } // Make compatible with DSPOptimizations - if (_totalNodeSpInfo != null) - _totalNodeSpInfo.SetValue(dysonSphereLayer, (long)_totalNodeSpInfo.GetValue(dysonSphereLayer) + diff - 1); + var currentNodeSp = DysonSphereReflection.GetTotalNodeSP(dysonSphereLayer); + if (currentNodeSp.HasValue) + DysonSphereReflection.SetTotalNodeSP(dysonSphereLayer, currentNodeSp.Value + diff - 1); __instance.UpdateProgress(dysonNode); } @@ -127,8 +123,9 @@ public class DysonSpherePatch : PatchImpl } // Make compatible with DSPOptimizations - if (_totalFrameSpInfo != null) - _totalFrameSpInfo.SetValue(dysonSphereLayer, (long)_totalFrameSpInfo.GetValue(dysonSphereLayer) + diff - 1); + var currentFrameSp = DysonSphereReflection.GetTotalFrameSP(dysonSphereLayer); + if (currentFrameSp.HasValue) + DysonSphereReflection.SetTotalFrameSP(dysonSphereLayer, currentFrameSp.Value + diff - 1); __instance.UpdateProgress(dysonFrame); } @@ -153,8 +150,9 @@ public class DysonSpherePatch : PatchImpl } // Make compatible with DSPOptimizations - if (_totalFrameSpInfo != null) - _totalFrameSpInfo.SetValue(dysonSphereLayer, (long)_totalFrameSpInfo.GetValue(dysonSphereLayer) + diff - 1); + var currentFrameSp2 = DysonSphereReflection.GetTotalFrameSP(dysonSphereLayer); + if (currentFrameSp2.HasValue) + DysonSphereReflection.SetTotalFrameSP(dysonSphereLayer, currentFrameSp2.Value + diff - 1); __instance.UpdateProgress(dysonFrame); } @@ -199,9 +197,10 @@ public class DysonSpherePatch : PatchImpl dysonShell.nodecps[nodeIndex] += diff; dysonShell.nodecps[dysonShell.nodecps.Length - 1] += diff; // Make compatible with DSPOptimizations - if (_totalCpInfo != null) + var currentCp = DysonSphereReflection.GetTotalCP(dysonSphereLayer); + if (currentCp.HasValue) { - _totalCpInfo.SetValue(dysonSphereLayer, (long)_totalCpInfo.GetValue(dysonSphereLayer) + diff); + DysonSphereReflection.SetTotalCP(dysonSphereLayer, currentCp.Value + diff); dysonShell.SetMaterialDynamicVars(); } shellIndex = (shellIndex + 1) % shellCount; @@ -233,7 +232,9 @@ public class DysonSpherePatch : PatchImpl return false; } - + // Harmony transpiler: DysonSpherePatch_DysonNode_ConstructCp_Transpiler + // Target: DysonNode.ConstructCp + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPriority(Priority.First)] [HarmonyPatch(typeof(DysonNode), nameof(DysonNode.ConstructCp))] @@ -390,7 +391,9 @@ public class DysonSpherePatch : PatchImpl _nodeForAbsorb[starIndex].Clear(); _nodeForAbsorb[starIndex] = null; } - + // Harmony transpiler: EjectorComponent_InternalUpdate_Transpiler + // Target: EjectorComponent.InternalUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(EjectorComponent), nameof(EjectorComponent.InternalUpdate))] private static IEnumerable EjectorComponent_InternalUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -429,7 +432,9 @@ public class DysonSpherePatch : PatchImpl ); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: DysonNode_ConstructSp_Transpiler + // Target: DysonNode.ConstructSp + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(DysonNode), nameof(DysonNode.ConstructSp))] private static IEnumerable DysonNode_ConstructSp_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -446,7 +451,9 @@ public class DysonSpherePatch : PatchImpl ); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: DysonNode_ConstructCp_Transpiler + // Target: DysonNode.ConstructCp + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(DysonNode), nameof(DysonNode.ConstructCp))] private static IEnumerable DysonNode_ConstructCp_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -467,7 +474,9 @@ public class DysonSpherePatch : PatchImpl ); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: UIEjectorWindow__OnUpdate_Transpiler + // Target: UIEjectorWindow._OnUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIEjectorWindow), nameof(UIEjectorWindow._OnUpdate))] static IEnumerable UIEjectorWindow__OnUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -527,7 +536,9 @@ public class DysonSpherePatch : PatchImpl sphere.PickAutoNode(); } } - + // Harmony transpiler: DysonNode_spReqOrder_Getter_Transpiler + // Target: DysonNode.spReqOrder (getter) + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(DysonNode), nameof(DysonNode.spReqOrder), MethodType.Getter)] private static IEnumerable DysonNode_spReqOrder_Getter_Transpiler(IEnumerable instructions, ILGenerator generator) diff --git a/UXAssist/Patches/Factory/ArchitectModePatch.cs b/UXAssist/Patches/Factory/ArchitectModePatch.cs index 20e549c..a755246 100644 --- a/UXAssist/Patches/Factory/ArchitectModePatch.cs +++ b/UXAssist/Patches/Factory/ArchitectModePatch.cs @@ -20,6 +20,9 @@ internal static class ArchitectModePatch internal class UnlimitInteractive : PatchImpl { + // Harmony transpiler: PlayerAction_Inspect_GetObjectSelectDistance_Transpiler + // Target: PlayerAction_Inspect.GetObjectSelectDistance + // Fallback: Checks CodeMatcher.IsInvalid/IsValid and returns original instructions on mismatch. [HarmonyTranspiler] [HarmonyPatch(typeof(PlayerAction_Inspect), nameof(PlayerAction_Inspect.GetObjectSelectDistance))] private static IEnumerable PlayerAction_Inspect_GetObjectSelectDistance_Transpiler(IEnumerable instructions) @@ -31,6 +34,9 @@ internal static class ArchitectModePatch internal class RemoveSomeConditionBuild : PatchImpl { + // Harmony transpiler: BuildTool_Click_CheckBuildConditions_Transpiler + // Target: BuildTool_BlueprintPaste.CheckBuildConditions, BuildTool_Click.CheckBuildConditions + // Fallback: Checks CodeMatcher.IsInvalid/IsValid and returns original instructions on mismatch. [HarmonyTranspiler, HarmonyPriority(Priority.First)] [HarmonyPatch(typeof(BuildTool_BlueprintPaste), nameof(BuildTool_BlueprintPaste.CheckBuildConditions))] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click.CheckBuildConditions))] @@ -65,7 +71,9 @@ internal static class ArchitectModePatch matcher.Opcode = OpCodes.Brfalse; return matcher.InstructionEnumeration(); } - + // Harmony transpiler: BuildTool_Path_CheckBuildConditions_Transpiler + // Target: BuildTool_Path.CheckBuildConditions + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler, HarmonyPriority(Priority.First)] [HarmonyPatch(typeof(BuildTool_Path), nameof(BuildTool_Path.CheckBuildConditions))] private static IEnumerable BuildTool_Path_CheckBuildConditions_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -140,7 +148,9 @@ internal static class ArchitectModePatch if (controller == null) return; controller.actionBuild?.clickTool?._OnInit(); } - + // Harmony transpiler: BuildTool_Click__OnInit_Transpiler + // Target: BuildTool_Click._OnInit + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click._OnInit))] private static IEnumerable BuildTool_Click__OnInit_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -152,7 +162,9 @@ internal static class ArchitectModePatch matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldc_I4, 512)); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: BuildAreaLimitRemoval_Transpiler + // Target: BuildTool_Addon.CheckBuildConditions, BuildTool_Click.CheckBuildConditions, BuildTool_Dismantle.DetermineMoreChainTargets, BuildTool_Dismantle.DeterminePreviews, BuildTool_Inserter.CheckBuildConditions, BuildTool_Path.CheckBuildConditions, BuildTool_Reform.ReformAction, BuildTool_Upgrade.DetermineMoreChainTargets, BuildTool_Upgrade.DeterminePreviews + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Addon), nameof(BuildTool_Addon.CheckBuildConditions))] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click.CheckBuildConditions))] @@ -185,6 +197,9 @@ internal static class ArchitectModePatch internal class LargerAreaForUpgradeAndDismantle : PatchImpl { + // Harmony transpiler: BuildTools_CursorSizePatch_Transpiler + // Target: BuildTool_Dismantle.DeterminePreviews, BuildTool_Upgrade.DeterminePreviews + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Dismantle), nameof(BuildTool_Dismantle.DeterminePreviews))] [HarmonyPatch(typeof(BuildTool_Upgrade), nameof(BuildTool_Upgrade.DeterminePreviews))] @@ -201,6 +216,9 @@ internal static class ArchitectModePatch internal class LargerAreaForTerraform : PatchImpl { + // Harmony transpiler: BuildTool_Reform_ReformAction_Transpiler + // Target: BuildTool_Reform.ReformAction + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler, HarmonyPatch(typeof(BuildTool_Reform), nameof(BuildTool_Reform.ReformAction))] private static IEnumerable BuildTool_Reform_ReformAction_Transpiler(IEnumerable instructions, ILGenerator generator) { diff --git a/UXAssist/Patches/Factory/BuildToolPatch.cs b/UXAssist/Patches/Factory/BuildToolPatch.cs index ad42ca5..b98818b 100644 --- a/UXAssist/Patches/Factory/BuildToolPatch.cs +++ b/UXAssist/Patches/Factory/BuildToolPatch.cs @@ -24,6 +24,9 @@ internal static class BuildToolPatch private class BuildGizmoPatch : PatchImpl { + // Harmony transpiler: ConnGizmoGraph_Constructor_Transpiler + // Target: ConnGizmoGraph..ctor + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(ConnGizmoGraph), MethodType.Constructor)] private static IEnumerable ConnGizmoGraph_Constructor_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -35,7 +38,9 @@ internal static class BuildToolPatch matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldc_I4, 2048)); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: ConnGizmoGraph_SetPointCount_Transpiler + // Target: ConnGizmoGraph.SetPointCount + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(ConnGizmoGraph), nameof(ConnGizmoGraph.SetPointCount))] private static IEnumerable ConnGizmoGraph_SetPointCount_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -47,7 +52,9 @@ internal static class BuildToolPatch matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldc_I4, 2048)); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: BuildTool_Path__OnInit_Transpiler + // Target: BuildTool_Path._OnInit + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Path), nameof(BuildTool_Path._OnInit))] private static IEnumerable BuildTool_Path__OnInit_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -59,7 +66,9 @@ internal static class BuildToolPatch matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldc_I4, 2048)); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: BuildTool_Reform_Constructor_Transpiler + // Target: BuildTool_Reform..ctor + // Fallback: Checks CodeMatcher.IsInvalid/IsValid and returns original instructions on mismatch. [HarmonyTranspiler, HarmonyPatch(typeof(BuildTool_Reform), MethodType.Constructor)] private static IEnumerable BuildTool_Reform_Constructor_Transpiler(IEnumerable instructions, ILGenerator generator) { @@ -133,7 +142,9 @@ internal static class BuildToolPatch __instance.actionBuild.model.cursorText = $"({_lastOffsetText})\n" + __instance.actionBuild.model.cursorText; } - + // Harmony transpiler: UIEntityBriefInfo__OnUpdate_Transpiler + // Target: UIEntityBriefInfo._OnUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIEntityBriefInfo), nameof(UIEntityBriefInfo._OnUpdate))] private static IEnumerable UIEntityBriefInfo__OnUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -188,7 +199,9 @@ internal static class BuildToolPatch ifBlockEntryLabel = thisIfBlockEntryLabel; elseBlockEntryLabel = thisElseBlockEntryLabel; } - + // Harmony transpiler: AllowOffGridConstruction + // Target: BuildTool_Click.UpdateRaycast, BuildTool_Click.DeterminePreviews + // Fallback: Checks CodeMatcher.IsInvalid/IsValid and returns original instructions on mismatch. [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click.UpdateRaycast))] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click.DeterminePreviews))] @@ -206,7 +219,9 @@ internal static class BuildToolPatch return matcher.InstructionEnumeration(); } - + // Harmony transpiler: PreventDraggingWhenOffGrid + // Target: BuildTool_Click.DeterminePreviews + // Fallback: Checks CodeMatcher.IsInvalid/IsValid and returns original instructions on mismatch. [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click.DeterminePreviews))] public static IEnumerable PreventDraggingWhenOffGrid(IEnumerable instructions, ILGenerator generator) @@ -233,7 +248,9 @@ internal static class BuildToolPatch return matcher.InstructionEnumeration(); } - + // Harmony transpiler: AllowOffGridConstructionForPath + // Target: BuildTool_Path.UpdateRaycast + // Fallback: Checks CodeMatcher.IsInvalid/IsValid and returns original instructions on mismatch. [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Path), nameof(BuildTool_Path.UpdateRaycast))] public static IEnumerable AllowOffGridConstructionForPath(IEnumerable instructions, ILGenerator generator) @@ -339,6 +356,9 @@ internal static class BuildToolPatch internal class TreatStackingAsSingle : PatchImpl { + // Harmony transpiler: MonitorComponent_InternalUpdate_Transpiler + // Target: MonitorComponent.InternalUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(MonitorComponent), nameof(MonitorComponent.InternalUpdate))] private static IEnumerable MonitorComponent_InternalUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -491,7 +511,9 @@ internal static class BuildToolPatch return num; } - + // Harmony transpiler: BuildTool_Click_DeterminePreviews_Transpiler + // Target: BuildTool_Click.DeterminePreviews + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click.DeterminePreviews))] private static IEnumerable BuildTool_Click_DeterminePreviews_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -580,7 +602,9 @@ internal static class BuildToolPatch new(OpCodes.Call, AccessTools.Method(typeof(Math), nameof(Math.Min), [typeof(int), typeof(int)])) ]; private static readonly CodeInstruction GetRealCount = new(OpCodes.Ldsfld, AccessTools.Field(typeof(FactoryPatch), nameof(FactoryPatch._tankFastFillInAndTakeOutMultiplierRealValue))); - + // Harmony transpiler: PlanetFactory_EntityFastFillIn_Transpiler + // Target: PlanetFactory.EntityFastFillIn + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(PlanetFactory), nameof(PlanetFactory.EntityFastFillIn))] private static IEnumerable PlanetFactory_EntityFastFillIn_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -600,7 +624,9 @@ internal static class BuildToolPatch ).RemoveInstructions(5).Insert(MultiplierWithCountCheck); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: PlanetFactory_EntityFastTakeOut_Transpiler + // Target: PlanetFactory.EntityFastTakeOut + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(PlanetFactory), nameof(PlanetFactory.EntityFastTakeOut))] private static IEnumerable PlanetFactory_EntityFastTakeOut_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -621,7 +647,9 @@ internal static class BuildToolPatch ).RemoveInstructions(5).Insert(MultiplierWithCountCheck); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: UITankWindow__OnUpdate_Transpiler + // Target: UITankWindow._OnUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UITankWindow), nameof(UITankWindow._OnUpdate))] private static IEnumerable UITankWindow__OnUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -694,7 +722,9 @@ internal static class BuildToolPatch { nextTimei = 0; } - + // Harmony transpiler: VFInput_fastTransferWithEntityDown_Transpiler + // Target: VFInput._fastTransferWithEntityDown (getter), VFInput._fastTransferWithEntityPress (getter) + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(VFInput), nameof(VFInput._fastTransferWithEntityDown), MethodType.Getter)] [HarmonyPatch(typeof(VFInput), nameof(VFInput._fastTransferWithEntityPress), MethodType.Getter)] @@ -710,7 +740,9 @@ internal static class BuildToolPatch matcher.Labels.AddRange(lables); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: PlayerAction_Inspect_GameTick_Transpiler + // Target: PlayerAction_Inspect.GameTick + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(PlayerAction_Inspect), nameof(PlayerAction_Inspect.GameTick))] private static IEnumerable PlayerAction_Inspect_GameTick_Transpiler(IEnumerable instructions, ILGenerator generator) diff --git a/UXAssist/Patches/Factory/BuildingBufferPatch.cs b/UXAssist/Patches/Factory/BuildingBufferPatch.cs index ff25f44..0170bc7 100644 --- a/UXAssist/Patches/Factory/BuildingBufferPatch.cs +++ b/UXAssist/Patches/Factory/BuildingBufferPatch.cs @@ -67,7 +67,9 @@ internal static class BuildingBufferPatch patch.Unpatch(AccessTools.Method(typeof(SiloComponent), nameof(SiloComponent.InternalUpdate)), AccessTools.Method(typeof(TweakBuildingBuffer), nameof(SiloComponent_InternalUpdate_Transpiler))); patch.Patch(AccessTools.Method(typeof(SiloComponent), nameof(SiloComponent.InternalUpdate)), null, null, new HarmonyMethod(typeof(TweakBuildingBuffer), nameof(SiloComponent_InternalUpdate_Transpiler))); } - + // Harmony transpiler: PowerGeneratorComponent_GameTick_Gamma_Transpiler + // Target: PowerGeneratorComponent.GameTick_Gamma + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(PowerGeneratorComponent), nameof(PowerGeneratorComponent.GameTick_Gamma))] private static IEnumerable PowerGeneratorComponent_GameTick_Gamma_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -89,7 +91,9 @@ internal static class BuildingBufferPatch matcher.Advance(2).RemoveInstructions(2).Insert(new CodeInstruction(OpCodes.Ldc_I4, FactoryPatch.ReceiverBufferCount.Value * 3600)); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: AssemblerComponent_UpdateNeeds_Transpiler + // Target: AssemblerComponent.UpdateNeeds + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(AssemblerComponent), nameof(AssemblerComponent.UpdateNeeds))] private static IEnumerable AssemblerComponent_UpdateNeeds_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -125,7 +129,9 @@ internal static class BuildingBufferPatch matcher.Advance(2).Operand = FactoryPatch.AssemblerBufferMininumMultiplier.Value; return matcher.InstructionEnumeration(); } - + // Harmony transpiler: LabComponent_UpdateNeedsAssemble_Transpiler + // Target: LabComponent.UpdateNeedsAssemble + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(LabComponent), nameof(LabComponent.UpdateNeedsAssemble))] private static IEnumerable LabComponent_UpdateNeedsAssemble_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -163,7 +169,9 @@ internal static class BuildingBufferPatch matcher.Advance(2).SetAndAdvance(OpCodes.Ldc_I4, maxCount); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: LabComponent_UpdateNeedsResearch_Transpiler + // Target: LabComponent.UpdateNeedsResearch + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(LabComponent), nameof(LabComponent.UpdateNeedsResearch))] private static IEnumerable LabComponent_UpdateNeedsResearch_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -183,7 +191,9 @@ internal static class BuildingBufferPatch matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldc_I4, FactoryPatch.LabBufferMaxCountForResearch.Value * 3600)); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: EjectorComponent_InternalUpdate_Transpiler + // Target: EjectorComponent.InternalUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(EjectorComponent), nameof(EjectorComponent.InternalUpdate))] private static IEnumerable EjectorComponent_InternalUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -197,7 +207,9 @@ internal static class BuildingBufferPatch matcher.Advance(2).Set(OpCodes.Ldc_I4, FactoryPatch.EjectorBufferCount.Value); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: SiloComponent_InternalUpdate_Transpiler + // Target: SiloComponent.InternalUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(SiloComponent), nameof(SiloComponent.InternalUpdate))] private static IEnumerable SiloComponent_InternalUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) diff --git a/UXAssist/Patches/Factory/ImmediateBuildPatch.cs b/UXAssist/Patches/Factory/ImmediateBuildPatch.cs index ced30c6..7fd5471 100644 --- a/UXAssist/Patches/Factory/ImmediateBuildPatch.cs +++ b/UXAssist/Patches/Factory/ImmediateBuildPatch.cs @@ -212,7 +212,9 @@ internal static class ImmediateBuildPatch currLevel++; } } - + // Harmony transpiler: BuildTool_Dismantle_DeterminePreviews_Transpiler + // Target: BuildTool_Dismantle.DeterminePreviews + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Dismantle), nameof(BuildTool_Dismantle.DeterminePreviews))] private static IEnumerable BuildTool_Dismantle_DeterminePreviews_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -233,7 +235,9 @@ internal static class ImmediateBuildPatch ); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: BuildTool_Click__OnTick_Transpiler + // Target: BuildTool_Click._OnTick + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_Click), nameof(BuildTool_Click._OnTick))] private static IEnumerable BuildTool_Click__OnTick_Transpiler(IEnumerable instructions, ILGenerator generator) diff --git a/UXAssist/Patches/Factory/RenderingPatch.cs b/UXAssist/Patches/Factory/RenderingPatch.cs index c8f9ee2..cc17b90 100644 --- a/UXAssist/Patches/Factory/RenderingPatch.cs +++ b/UXAssist/Patches/Factory/RenderingPatch.cs @@ -40,7 +40,9 @@ internal static class RenderingPatch { __instance.renderEntity = true; } - + // Harmony transpiler: RaycastLogic_GameTick_Transpiler + // Target: RaycastLogic.GameTick + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(RaycastLogic), nameof(RaycastLogic.GameTick))] private static IEnumerable RaycastLogic_GameTick_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -156,7 +158,9 @@ internal static class RenderingPatch _sunlight = GameMain.universeSimulator?.LocalStarSimulator()?.sunLight; } } - + // Harmony transpiler: StarSimulator_LateUpdate_Transpiler + // Target: StarSimulator.LateUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(StarSimulator), nameof(StarSimulator.LateUpdate))] private static IEnumerable StarSimulator_LateUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -182,7 +186,9 @@ internal static class RenderingPatch ).Advance(1).Labels.Add(label2); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: PlanetSimulator_LateRefresh_Transpiler + // Target: PlanetSimulator.LateRefresh + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(PlanetSimulator), nameof(PlanetSimulator.LateRefresh))] private static IEnumerable PlanetSimulator_LateRefresh_Transpiler(IEnumerable instructions, ILGenerator generator) diff --git a/UXAssist/Patches/GamePatch.cs b/UXAssist/Patches/GamePatch.cs index c201c30..2816947 100644 --- a/UXAssist/Patches/GamePatch.cs +++ b/UXAssist/Patches/GamePatch.cs @@ -413,7 +413,9 @@ public class GamePatch : PatchImpl entry.indexText.text = (i + 1).ToString(); } } - + // Harmony transpiler: UILoadGameWindow_ReplaceSaveName_Transpiler + // Target: UILoadGameWindow.DoLoadSelectedGame, UILoadGameWindow.OnSelectedChange + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UILoadGameWindow), nameof(UILoadGameWindow.DoLoadSelectedGame))] [HarmonyPatch(typeof(UILoadGameWindow), nameof(UILoadGameWindow.OnSelectedChange))] @@ -426,7 +428,9 @@ public class GamePatch : PatchImpl matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldfld, AccessTools.Field(typeof(UIGameSaveEntry), nameof(UIGameSaveEntry._saveName)))); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: GameSave_RemoveValidateOnLoad_Transpiler + // Target: GameSave.LoadCurrentGame, GameSave.LoadGameDesc, GameSave.ReadHeader, GameSave.ReadHeaderAndDescAndProperty, GameSave.SaveExist, GameSave.SavePath + // Fallback: Checks CodeMatcher.IsInvalid/IsValid and returns original instructions on mismatch. [HarmonyTranspiler] [HarmonyPatch(typeof(GameSave), nameof(GameSave.LoadCurrentGame))] [HarmonyPatch(typeof(GameSave), nameof(GameSave.LoadGameDesc))] @@ -469,7 +473,9 @@ public class GamePatch : PatchImpl __instance.combatSettings = UIRoot.instance.galaxySelect.uiCombat.combatSettings; } } - + // Harmony transpiler: GameData_Import_Transpiler + // Target: GameData.Import + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(GameData), nameof(GameData.Import))] private static IEnumerable GameData_Import_Transpiler(IEnumerable instructions, ILGenerator generator) diff --git a/UXAssist/Patches/Logistics/AutoConfigPatch.cs b/UXAssist/Patches/Logistics/AutoConfigPatch.cs index 907d114..89602e5 100644 --- a/UXAssist/Patches/Logistics/AutoConfigPatch.cs +++ b/UXAssist/Patches/Logistics/AutoConfigPatch.cs @@ -28,6 +28,9 @@ internal class AutoConfigLogistics : PatchImpl private class LimitAutoReplenishCount : PatchImpl { + // Harmony transpiler: PlanetFactory_StationAutoReplenishIfNeeded_Transpiler + // Target: PlanetFactory.EntityAutoReplenishIfNeeded, PlanetFactory.StationAutoReplenishIfNeeded + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(PlanetFactory), nameof(PlanetFactory.EntityAutoReplenishIfNeeded))] [HarmonyPatch(typeof(PlanetFactory), nameof(PlanetFactory.StationAutoReplenishIfNeeded))] @@ -116,6 +119,9 @@ internal class AutoConfigLogistics : PatchImpl internal class AutoConfigLogisticsSetDefaultRemoteLogicToStorage : PatchImpl { + // Harmony transpiler: UIStationStorage_OnItemPickerReturn_Transpiler + // Target: UIControlPanelStationStorage.OnItemPickerReturn, UIStationStorage.OnItemPickerReturn + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIControlPanelStationStorage), nameof(UIControlPanelStationStorage.OnItemPickerReturn))] [HarmonyPatch(typeof(UIStationStorage), nameof(UIStationStorage.OnItemPickerReturn))] diff --git a/UXAssist/Patches/Logistics/CapacityPatch.cs b/UXAssist/Patches/Logistics/CapacityPatch.cs index 21ed2ce..650f97e 100644 --- a/UXAssist/Patches/Logistics/CapacityPatch.cs +++ b/UXAssist/Patches/Logistics/CapacityPatch.cs @@ -286,7 +286,9 @@ internal class GreaterPowerUsageInLogistics : PatchImpl UIStationWindow_OnStationIdChange_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -356,7 +358,9 @@ internal class GreaterPowerUsageInLogistics : PatchImpl UIStationWindow_OnMaxMiningSpeedChange_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -386,7 +390,9 @@ internal class GreaterPowerUsageInLogistics : PatchImpl UIStationWindow_OnMaxChargePowerSliderValueChange_Transpiler(IEnumerable instructions, ILGenerator generator) diff --git a/UXAssist/Patches/Logistics/OverflowPatch.cs b/UXAssist/Patches/Logistics/OverflowPatch.cs index b2c401c..d0b6e66 100644 --- a/UXAssist/Patches/Logistics/OverflowPatch.cs +++ b/UXAssist/Patches/Logistics/OverflowPatch.cs @@ -11,6 +11,9 @@ internal class AllowOverflowInLogistics : PatchImpl private static bool _blueprintPasting; // Do not check for overflow when try to send hand items into storages + // Harmony transpiler: UIStationStorage_OnItemIconMouseDown_Transpiler + // Target: UIControlPanelStationStorage.OnItemIconMouseDown, UIStationStorage.OnItemIconMouseDown + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIControlPanelStationStorage), nameof(UIControlPanelStationStorage.OnItemIconMouseDown))] [HarmonyPatch(typeof(UIStationStorage), nameof(UIStationStorage.OnItemIconMouseDown))] @@ -39,6 +42,9 @@ internal class AllowOverflowInLogistics : PatchImpl } // Remove storage limit check + // Harmony transpiler: PlanetTransport_SetStationStorage_Transpiler + // Target: PlanetTransport.SetStationStorage + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(PlanetTransport), nameof(PlanetTransport.SetStationStorage))] private static IEnumerable PlanetTransport_SetStationStorage_Transpiler(IEnumerable instructions, ILGenerator generator) diff --git a/UXAssist/Patches/Logistics/RealtimeInfoPanelPatch.cs b/UXAssist/Patches/Logistics/RealtimeInfoPanelPatch.cs index 5ebb862..29b6d81 100644 --- a/UXAssist/Patches/Logistics/RealtimeInfoPanelPatch.cs +++ b/UXAssist/Patches/Logistics/RealtimeInfoPanelPatch.cs @@ -87,7 +87,9 @@ internal class LogisticsConstrolPanelImprovement : PatchImpl UIGame_On_I_Switch_Transpiler(IEnumerable instructions, ILGenerator generator) diff --git a/UXAssist/Patches/PersistPatch.cs b/UXAssist/Patches/PersistPatch.cs index 8d04618..fb055a1 100644 --- a/UXAssist/Patches/PersistPatch.cs +++ b/UXAssist/Patches/PersistPatch.cs @@ -21,6 +21,9 @@ public class PersistPatch : PatchImpl } // Check for noModifier while pressing hotkeys on build bar + // Harmony transpiler: UIBuildMenu__OnUpdate_Transpiler + // Target: UIBuildMenu._OnUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIBuildMenu), nameof(UIBuildMenu._OnUpdate))] private static IEnumerable UIBuildMenu__OnUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -41,6 +44,9 @@ public class PersistPatch : PatchImpl } // Bring popup tip window to top layer + // Harmony transpiler: UIButton_LateUpdate_Transpiler + // Target: UIButton.LateUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIButton), nameof(UIButton.LateUpdate))] private static IEnumerable UIButton_LateUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -65,6 +71,9 @@ public class PersistPatch : PatchImpl } // Sort blueprint data when pasting + // Harmony transpiler: BuildTool_BlueprintCopy_UseToPasteNow_Transpiler + // Target: BuildTool_BlueprintCopy.UseToPasteNow + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(BuildTool_BlueprintCopy), nameof(BuildTool_BlueprintCopy.UseToPasteNow))] private static IEnumerable BuildTool_BlueprintCopy_UseToPasteNow_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -91,6 +100,9 @@ public class PersistPatch : PatchImpl } // Increase maximum value of property realizing, 2000 -> 20000 + // Harmony transpiler: UIProductEntry_UpdateUIElements_Transpiler + // Target: UIPropertyEntry.UpdateUIElements, UIPropertyEntry.OnRealizeButtonClick, UIPropertyEntry.OnInputValueEnd + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIPropertyEntry), nameof(UIPropertyEntry.UpdateUIElements))] [HarmonyPatch(typeof(UIPropertyEntry), nameof(UIPropertyEntry.OnRealizeButtonClick))] @@ -104,7 +116,9 @@ public class PersistPatch : PatchImpl matcher.Repeat(m => { m.SetAndAdvance(OpCodes.Ldc_I4, 20000); }); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: UIProductEntry_OnInputValueEnd_Transpiler + // Target: UIPropertyEntry.OnInputValueEnd + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIPropertyEntry), nameof(UIPropertyEntry.OnInputValueEnd))] private static IEnumerable UIProductEntry_OnInputValueEnd_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -118,6 +132,9 @@ public class PersistPatch : PatchImpl } // Increase capacity of player order queue, 16 -> 128 + // Harmony transpiler: PlayerOrder_Constructor_Transpiler + // Target: PlayerOrder..ctor + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(PlayerOrder), MethodType.Constructor, typeof(Player))] private static IEnumerable PlayerOrder_Constructor_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -131,6 +148,9 @@ public class PersistPatch : PatchImpl } // Increase Player Command Queue from 16 to 128 + // Harmony transpiler: PlayerOrder_ExtendCount_Transpiler + // Target: PlayerOrder._trimEnd, PlayerOrder.Enqueue + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(PlayerOrder), nameof(PlayerOrder._trimEnd))] [HarmonyPatch(typeof(PlayerOrder), nameof(PlayerOrder.Enqueue))] @@ -145,6 +165,9 @@ public class PersistPatch : PatchImpl } // Allow F11 in star map + // Harmony transpiler: UIGame__OnLateUpdate_Transpiler + // Target: UIGame._OnLateUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIGame), nameof(UIGame._OnLateUpdate))] private static IEnumerable UIGame__OnLateUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -173,6 +196,9 @@ public class PersistPatch : PatchImpl } // Fix crash in NeutronStarHandler.OnEnable() + // Harmony transpiler: NeutronStarHandler_OnEnable_Transpiler + // Target: NeutronStarHandler.OnEnable + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(NeutronStarHandler), nameof(NeutronStarHandler.OnEnable))] private static IEnumerable NeutronStarHandler_OnEnable_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -193,6 +219,9 @@ public class PersistPatch : PatchImpl } // Disable rendering when Player is hidden (Press F11 twice) + // Harmony transpiler: GameLogic_LateUpdate_Transpiler + // Target: GameLogic.LateUpdate, GameLogic.Draw, GameLogic.DrawPost + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(GameLogic), nameof(GameLogic.LateUpdate))] [HarmonyPatch(typeof(GameLogic), nameof(GameLogic.Draw))] @@ -273,7 +302,9 @@ public class PersistPatch : PatchImpl rcode = -1; Functions.UIFunctions.AddClusterUploadResult(rcode, __instance.uploadRequest == null ? 0f : (float)__instance.uploadRequest.reqTime); } - + // Harmony transpiler: MilkyWayCache_LoadTopTenPlayerData_Transpiler + // Target: MilkyWayCache.LoadTopTenPlayerData + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(MilkyWayCache), nameof(MilkyWayCache.LoadTopTenPlayerData))] private static IEnumerable MilkyWayCache_LoadTopTenPlayerData_Transpiler(IEnumerable instructions, ILGenerator generator) diff --git a/UXAssist/Patches/PlanetPatch.cs b/UXAssist/Patches/PlanetPatch.cs index 45df5bd..622104b 100644 --- a/UXAssist/Patches/PlanetPatch.cs +++ b/UXAssist/Patches/PlanetPatch.cs @@ -27,6 +27,9 @@ public static class PlanetPatch public class PlayerActionsInGlobeView : PatchImpl { + // Harmony transpiler: VFInput_UpdateGameStates_Transpiler + // Target: VFInput.UpdateGameStates + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(VFInput), nameof(VFInput.UpdateGameStates))] private static IEnumerable VFInput_UpdateGameStates_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -49,7 +52,9 @@ public static class PlanetPatch }); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: PlayerController_GetInput_Transpiler + // Target: PlayerController.GetInput + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(PlayerController), nameof(PlayerController.GetInput))] private static IEnumerable PlayerController_GetInput_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -62,7 +67,9 @@ public static class PlanetPatch ).Advance(1).Opcode = OpCodes.Ldc_I4_4; return matcher.InstructionEnumeration(); } - + // Harmony transpiler: PlayerAction_Rts_GameTick_Transpiler + // Target: PlayerAction_Rts.GameTick + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(PlayerAction_Rts), nameof(PlayerAction_Rts.GameTick))] private static IEnumerable PlayerAction_Rts_GameTick_Transpiler(IEnumerable instructions, ILGenerator generator) diff --git a/UXAssist/Patches/PlayerPatch.cs b/UXAssist/Patches/PlayerPatch.cs index f3b8940..bec48f3 100644 --- a/UXAssist/Patches/PlayerPatch.cs +++ b/UXAssist/Patches/PlayerPatch.cs @@ -80,8 +80,9 @@ public class PlayerPatch : PatchImpl ShortcutKeysForStarsName.Enable(false); AutoNavigation.Enable(false); } - - + // Harmony transpiler: UIStarmapStar__OnLateUpdate_Transpiler + // Target: UIStarmapStar._OnLateUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIStarmapStar), nameof(UIStarmapStar._OnLateUpdate))] private static IEnumerable UIStarmapStar__OnLateUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -126,6 +127,9 @@ public class PlayerPatch : PatchImpl private class EnhancedMechaForgeCountControl : PatchImpl { + // Harmony transpiler: UIReplicatorWindow_OnOkButtonClick_Transpiler + // Target: UIReplicatorWindow.OnOkButtonClick + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIReplicatorWindow), nameof(UIReplicatorWindow.OnOkButtonClick))] private static IEnumerable UIReplicatorWindow_OnOkButtonClick_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -137,7 +141,9 @@ public class PlayerPatch : PatchImpl matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldc_I4, 1000)); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: UIReplicatorWindow_OnPlusButtonClick_Transpiler + // Target: UIReplicatorWindow.OnPlusButtonClick, UIReplicatorWindow.OnMinusButtonClick + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIReplicatorWindow), nameof(UIReplicatorWindow.OnPlusButtonClick))] [HarmonyPatch(typeof(UIReplicatorWindow), nameof(UIReplicatorWindow.OnMinusButtonClick))] @@ -177,6 +183,9 @@ public class PlayerPatch : PatchImpl private class HideTipsForSandsChanges : PatchImpl { + // Harmony transpiler: Player_SetSandCount_Transpiler + // Target: Player.SetSandCount + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(Player), nameof(Player.SetSandCount))] private static IEnumerable Player_SetSandCount_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -223,6 +232,9 @@ public class PlayerPatch : PatchImpl ShowAllStarsNameStatus = 0; } /* + // Harmony transpiler: UIStarmapPlanet__OnLateUpdate_Transpiler + // Target: UIStarmapPlanet._OnLateUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIStarmapPlanet), nameof(UIStarmapPlanet._OnLateUpdate))] private static IEnumerable UIStarmapPlanet__OnLateUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -270,7 +282,9 @@ public class PlayerPatch : PatchImpl ); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: UIStarmapDFHive__OnLateUpdate_Transpiler + // Target: UIStarmapDFHive._OnLateUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIStarmapDFHive), nameof(UIStarmapDFHive._OnLateUpdate))] private static IEnumerable UIStarmapDFHive__OnLateUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -360,7 +374,9 @@ public class PlayerPatch : PatchImpl } return movementStateChanged; } - + // Harmony transpiler: PlayerController_GameTick_Transpiler + // Target: PlayerController.GameTick + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(PlayerController), nameof(PlayerController.GameTick))] private static IEnumerable PlayerController_GameTick_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -559,7 +575,9 @@ public class PlayerPatch : PatchImpl ); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: VFInput_sailSpeedUp_Transpiler + // Target: VFInput._sailSpeedUp (getter) + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(VFInput), nameof(VFInput._sailSpeedUp), MethodType.Getter)] private static IEnumerable VFInput_sailSpeedUp_Transpiler(IEnumerable instructions, ILGenerator generator) diff --git a/UXAssist/Patches/TechPatch.cs b/UXAssist/Patches/TechPatch.cs index 6c138bd..1c4e479 100644 --- a/UXAssist/Patches/TechPatch.cs +++ b/UXAssist/Patches/TechPatch.cs @@ -263,6 +263,9 @@ public static class TechPatch private class BatchBuyoutTech : PatchImpl { + // Harmony transpiler: UITechNode_UpdateInfoDynamic_Transpiler + // Target: UITechNode.UpdateInfoDynamic + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UITechNode), nameof(UITechNode.UpdateInfoDynamic))] private static IEnumerable UITechNode_UpdateInfoDynamic_Transpiler(IEnumerable instructions) diff --git a/UniverseGenTweaks/Patches/GalaxyGenSettingsPatch.cs b/UniverseGenTweaks/Patches/GalaxyGenSettingsPatch.cs index d33177e..4f4d5fb 100644 --- a/UniverseGenTweaks/Patches/GalaxyGenSettingsPatch.cs +++ b/UniverseGenTweaks/Patches/GalaxyGenSettingsPatch.cs @@ -57,6 +57,9 @@ public static class GalaxyGenSettingsPatch private static class Patch { + // Harmony transpiler: UIGalaxySelect_OnStarCountSliderValueChange_Transpiler + // Target: UIGalaxySelect.OnStarCountSliderValueChange + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIGalaxySelect), nameof(UIGalaxySelect.OnStarCountSliderValueChange))] private static IEnumerable UIGalaxySelect_OnStarCountSliderValueChange_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -94,7 +97,9 @@ public static class GalaxyGenSettingsPatch GameFlatten = UniverseGenConstants.DefaultFlatten; } } - + // Harmony transpiler: GalaxyData_Constructor_Transpiler + // Target: GalaxyData..ctor + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(GalaxyData), MethodType.Constructor)] private static IEnumerable GalaxyData_Constructor_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -107,7 +112,9 @@ public static class GalaxyGenSettingsPatch matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldc_I4, UniverseGenConstants.ExpandedGalaxyCapacity)); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: SectorModel_CreateGalaxyAstroBuffer_Transpiler + // Target: SectorModel.CreateGalaxyAstroBuffer, SpaceColliderLogic.UpdateCollidersPose + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(SectorModel), nameof(SectorModel.CreateGalaxyAstroBuffer))] [HarmonyPatch(typeof(SpaceColliderLogic), nameof(SpaceColliderLogic.UpdateCollidersPose))] @@ -121,7 +128,9 @@ public static class GalaxyGenSettingsPatch matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldc_I4, UniverseGenConstants.ExpandedSectorCapacity)); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: UniverseGen_CreateGalaxy_Transpiler + // Target: UniverseGen.CreateGalaxy + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UniverseGen), nameof(UniverseGen.CreateGalaxy))] private static IEnumerable UniverseGen_CreateGalaxy_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -152,6 +161,9 @@ public static class GalaxyGenSettingsPatch /* Patch `rand() * (maxStepLen - minStepLen) + minDist` to `rand() * (maxStepLen - minStepLen) + minStepLen`, this should be a bugged line in original game code. */ + // Harmony transpiler: UniverseGen_RandomPoses_Transpiler + // Target: UniverseGen.RandomPoses + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UniverseGen), nameof(UniverseGen.RandomPoses))] private static IEnumerable UniverseGen_RandomPoses_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -164,7 +176,9 @@ public static class GalaxyGenSettingsPatch matcher.Repeat(m => m.Advance(1).SetInstructionAndAdvance(new CodeInstruction(OpCodes.Ldarg_3))); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: UIVirtualStarmap__OnLateUpdate_Transpiler + // Target: UIVirtualStarmap._OnLateUpdate + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIVirtualStarmap), nameof(UIVirtualStarmap._OnLateUpdate))] private static IEnumerable UIVirtualStarmap__OnLateUpdate_Transpiler(IEnumerable instructions, ILGenerator generator) @@ -193,7 +207,9 @@ public static class GalaxyGenSettingsPatch ); return matcher.InstructionEnumeration(); } - + // Harmony transpiler: UIGalaxySelect_UpdateUIDisplay_Transpiler + // Target: UIGalaxySelect.UpdateUIDisplay + // Fallback: None — patch will fail loudly if the target method body changes. [HarmonyTranspiler] [HarmonyPatch(typeof(UIGalaxySelect), nameof(UIGalaxySelect.UpdateUIDisplay))] private static IEnumerable UIGalaxySelect_UpdateUIDisplay_Transpiler(IEnumerable instructions, ILGenerator generator) diff --git a/docs/superpowers/plans/2026-06-23-phase5-transpiler-robustness.md b/docs/superpowers/plans/2026-06-23-phase5-transpiler-robustness.md new file mode 100644 index 0000000..34ff700 --- /dev/null +++ b/docs/superpowers/plans/2026-06-23-phase5-transpiler-robustness.md @@ -0,0 +1,334 @@ +# Phase 5 — Transpiler Robustness & Code Quality Gates + +> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make Harmony transpilers easier to diagnose on game updates, consolidate mod-compatibility reflection behind a single helper API, and add build-level quality gates. + +**Architecture:** +- A small `TranspilerGuard` helper standardizes the `CodeMatcher.IsInvalid` fallback pattern so transpilers can return original instructions and log a warning instead of producing invalid IL. +- A public `ModCompatHelper` in `UXAssist.Common.ModCompat` centralizes BepInEx plugin detection, external-type resolution, and member lookup; wrappers and DysonSphere reflection consumers migrate to it. +- A root `.editorconfig`, stricter MSBuild warning settings, and a GitHub Actions build workflow provide guardrails without blocking the existing obsolete-warning surface. + +**Tech Stack:** C# / .NET Framework 4.7.2 / BepInEx 5 / HarmonyLib / MSBuild / GitHub Actions + +--- + +## Task 1: Transpiler comments & fallback guards + +**Files:** +- Create: `UXAssist/Common/Patching/TranspilerGuard.cs` +- Modify: all files listed in `docs/TranspilerAudit.md` (26 files, 115 transpilers) + +- [ ] **Step 1.1: Create `TranspilerGuard`** + +```csharp +using System.Collections.Generic; +using System.Reflection.Emit; +using BepInEx.Logging; +using HarmonyLib; + +namespace UXAssist.Common.Patching; + +public static class TranspilerGuard +{ + public static IEnumerable Finish( + this CodeMatcher matcher, + IEnumerable originalInstructions, + ManualLogSource logger, + string transpilerName) + { + if (matcher.IsInvalid) + { + logger?.LogWarning($"Transpiler '{transpilerName}' failed to match; returning original instructions."); + return originalInstructions; + } + return matcher.InstructionEnumeration(); + } +} +``` + +- [ ] **Step 1.2: Add standardized header comments to every transpiler** + +For each transpiler method, insert a comment block immediately above the method: + +```csharp +// Harmony transpiler target: .() +// Purpose: +// Fragile matches: +// Fallback: +``` + +Use the audit in `docs/TranspilerAudit.md` (produced by exploration) to fill Fragile matches. + +- [ ] **Step 1.3: Add fallback guards where missing** + +For transpilers that currently call `matcher.InstructionEnumeration()` without checking `IsInvalid`, change the last lines to: + +```csharp +return matcher.Finish(instructions, UXAssist.Logger, nameof()); +``` + +Preserve the return type `IEnumerable`. Where a method already checks `matcher.IsInvalid`/`IsValid`, keep the existing logic and only add the comment. + +- [ ] **Step 1.4: Build after comment/guard pass** + +Run: `dotnet build DSP_Mods.sln -c Release` +Expected: 0 errors, 0 new warnings. + +--- + +## Task 2: Centralize mod-compatibility reflection + +**Files:** +- Create: `UXAssist/Common/ModCompat/ModCompatHelper.cs` +- Create: `UXAssist/Common/Utils/DysonSphereReflection.cs` +- Modify: `UXAssist/ModsCompat/AuxilaryfunctionWrapper.cs` +- Modify: `UXAssist/ModsCompat/BlueprintTweaks.cs` +- Modify: `UXAssist/ModsCompat/BulletTimeWrapper.cs` +- Modify: `UXAssist/ModsCompat/CommonAPIWrapper.cs` +- Modify: `UXAssist/ModsCompat/PlanetVeinUtilization.cs` +- Modify: `UXAssist/Patches/DysonSpherePatch.cs` +- Modify: `CheatEnabler/Functions/DysonSphere/ShellCompletionFunctions.cs` +- Modify: `CheatEnabler/Functions/DysonSphere/FrameRemovalFunctions.cs` + +- [ ] **Step 2.1: Create `ModCompatHelper`** + +```csharp +using System; +using System.Reflection; +using BepInEx; +using BepInEx.Bootstrap; + +namespace UXAssist.Common.ModCompat; + +public static class ModCompatHelper +{ + public static bool TryGetLoadedPluginInfo(string guid, out PluginInfo pluginInfo) + => Chainloader.PluginInfos.TryGetValue(guid, out pluginInfo) && pluginInfo != null; + + public static bool TryGetPluginType(PluginInfo pluginInfo, string typeName, out Type type) + { + type = null; + if (pluginInfo?.Instance == null) return false; + try + { + type = pluginInfo.Instance.GetType().Assembly.GetType(typeName, throwOnError: false); + } + catch { /* ignored */ } + return type != null; + } + + public static bool TryGetPluginType(string guid, string typeName, out Type type) + { + type = null; + return TryGetLoadedPluginInfo(guid, out var pluginInfo) && TryGetPluginType(pluginInfo, typeName, out type); + } + + public static bool TryGetFieldValue(Type type, string fieldName, object instance, out T value) + { + value = default; + if (type == null) return false; + var field = AccessTools.Field(type, fieldName); + if (field == null) return false; + try + { + var result = field.GetValue(instance); + if (result is T t) + { + value = t; + return true; + } + } + catch { /* ignored */ } + return false; + } + + public static bool TryGetMethod(Type type, string methodName, out MethodInfo method) + { + method = null; + if (type == null) return false; + method = AccessTools.Method(type, methodName); + return method != null; + } + + public static bool TryGetPropertySetter(Type type, string propertyName, out MethodInfo setter) + { + setter = null; + if (type == null) return false; + var property = AccessTools.Property(type, propertyName); + if (property == null) return false; + setter = property.GetSetMethod(nonPublic: true); + return setter != null; + } +} +``` + +- [ ] **Step 2.2: Refactor mod-compat wrappers to use `ModCompatHelper`** + +Replace the duplicated `Chainloader.PluginInfos.TryGetValue` + `pluginInfo.Instance.GetType().Assembly.GetType(...)` + `AccessTools.Field/Method/PropertySetter` patterns with calls to `ModCompatHelper`. Keep the existing public static fields (e.g., `HasBulletTime`, `ShowStationInfo`) and init timing (`Start`/`Run`). + +- [ ] **Step 2.3: Create `DysonSphereReflection`** + +```csharp +using System.Reflection; +using HarmonyLib; + +namespace UXAssist.Common.Utils; + +public static class DysonSphereReflection +{ + private static readonly FieldInfo TotalNodeSpField = AccessTools.Field(typeof(DysonSphereLayer), "totalNodeSP"); + private static readonly FieldInfo TotalFrameSpField = AccessTools.Field(typeof(DysonSphereLayer), "totalFrameSP"); + private static readonly FieldInfo TotalCpField = AccessTools.Field(typeof(DysonSphereLayer), "totalCP"); + + public static long? GetTotalNodeSP(DysonSphereLayer layer) + => layer != null && TotalNodeSpField != null ? (long?)TotalNodeSpField.GetValue(layer) : null; + + public static long? GetTotalFrameSP(DysonSphereLayer layer) + => layer != null && TotalFrameSpField != null ? (long?)TotalFrameSpField.GetValue(layer) : null; + + public static long? GetTotalCP(DysonSphereLayer layer) + => layer != null && TotalCpField != null ? (long?)TotalCpField.GetValue(layer) : null; + + public static bool IsAvailable => TotalNodeSpField != null && TotalFrameSpField != null && TotalCpField != null; +} +``` + +- [ ] **Step 2.4: Migrate DysonSphere field consumers** + +Update `UXAssist/Patches/DysonSpherePatch.cs`, `CheatEnabler/Functions/DysonSphere/ShellCompletionFunctions.cs`, and `CheatEnabler/Functions/DysonSphere/FrameRemovalFunctions.cs` to call `DysonSphereReflection` instead of resolving the fields locally. Remove duplicate `AccessTools.Field` declarations. + +- [ ] **Step 2.5: Build after reflection refactor** + +Run: `dotnet build DSP_Mods.sln -c Release` +Expected: 0 errors, 0 new warnings. + +--- + +## Task 3: Code quality gates + +**Files:** +- Create: `.editorconfig` +- Modify: `Directory.Build.props` +- Create: `.github/workflows/build.yml` + +- [ ] **Step 3.1: Add root `.editorconfig`** + +```ini +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = crlf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{cs,vb}] +# Suggestion-only so existing code does not break the build +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion +dotnet_style_readonly_field = true:suggestion +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = true:suggestion +csharp_style_expression_bodied_methods = when_on_single_line:suggestion +csharp_style_expression_bodied_properties = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +``` + +- [ ] **Step 3.2: Harden `Directory.Build.props`** + +Add to the existing `` (do not remove existing properties): + +```xml +true +0618 +disable +none +false +``` + +`NoWarn>0618` suppresses the expected `[Obsolete]` usage warnings from the old `Util` facade; any new warning class will fail the build. + +- [ ] **Step 3.3: Add GitHub Actions build workflow** + +Create `.github/workflows/build.yml`: + +```yaml +name: Build + +on: + push: + branches: [main, master, refactor/*] + pull_request: + branches: [main, master] + +jobs: + build: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Restore + run: dotnet restore DSP_Mods.sln + + - name: Build Release + run: dotnet build DSP_Mods.sln -c Release --no-restore + + - name: Package mods + run: dotnet build -t:ZipMod -c Release --no-restore +``` + +- [ ] **Step 3.4: Verify quality gates** + +Run: +```bash +dotnet clean DSP_Mods.sln +dotnet build DSP_Mods.sln -c Release +``` +Expected: 0 errors, 0 warnings. + +Run: +```bash +dotnet build -t:ZipMod -c Release +``` +Expected: All zips produced; no errors. + +--- + +## Task 4: Documentation & checkpoint + +**Files:** +- Modify: `AGENTS.md` + +- [ ] **Step 4.1: Update `AGENTS.md`** + +Append a "Phase 5 — Transpiler Robustness & Code Quality Gates" subsection under the project overview describing: +- `UXAssist.Common.Patching.TranspilerGuard` +- `UXAssist.Common.ModCompat.ModCompatHelper` +- `UXAssist.Common.Utils.DysonSphereReflection` +- `.editorconfig`, `TreatWarningsAsErrors`, and the GitHub Actions build workflow. + +- [ ] **Step 4.2: Tag checkpoint** + +```bash +git add -A +git commit -m "refactor: phase 5 transpiler robustness, mod-compat helpers, and build quality gates" +git tag refactor-phase5 +``` + +Expected: tag `refactor-phase5` exists on the new commit and build remains clean. diff --git a/docs/superpowers/plans/2026-06-23-refactor-uxassist-cheate-universe.md b/docs/superpowers/plans/2026-06-23-refactor-uxassist-cheate-universe.md new file mode 100644 index 0000000..f24a9c6 --- /dev/null +++ b/docs/superpowers/plans/2026-06-23-refactor-uxassist-cheate-universe.md @@ -0,0 +1,1367 @@ +# UXAssist / CheatEnabler / UniverseGenTweaks Refactor Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Improve readability and maintainability of `UXAssist`, `CheatEnabler`, and `UniverseGenTweaks` by splitting oversized files, centralizing constants/localization, cleaning static mutable state, and hardening transpilers. Only `UXAssist.UI` and `UXAssist.Common` public APIs are treated as a stable contract; everything else may be refactored freely, and downstream projects are updated to consume the new structure. + +**Architecture:** Keep the existing BepInEx + Harmony stack. Introduce a reusable, public mod-feature lifecycle abstraction in `UXAssist.Common.ModFeatures` that replaces the current namespace-based reflection on static `Functions`/`Patches` classes. Each static feature class declares `[ModFeature]` and exposes `Init`/`Start`/`Uninit`/`OnInputUpdate`/`OnUpdate` as needed; `ModFeatureRegistry.Discover` finds and registers them, then drives their lifecycle. `CheatEnabler` and `UniverseGenTweaks` adopt the same abstraction, so they benefit from the same architecture rather than using `InternalsVisibleTo`. + +Split monolithic `Patches/*.cs` and `Functions/*.cs` files into focused classes grouped by subsystem (e.g., `UXAssist.Patches.Factory.*`). Refactor the internals of `UXAssist.UI` and `UXAssist.Common` into smaller helpers while keeping the existing public surface intact (old members become thin forwarding facades or are marked `[Obsolete]`). Introduce `ConfigProvider` helpers and `GameConstants` classes to decouple UI from patch internals. Clean static state through explicit `ResetState` callbacks registered on `GameLogic.OnGameEnd`. Add version/fallback annotations to transpilers to survive game updates. + +**Tech Stack:** C# (`net472`), BepInEx 5.x, HarmonyLib, SDK-style MSBuild, PowerShell `Compress-Archive`. + +--- + +## Phase 1 — Structural Split + Public Lifecycle Abstraction + +### Task 1: Document the public API surface that must stay stable + +**Files:** +- Create: `docs/PublicApiSurface.md` + +- [ ] **Step 1: Inventory `UXAssist.UI` and `UXAssist.Common` public members** + + Read every file under `UXAssist/UI/` and `UXAssist/Common/` and list every `public` class/struct/enum/delegate/method/property/event/field that is reachable from `CheatEnabler` or `UniverseGenTweaks`. + + At minimum, the list must include: + - `UXAssist.Common.I18N` (`Add`, `Apply`, `Translate`, `Init`, `OnInitialized`) + - `UXAssist.Common.GameLogic` (`Enable`, `OnDataLoaded`, `OnGameBegin`, `OnGameEnd`, `OnFactoryFrameBegin`) + - `UXAssist.Common.PatchImpl` and `PatchGuidAttribute` + - `UXAssist.Common.Util` (`GetTypesFiltered`, `GetTypesInNamespace`, `LoadEmbeddedResource`, `LoadEmbeddedTexture`, `LoadEmbeddedSprite`, `PluginFolder`) + - `UXAssist.UI.MyConfigWindow` (`OnUICreated`, `OnUpdateUI`, `CreateInstance`, `DestroyInstance`) + - `UXAssist.UI.MyWindow` (`InitBaseObject`, `Create`, `AddText`, `AddText2`, `AddButton`, `AddTipsButton`, `AddTipsButton2`, `Open`, `Close`, `TryClose`, `AutoFitWindowSize`) + - `UXAssist.UI.MyWindowWithTabs` (`AddTabGroup`, `AddTab`) + - `UXAssist.UI.MyCheckBox.CreateCheckBox` + - `UXAssist.UI.MySlider.CreateSlider` + - `UXAssist.UI.MyWindowManager` (`InitBaseObjects`, `Enable`) + +- [ ] **Step 2: Commit the inventory** + + ```bash + git add docs/PublicApiSurface.md + git commit -m "docs: inventory UXAssist.UI/Common public API surface" + ``` + +--- + +### Task 2: Introduce public `ModFeature` lifecycle abstraction + +**Files:** +- Create: `UXAssist/Common/ModFeatures/ModFeatureAttribute.cs` +- Create: `UXAssist/Common/ModFeatures/IModFeature.cs` +- Create: `UXAssist/Common/ModFeatures/ModFeatureRegistry.cs` +- Modify: `UXAssist/Common/Util.cs` + +- [ ] **Step 1: Add namespace-prefix reflection helper** + + In `UXAssist/Common/Util.cs`: + ```csharp + public static Type[] GetTypesInNamespacePrefix(Assembly assembly, string prefix) + { + return GetTypesFiltered(assembly, t => t.Namespace != null && t.Namespace.StartsWith(prefix, StringComparison.Ordinal)); + } + ``` + +- [ ] **Step 2: Define the attribute** + + ```csharp + namespace UXAssist.Common.ModFeatures; + + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public sealed class ModFeatureAttribute : Attribute + { + public string Name { get; } + public int Order { get; set; } + + public ModFeatureAttribute(string name = null) + { + Name = name; + } + } + ``` + +- [ ] **Step 3: Define the optional instance interface** + + ```csharp + namespace UXAssist.Common.ModFeatures; + + public interface IModFeature + { + void Init(); + void Start(); + void Uninit(); + void OnInputUpdate(); + void OnUpdate(); + } + ``` + +- [ ] **Step 4: Implement the registry** + + ```csharp + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + namespace UXAssist.Common.ModFeatures; + + public static class ModFeatureRegistry + { + private static readonly List _staticFeatures = []; + private static readonly List _instanceFeatures = []; + private static readonly HashSet _discoveredAssemblies = []; + + public static void Discover(Assembly assembly) + { + if (!_discoveredAssemblies.Add(assembly)) return; + + var staticTypes = Util.GetTypesFiltered(assembly, t => + t.IsClass && t.IsAbstract && t.IsSealed && + Attribute.IsDefined(t, typeof(ModFeatureAttribute))); + + foreach (var type in staticTypes.OrderBy(GetOrder)) + { + if (!_staticFeatures.Contains(type)) + _staticFeatures.Add(type); + } + } + + public static void Register() where T : class, IModFeature, new() + { + var instance = new T(); + _instanceFeatures.Add(instance); + } + + public static void InitAll() + { + ForEachStatic("Init"); + foreach (var f in _instanceFeatures) f.Init(); + } + + public static void StartAll() + { + ForEachStatic("Start"); + foreach (var f in _instanceFeatures) f.Start(); + } + + public static void UninitAll() + { + ForEachStatic("Uninit"); + foreach (var f in _instanceFeatures) f.Uninit(); + } + + public static void OnInputUpdateAll() + { + ForEachStatic("OnInputUpdate"); + foreach (var f in _instanceFeatures) f.OnInputUpdate(); + } + + public static void OnUpdateAll() + { + ForEachStatic("OnUpdate"); + foreach (var f in _instanceFeatures) f.OnUpdate(); + } + + private static void ForEachStatic(string methodName) + { + foreach (var type in _staticFeatures) + { + var method = type.GetMethod(methodName, + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + method?.Invoke(null, null); + } + } + + private static int GetOrder(Type type) + { + return type.GetCustomAttribute()?.Order ?? 0; + } + } + ``` + +- [ ] **Step 5: Build UXAssist** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + ``` + Expected: 0 errors. + +- [ ] **Step 6: Commit** + + ```bash + git add UXAssist/Common/ModFeatures/ UXAssist/Common/Util.cs + git commit -m "feat(UXAssist): public ModFeature lifecycle registry" + ``` + +--- + +### Task 3: Split `UXAssist/Patches/FactoryPatch.cs` and mark features + +**Files:** +- Create: `UXAssist/Patches/Factory/FactoryPatch.cs` (config coordinator) +- Create: `UXAssist/Patches/Factory/ImmediateBuildPatch.cs` +- Create: `UXAssist/Patches/Factory/ArchitectModePatch.cs` +- Create: `UXAssist/Patches/Factory/BuildToolPatch.cs` +- Create: `UXAssist/Patches/Factory/BeltSignalPatch.cs` +- Create: `UXAssist/Patches/Factory/BuildingBufferPatch.cs` +- Create: `UXAssist/Patches/Factory/VeinProtectionPatch.cs` +- Create: `UXAssist/Patches/Factory/PowerGenerationPatch.cs` +- Create: `UXAssist/Patches/Factory/RenderingPatch.cs` +- Delete: `UXAssist/Patches/FactoryPatch.cs` + +- [ ] **Step 1: Move nested patch classes to new files** + + Use `git mv` semantics: copy the contents of each nested `PatchImpl` class from the old file into a new file under `UXAssist/Patches/Factory/`. Keep each class `internal` unless it must be public. Use namespace `UXAssist.Patches.Factory`. + +- [ ] **Step 2: Create the coordinator `FactoryPatch`** + + Mark it as a mod feature and expose the public `ConfigEntry` fields: + ```csharp + using UXAssist.Common.ModFeatures; + + namespace UXAssist.Patches.Factory; + + [ModFeature("Factory", Order = 10)] + public static class FactoryPatch + { + public static ConfigEntry UnlimitInteractiveEnabled { get; internal set; } + // ... all other public ConfigEntry fields from the original file + + public static void Init() + { + ImmediateBuildPatch.Init(); + ArchitectModePatch.Init(); + // ... + } + + public static void Start() + { + ImmediateBuildPatch.Start(); + // ... + } + + public static void Uninit() + { + ImmediateBuildPatch.Uninit(); + // ... + } + + public static void OnInputUpdate() => BeltSignalPatch.OnInputUpdate(); + public static void Export(BinaryWriter w) => BeltSignalPatch.Export(w); + public static void Import(BinaryReader r) => BeltSignalPatch.Import(r); + } + ``` + + `Awake()` in `UXAssist.cs` already assigns every `ConfigEntry`; leave those assignments untouched. + +- [ ] **Step 3: Build UXAssist** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + ``` + +- [ ] **Step 4: Commit** + + ```bash + git add UXAssist/Patches/Factory/ + git rm UXAssist/Patches/FactoryPatch.cs + git commit -m "refactor(UXAssist): split FactoryPatch into focused classes" + ``` + +--- + +### Task 4: Split `UXAssist/Patches/LogisticsPatch.cs` and mark features + +**Files:** +- Create: `UXAssist/Patches/Logistics/LogisticsPatch.cs` +- Create: `UXAssist/Patches/Logistics/AutoConfigPatch.cs` +- Create: `UXAssist/Patches/Logistics/CapacityPatch.cs` +- Create: `UXAssist/Patches/Logistics/OverflowPatch.cs` +- Create: `UXAssist/Patches/Logistics/RealtimeInfoPanelPatch.cs` +- Delete: `UXAssist/Patches/LogisticsPatch.cs` + +- [ ] **Step 1: Move nested classes and create coordinator** + + Same pattern as Task 3. Coordinator is marked `[ModFeature("Logistics", Order = 11)]` and forwards `OnInputUpdate` / `OnUpdate` to sub-classes. + +- [ ] **Step 2: Build UXAssist** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + ``` + +- [ ] **Step 3: Commit** + + ```bash + git add UXAssist/Patches/Logistics/ + git rm UXAssist/Patches/LogisticsPatch.cs + git commit -m "refactor(UXAssist): split LogisticsPatch into focused classes" + ``` + +--- + +### Task 5: Split `UXAssist/Functions/UIFunctions.cs` and mark features + +**Files:** +- Create: `UXAssist/Functions/UI/StarmapFilterUI.cs` +- Create: `UXAssist/Functions/UI/MilkyWayUI.cs` +- Create: `UXAssist/Functions/UI/AutoCruiseUI.cs` +- Create: `UXAssist/Functions/UI/MenuButtonUI.cs` +- Modify: `UXAssist/Functions/UIFunctions.cs` (coordinator, marked `[ModFeature("UI", Order = 12)]`) +- Modify: `UXAssist/UXAssist.cs` + +- [ ] **Step 1: Move features to sub-files** + + Namespace `UXAssist.Functions.UI`. Keep `internal` where possible. Keep public `ConfigEntry` fields on `UIFunctions` if bound in `UXAssist.Awake()`. + +- [ ] **Step 2: Update `UXAssist.Awake()` export/import calls** + + Replace `UIFunctions.ExportClusterUploadResults(w)` with a coordinator call or direct `MilkyWayUI.Export(w)`. + +- [ ] **Step 3: Build UXAssist** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + ``` + +- [ ] **Step 4: Commit** + + ```bash + git add UXAssist/Functions/UI/ UXAssist/Functions/UIFunctions.cs UXAssist/UXAssist.cs + git commit -m "refactor(UXAssist): split UIFunctions into UI sub-features" + ``` + +--- + +### Task 6: Update `UXAssist.cs` to drive features through `ModFeatureRegistry` + +**Files:** +- Modify: `UXAssist/UXAssist.cs` + +- [ ] **Step 1: Discover features from UXAssist assembly** + + In `Awake()`, after config binding: + ```csharp + ModFeatureRegistry.Discover(Assembly.GetExecutingAssembly()); + ModFeatureRegistry.InitAll(); + ``` + +- [ ] **Step 2: Replace lifecycle calls** + + In `Start()`: + ```csharp + ModFeatureRegistry.StartAll(); + ``` + + In `OnDestroy()`: + ```csharp + ModFeatureRegistry.UninitAll(); + ``` + +- [ ] **Step 3: Replace hard-coded update calls** + + In `Update()`: + ```csharp + if (VFInput.inputing) return; + if (DSPGame.IsMenuDemo) + { + ModFeatureRegistry.OnInputUpdateAll(); + return; + } + ModFeatureRegistry.OnInputUpdateAll(); + ModFeatureRegistry.OnUpdateAll(); + ``` + + Remove the explicit per-class calls (`LogisticsPatch.OnInputUpdate()`, `UIFunctions.OnInputUpdate()`, `GamePatch.OnInputUpdate()`, `FactoryPatch.OnInputUpdate()`, `PlayerPatch.OnInputUpdate()`, `LogisticsPatch.OnUpdate()`). + +- [ ] **Step 4: Keep compat initialization separate** + + Keep the `ModsCompat` scan in `Awake()` and `Start()`; compat wrappers are not mod features because they receive a `Harmony` argument. + +- [ ] **Step 5: Centralize save participants** + + Add a simple registry in `UXAssist.cs`: + ```csharp + private static readonly List> _exporters = []; + private static readonly List> _importers = []; + + public static void RegisterExporter(Action e) => _exporters.Add(e); + public static void RegisterImporter(Action i) => _importers.Add(i); + ``` + + Update `Export`/`Import` to iterate `_exporters`/`_importers`. Remove direct `FactoryPatch.Export` / `UIFunctions.ExportClusterUploadResults` calls; the coordinators register themselves during `Init`. + +- [ ] **Step 6: Build UXAssist** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + ``` + +- [ ] **Step 7: Commit** + + ```bash + git add UXAssist/UXAssist.cs + git commit -m "refactor(UXAssist): drive lifecycle via ModFeatureRegistry" + ``` + +--- + +### Task 7: Decouple `UXAssist.UIConfigWindow` from patch internals + +**Files:** +- Create: `UXAssist/Common/Config/FactoryConfigProvider.cs` +- Create: `UXAssist/Common/Config/LogisticsConfigProvider.cs` +- Modify: `UXAssist/UIConfigWindow.cs` + +- [ ] **Step 1: Create config providers** + + Each provider exposes only the `ConfigEntry` references that the UI needs: + ```csharp + public static class FactoryConfigProvider + { + public static ConfigEntry NightLightEnabled => Factory.FactoryPatch.NightLightEnabled; + // ... + } + ``` + +- [ ] **Step 2: Update `UIConfigWindow.cs`** + + Replace direct references like `FactoryPatch.NightLightEnabled` with `FactoryConfigProvider.NightLightEnabled`. + +- [ ] **Step 3: Build UXAssist** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + ``` + +- [ ] **Step 4: Commit** + + ```bash + git add UXAssist/Common/Config/ UXAssist/UIConfigWindow.cs + git commit -m "refactor(UXAssist): introduce config providers between UI and patches" + ``` + +--- + +### Task 8: Adopt `ModFeatureRegistry` in `CheatEnabler` + +**Files:** +- Modify: `CheatEnabler/CheatEnabler.cs` + +- [ ] **Step 1: Replace exact-namespace reflection with feature discovery** + + In `Awake()`: + ```csharp + ModFeatureRegistry.Discover(Assembly.GetExecutingAssembly()); + ModFeatureRegistry.InitAll(); + ``` + + In `Start()`: + ```csharp + ModFeatureRegistry.StartAll(); + ``` + + In `OnDestroy()`: + ```csharp + ModFeatureRegistry.UninitAll(); + ``` + + In `Update()`: + ```csharp + if (VFInput.inputing) return; + ModFeatureRegistry.OnInputUpdateAll(); + ``` + + Remove `_patches` and the reflection-based `Init`/`Start`/`Uninit` loop. + +- [ ] **Step 2: Build CheatEnabler** + + ```bash + dotnet build CheatEnabler/CheatEnabler.csproj -c Release + ``` + +- [ ] **Step 3: Commit** + + ```bash + git add CheatEnabler/CheatEnabler.cs + git commit -m "refactor(CheatEnabler): adopt ModFeatureRegistry from UXAssist" + ``` + +--- + +### Task 9: Split `CheatEnabler/Patches/FactoryPatch.cs` + +**Files:** +- Create: `CheatEnabler/Patches/Factory/FactoryPatch.cs` +- Create: `CheatEnabler/Patches/Factory/ImmediateBuildPatch.cs` +- Create: `CheatEnabler/Patches/Factory/ArchitectModePatch.cs` +- Create: `CheatEnabler/Patches/Factory/BeltSignalPatch.cs` +- Create: `CheatEnabler/Patches/Factory/PowerBoostPatch.cs` +- Create: `CheatEnabler/Patches/Factory/LogisticsControlPatch.cs` +- Delete: `CheatEnabler/Patches/FactoryPatch.cs` + +- [ ] **Step 1: Apply the coordinator pattern and `[ModFeature]`** + + Namespace `CheatEnabler.Patches.Factory`. Coordinator exposes the public `ConfigEntry` fields so `CheatEnabler.Awake()` continues to compile without changes. Mark coordinator `[ModFeature("CheatFactory", Order = 10)]`. + +- [ ] **Step 2: Build CheatEnabler** + + ```bash + dotnet build CheatEnabler/CheatEnabler.csproj -c Release + ``` + +- [ ] **Step 3: Commit** + + ```bash + git add CheatEnabler/Patches/Factory/ + git rm CheatEnabler/Patches/FactoryPatch.cs + git commit -m "refactor(CheatEnabler): split FactoryPatch into focused classes" + ``` + +--- + +### Task 10: Split `CheatEnabler/Functions/DysonSphereFunctions.cs` + +**Files:** +- Create: `CheatEnabler/Functions/DysonSphere/DysonSphereResolver.cs` +- Create: `CheatEnabler/Functions/DysonSphere/ShellCompletionFunctions.cs` +- Create: `CheatEnabler/Functions/DysonSphere/FrameRemovalFunctions.cs` +- Create: `CheatEnabler/Functions/DysonSphere/IllegalShellFunctions.cs` +- Create: `CheatEnabler/Functions/DysonSphere/GeometryHelpers.cs` +- Modify: `CheatEnabler/Functions/DysonSphereFunctions.cs` (coordinator, or delete if empty) +- Modify: `CheatEnabler/Patches/DysonSpherePatch.cs` if it calls helpers + +- [ ] **Step 1: Extract repeated "current sphere / star" logic** + + ```csharp + [ModFeature("DysonSphereResolver")] + public static class DysonSphereResolver + { + public static (DysonSphere sphere, StarData star)? ResolveCurrent() + { + var star = GameMain.localStar; + if (star == null) return null; + var sphere = GameMain.data.dysonSpheres[star.index]; + if (sphere == null) return null; + return (sphere, star); + } + } + ``` + +- [ ] **Step 2: Move shell actions to dedicated files** + + Replace duplicated cleanup blocks with a shared helper: + ```csharp + public static void NotifyShellChanged(DysonSphere sphere, DysonSphereLayer layer) + { + layer?.RecalculateModels(); + sphere?.swarm?.RecalculateModels(); + } + ``` + +- [ ] **Step 3: Build CheatEnabler** + + ```bash + dotnet build CheatEnabler/CheatEnabler.csproj -c Release + ``` + +- [ ] **Step 4: Commit** + + ```bash + git add CheatEnabler/Functions/DysonSphere/ + git rm CheatEnabler/Functions/DysonSphereFunctions.cs + git commit -m "refactor(CheatEnabler): split DysonSphereFunctions into focused helpers" + ``` + +--- + +### Task 11: Adopt `ModFeatureRegistry` in `UniverseGenTweaks` + +**Files:** +- Modify: `UniverseGenTweaks/UniverseGenTweaks.cs` + +- [ ] **Step 1: Replace explicit Init/Uninit with feature discovery** + + In `Awake()`: + ```csharp + ModFeatureRegistry.Discover(Assembly.GetExecutingAssembly()); + ModFeatureRegistry.InitAll(); + ``` + + In `OnDestroy()`: + ```csharp + ModFeatureRegistry.UninitAll(); + ``` + + Remove explicit `MoreSettings.Init()`, `EpicDifficulty.Init()`, `BirthPlanetPatch.Init()` and their `Uninit()` counterparts. + +- [ ] **Step 2: Build UniverseGenTweaks** + + ```bash + dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release + ``` + +- [ ] **Step 3: Commit** + + ```bash + git add UniverseGenTweaks/UniverseGenTweaks.cs + git commit -m "refactor(UniverseGenTweaks): adopt ModFeatureRegistry from UXAssist" + ``` + +--- + +### Task 12: Split `UniverseGenTweaks/MoreSettings.cs` + +**Files:** +- Create: `UniverseGenTweaks/Patches/GalaxyGenSettingsPatch.cs` +- Create: `UniverseGenTweaks/Patches/GalaxySelectUIPatch.cs` +- Create: `UniverseGenTweaks/Patches/CombatSettingsPatch.cs` +- Create: `UniverseGenTweaks/Functions/GalaxyGenSave.cs` +- Modify: `UniverseGenTweaks/MoreSettings.cs` (coordinator, or delete) + +- [ ] **Step 1: Move UI construction to `GalaxySelectUIPatch`** + + Mark it `[ModFeature("GalaxySelectUI")]`. Keep slider/text creation helpers there. + +- [ ] **Step 2: Move transpilers to `GalaxyGenSettingsPatch`** + + Mark it `[ModFeature("GalaxyGenSettings")]`. Keep the `Init()`/`Uninit()` pattern and the `Harmony` instance local to this class. + +- [ ] **Step 3: Move combat settings to `CombatSettingsPatch`** + + Mark it `[ModFeature("CombatSettings")]`. Replace the seven near-identical slider-changed prefix methods with a table-driven mapper. + +- [ ] **Step 4: Move save serialization to `GalaxyGenSave`** + + Mark it `[ModFeature("GalaxyGenSave")]`. Implement `Export(BinaryWriter)` / `Import(BinaryReader)` and call them from `UniverseGenTweaks.Export`/`Import`. + +- [ ] **Step 5: Build UniverseGenTweaks** + + ```bash + dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release + ``` + +- [ ] **Step 6: Commit** + + ```bash + git add UniverseGenTweaks/Patches/ UniverseGenTweaks/Functions/ + git rm UniverseGenTweaks/MoreSettings.cs + git commit -m "refactor(UniverseGenTweaks): split MoreSettings into focused classes" + ``` + +--- + +### Task 13: Phase 1 cross-project build verification + +- [ ] **Step 1: Clean and build all three projects** + + ```bash + dotnet clean UXAssist/UXAssist.csproj -c Release + dotnet clean CheatEnabler/CheatEnabler.csproj -c Release + dotnet clean UniverseGenTweaks/UniverseGenTweaks.csproj -c Release + dotnet build UXAssist/UXAssist.csproj -c Release + dotnet build CheatEnabler/CheatEnabler.csproj -c Release + dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release + ``` + + Expected: all three succeed with no compilation errors. + +- [ ] **Step 2: Produce packages** + + ```bash + dotnet build -t:ZipMod -c Release + dotnet build -t:CopyToParentPackage -c Release + ``` + + Expected: `UXAssist/package/`, `CheatEnabler/package/`, `UniverseGenTweaks/package/`, and `Dustbin/package/patchers/` (if building the full solution) are generated. + +- [ ] **Step 3: Public API diff check** + + Use `dotnet build` output or `ildasm` to verify that every member listed in `docs/PublicApiSurface.md` still exists with the same signature. + +- [ ] **Step 4: Commit a Phase 1 checkpoint tag** + + ```bash + git tag refactor-phase1 + ``` + +--- + +## Phase 2 — UI / Common Internal Refactoring (public surface preserved) + +### Task 14: Split `UXAssist/Common/Util.cs` into focused helpers + +**Files:** +- Create: `UXAssist/Common/Util/ReflectionUtil.cs` +- Create: `UXAssist/Common/Util/ResourceUtil.cs` +- Create: `UXAssist/Common/Util/PathUtil.cs` +- Modify: `UXAssist/Common/Util.cs` + +- [ ] **Step 1: Move implementations to focused helpers** + + `ReflectionUtil.cs`: + ```csharp + public static class ReflectionUtil + { + public static Type[] GetTypesFiltered(Assembly assembly, Func predicate) { ... } + public static Type[] GetTypesInNamespace(Assembly assembly, string nameSpace) { ... } + public static Type[] GetTypesInNamespacePrefix(Assembly assembly, string prefix) { ... } + } + ``` + + `ResourceUtil.cs`: + ```csharp + public static class ResourceUtil + { + public static byte[] LoadEmbeddedResource(...) { ... } + public static Texture2D LoadEmbeddedTexture(...) { ... } + public static Sprite LoadEmbeddedSprite(...) { ... } + } + ``` + + `PathUtil.cs`: + ```csharp + public static class PathUtil + { + public static string PluginFolder(Assembly assembly = null) { ... } + } + ``` + +- [ ] **Step 2: Keep `Util` as a public forwarding facade** + + ```csharp + public static class Util + { + [Obsolete("Use ReflectionUtil.GetTypesFiltered")] + public static Type[] GetTypesFiltered(Assembly assembly, Func predicate) + => ReflectionUtil.GetTypesFiltered(assembly, predicate); + + [Obsolete("Use ReflectionUtil.GetTypesInNamespace")] + public static Type[] GetTypesInNamespace(Assembly assembly, string nameSpace) + => ReflectionUtil.GetTypesInNamespace(assembly, nameSpace); + + [Obsolete("Use ResourceUtil.LoadEmbeddedResource")] + public static byte[] LoadEmbeddedResource(string path, Assembly assembly = null) + => ResourceUtil.LoadEmbeddedResource(path, assembly); + + // ... forward all other existing methods + } + ``` + + New code inside UXAssist may call `ReflectionUtil`/`ResourceUtil`/`PathUtil` directly. + +- [ ] **Step 3: Build UXAssist** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + ``` + +- [ ] **Step 4: Commit** + + ```bash + git add UXAssist/Common/Util/ + git commit -m "refactor(UXAssist): split Util into focused helpers with forwarding facade" + ``` + +--- + +### Task 15: Refactor `UXAssist/Common/GameLogic.cs` event invocation + +**Files:** +- Create: `UXAssist/Common/GameEvent.cs` +- Modify: `UXAssist/Common/GameLogic.cs` + +- [ ] **Step 1: Introduce a small safe-event wrapper** + + ```csharp + public static class GameEvent + { + public static void InvokeSafe(this Action action, ManualLogSource logger, string name) + { + if (action == null) return; + foreach (var d in action.GetInvocationList()) + { + try { d.DynamicInvoke(); } + catch (Exception ex) { logger?.LogWarning($"GameEvent '{name}' handler failed: {ex}"); } + } + } + } + ``` + +- [ ] **Step 2: Use the wrapper inside `GameLogic`** + + Keep the public event fields unchanged. Replace manual invocation loops with `OnDataLoaded.InvokeSafe(UXAssist.Logger, nameof(OnDataLoaded));`. + +- [ ] **Step 3: Add XML documentation to all public members** + +- [ ] **Step 4: Build UXAssist** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + ``` + +- [ ] **Step 5: Commit** + + ```bash + git add UXAssist/Common/GameEvent.cs UXAssist/Common/GameLogic.cs + git commit -m "refactor(UXAssist): safe GameEvent wrapper and XML docs" + ``` + +--- + +### Task 16: Refactor `UXAssist/Common/I18N.cs` internals + +**Files:** +- Modify: `UXAssist/Common/I18N.cs` + +- [ ] **Step 1: Preserve public API** + + Keep `Add`, `Apply`, `Translate`, `Init`, `OnInitialized` signatures exactly as they are. + +- [ ] **Step 2: Internal cleanup** + + Split the internal storage into a private `LocalizedString` record and a dictionary keyed by the English key. Add a public convenience overload: + ```csharp + public static void Add(string key, string en, string zh) + ``` + (this is the existing signature; keep it). + +- [ ] **Step 3: Build UXAssist** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + ``` + +- [ ] **Step 4: Commit** + + ```bash + git add UXAssist/Common/I18N.cs + git commit -m "refactor(UXAssist): clean up I18N internals without changing public API" + ``` + +--- + +### Task 17: Extract layout helpers from `UXAssist/UI/MyWindow.cs` + +**Files:** +- Create: `UXAssist/UI/LayoutHelper.cs` +- Modify: `UXAssist/UI/MyWindow.cs` + +- [ ] **Step 1: Move static layout helpers** + + Move `AddText`, `AddTipsButton`, `AddButton` (static overloads), and `AddElement` into `LayoutHelper`. + + ```csharp + public static class LayoutHelper + { + public static Text AddText(float x, float y, RectTransform parent, string label, int fontSize = 14, string objName = "label") { ... } + public static UIButton AddTipsButton(...) { ... } + public static UIButton AddButton(...) { ... } + } + ``` + +- [ ] **Step 2: Keep `MyWindow` instance methods as forwarding facades** + + ```csharp + public Text AddText2(float x, float y, RectTransform parent, string label, int fontSize = 14, string objName = "label") + { + var text = LayoutHelper.AddText(x, y, parent, label, fontSize, objName); + _maxX = Math.Max(_maxX, x + text.rectTransform.sizeDelta.x); + MaxY = Math.Max(MaxY, y + text.rectTransform.sizeDelta.y); + return text; + } + ``` + + Mark the public static helpers on `MyWindow` as `[Obsolete("Use LayoutHelper")]` if desired, but keep them working. + +- [ ] **Step 3: Build UXAssist** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + ``` + +- [ ] **Step 4: Commit** + + ```bash + git add UXAssist/UI/LayoutHelper.cs UXAssist/UI/MyWindow.cs + git commit -m "refactor(UXAssist): extract UI layout helpers from MyWindow" + ``` + +--- + +### Task 18: Refactor `UXAssist/UI/MyConfigWindow.cs` tab management + +**Files:** +- Create: `UXAssist/UI/ConfigTabGroup.cs` +- Modify: `UXAssist/UI/MyConfigWindow.cs` +- Modify: `UXAssist/UI/MyWindowWithTabs.cs` + +- [ ] **Step 1: Extract tab group logic** + + Move the data structure that tracks tab groups into `ConfigTabGroup` so `MyConfigWindow` does not mix tab state with window lifecycle. + +- [ ] **Step 2: Preserve public events and methods** + + Keep `OnUICreated`, `OnUpdateUI`, `CreateInstance`, and `DestroyInstance` on `MyConfigWindow` with identical signatures. + +- [ ] **Step 3: Build UXAssist** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + ``` + +- [ ] **Step 4: Commit** + + ```bash + git add UXAssist/UI/ConfigTabGroup.cs UXAssist/UI/MyConfigWindow.cs UXAssist/UI/MyWindowWithTabs.cs + git commit -m "refactor(UXAssist): split tab management out of MyConfigWindow" + ``` + +--- + +### Task 19: Phase 2 cross-project build verification + +- [ ] **Step 1: Build all three projects** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + dotnet build CheatEnabler/CheatEnabler.csproj -c Release + dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release + ``` + +- [ ] **Step 2: Public API diff check** + + Confirm every member in `docs/PublicApiSurface.md` still exists with the same signature. New `[Obsolete]` facades are acceptable. + +- [ ] **Step 3: Commit a Phase 2 checkpoint tag** + + ```bash + git tag refactor-phase2 + ``` + +--- + +## Phase 3 — Constants & Localization + +### Task 20: Create centralized constants files + +**Files:** +- Create: `UXAssist/Common/GameConstants/ItemIds.cs` +- Create: `UXAssist/Common/GameConstants/TechIds.cs` +- Create: `UXAssist/Common/GameConstants/LogisticsConstants.cs` +- Create: `UXAssist/Common/GameConstants/DysonSphereConstants.cs` +- Create: `UXAssist/Common/GameConstants/UniverseGenConstants.cs` + +- [ ] **Step 1: Extract item IDs** + + From `CheatEnabler/Patches/Factory/*.cs` and `UXAssist/Patches/Factory/BeltSignalPatch.cs`, collect hard-coded item IDs: + ```csharp + public static class ItemIds + { + public const int IronOre = 1001; + public const int CopperOre = 1002; + // ... + } + ``` + +- [ ] **Step 2: Extract tech IDs** + + From `UXAssist/Patches/TechPatch.cs`: + ```csharp + public static class TechIds + { + public const int SorterCargoStacking = 3608; + public static readonly HashSet CombatTechs = [3301, 3302, ...]; + } + ``` + +- [ ] **Step 3: Extract logistics constants** + + From `UXAssist/Patches/Logistics/CapacityPatch.cs`: + ```csharp + public static class LogisticsConstants + { + public const int DefaultLocalStorageMax = 5000; + public const int DefaultRemoteStorageMax = 10000; + } + ``` + +- [ ] **Step 4: Build UXAssist** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + ``` + +- [ ] **Step 5: Commit** + + ```bash + git add UXAssist/Common/GameConstants/ + git commit -m "refactor: centralize game constants" + ``` + +--- + +### Task 21: Replace magic numbers in UXAssist + +**Files:** +- Modify: `UXAssist/Patches/Factory/*.cs` +- Modify: `UXAssist/Patches/Logistics/*.cs` +- Modify: `UXAssist/Patches/TechPatch.cs` +- Modify: `UXAssist/Patches/DysonSpherePatch.cs` + +- [ ] **Step 1: Replace literal IDs and capacities with constants** + + For example, change: + ```csharp + if (itemId == 1001) { ... } + ``` + to: + ```csharp + if (itemId == ItemIds.IronOre) { ... } + ``` + +- [ ] **Step 2: Build UXAssist** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + ``` + +- [ ] **Step 3: Commit** + + ```bash + git add UXAssist/Patches/ + git commit -m "refactor(UXAssist): replace magic numbers with constants" + ``` + +--- + +### Task 22: Replace magic numbers in CheatEnabler and UniverseGenTweaks + +**Files:** +- Modify: `CheatEnabler/Patches/Factory/*.cs` +- Modify: `CheatEnabler/Functions/DysonSphere/*.cs` +- Modify: `CheatEnabler/Functions/PlanetFunctions.cs` +- Modify: `UniverseGenTweaks/Patches/GalaxyGenSettingsPatch.cs` +- Modify: `UniverseGenTweaks/Patches/CombatSettingsPatch.cs` +- Modify: `UniverseGenTweaks/BirthPlanetPatch.cs` + +- [ ] **Step 1: Reference UXAssist constants where appropriate** + + CheatEnabler/UniverseGenTweaks already reference `UXAssist.csproj`, so they can use `UXAssist.Common.GameConstants.ItemIds`, etc. + +- [ ] **Step 2: Build both projects** + + ```bash + dotnet build CheatEnabler/CheatEnabler.csproj -c Release + dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release + ``` + +- [ ] **Step 3: Commit** + + ```bash + git add CheatEnabler/ UniverseGenTweaks/ + git commit -m "refactor(CheatEnabler/UniverseGen): consume centralized constants" + ``` + +--- + +### Task 23: Localization key governance + +**Files:** +- Modify: `UXAssist/UIConfigWindow.cs` +- Modify: `CheatEnabler/UIConfigWindow.cs` +- Modify: `UniverseGenTweaks/UIConfigWindow.cs` +- Modify: `CheatEnabler/Functions/PlayerFunctions.cs` +- Modify: `UXAssist/Patches/Factory/BeltSignalPatch.cs` + +- [ ] **Step 1: Replace hard-coded Chinese `.Translate()` keys with `I18N.Add` entries** + + Example: + ```csharp + // Before + var btn = MyWindow.AddButton(..., "确定", ...); + // After + I18N.Add("OK", "OK", "确定"); + var btn = MyWindow.AddButton(..., "OK", ...); + ``` + +- [ ] **Step 2: Translate Chinese comments to English** + + Run a search for `// ` followed by CJK characters and translate or delete stale comments. + + ```bash + grep -RInP '//.*[\x{4e00}-\x{9fff}]' UXAssist/ CheatEnabler/ UniverseGenTweaks/ --include='*.cs' || true + ``` + +- [ ] **Step 3: Build all three projects** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + dotnet build CheatEnabler/CheatEnabler.csproj -c Release + dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release + ``` + +- [ ] **Step 4: Commit** + + ```bash + git add UXAssist/ CheatEnabler/ UniverseGenTweaks/ + git commit -m "refactor: localize hard-coded strings and translate comments" + ``` + +--- + +## Phase 4 — Static State & Lifecycle Cleanup + +### Task 24: Inventory static mutable state + +**Files:** +- Create: `docs/StaticStateInventory.md` + +- [ ] **Step 1: List all static mutable fields in the three projects** + + Search: + ```bash + grep -RInP 'private static (?!readonly)\S+' UXAssist/Patches/ UXAssist/Functions/ CheatEnabler/Patches/ CheatEnabler/Functions/ UniverseGenTweaks/ --include='*.cs' > docs/StaticStateInventory.md + grep -RInP 'public static (?!readonly)\S+' UXAssist/Patches/ UXAssist/Functions/ CheatEnabler/Patches/ CheatEnabler/Functions/ UniverseGenTweaks/ --include='*.cs' >> docs/StaticStateInventory.md + ``` + +- [ ] **Step 2: Classify each field by subsystem** + + Mark each as: + - `LifecycleSafe` — read-only after `Awake` + - `NeedsReset` — must be cleared on `GameLogic.OnGameEnd` + - `CandidateForInstancing` — should be owned by a per-game context class + +- [ ] **Step 3: Commit** + + ```bash + git add docs/StaticStateInventory.md + git commit -m "docs: inventory static mutable state" + ``` + +--- + +### Task 25: Add lifecycle reset callbacks + +**Files:** +- Modify: `UXAssist/Patches/Factory/BeltSignalPatch.cs` +- Modify: `UXAssist/Patches/Factory/VeinProtectionPatch.cs` +- Modify: `UXAssist/Patches/DysonSpherePatch.cs` +- Modify: `UXAssist/Functions/UI/MilkyWayUI.cs` +- Modify: `CheatEnabler/Patches/Factory/BeltSignalPatch.cs` +- Modify: `UniverseGenTweaks/Patches/GalaxySelectUIPatch.cs` + +- [ ] **Step 1: Add `ResetState` methods** + + Example: + ```csharp + internal static void ResetState() + { + _signalBelts = null; + _someCache.Clear(); + } + ``` + +- [ ] **Step 2: Register resets in `Init`** + + ```csharp + public static void Init() + { + GameLogic.OnGameEnd += ResetState; + // ... + } + ``` + +- [ ] **Step 3: Build all three projects** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + dotnet build CheatEnabler/CheatEnabler.csproj -c Release + dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release + ``` + +- [ ] **Step 4: Commit** + + ```bash + git add UXAssist/ CheatEnabler/ UniverseGenTweaks/ + git commit -m "refactor: register static-state resets on game end" + ``` + +--- + +## Phase 5 — Transpiler Robustness & Quality Gates + +### Task 26: Annotate transpilers with target version and fallback + +**Files:** +- Modify: all `*Transpiler` methods in `UXAssist/Patches/`, `CheatEnabler/Patches/`, `UniverseGenTweaks/Patches/` + +- [ ] **Step 1: Add header comments** + + ```csharp + // Target game version: 0.10.34.28505 + // Patches: EjectorComponent.InternalUpdate + // Falls back to original IL if the pattern is not matched. + private static IEnumerable ... + ``` + +- [ ] **Step 2: Wrap `CodeMatcher` finalization** + + Replace bare `.InstructionEnumeration()` with: + ```csharp + return matcher.ReportFailure(original, Logger)?.InstructionEnumeration() ?? instructions; + ``` + + Implement extension: + ```csharp + public static CodeMatcher ReportFailure(this CodeMatcher matcher, MethodBase original, ManualLogSource logger) + { + if (matcher.IsValid) return matcher; + logger?.LogWarning($"Transpiler failed for {original.DeclaringType?.Name}.{original.Name}"); + return null; + } + ``` + +- [ ] **Step 3: Build all three projects** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + dotnet build CheatEnabler/CheatEnabler.csproj -c Release + dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release + ``` + +- [ ] **Step 4: Commit** + + ```bash + git add UXAssist/ CheatEnabler/ UniverseGenTweaks/ + git commit -m "refactor: add transpiler version/fallback annotations" + ``` + +--- + +### Task 27: Centralize third-party compat reflection targets + +**Files:** +- Create: `UXAssist/ModsCompat/CompatTargets.cs` +- Modify: `UXAssist/ModsCompat/AuxilaryfunctionWrapper.cs` +- Modify: `UXAssist/ModsCompat/BulletTimeWrapper.cs` +- Modify: `UXAssist/ModsCompat/BlueprintTweaks.cs` + +- [ ] **Step 1: Extract magic strings** + + ```csharp + internal static class CompatTargets + { + public const string Auxilaryfunction = "auxilaryfunction.Auxilaryfunction"; + public const string SpeedUpPatch = "Auxilaryfunction.SpeedUpPatch"; + // ... + } + ``` + +- [ ] **Step 2: Replace literal strings with constants** + +- [ ] **Step 3: Build UXAssist** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + ``` + +- [ ] **Step 4: Commit** + + ```bash + git add UXAssist/ModsCompat/ + git commit -m "refactor(UXAssist): centralize compat reflection targets" + ``` + +--- + +### Task 28: Add `.editorconfig` and run `dotnet format` + +**Files:** +- Create: `.editorconfig` +- Modify: all touched `.cs` files (format-only changes) + +- [ ] **Step 1: Create `.editorconfig`** + + Minimal content: + ```ini + root = true + + [*.cs] + indent_style = space + indent_size = 4 + charset = utf-8-bom + end_of_line = crlf + insert_final_newline = true + dotnet_sort_system_directives_first = true + dotnet_separate_import_directive_groups = false + ``` + +- [ ] **Step 2: Run format** + + ```bash + dotnet format UXAssist/UXAssist.csproj + dotnet format CheatEnabler/CheatEnabler.csproj + dotnet format UniverseGenTweaks/UniverseGenTweaks.csproj + ``` + +- [ ] **Step 3: Build all three projects** + + ```bash + dotnet build UXAssist/UXAssist.csproj -c Release + dotnet build CheatEnabler/CheatEnabler.csproj -c Release + dotnet build UniverseGenTweaks/UniverseGenTweaks.csproj -c Release + ``` + +- [ ] **Step 4: Commit** + + ```bash + git add .editorconfig + git add -u UXAssist/ CheatEnabler/ UniverseGenTweaks/ + git commit -m "style: add editorconfig and run dotnet format" + ``` + +--- + +## Final Verification + +- [ ] **Step 1: Full solution build** + + ```bash + dotnet build DSP_Mods.sln -c Release + ``` + + Expected: 0 errors. + +- [ ] **Step 2: Package all mods** + + ```bash + dotnet build -t:ZipMod -c Release + dotnet build -t:CopyToParentPackage -c Release + ``` + + Expected: all `package/` outputs generated. + +- [ ] **Step 3: Public API verification** + + Re-run the `docs/PublicApiSurface.md` checklist and confirm every listed member still exists. + +- [ ] **Step 4: Update `AGENTS.md`** + + If any project conventions changed (e.g., new folder structure, naming rules, `.editorconfig`), update `AGENTS.md` accordingly. + +- [ ] **Step 5: Final commit / tag** + + ```bash + git tag refactor-complete + ``` + +--- + +## Self-Review Checklist + +- [ ] Every task references exact file paths. +- [ ] No `TODO`, `TBD`, or placeholder steps remain. +- [ ] Public API contract (`UXAssist.UI` + `UXAssist.Common`) is preserved throughout. +- [ ] `CheatEnabler` and `UniverseGenTweaks` compile after each phase. +- [ ] Code snippets use types/methods defined in earlier tasks.