1. 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
     
  2. Wulf

    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.
     
  3. To be clear, after patching, the flow is:

    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)

    So if I wanted to find where "the rubber meets the road," what is this seminal hook that boots OxideMod?
     
  4. Wulf

    Wulf Community Admin

    The InitOxide hook is the main hook that tells Oxide to load its basics.
     
  5. 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
    Which I can follow to this OxideMod source code:

    Code:
            public static void Initialize()
            {
                // Create if not already created
                if (Oxide != null) return;
                Oxide = new OxideMod(DebugCallback);
                Oxide.Load();
            }
    Then my next question becomes: How does the Rust game engine know to load the Oxide.Core.dll file so the Oxide.Core.Interface::Initialize() function is available to be called?
     
  6. 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
    
    Does the kernel32.dll part of Oxide.Core.kernel32.dll imply that it's doing something like telling C# JIT to load the Oxide.Core.dll ?

    And there's another part in there like:
    Code:
    get_Oxide OxideMod OxideConfig Oxide.Core.Configuration get_Options OxideOptions Modded set_GameTags 
    I suppose this is getting added by the OxidePatcher in the section of Rust.opj file:

    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"
              }
            },
    
    However, inside Facepunch.Console.dll, I also find a reference to Oxide.Core
    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
    How is this dll patched to add that reference to Oxide.Core? I don't see that function inside the Rust.opj

    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
    So I'm thinking somehow Facepunch.Console.dll and Facepunch.Rcon.dll are modified by the OxidePatcher but not using the Rust.opj. I guess I should dig into the OxidePatcher source code and it's hard-coded in there.

    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
  7. Wulf

    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.
     
  8. If you look inside Assembly-CSharp.dll with a text editor, you'll see Oxide.Core referenced in 2 different areas. The first area has that kernel32.dll. I thought that was relevant because I'm used to seeing kernel32 used to load dlls at runtime, but I suppose that's not how C# works.
     
  9. Wulf

    Wulf Community Admin

    That wouldn't be from Oxide. Oxide.Core.Interface::Initialize() is what is used to load the Oxide DLL when that hook is reached.
     
  10. Ok, I get that part and could follow the OxideMod source code from there. I'm having a problem understanding how Assembly-CSharp knows about the Initialize() function in the first place.

    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
     
  11. Wulf

    Wulf Community Admin

    It is injected into the DLL, which you can see with the IL code you posted in one of your previous posts. The entire patcher is made to inject code into the DLL so that the other DLL "knows" about it. ;)
     
  12. I read through the OxidePatcher source to complete my understanding about how the Assemby-CSharp.dll knows about Oxide.Core.dll.

    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"
              }
            }
    While I'm not exactly sure what every little bit of the OxidePatcher source code does, it appears this line in InitOxide.cs and Simple.cs is key to getting the Oxide.Core.dll reference embedded into the patched Assembly-CSharp.dll

    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");
    
    Later, when the hook is inserted with ILWeaver, I guess Mono.Cecil library with "Module.Import" takes care of adding a reference to Oxide.Core.dll in the patched Assembly-CSharp.dll

    Code:
    Instruction firstinjected = weaver.Add(Instruction.Create(OpCodes.Call, weaver.Module.Import(initoxidemethod)));
    Code:
    weaver.Add(Instruction.Create(OpCodes.Call, original.Module.Import(callhookmethod)));
    I'm sure Oxide devs already know all this considering they made the software. I found a few other threads here that kind of asked questions about similar topics but without complete answers, so I came back to add what I've found in case someone needs it.

    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