Modification de la dernière époque - De dnSpy à Ghidra

Last Epoch est un ARPG solo basé sur Unity et C #. Le jeu dispose d'un système d'artisanat - le joueur trouve des modificateurs, qu'il applique ensuite à l'équipement. Avec chaque modificateur, «l'instabilité» s'accumule, ce qui augmente les chances de casser l'objet



J'ai poursuivi deux objectifs:



  • Supprimer la "casse" d'un élément suite à l'application de modificateurs
  • N'utilisez pas de modificateurs lors de la fabrication


Couper



Voici à quoi ressemble la fenêtre de fabrication dans le jeu:



Fenêtre de fabrication de la dernière époque



Première partie, où nous éditons le code .NET sans inscription ni SMS



Pour commencer, je décrirai le processus de modification de l'ancienne version du jeu (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 avec Assembly-CSharp.dll ouvert



. , — , 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éthode d'édition dnSpy





Fracture, :



dnSpy x CraftingSlotManager.Forge ()



int num3 = -10; — .



,



0.7.9 IL2CPP , . IL-, … ?



-, No-CD, OllyDbg 10 . , -





, .dll- GameAssembly.dll 55 . , .



dll- Ghidra' , ( Analyze Address Table)



Ghidra



, , , — .



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



dll :



Sortie Il2CppDumper



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



dnSpy (restauré) x CraftingSlotManager



, , !



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


VA — ce, :



Offset de forge 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 réalité, de nombreux langages populaires sont compilés en code intermédiaire, qui est parfaitement interprété et modifié par les décompilateurs de profil. Pour de telles modifications, les compétences de programmation habituelles sont souvent suffisantes.



Et alors que les binaires natifs peuvent être dangereux pour vos yeux et votre cerveau, une connaissance superficielle du fonctionnement des programmes à un niveau proche du matériel est souvent suffisante en conjonction avec des outils open source modernes pour de petites modifications.




All Articles