[CollectionManagerScene_COLLECTION] An exception occurred when calling CacheCustomDecks: System.MissingMethodException: Method 'CollectionDeckBoxVisual.string_4 = IsValid, enum20_0 = null' not found.
at Triton.Game.Mono.MonoClass.method_7(String string_4, Enum20[] enum20_0, Object[] object_0)
at Triton.Game.Mono.MonoClass.method_10[T](String string_4, Enum20[] enum20_0, Object[] object_0)
at Triton.Game.Mono.MonoClass.method_11[T](String string_4, Object[] object_0)
at Triton.Game.Mapping.CollectionDeckBoxVisual.IsValid()
at Triton.Bot.Utility.smethod_4()
at Triton.Bot.Logic.Bots.DefaultBot.DefaultBot.Struct57.MoveNext().
internal static void smethod_4() { try { CollectionDeckTray collectionDeckTray = CollectionDeckTray.Get(); if (collectionDeckTray != null) { foreach (TraySection traySection in collectionDeckTray.m_decksContent.m_traySections) { CollectionDeckBoxVisual deckBox = traySection.m_deckBox; long deckID = deckBox.GetDeckID(); string text = deckBox.m_deckName.Text; bool isWild = deckBox.m_isWild; if (deckID != -1L && deckBox.IsValid()) { CustomDeckCache customDeckCache = null; bool flag = true; foreach (CustomDeckCache customDeckCache2 in MainSettings.Instance.CustomDecks) { if (customDeckCache2.DeckId == deckID) { customDeckCache = customDeckCache2; customDeckCache.Name = text; customDeckCache.IsWild = isWild; customDeckCache.Save(); flag = false; break; } } if (customDeckCache == null) { customDeckCache = new CustomDeckCache(deckID) { DeckId = deckID, HeroCardId = deckBox.m_heroCardID, Name = deckBox.GetDeckNameText().Text, IsWild = isWild }; } CollectionDeck deck = CollectionManager.Get().GetDeck(deckID); if (!deck.m_netContentsLoaded) { if (customDeckCache.CardIds.Count == 30 && flag) { MainSettings.Instance.CustomDecks.Add(customDeckCache); } } else { customDeckCache.CardIds.Clear(); foreach (CollectionDeckSlot collectionDeckSlot in deck.m_slots) { for (int i = 0; i < collectionDeckSlot.Count; i++) { customDeckCache.CardIds.Add(collectionDeckSlot.CardID); } } customDeckCache.Save(); if (flag) { MainSettings.Instance.CustomDecks.Add(customDeckCache); MainSettings.Instance.Save(); } } } } } } catch (Exception) { MainSettings.Instance.CustomDecks.Clear(); MainSettings.Instance.Save(); throw; } }
public bool IsValid() { return base.method_11<bool>("IsValid", Array.Empty<object>()); }
public class CollectionDeckBoxVisual自己在调用
public long GetDeckID() { return base.method_11<long>("GetDeckID", Array.Empty<object>()); }
internal T method_11<T>(string string_4, params object[] object_0) where T : struct { return this.method_10<T>(string_4, null, object_0); }
internal T method_10<T>(string string_4, Class276.Enum20[] enum20_0, params object[] object_0) where T : struct { IntPtr intPtr = this.method_7(string_4, enum20_0, object_0); if (intPtr == IntPtr.Zero) { return default(T); } if (typeof(T) == typeof(bool)) { IntPtr addr = MonoClass.Class276_0.method_26(intPtr); return (T)((object)(MonoClass.ExternalProcessMemory_0.Read<byte>(addr) > 0)); } return MonoClass.ExternalProcessMemory_0.Read<T>(MonoClass.Class276_0.method_26(intPtr)); }
internal IntPtr method_7(string string_4, Class276.Enum20[] enum20_0, params object[] object_0) { IntPtr classInstance = this.GetClassInstance(); if (classInstance == IntPtr.Zero) { throw new Exception("Cannot call a method on an object instance that has no address!"); } IntPtr intPtr = this.method_0(string_4, enum20_0); if (intPtr == IntPtr.Zero) { string text = string.Empty; if (enum20_0 != null) { for (int i = 0; i < enum20_0.Length; i++) { text += enum20_0[i]; } } else { text = "null"; } throw new MissingMethodException(this.ClassName, "string_4 = " + string_4 + ", enum20_0 = " + text); } return MonoClass.Class276_0.method_43(intPtr, classInstance, object_0); }
internal IntPtr method_0(string string_4, Class276.Enum20[] enum20_0) { if (this.IntPtr_0 == IntPtr.Zero) { throw new InvalidOperationException("Cannot get a method pointer on an object that has no MonoClass pointer."); } return MonoClass.smethod_4(this.IntPtr_0, string_4, enum20_0); }
这个里面的this,应该某一个class的IntPtr_0
internal IntPtr IntPtr_0 { get { IntPtr? intPtr = this.nullable_0; if (intPtr == null) { IntPtr? intPtr2 = this.nullable_0 = new IntPtr?(this.vmethod_0(this.AssemblyPath, this.ClassNamespace, this.ClassName)); return intPtr2.Value; } return intPtr.GetValueOrDefault(); } }
private static IntPtr smethod_4(IntPtr intptr_1, string string_4, Class276.Enum20[] enum20_0) { MonoClass.Class274 @class = new MonoClass.Class274(); @class.string_0 = string_4; @class.enum20_0 = enum20_0; Dictionary<string, List<MonoClass.Class273>> dictionary; if (!MonoClass.dictionary_3.TryGetValue(intptr_1, out dictionary)) { MonoClass.dictionary_3.Add(intptr_1, new Dictionary<string, List<MonoClass.Class273>>()); dictionary = MonoClass.dictionary_3[intptr_1]; } List<MonoClass.Class273> list; if (!dictionary.TryGetValue(@class.string_0, out list)) { dictionary.Add(@class.string_0, new List<MonoClass.Class273>()); list = dictionary[@class.string_0]; } MonoClass.Class273 class2 = list.FirstOrDefault(new Func<MonoClass.Class273, bool>(@class.method_0)); if (class2 == null) { IntPtr intPtr = MonoClass.Class276_0.method_33(intptr_1, @class.string_0, @class.enum20_0); if (intPtr != IntPtr.Zero) { class2 = new MonoClass.Class273(@class.string_0, intPtr, @class.enum20_0); list.Add(class2); } } if (class2 == null) { return IntPtr.Zero; } return class2.IntPtr_0; }
这里的intptr_1是上面的类的
下面有一个很奇怪的写法,赋值之后,还可以直接做比较
int a = 0; if ((a = 10) > 9) { Console.WriteLine(a); }
internal IntPtr method_33(IntPtr intptr_37, string string_0, params Class276.Enum20[] enum20_0) { while (intptr_37 != IntPtr.Zero) { using (AllocatedMemory allocatedMemory = this.externalProcessMemory_0.CreateAllocatedMemory(256)) { allocatedMemory.AllocateOfChunk<IntPtr>("Itr"); IntPtr intPtr; while ((intPtr = this.method_35(intptr_37, allocatedMemory["Itr"])) != IntPtr.Zero) { IntPtr address = this.method_37(intPtr); if (this.externalProcessMemory_0.ReadStringA(address) == string_0) { if (enum20_0 != null) { Class276.Enum20[] array = this.method_31(intPtr); if (array.Length != enum20_0.Length || !array.SequenceEqual(enum20_0)) { continue; } } return intPtr; } } intptr_37 = this.method_25(intptr_37); } } return IntPtr.Zero; }
有2个while循环
第一个循环,是传入的intptr_37不为IntPtr.Zero【这里的intptr_37就是类开始的地址】
基于externalProcessMemory_0分配256的内存
256的内存里面,分配一个内存chunk
intPtr = method_35(intptr_37,新分配的内存chunk)
第二个循环内部
[intPtr = method_35(intptr_37,新分配的内存chunk)] 这个在下一次循环的时候会再做一次
address = method_37(intPtr);
用externalProcessMemory_0.ReadStringA(address)读取方法名
判断方法名是否匹配,如果匹配,就返回intPtr;否则回到第二个循环
第二个循环结束,还没有找到符合要求的,
会执行intptr_37 = this.method_25(intptr_37);
第一个循环
1. 重新分配内存chunk
2. 第二个循环
3. method25修改intptr_37的值
其中1和3的的修改,是为了在第二个循环中使用
internal IntPtr method_25(IntPtr intptr_37) { return this.method_17<IntPtr>(this.intptr_28, new object[] { intptr_37 }); }
+ intptr_0 0x10000000 System.IntPtr
+ intptr_28 0x100165A7 System.IntPtr 和intptr_0相比,相差0x165A7,也就是91559
这里的第二个参数,只传了intptr_37
private T method_17<T>(IntPtr intptr_37, params object[] object_0) where T : struct { return this.externalProcessMemory_0.CallInjected<T>(intptr_37, CallingConvention.Cdecl, object_0); }
private readonly ExternalProcessMemory externalProcessMemory_0;
public class ExternalProcessMemory : MemoryBase 这个类在C#写的GreyMagic里面
第二个循环
1. intPtr = method_35(intptr_37,新分配的内存chunk)
(intPtr = this.method_35(intptr_37, allocatedMemory["Itr"])
这里虽然2个参数没变,但是执行后的返回结果一直在变
2. address = method_37(intPtr);
3. 读取方法名,看是否匹配
this.externalProcessMemory_0.ReadStringA(address)
注意,在第二个循环里面intptr_37的不会变化的,然后是address在变 。不太清楚allocatedMemory["Itr"]是否会变化
internal IntPtr method_35(IntPtr intptr_37, IntPtr intptr_38) { return this.method_17<IntPtr>(this.intptr_19, new object[] { intptr_37, intptr_38 }); }
第一个参数是this.intptr_19 = this.intptr_0 + 120353; intptr_0 是mono.dll的地址
第二个参数指针数组传递了2个地址,intptr_37和intptr_38。 intptr_37是类开始的地址,intptr_38是新分配的内存chunk
这里计算的返回结果,会作为method_37的第一个参数。第二个参数指针数组传递的值一直没变,但是返回结果一直在变。
internal IntPtr method_37(IntPtr intPtr ) { return this.method_17<IntPtr>(this.intptr_21, new object[] { intPtr }); }
这里第一个参数是this.intptr_21 = this.intptr_0 + 231261;
第二个参数,指针数组只传递了一个地址intPtr 。 这个地址是intPtr = method_35(intptr_37,新分配的内存chunk)
这个方法调用后,是为了拿到方法名的地址,根据地址读出方法名
private T method_17<T>(IntPtr intptr_37, params object[] object_0) where T : struct { return this.externalProcessMemory_0.CallInjected<T>(intptr_37, CallingConvention.Cdecl, object_0); }
第二个循环,对于类的执行结果如下
+ intptr_37 0x0AD14698 System.IntPtr
+ allocatedMemory["Itr"] 0x03430000 System.IntPtr
+ intPtr 0x0AFC00A0 System.IntPtr
+ address 0x0BCC9A22 System.IntPtr
this.externalProcessMemory_0.ReadStringA(address) ".ctor" string
+ intptr_37 0x0AD14698 System.IntPtr
+ allocatedMemory["Itr"] 0x03430000 System.IntPtr
+ intPtr 0x0AFC00C0 System.IntPtr
+ address 0x0BD05E38 System.IntPtr
this.externalProcessMemory_0.ReadStringA(address) "get_SCALED_UP_LOCAL_SCALE" string
+ intptr_37 0x0AD14698 System.IntPtr
+ allocatedMemory["Itr"] 0x03430000 System.IntPtr
+ intPtr 0x0AFCE148 System.IntPtr
+ address 0x0BD05E52 System.IntPtr
this.externalProcessMemory_0.ReadStringA(address) "set_SCALED_UP_LOCAL_SCALE" string
+ intptr_37 0x0AD14698 System.IntPtr
+ allocatedMemory["Itr"] 0x03430000 System.IntPtr
+ intPtr 0x0AFBFDF8 System.IntPtr
+ address 0x0BCF8FDF System.IntPtr
this.externalProcessMemory_0.ReadStringA(address) "Awake" string
+ intptr_37 0x0AD14698 System.IntPtr
+ allocatedMemory["Itr"] 0x03430000 System.IntPtr
+ intPtr 0x0AFCE168 System.IntPtr
+ address 0x0BC6668E System.IntPtr
this.externalProcessMemory_0.ReadStringA(address) "Update" string
+ intptr_37 0x0AD14698 System.IntPtr
+ allocatedMemory["Itr"] 0x03430000 System.IntPtr
+ intPtr 0x0AFCE188 System.IntPtr
+ address 0x0BCF9174 System.IntPtr
this.externalProcessMemory_0.ReadStringA(address) "Show" string
+ intptr_37 0x0AD14698 System.IntPtr
+ allocatedMemory["Itr"] 0x03430000 System.IntPtr
+ intPtr 0x0AFCE1A8 System.IntPtr
+ address 0x0BCBE79B System.IntPtr
this.externalProcessMemory_0.ReadStringA(address) "Hide" string
+ intptr_37 0x0AD14698 System.IntPtr
+ allocatedMemory["Itr"] 0x03430000 System.IntPtr
+ intPtr 0x0AFCE1C8 System.IntPtr
+ address 0x0BCFAB02 System.IntPtr
this.externalProcessMemory_0.ReadStringA(address) "IsShown" string
smethod_4方法的某一次返回值为 + IntPtr_0 0x0AFCE2C8 System.IntPtr,对应的方法名的GetDeckID
- class2 {Triton.Game.Mono.MonoClass.Class273} Triton.Game.Mono.MonoClass.Class273
Enum20_0 null ns27.Class276.Enum20[]
+ IntPtr_0 0x0AFCE2C8 System.IntPtr
Name "GetDeckID" string
enum20_0 null ns27.Class276.Enum20[]
+ intptr_0 0x00000000 System.IntPtr
string_0 null string
method_0方法中监视this的值,
- this {Triton.Game.Mapping.CollectionDeckBoxVisual} Triton.Game.Mono.MonoClass {Triton.Game.Mapping.CollectionDeckBoxVisual}
+ Address 0x1C995690 System.IntPtr
AssemblyPath @"C:\Program Files (x86)\Hearthstone\Hearthstone_Data\Managed\Assembly-CSharp.dll" string
ClassName "CollectionDeckBoxVisual" string
处理方案
拿到C:\Program Files (x86)\Hearthstone\Hearthstone_Data\Managed\Assembly-CSharp.dll
使用ILSpy反编译,然后搜索CollectionDeckBoxVisual,发现这个类是存在的
搜索CollectionDeckBoxVisual.IsValid,这个方法已经不在了,新的方法名字是
public bool IsValidForCurrentMode() { CollectionDeck collectionDeck = GetCollectionDeck(); if (collectionDeck == null) { return false; } bool @bool = Options.Get().GetBool(Option.IN_WILD_MODE); return collectionDeck.IsValidForRuleset && collectionDeck.IsValidForFormat(@bool); }
所以,处理思路是,把HearthBuddy里面的IsValid改为IsValidForCurrentMode【注意这里最后是mode不是model】