I would like to better understand the way in which OxideMod is connected to the Rust game engine.
From using both OxideMod and OxidePatcher and briefly reviewing their source code, I understand that hooks are added to the Assembly-CSharp that allow some functions (found by using .Net reflection) to be exposed to OxideMod through what appear to be triggers added as stub code to the original IL.
My (purposefully) simple questions are:
1) Why is it necessary to patch the Assembly-Csharp.dll in the first place? What does it do that is required to have a modding system?
2) How is the main OxideMod compiled application actually loaded and executed when the main game engine starts? Is that an additional feature provided when the Assembly-Csharp.dll is modified by the OxidePatcher?
I'm not asking about plugin development and already know how to make Oxide C# extensions. I want to learn more about the very initial way in which Oxide interfaces with Rust to make this whole mod system work.
A summary answer or pointer to specific areas of the source code would be greatly appreciated. TIA
Understanding link between Rust -> Assembly-CSharp.dll -> OxideMod
Discussion in 'Rust Development' started by Kolyma, Jun 24, 2017.
-
Wulf Community Admin
Oxide patches Assembly-CSharp.dll (the game's main DLL) to add hooks. Hooks are what Oxide calls in plugins when that code is hit in the game. Hooks also are what make Oxide load.
-
1) Something happens in game where functions in Assembly-CSharp are invoked
2) The area of code modified by OxidePatcher hits the hook that was added
3) #2 calls something in the main OxideMod program with relevant particular variables & return codes
4) OxideMod then cycles through all the loaded plugins using that hook (I found this looping process in OxideMod source)
-
Wulf Community Admin
-
Thanks for your help. I'm starting to better understand the flow and connections. Ok, so from the OxidePatcher, I see this for InitOxide:
Code:IL_0058: ldstr "Command Line: " IL_005d: ldloc.0 IL_005e: call System.String System.String::Concat(System.String,System.String) IL_0063: call System.Void UnityEngine.Debug::Log(System.Object) IL_0068: call System.Void Oxide.Core.Interface::Initialize() IL_006d: ret
Code:public static void Initialize() { // Create if not already created if (Oxide != null) return; Oxide = new OxideMod(DebugCallback); Oxide.Load(); }
-
OK, so after some more testing and digging into things, I can see that inside the Bundles directory of the compiled OxideMod source code, there are 3 dlls that get copied over to a clean copy of Rust, thereby overwriting some original game server files.
It appears inside each of these dlls, Assembly-CSharp.dll, Facepunch.Console.dll, and Facepunch.Rcon.dll, there is a reference to Oxide.Core (I'm supposing it means Oxide.Core.dll, which when loaded, then loads all the other related Oxide.* files)
Assembly-CSharp.dll
Code:Assembly-CSharp.dll Assembly-CSharp Facepunch.Console mscorlib System Facepunch.Network Rust.Data Facepunch.System UnityEngine System.Core Rust.Global Facepunch.Steamworks UnityEngine.UI Assembly-CSharp-firstpass Facepunch.Rcon EasyAntiCheat.Server Rust.Workshop Facepunch.UnityEngine ApexAI ApexSerialization Facepunch.Raknet Newtonsoft.Json Ionic.Zip.Reduced LZ4 Oxide.Core kernel32.dll RustNative <Module> ConsoleGen Object All ConsoleSystem Command .ctor Name Parent FullName ServerAdmin Description Variable Func`1 GetOveride Action`1 SetOveride Arg Call ServerUser Saved .cctor Single ToString <All>m__0 CompilerGeneratedAttribute System.Runtime.CompilerServices StringExtensions Facepunch.Extend ToFloat
And there's another part in there like:
Code:get_Oxide OxideMod OxideConfig Oxide.Core.Configuration get_Options OxideOptions Modded set_GameTags
Code:"Type": "Modify", "Hook": { "InjectionIndex": 81, "RemoveCount": 0, "Instructions": [ { "OpCode": "call", "OpType": "Method", "Operand": "Oxide.Core|Oxide.Core.Interface|get_Oxide" }, { "OpCode": "call", "OpType": "Method", "Operand": "Oxide.Core|Oxide.Core.OxideMod|get_Config" }, { "OpCode": "call", "OpType": "Method", "Operand": "Oxide.Core|Oxide.Core.Configuration.OxideConfig|get_Options" }, { "OpCode": "ldfld", "OpType": "Field", "Operand": "Oxide.Core|Oxide.Core.Configuration.OxideConfig/OxideOptions|Modded" }, { "OpCode": "brfalse", "OpType": "Instruction", "Operand": 81 }, { "OpCode": "ldstr", "OpType": "String", "Operand": "oxide,modded," }, { "OpCode": "ldloc_3", "OpType": "None", "Operand": null }, { "OpCode": "call", "OpType": "Method", "Operand": "mscorlib|System.String|Concat(System.String, System.String)" }, { "OpCode": "stloc_3", "OpType": "None", "Operand": null } ], "HookTypeName": "Modify", "Name": "AddGameTags [patch]", "HookName": "AddGameTags", "AssemblyName": "Assembly-CSharp.dll", "TypeName": "ServerMgr", "Flagged": false, "Signature": { "Exposure": 0, "Name": "UpdateServerInformation", "ReturnType": "System.Void", "Parameters": [] }, "MSILHash": "e6O83Oz+Tk3hgAGIVetOfWEOqZ87hj0RT3zA9OFNoIM=", "BaseHookName": null, "HookCategory": "_Patches" } },
Code:Facepunch.Console.dll Facepunch.Console mscorlib System.Core Facepunch.UnityEngine UnityEngine Facepunch.Network Facepunch.System Newtonsoft.Json Facepunch.Input Oxide.Core <Module> ConsoleSystem Object System LastError CurrentArgs OnSendToServer Func`2 HasChanges ClientCanRunAdminCommands Func`1 Dictionary`2 System.Collections.Generic Enumerator KeyValuePair`2 Interface CallHook CommandLine Facepunch GetSwitches GetEnumerator get_Current get_Value String op_Equality get_Key Substring MoveNext IDisposable
Seems there is a similar situation with Facepunch.Rcon.dll
Code:websocket-sharp UnityEngine Oxide.Core <Module> Listener Object Password Port Address OnMessage Action`3 IPEndPoint System.Net server WebSocketServer WebSocketSharp.Server IPAddress Any TryParse .ctor String Concat Func`1 AddWebSocketService TimeSpan FromSeconds set_WaitTime Start Stop Shutdown get_WebSocketServices WebSocketServiceManager Broadcast BroadcastMessage str IEnumerator`1 System.Collections.Generic
But for the Assembly-CSharp.dll I'm now thinking that the Rust.opj sections (AddGameTags) referring to Oxide.Core are not the way in which the Oxide.Core.dll is initially loaded, but that happens with the Oxide.Core.kernel32.dll area, which again is patched into the source dll outside of Rust.opj functionality (also probably hard-coded in the OxidePatcher source).
Thanks in advance for any help you can provide. I'd really like to understand exactly how an unpatched Rust game server becomes able to launch OxideMod. After coming to C# from knowing other languages, I'm finally ready to learn some of the low-level concepts about how all this stuff is linked at the CIL/JIT level. It appears to me, OxideMod is a great place to learn about that because of the exotic way in which it's patched into an existing .Net application.Last edited by a moderator: Jun 24, 2017 -
Wulf Community Admin
All of the DLLs we patched are patched using the OxidePatcher, which there are references to in the Rust.opj. The only loading of Oxide is happening in the Assembly-CSharp.dll, the rest of the DLLs are for hooks for Oxide and plugins to utilize for various functionality.
The loading happens when the Initialize hook is called, which calls the Oxide.Core.dll as per reference with the injected hook.
Also, there also is no Oxide.Core.kernel32.dll, not sure where you got that from. -
-
Wulf Community Admin
-
Does C# just automagically look on the file system (including the search path) for Oxide.dll, then see if there is a Core.Interface::Initialize() function in there. Upon not finding that filename, does it proceed to looking for an Oxide.Core.dll and Interface::Initialize()... and if not, look for Oxide.Core.Interface.dll ?
I know it's looking for the Oxide.Core.dll filename because if I rename that file, the Rust game server doesn't load and I get load errors for Oxide.Core.dll in the log file. I've done this selectively for each patched dll file and seen similar behavior. I also looked inside the OxidePatcher source code and only see references to "Oxide.Core.dll" when the patcher is loaded and used for regular patching functions. I don't see how it patches the dll's to load-in-advance Oxide.Core.dll
Apologies for possibly being difficult with my persistence. I'm pretty sure I'm down to just understanding exactly how Assembly-CSharp.dll is able to call Oxide.Core.Interface::Initialize() when that function is not actually defined in Assembly-CSharp.dll -
Wulf Community Admin
-
The InitOxide hook starts the OxideMod boot, of course:
Code:{ "Type": "InitOxide", "Hook": { "InjectionIndex": 24, "HookTypeName": "Initialize Oxide", "Name": "InitOxide [internal]", "HookName": "InitOxide", "AssemblyName": "Assembly-CSharp.dll", "TypeName": "Bootstrap", "Flagged": false, "Signature": { "Exposure": 2, "Name": "Init_Tier0", "ReturnType": "System.Void", "Parameters": [] }, "MSILHash": "nw3EJ7ZAnzLu09ycwwpomh+opPLDJ3jHxwZUC3xDzNk=", "BaseHookName": null, "HookCategory": "Server" } }
Code:public override bool ApplyPatch(MethodDefinition original, ILWeaver weaver, AssemblyDefinition oxideassembly, Patcher patcher = null) { MethodDefinition initoxidemethod = oxideassembly.MainModule.Types .Single((t) => t.FullName == "Oxide.Core.Interface") .Methods.Single((m) => m.IsStatic && m.Name == "Initialize");
Code:public override bool ApplyPatch(MethodDefinition original, ILWeaver weaver, AssemblyDefinition oxideassembly, Patcher patcher = null) { // Get the call hook method MethodDefinition callhookmethod = oxideassembly.MainModule.Types .Single((t) => t.FullName == "Oxide.Core.Interface") .Methods.Single((m) => m.IsStatic && m.Name == "CallHook");
Code:Instruction firstinjected = weaver.Add(Instruction.Create(OpCodes.Call, weaver.Module.Import(initoxidemethod)));
Code:weaver.Add(Instruction.Create(OpCodes.Call, original.Module.Import(callhookmethod)));
That OxidePatcher is really cool software, especially in the way it handles injecting hooks and adjusting any other related addressing in the CIL affected by locations changed around the injected code.Last edited by a moderator: Jun 26, 2017