Modificación de Last Epoch: de dnSpy a Ghidra

Last Epoch es un ARPG para un jugador basado en Unity y C #. El juego tiene un sistema de elaboración: el jugador encuentra modificadores, que luego aplica al equipo. Con cada modificador, se acumula "inestabilidad", lo que aumenta la posibilidad de romper el objeto.



Perseguí dos objetivos:



  • Eliminar la "rotura" de un artículo como resultado de la aplicación de modificadores
  • No uses modificadores al crear


cortar



Así es como se ve la ventana de creación en el juego:



Ventana de elaboración de la última época



Parte uno, donde editamos código .NET sin registro y SMS



Para empezar, describiré el proceso de modificación de la versión antigua del juego (0.7.8)



C# IL (Intermediate Language) . IL- . Unity IL- <GameFolder>/Managed/Assembly-CSharp.dll



IL- dnSpy — .NET, . dnSpy .NET , IDE.





, dnSpy Assembly-CSharp.dll



dnSpy con Assembly-CSharp.dll abierto



. , — , Craft .



CraftingSlotManager:



dnSpy x CraftingSlotManager



Forge() :



ndSpy x CraftingSlotManager.Forge ()



// CraftingSlotManager
// Token: 0x06002552 RID: 9554 RVA: 0x0015E958 File Offset: 0x0015CB58
public void Forge()
{
    if (!this.forging)
    {
        this.forging = true;
        base.StartCoroutine(this.ForgeBlocker(10));
        bool flag = false;
        int num = -1;
        if (this.main.HasContent())
        {
            int num2 = 0;
            int num3 = 0;
            if (this.debugNoFracture)
            {
                num3 = -10;
            }
            float num4 = 1f;
            int num5 = -1;
            bool flag2 = false;
            ItemData data = this.main.GetContent()[0].data;
            ItemData itemData = null;
            if (this.support.HasContent())
            {
                itemData = this.support.GetContent()[0].data;
                num5 = (int)itemData.subType;
                if (itemData.subType == 0)
                {
                    num3--;
                    flag2 = true;
                }
                else if (itemData.subType == 1)
                {
                    num4 = UnityEngine.Random.Range(0.4f, 1f);
                    flag2 = true;
                }
            }
            if (this.appliedAffixID >= 0)
            {
                Debug.Log("applied ID: " + this.appliedAffixID.ToString());
                if (this.forgeButtonText.text == "Forge")
                {
                    if (data.AddAffixTier(this.appliedAffixID, Mathf.RoundToInt((float)(5 + num2) * num4), num3))
                    {
                        num = this.appliedAffixID;
                        flag = true;
                    }
                    GlobalDataTracker.instance.CheckForShard(this.appliedAffixID);
                    if (flag2)
                    {
                        this.support.Clear();
                    }
                    if (!GlobalDataTracker.instance.CheckForShard(this.appliedAffixID))
                    {
                        this.DeselectAffixID();
                    }
                }
            }
            else if (this.modifier.HasContent())
            {
                Debug.Log("modifier lets go");
                ItemData data2 = this.modifier.GetContent()[0].data;
                if (data2.itemType == 102)
                {
                    if (data2.subType == 0)
                    {
                        Debug.Log("shatter it");
                        Notifications.CraftingOutcome(data.Shatter());
                        if (num5 == 0)
                        {
                            flag2 = false;
                        }
                        this.main.Clear();
                        flag = true;
                        this.ResetAffixList();
                    }
                    else if (data2.subType == 1)
                    {
                        Debug.Log("refine it");
                        if (data.AddInstability(Mathf.RoundToInt((float)(2 + num2) * num4), num3, 0))
                        {
                            data.ReRollAffixRolls();
                        }
                        flag = true;
                    }
                    else if (data2.subType == 2 && data.affixes.Count > 0)
                    {
                        Debug.Log("remove it");
                        if (data.AddInstability(Mathf.RoundToInt((float)(2 + num2) * num4), num3, 0))
                        {
                            ItemAffix affixToRemove = data.affixes[UnityEngine.Random.Range(0, data.affixes.Count)];
                            data.RemoveAffix(affixToRemove);
                        }
                        flag = true;
                    }
                    else if (data2.subType == 3 && data.affixes.Count > 0)
                    {
                        Debug.Log("cleanse it");
                        List<ItemAffix> list = new List<ItemAffix>();
                        foreach (ItemAffix item in data.affixes)
                        {
                            list.Add(item);
                        }
                        foreach (ItemAffix affixToRemove2 in list)
                        {
                            data.RemoveAffix(affixToRemove2);
                        }
                        if (num5 == 0)
                        {
                            flag2 = false;
                        }
                        data.SetInstability((int)((Mathf.Clamp(UnityEngine.Random.Range(5f, 15f), 0f, (float)data.instability) + (float)num2) * num4));
                        flag = true;
                    }
                    else if (data2.subType == 4 && data.sockets == 0)
                    {
                        Debug.Log("socket it");
                        data.AddSocket(1);
                        data.SetInstability((int)((Mathf.Clamp(UnityEngine.Random.Range(5f, 15f), 0f, (float)data.instability) + (float)num2) * num4));
                        flag = true;
                    }
                }
            }
            if (flag)
            {
                UISounds.playSound(UISounds.UISoundLabel.CraftingSuccess);
                if (this.modifier.HasContent())
                {
                    ItemData data3 = this.modifier.GetContent()[0].data;
                    this.modifier.Clear();
                    if (num >= 0 && GlobalDataTracker.instance.CheckForShard(num))
                    {
                        this.PopShardToModifierSlot(num);
                    }
                    else if (data3.itemType == 102)
                    {
                        foreach (SingleSubTypeContainer singleSubTypeContainer in ItemContainersManager.instance.materials.Containers)
                        {
                            if (singleSubTypeContainer.CanAddItemType((int)data3.itemType) && singleSubTypeContainer.allowedSubID == (int)data3.subType && singleSubTypeContainer.HasContent())
                            {
                                singleSubTypeContainer.MoveItemTo(singleSubTypeContainer.GetContent()[0], 1, this.modifier, new IntVector2?(IntVector2.Zero), Context.SILENT);
                                break;
                            }
                        }
                    }
                    if (num >= 0 && this.prefixTierVFXObjects.Length != 0 && this.suffixTierVFXObjects.Length != 0)
                    {
                        ItemData itemData2 = null;
                        if (this.main.HasContent())
                        {
                            itemData2 = this.main.GetContent()[0].data;
                        }
                        if (itemData2 != null && this.main.HasContent())
                        {
                            List<ItemAffix> list2 = new List<ItemAffix>();
                            List<ItemAffix> list3 = new List<ItemAffix>();
                            foreach (ItemAffix itemAffix in itemData2.affixes)
                            {
                                if (itemAffix.affixType == AffixList.AffixType.PREFIX)
                                {
                                    list2.Add(itemAffix);
                                }
                                else
                                {
                                    list3.Add(itemAffix);
                                }
                            }
                            for (int i = 0; i < list2.Count; i++)
                            {
                                if ((int)list2[i].affixId == num && this.prefixTierVFXObjects[i])
                                {
                                    this.prefixTierVFXObjects[i].SetActive(true);
                                }
                            }
                            for (int j = 0; j < list3.Count; j++)
                            {
                                if ((int)list3[j].affixId == num && this.suffixTierVFXObjects[j])
                                {
                                    this.suffixTierVFXObjects[j].SetActive(true);
                                }
                            }
                        }
                    }
                }
                if (!flag2)
                {
                    goto IL_6B3;
                }
                this.support.Clear();
                using (List<SingleSubTypeContainer>.Enumerator enumerator2 = ItemContainersManager.instance.materials.Containers.GetEnumerator())
                {
                    while (enumerator2.MoveNext())
                    {
                        SingleSubTypeContainer singleSubTypeContainer2 = enumerator2.Current;
                        if (singleSubTypeContainer2.CanAddItemType((int)itemData.itemType) && singleSubTypeContainer2.allowedSubID == (int)itemData.subType && singleSubTypeContainer2.HasContent())
                        {
                            singleSubTypeContainer2.MoveItemTo(singleSubTypeContainer2.GetContent()[0], 1, this.support, new IntVector2?(IntVector2.Zero), Context.SILENT);
                            break;
                        }
                    }
                    goto IL_6B3;
                }
            }
            this.modifier.Clear();
            this.support.Clear();
        }
        IL_6B3:
        if (!flag)
        {
            UISounds.playSound(UISounds.UISoundLabel.CraftingFailure);
        }
        this.slamVFX.SetActive(true);
        this.UpdateItemInfo();
        this.UpdateFractureChanceDisplay();
        this.UpdateForgeButton();
        ShardCountText.UpdateAll();
    }
}


. , ( num1, num2...). , .



CraftingSlot', . CraftingSlotManager .





: this.modifier this.support



, .



:



this.modifier.Clear();
this.support.Clear();


, ( , , ) — .



this.modifier.Clear(); this.support.Clear();



dnSpy — .dll — :



método de edición dnSpy





Fracture, :



dnSpy x CraftingSlotManager.Forge ()



int num3 = -10; — .



,



0.7.9 IL2CPP , . IL-, … ?



El autor del artículo no es un ingeniero inverso, sino que tiene detrás solo los restos de un curso universitario en Assembler y un No-CD hecho en OllyDbg hace 10 años. Es posible que algunas cosas se hayan hecho incorrectamente o podrían haberse hecho mucho mejor y más fácilmente.





, .dll- GameAssembly.dll 55 . , .



dll- Ghidra' , ( Analyze Address Table)



Ghidra



, , , — .



IL2CPP Il2CppDumper, - ( <GameFolder>/il2cpp_data/Metadata/global-metadata.dat). , .



dll :



Salida Il2CppDumper



DummyDll dll- IL-. Assembly-CSharp.dll dnSpy CraftingSlotManager:



dnSpy (restaurado) x CraftingSlotManager



, , !



Address(RVA = "0x5B9FC0", Offset = "0x5B89C0", VA = "0x1805B9FC0")


VA — ce, :



Desplazamiento de forja de Ghidra



, .



? , Il2CppDumper , — , ghidra.py script.json, . , , .





, this.modifier.Clear(); this.support.Clear();. . .



Ghidra



— . , CALL NOP



( C, Clear Code Bytes), 90 . !



Ghidra



OneSlotItemContainer$$Clear() Forge() ( , this.main.Clear(); , ).





int num3 = -10; . — , ~60 , , . 15 , .



Ghidra



, ( 4 MOVZX AND), . , .



( ) dnSpy , "" AddInstability



public bool AddInstability(int addedInstability, int fractureTierModifier = 0, int affixTier = 0)
    {
        int num = this.RollFractureTier(fractureTierModifier, affixTier);
        if (num > 0)
        {
            this.Fracture(num); // <-----   
            return false;
        }
        this.instability = ((int)this.instability + addedInstability).clampToByte();
        this.RebuildID();
        return true;
    }


:



Ghidra



, CALL ItemData$$RollFractureTier, TEST EAX :



Ghidra



, uVar3 < 1. — ( ) JG(Jump short if greater) JLE(Jump short if less or equal).



— . CALL XOR EAX, EAX ( ), NOP'.



Ghidra



! , GameAssembly.dll (- .bin ) .





" ", , .



En realidad, muchos lenguajes populares se compilan en código intermedio, que los descompiladores de perfiles interpretan y modifican de manera excelente. Para tales modificaciones, las habilidades de programación habituales suelen ser suficientes.



Y aunque los binarios nativos pueden ser peligrosos para los ojos y el cerebro, un conocimiento superficial de cómo funcionan los programas a un nivel cercano al hardware suele ser suficiente junto con las herramientas modernas de código abierto para pequeñas modificaciones.




All Articles