newman55 / unity-mod-manager

UnityModManager
MIT License
434 stars 101 forks source link

Need to test new update v 0.13 #16

Closed newman55 closed 4 years ago

newman55 commented 5 years ago

The scheme of UnityModManagerConfig.xml has changed. The UnityModManager class has been moved to the UnityModManager.dll file. All libraries are moved to the UnityModManager folder. Added 0Harmony with the traditional name. Added calls associated with Update, LateUpdate, FixedUpdate. Added a new way to install the manager used as in Kingmaker Mod Loader. For Pathfinder: Kingmaker configured an alternative installation method. For Pathfinder: Kingmaker has been changed the mods starting point to Kingmaker.GameStarter.StartGame: Before

90% of existing mods must be compatible. Others will need to be updated (recompiled).

Link to download

RobRendell commented 5 years ago

How do you want to measure the compatibility rating? Do you want individual modders to chime in on this issue, reporting whether they've tested their own mods, and whether they're compatible? Or do you want some sample of players or modders from each game that UMM supports reporting on compatibility of all mods they use?

For the record, my two Pathfinder: Kingmaker mods Craft Magic Items and Faster Menu Book Zoom appear to be compatible with the version currently available in the link.

newman55 commented 5 years ago

@RobRendell Probably more interested in tests from different games. Because I do not have all the games and I do not want to release the update without making sure that it does not break their work.

spacehamster commented 5 years ago

Can you explain what the xml entries mean?

Could you explain the new xml fields 
<EntryPoint>[UnityEngine.UIModule.dll]UnityEngine.Canvas.cctor:Before</EntryPoint>
<StartingPoint>[Assembly-CSharp.dll]Kingmaker.GameStarter.StartGame:Before</StartingPoint>
<UIStartingPoint>[Assembly-CSharp.dll]Kingmaker.MainMenu.Start:After</UIStartingPoint>
<OldPatchTarget>[Assembly-CSharp.dll]Kingmaker.GameStarter.Awake:Before</OldPatchTarget>

When building, a command window pops up with the following message

Does C:\Code\unity-mod-manager\UnityModManagerApp\bin\Release\UnityModManager.exe specify a file name
or directory name on the target
(F = file, D = directory)?

On Mac, trying to install results in this error https://pastebin.com/mC855Kth <MachineConfig>MonoBleedingEdge\etc\mono\4.5\machine.config</MachineConfig> needs to be changed to <MachineConfig>kingmaker.app/Contents/MonoBleedingEdge/etc/mono/4.5/machine.config</MachineConfig> to install correctly

After installing on mac, launching the game gives this error https://pastebin.com/L3u1xyqK

On windows, I've found some mods to be incompatible with version 0.13, including Kingdom Resolution, Bag of Tricks and Toggle AI and Stealth Hotkey. The error they throw is

[KingdomResolution] System.TypeLoadException: Could not load type UnityModManagerNet.UnityModManager+UI, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null while decoding custom attribute: (null)
  at (wrapper managed-to-native) System.MonoCustomAttrs.GetCustomAttributesInternal(System.Reflection.ICustomAttributeProvider,System.Type,bool)
  ...snip...
  at Harmony12.HarmonyInstance.PatchAll (System.Reflection.Assembly assembly) [0x00007] in <455e5c1456634e14910c281b79c6c325>:0 
  at KingdomResolution.Main.Load (UnityModManagerNet.UnityModManager+ModEntry modEntry) [0x0003f] in <2f7eed5c815f4083947aa1e6638eebb5>:0 

which is caused by patching UnityModManager.UI::Update

[Harmony12.HarmonyPatch(typeof(UnityModManager.UI), "Update")]
internal static class UnityModManager_UI_Update_Patch
{
    private static void Postfix(UnityModManager.UI __instance, ref Rect ___mWindowRect, ref Vector2[] ___mScrollPosition, ref int ___tabId)
    {
        ummRect = ___mWindowRect;
        ummScrollPosition = ___mScrollPosition;
        ummTabId = ___tabId;
    }
}

In Kingdom Resolution, this was done in order to show tooltips, but Toggle AI and Stealth Hotkey does it to capture key events, and Bag of Tricks does it for both. The best solution for those types of incompatibility would probably be for the mods in question to be updated to 0.13

Do you know when the crypto rng entry point runs? In KML it was

KingmakerModLoader.StartUp..cctor() (at :0)
KingmakerModLoader.RngWrapper..cctor() (at :0)
...snip...
System.Guid.NewGuid() (at :0)
Kingmaker.GameStatistic..ctor() (at :0)
Kingmaker.Game..ctor() (at :0)
Kingmaker.Game..cctor() (at :0)
Kingmaker.Kingdom.KingdomState.get_Instance() (at :0)
Kingmaker.Kingdom.KingdomState.get_Founded() (at :0)
Kingmaker.Visual.Sound.SoundState.ResetState(Kingmaker.Visual.Sound.SoundStateType state) (at :0)
Kingmaker.UI.MainMenuUI.SplashScreenController+<DelayedShow>c__Iterator0.MoveNext() (at :0)
UnityEngine.SetupCoroutine.InvokeMoveNext(System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at :0)

Also, If the lack of control over the initialization time is an issue with the crypto rng entry point, there is an alternative Unity Doorstop which runs before any unity code.

newman55 commented 5 years ago

@spacehamster

EntryPoint - a point in assembly for injection. StartingPoint - loading mods. UIStartingPoint - launch UI. OldPatchTarget - is the PatchTarget from the previous version. Only needed to remove the old version.

I added a mechanism that recompiles assemblies by replacing the UnityModManager namespace with a new namespace that is in UnityModManager.dll. But Harmony does not find them.

RNG starts late. I did not find a way to initialize it early. I will try to replace it with Unity Doorstop. Maybe it will be even better.

Edit: I tried Unity Doorstop with default example in three games and none worked. I do not know what's the matter.

6d306e73746572 commented 5 years ago

Bag of Tricks (Pathfinder Kingmaker) and Toolbox (Garden Paws) required updating the references (the error spacehamster already pointed out). All the rest worked immediately without any errors.

spacehamster commented 5 years ago

@newman55 I was able to get something like this to work on a test app. https://gist.github.com/spacehamster/2e5d76794f6e83f9c1d9f156c0a35544 I'm not sure if this is the best way of doing it though.

newman55 commented 5 years ago

@spacehamster My mistake was that I used 32 bit version instead of 64. Now your script works. It looks like a good solution. Thanks.

newman55 commented 5 years ago

Link updated. RNG installation method replaced by Unity Doorstop. I disabled it for Unix systems assuming that they need to be compiled specifically.

For Pathfinder: Kingmaker has been changed the mods starting point as was in 0.12

Tyler-IN commented 5 years ago

Just curious, why is starting "late" an issue? What do you need to hook so early that the game isn't even loaded? There are other non-invasive means of getting in sooner than the RNG that are also cross-platform. Loaders like Doorstop are a good start.

edit; KML also recompiled UMM mods to run against unpatched game assemblies. Same code or something different?

edit2; Doorstop is windows only. I don't think it'd take much work to make it cross-platform. The windows library hooking would have to change, but replacing __ImageBase is easy.

spacehamster commented 5 years ago

I didn't realize Doorstop was windows only. Unless it is made cross platform, the rng wrapper is probably the better solution. In pathfinder kingmaker, UMM runs before the localization manager is initialized, while KML runs after, so mods that patched the localization manager needed to be updated to support KML. I was worried similar issues might happen with other games.

Another mod loader, BepInEx allows mono.cecil in memory patches, which I think is only possible by hooking in before the assemblies are loaded.

newman55 commented 5 years ago

For Pathfinder, the Doorstop is now synchronized with the injection. In addition, the injections are transferred to unity files, which should reduce the cases of removing injections after updates. I think for UNIX OS it should not be a problem to use injections.

@Tyler-IN I used two solutions and both give the same error.

var modDef = ModuleDefMD.Load(File.ReadAllBytes(assemblyPath));
foreach (var item in modDef.GetTypeRefs())
{
    if (item.FullName == "UnityModManagerNet.UnityModManager")
    {
        item.ResolutionScope = new AssemblyRefUser(thisModuleDef.Assembly);
    }
}
modDef.Write(assemblyCachePath);
var asmDef = AssemblyDefinition.ReadAssembly(assemblyPath);
var modDef = asmDef.MainModule;
if (modDef.TryGetTypeReference("UnityModManagerNet.UnityModManager", out var typeRef))
{
    var managerAsmRef = new AssemblyNameReference("UnityModManager", version);
    if (typeRef.Scope is AssemblyNameReference asmNameRef)
    {
        typeRef.Scope = managerAsmRef;
        modDef.AssemblyReferences.Add(managerAsmRef);
        asmDef.Write(assemblyCachePath);
    }
}
[BagOfTricks] System.TypeLoadException: Could not load type UnityModManagerNet.UnityModManager+UI, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null while decoding custom attribute: (null)
  at (wrapper managed-to-native) System.MonoCustomAttrs.GetCustomAttributesInternal(System.Reflection.ICustomAttributeProvider,System.Type,bool)
  at System.MonoCustomAttrs.GetCustomAttributesBase (System.Reflection.ICustomAttributeProvider obj, System.Type attributeType, System.Boolean inheritedOnly) [0x00013] in <e1a80661d61443feb3dbdaac88eeb776>:0 
  at System.MonoCustomAttrs.GetCustomAttributes (System.Reflection.ICustomAttributeProvider obj, System.Type attributeType, System.Boolean inherit) [0x00037] in <e1a80661d61443feb3dbdaac88eeb776>:0 
  at System.RuntimeType.GetCustomAttributes (System.Boolean inherit) [0x00000] in <e1a80661d61443feb3dbdaac88eeb776>:0 
  at Harmony12.HarmonyMethodExtensions.GetHarmonyMethods (System.Type type) [0x00001] in <455e5c1456634e14910c281b79c6c325>:0 
  at Harmony12.HarmonyInstance.<PatchAll>b__9_0 (System.Type type) [0x00001] in <455e5c1456634e14910c281b79c6c325>:0 
  at Harmony12.CollectionExtensions.Do[T] (System.Collections.Generic.IEnumerable`1[T] sequence, System.Action`1[T] action) [0x0001b] in <455e5c1456634e14910c281b79c6c325>:0 
  at Harmony12.HarmonyInstance.PatchAll (System.Reflection.Assembly assembly) [0x00007] in <455e5c1456634e14910c281b79c6c325>:0 
  at BagOfTricks.Main.Load (UnityModManagerNet.UnityModManager+ModEntry modEntry) [0x001d5] in <18872d5ec2d24f5a83aeab464b1677ac>:0 
newman55 commented 5 years ago

I updated it on the nexus. Thank you all for participating.

RobRendell commented 5 years ago

When I run UMM version 0.12.7, it doesn't detect that there's an update... is that because 0.12 won't automatically update to 0.13?

newman55 commented 5 years ago

That's right. Downloader has changed a bit.

ghorsington commented 5 years ago

I didn't realize Doorstop was windows only. Unless it is made cross platform, the rng wrapper is probably the better solution.

Doorstop is indeed only for Windows, but people have managed to run their games on Linux/macOS with Doorstop via Wine. It's nice to see Doorstop being used, but it's still a niche solution initially made for BepInEx and aimed mainly at Windows/Wine.

While it is possible to create patcherless versions of Doorstop for Linux and macOS (especially Linux because of how nice the ELF format is), in the end true cross-platform solution is still hardpatching the DLLs (for instance, frameworks like MonoMod, IPA and BepInEx all provide hardpatchers as the only true cross-platform solution). Even then hardpatching is powerless against games running on IL2CPP.

Can't have a silver bullet here.

EDIT: A note about using RNG: while you can't control when your custom RNG gets loaded (and thus can't control when your mod manager bootstraps), it's still a nice entrypoint option to have.

EDIT 2: If you do end up using Doorstop in the future as well, note that your code gets to run as the very first thing in the root appdomain. That means that you get only a minimal set of base assemblies (unless you trigger the CLR to resolve more assemblies) and that some of UnityEngine functionality is still not initialised. Basically, don't do too much stuff in Doorstop code and keep assembly references to the minimum. Also, if you prefer creating shortcuts to making a static doorstop_config.ini, you can also pass all the data via command-line arguments.

newman55 commented 5 years ago

@denikson, thanks for your work. Doorstop was included in UMM and is already in use. To run scripts used delayed start via harmony. For unix systems is used another method.