Using dotPeek, I was able to see a method that I want to change a part of, specifically the 4 in CreateLobbyAsync to another integer.
public async void StartHost()
{
if (!(bool) (UnityEngine.Object) UnityEngine.Object.FindObjectOfType<MenuManager>())
{
Debug.Log((object) "Menu manager script is not present in scene; unable to start host");
}
else
{
if (GameNetworkManager.Instance.currentLobby.HasValue)
{
Debug.Log((object) "Tried starting host but currentLobby is not null! This should not happen. Leaving currentLobby and setting null.");
this.LeaveCurrentSteamLobby();
}
if (!this.disableSteam)
{
GameNetworkManager gameNetworkManager = GameNetworkManager.Instance;
gameNetworkManager.currentLobby = await SteamMatchmaking.CreateLobbyAsync(4);
gameNetworkManager = (GameNetworkManager) null;
}
UnityEngine.Object.FindObjectOfType<MenuManager>().StartHosting();
this.SubscribeToConnectionCallbacks();
this.isHostingGame = true;
this.connectedPlayers = 1;
}
}
However, The underlying IL shows that the actual functionality I want to change is not held within the accessible method, but in another automatically generated one.
Here is the method I wanted to change.
StartHost() cil managed
{
.custom instance void [netstandard]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [netstandard]System.Type)
= (
01 00 23 47 61 6d 65 4e 65 74 77 6f 72 6b 4d 61 // ..#GameNetworkMa
6e 61 67 65 72 2b 3c 53 74 61 72 74 48 6f 73 74 // nager+<StartHost
3e 64 5f 5f 37 39 00 00 // >d__79..
)
// type(valuetype GameNetworkManager/'<StartHost>d__79')
.maxstack 2
.locals init (
[0] valuetype GameNetworkManager/'<StartHost>d__79' V_0
)
IL_0000: ldloca.s V_0
IL_0002: call valuetype [netstandard]System.Runtime.CompilerServices.AsyncVoidMethodBuilder [netstandard]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Create()
IL_0007: stfld valuetype [netstandard]System.Runtime.CompilerServices.AsyncVoidMethodBuilder GameNetworkManager/'<StartHost>d__79'::'<>t__builder'
IL_000c: ldloca.s V_0
IL_000e: ldarg.0 // this
IL_000f: stfld class GameNetworkManager GameNetworkManager/'<StartHost>d__79'::'<>4__this'
IL_0014: ldloca.s V_0
IL_0016: ldc.i4.m1
IL_0017: stfld int32 GameNetworkManager/'<StartHost>d__79'::'<>1__state'
IL_001c: ldloca.s V_0
IL_001e: ldflda valuetype [netstandard]System.Runtime.CompilerServices.AsyncVoidMethodBuilder GameNetworkManager/'<StartHost>d__79'::'<>t__builder'
IL_0023: ldloca.s V_0
IL_0025: call instance void [netstandard]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Start<valuetype GameNetworkManager/'<StartHost>d__79'>(!!0/*valuetype GameNetworkManager/'<StartHost>d__79'*/&)
IL_002a: ret
} // end of method GameNetworkManager::StartHost
Here is the Referenced real functionality that I have been unable to access
.class nested private sealed auto ansi beforefieldinit
'<StartHost>d__79'
extends [netstandard]System.ValueType
implements [netstandard]System.Runtime.CompilerServices.IAsyncStateMachine
{
.custom instance void [netstandard]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 )
.field public int32 '<>1__state'
.field public valuetype [netstandard]System.Runtime.CompilerServices.AsyncVoidMethodBuilder '<>t__builder'
.field public class GameNetworkManager '<>4__this'
.field private class GameNetworkManager '<>7__wrap1'
.field private valuetype [netstandard]System.Runtime.CompilerServices.TaskAwaiter`1<valuetype [netstandard]System.Nullable`1<valuetype [Facepunch.Steamworks.Win64]Steamworks.Data.Lobby>> '<>u__1'
.method private final hidebysig virtual newslot instance void
MoveNext() cil managed
{
.override method instance void [netstandard]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext()
.maxstack 3
.locals init (
[0] int32 V_0,
[1] class GameNetworkManager V_1,
[2] valuetype [netstandard]System.Nullable`1<valuetype [Facepunch.Steamworks.Win64]Steamworks.Data.Lobby> V_2,
[3] valuetype [netstandard]System.Runtime.CompilerServices.TaskAwaiter`1<valuetype [netstandard]System.Nullable`1<valuetype [Facepunch.Steamworks.Win64]Steamworks.Data.Lobby>> V_3,
[4] class [netstandard]System.Exception V_4
)
IL_0000: ldarg.0 // this
IL_0001: ldfld int32 GameNetworkManager/'<StartHost>d__79'::'<>1__state'
IL_0006: stloc.0 // V_0
IL_0007: ldarg.0 // this
IL_0008: ldfld class GameNetworkManager GameNetworkManager/'<StartHost>d__79'::'<>4__this'
IL_000d: stloc.1 // V_1
.try
{
IL_000e: ldloc.0 // V_0
IL_000f: brfalse IL_009e
// [654 5 - 654 89]
IL_0014: call !!0/*class MenuManager*/ [UnityEngine.CoreModule]UnityEngine.Object::FindObjectOfType<class MenuManager>()
IL_0019: call bool [UnityEngine.CoreModule]UnityEngine.Object::op_Implicit(class [UnityEngine.CoreModule]UnityEngine.Object)
IL_001e: brtrue.s IL_002f
// [656 7 - 656 94]
IL_0020: ldstr "Menu manager script is not present in scene; unable to start host"
IL_0025: call void [UnityEngine.CoreModule]UnityEngine.Debug::Log(object)
IL_002a: leave IL_010e
// [660 7 - 660 61]
IL_002f: call class GameNetworkManager GameNetworkManager::get_Instance()
IL_0034: callvirt instance valuetype [netstandard]System.Nullable`1<valuetype [Facepunch.Steamworks.Win64]Steamworks.Data.Lobby> GameNetworkManager::get_currentLobby()
IL_0039: stloc.2 // V_2
IL_003a: ldloca.s V_2
IL_003c: call instance bool valuetype [netstandard]System.Nullable`1<valuetype [Facepunch.Steamworks.Win64]Steamworks.Data.Lobby>::get_HasValue()
IL_0041: brfalse.s IL_0053
// [662 9 - 662 143]
IL_0043: ldstr "Tried starting host but currentLobby is not null! This should not happen. Leaving currentLobby and setting null."
IL_0048: call void [UnityEngine.CoreModule]UnityEngine.Debug::Log(object)
// [663 9 - 663 38]
IL_004d: ldloc.1 // V_1
IL_004e: call instance void GameNetworkManager::LeaveCurrentSteamLobby()
// [665 7 - 665 30]
IL_0053: ldloc.1 // V_1
IL_0054: ldfld bool GameNetworkManager::disableSteam
IL_0059: brtrue.s IL_00d5
// [667 9 - 667 76]
IL_005b: ldarg.0 // this
IL_005c: call class GameNetworkManager GameNetworkManager::get_Instance()
IL_0061: stfld class GameNetworkManager GameNetworkManager/'<StartHost>d__79'::'<>7__wrap1'
// [668 9 - 668 85]
IL_0066: ldc.i4.4
IL_0067: call class [netstandard]System.Threading.Tasks.Task`1<valuetype [netstandard]System.Nullable`1<valuetype [Facepunch.Steamworks.Win64]Steamworks.Data.Lobby>> [Facepunch.Steamworks.Win64]Steamworks.SteamMatchmaking::CreateLobbyAsync(int32)
IL_006c: callvirt instance valuetype [netstandard]System.Runtime.CompilerServices.TaskAwaiter`1<!0/*valuetype [netstandard]System.Nullable`1<valuetype [Facepunch.Steamworks.Win64]Steamworks.Data.Lobby>*/> class [netstandard]System.Threading.Tasks.Task`1<valuetype [netstandard]System.Nullable`1<valuetype [Facepunch.Steamworks.Win64]Steamworks.Data.Lobby>>::GetAwaiter()
IL_0071: stloc.3 // V_3
IL_0072: ldloca.s V_3
IL_0074: call instance bool valuetype [netstandard]System.Runtime.CompilerServices.TaskAwaiter`1<valuetype [netstandard]System.Nullable`1<valuetype [Facepunch.Steamworks.Win64]Steamworks.Data.Lobby>>::get_IsCompleted()
IL_0079: brtrue.s IL_00ba
IL_007b: ldarg.0 // this
IL_007c: ldc.i4.0
IL_007d: dup
IL_007e: stloc.0 // V_0
IL_007f: stfld int32 GameNetworkManager/'<StartHost>d__79'::'<>1__state'
IL_0084: ldarg.0 // this
IL_0085: ldloc.3 // V_3
IL_0086: stfld valuetype [netstandard]System.Runtime.CompilerServices.TaskAwaiter`1<valuetype [netstandard]System.Nullable`1<valuetype [Facepunch.Steamworks.Win64]Steamworks.Data.Lobby>> GameNetworkManager/'<StartHost>d__79'::'<>u__1'
IL_008b: ldarg.0 // this
IL_008c: ldflda valuetype [netstandard]System.Runtime.CompilerServices.AsyncVoidMethodBuilder GameNetworkManager/'<StartHost>d__79'::'<>t__builder'
IL_0091: ldloca.s V_3
IL_0093: ldarg.0 // this
IL_0094: call instance void [netstandard]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::AwaitUnsafeOnCompleted<valuetype [netstandard]System.Runtime.CompilerServices.TaskAwaiter`1<valuetype [netstandard]System.Nullable`1<valuetype [Facepunch.Steamworks.Win64]Steamworks.Data.Lobby>>, valuetype GameNetworkManager/'<StartHost>d__79'>(!!0/*valuetype [netstandard]System.Runtime.CompilerServices.TaskAwaiter`1<valuetype [netstandard]System.Nullable`1<valuetype [Facepunch.Steamworks.Win64]Steamworks.Data.Lobby>>*/&, !!1/*valuetype GameNetworkManager/'<StartHost>d__79'*/&)
IL_0099: leave IL_0121
IL_009e: ldarg.0 // this
IL_009f: ldfld valuetype [netstandard]System.Runtime.CompilerServices.TaskAwaiter`1<valuetype [netstandard]System.Nullable`1<valuetype [Facepunch.Steamworks.Win64]Steamworks.Data.Lobby>> GameNetworkManager/'<StartHost>d__79'::'<>u__1'
IL_00a4: stloc.3 // V_3
IL_00a5: ldarg.0 // this
IL_00a6: ldflda valuetype [netstandard]System.Runtime.CompilerServices.TaskAwaiter`1<valuetype [netstandard]System.Nullable`1<valuetype [Facepunch.Steamworks.Win64]Steamworks.Data.Lobby>> GameNetworkManager/'<StartHost>d__79'::'<>u__1'
IL_00ab: initobj valuetype [netstandard]System.Runtime.CompilerServices.TaskAwaiter`1<valuetype [netstandard]System.Nullable`1<valuetype [Facepunch.Steamworks.Win64]Steamworks.Data.Lobby>>
IL_00b1: ldarg.0 // this
IL_00b2: ldc.i4.m1
IL_00b3: dup
IL_00b4: stloc.0 // V_0
IL_00b5: stfld int32 GameNetworkManager/'<StartHost>d__79'::'<>1__state'
IL_00ba: ldloca.s V_3
IL_00bc: call instance !0/*valuetype [netstandard]System.Nullable`1<valuetype [Facepunch.Steamworks.Win64]Steamworks.Data.Lobby>*/ valuetype [netstandard]System.Runtime.CompilerServices.TaskAwaiter`1<valuetype [netstandard]System.Nullable`1<valuetype [Facepunch.Steamworks.Win64]Steamworks.Data.Lobby>>::GetResult()
IL_00c1: stloc.2 // V_2
IL_00c2: ldarg.0 // this
IL_00c3: ldfld class GameNetworkManager GameNetworkManager/'<StartHost>d__79'::'<>7__wrap1'
IL_00c8: ldloc.2 // V_2
IL_00c9: callvirt instance void GameNetworkManager::set_currentLobby(valuetype [netstandard]System.Nullable`1<valuetype [Facepunch.Steamworks.Win64]Steamworks.Data.Lobby>)
// [669 9 - 669 55]
IL_00ce: ldarg.0 // this
IL_00cf: ldnull
IL_00d0: stfld class GameNetworkManager GameNetworkManager/'<StartHost>d__79'::'<>7__wrap1'
// [671 7 - 671 72]
IL_00d5: call !!0/*class MenuManager*/ [UnityEngine.CoreModule]UnityEngine.Object::FindObjectOfType<class MenuManager>()
IL_00da: callvirt instance void MenuManager::StartHosting()
// [672 7 - 672 44]
IL_00df: ldloc.1 // V_1
IL_00e0: call instance void GameNetworkManager::SubscribeToConnectionCallbacks()
// [673 7 - 673 32]
IL_00e5: ldloc.1 // V_1
IL_00e6: ldc.i4.1
IL_00e7: stfld bool GameNetworkManager::isHostingGame
// [674 7 - 674 32]
IL_00ec: ldloc.1 // V_1
IL_00ed: ldc.i4.1
IL_00ee: stfld int32 GameNetworkManager::connectedPlayers
IL_00f3: leave.s IL_010e
} // end of .try
catch [netstandard]System.Exception
{
IL_00f5: stloc.s V_4
IL_00f7: ldarg.0 // this
IL_00f8: ldc.i4.s -2 // 0xfe
IL_00fa: stfld int32 GameNetworkManager/'<StartHost>d__79'::'<>1__state'
IL_00ff: ldarg.0 // this
IL_0100: ldflda valuetype [netstandard]System.Runtime.CompilerServices.AsyncVoidMethodBuilder GameNetworkManager/'<StartHost>d__79'::'<>t__builder'
IL_0105: ldloc.s V_4
IL_0107: call instance void [netstandard]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::SetException(class [netstandard]System.Exception)
IL_010c: leave.s IL_0121
} // end of catch
IL_010e: ldarg.0 // this
IL_010f: ldc.i4.s -2 // 0xfe
IL_0111: stfld int32 GameNetworkManager/'<StartHost>d__79'::'<>1__state'
IL_0116: ldarg.0 // this
IL_0117: ldflda valuetype [netstandard]System.Runtime.CompilerServices.AsyncVoidMethodBuilder GameNetworkManager/'<StartHost>d__79'::'<>t__builder'
IL_011c: call instance void [netstandard]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::SetResult()
IL_0121: ret
} // end of method '<StartHost>d__79'::MoveNext
.method private final hidebysig virtual newslot instance void
SetStateMachine(
class [netstandard]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine
) cil managed
{
.custom instance void [netstandard]System.Diagnostics.DebuggerHiddenAttribute::.ctor()
= (01 00 00 00 )
.override method instance void [netstandard]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine(class [netstandard]System.Runtime.CompilerServices.IAsyncStateMachine)
.maxstack 8
IL_0000: ldarg.0 // this
IL_0001: ldflda valuetype [netstandard]System.Runtime.CompilerServices.AsyncVoidMethodBuilder GameNetworkManager/'<StartHost>d__79'::'<>t__builder'
IL_0006: ldarg.1 // stateMachine
IL_0007: call instance void [netstandard]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::SetStateMachine(class [netstandard]System.Runtime.CompilerServices.IAsyncStateMachine)
IL_000c: ret
} // end of method '<StartHost>d__79'::SetStateMachine
} // end of class '<StartHost>d__79'
I have been sofar unable to access the parts I actually want to change, the integer held within d__79 because I cannot access the automatically generated method using Harmony.
What I have so far, the patcher works and returns the original accessible but useless Method.
When I added MethodType.Enumerator to the HarmonyPatch(), it made it simply return a "The given key was not present in the dictionary." error. I assume because I am trying to access the GetNext of StartHost instead of d__79.
[HarmonyTranspiler]
[HarmonyPatch(typeof(GameNetworkManager), nameof(GameNetworkManager.StartHost)]
static IEnumerable<CodeInstruction> TranspileMoveNext(IEnumerable<CodeInstruction> instr)
{
var codes = new List<CodeInstruction>(instr);
for (int i = 0; i < codes.Count; i++)
{
Plugin.mls.LogError(codes[i].ToString());
if (codes[i].opcode == OpCodes.Ldc_I4_4)
{
//Plugin.mls.LogError(codes[i+1].ToString());
if (codes[i - 1].ToString() == "call int Steamworks.Data.Lobby::get_MemberCount()")
{
codes[i].opcode = OpCodes.Ldc_I4_8;
}
}
}
return codes.AsEnumerable();
}
Do I have any options here or is this a lost cause?
I've stumbled upon this issue, and during my research, I read about your troubles. So, I think I'll leave a mark here after solving the issue. You can find the real implementation on my Github, as I am creating a mod for a game I like.
The thing about async void is that it uses a state machine to achieve asynchronous behavior, so you need to patch the state machine object.
But, let's understand it from the beginning. Let's create a simple class:
Now, let's review the cleaned IL of this class
By analyzing this code, you can determine that this method creates a hidden class that implements the
IAsyncStateMachine
interface. You should be able to seeMoveNext
andSetStateMachine
methods. Let's jump to the documentation ofIAsyncStateMachine
to understand what these methods do.What's left is to patch the method we need.
VoilĂ ! Now you need to deal with handling async IL within the state machine