Epoch Terakhir adalah ARPG pemain tunggal berdasarkan Unity dan C #. Gim ini memiliki sistem kerajinan - pemain menemukan pengubah, yang kemudian diterapkan pada peralatan. Dengan setiap pengubah, "ketidakstabilan" terakumulasi, yang meningkatkan kemungkinan memecahkan item
Saya mengejar dua tujuan:
- Hapus "kerusakan" item sebagai hasil dari penerapan pengubah
- Jangan gunakan pengubah saat membuat

Seperti inilah tampilan jendela kerajinan di dalam game:

Bagian satu, di mana kami mengedit kode .NET tanpa registrasi dan SMS
Untuk memulainya, saya akan menjelaskan proses memodifikasi versi lama dari game (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

. , — , Craft .
— 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 — :

Fracture, :

— int num3 = -10; — .
,
0.7.9 IL2CPP , . IL-, … ?
-, No-CD, OllyDbg 10 . , -
, .dll- GameAssembly.dll 55 . , .
dll- Ghidra' , ( Analyze Address Table)

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

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

, , !
Address(RVA = "0x5B9FC0", Offset = "0x5B89C0", VA = "0x1805B9FC0")
VA — ce, :

, .
? , Il2CppDumper , — , ghidra.py script.json, . , , .
, this.modifier.Clear(); this.support.Clear();. . .

— . , CALL NOP
( C, Clear Code Bytes), 90 . !

OneSlotItemContainer$$Clear() Forge() ( , this.main.Clear(); , ).
int num3 = -10; . — , ~60 , , . 15 , .

, ( 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;
}
:

, CALL ItemData$$RollFractureTier, TEST EAX :

, uVar3 < 1. — ( ) JG(Jump short if greater) JLE(Jump short if less or equal).
— . CALL XOR EAX, EAX ( ), NOP'.

! , GameAssembly.dll (- .bin ) .
" ", , .
Pada kenyataannya, banyak bahasa populer dikompilasi menjadi kode perantara, yang ditafsirkan dan dimodifikasi dengan sangat baik oleh pengurai profil. Untuk modifikasi seperti itu, keterampilan pemrograman biasa sudah cukup.
Dan sementara binari asli bisa berbahaya bagi mata dan otak Anda, pengetahuan dangkal tentang bagaimana program bekerja pada tingkat yang dekat dengan perangkat keras sering kali cukup berhubungan dengan alat sumber terbuka modern untuk modifikasi kecil.