Last Epoch是基于Unity和C#的单人ARPG。游戏具有制作系统-玩家找到修改器,然后将其应用于设备。对于每个修饰符,“不稳定性”会累积,这增加了破坏该项目的机会
我追求两个目标:
- 消除由于应用修饰符而导致的“破损”
- 制作时请勿使用修饰符
这是游戏中制作窗口的样子:
第一部分,我们无需注册和SMS即可编辑.NET代码
首先,我将描述修改游戏旧版本(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 ) .
" ", , .
实际上,许多流行的语言都被编译成中间代码,由配置文件反编译器很好地解释和修改。对于此类修改,通常的编程技能通常就足够了。
尽管本机二进制文件可能对您的眼睛和大脑造成危险,但结合现代的开放源代码工具进行小幅修改,对程序如何在接近硬件的水平下工作的肤浅了解通常就足够了。