Closed ATLaptic closed 3 years ago
Wow, it's been quite a while since I looked into this library!
For the first question: To replace the original DLL use the Save(createBackup: bool)
overload! Pass true if you want dnpatch to create a backup file first, pass false if not. That should solve your issue! If not let me know whether writing to a different path works!
For the second question: I'm honestly not too sure anymore but I believe that dnpatch doesn't support inserting instructions (or at least the master branch doesn't). The v1 branch should have a method called Append
, however that branch isn't documented but you can check the source and examples provided. I'm also not entirely sure whether that branch is fully stable.
That being said, I didn't know people were still using dnpatch so I should probably consider picking the v1 branch up again and finalize it!
Let me know if the provided answers worked for you!
p.Save(false);
p.Save("C://Program Files (x86)/Steam/steamapps/common/Game/Game_Data/Managed/Assembly-CSharp.dll");
I assume this is how you use the overload but it didn't worked, I've tried switching the order of them but it still gave me this error
dnlib.DotNet.Writer.ModuleWriterException: 'Error calculating max stack value. If the method's obfuscated, set CilBody.KeepOldMaxStack or MetaDataOptions.Flags (KeepOldMaxStack, global option) to ignore this error. Otherwise fix your generated CIL code so it conforms to the ECMA standard.'
I've tried changing the dll path but it didn't work.
As for the second one, I've downloaded, builded, and added the v1 version of dnpatch to my project but doesn't seem to come with the Append method. Maybe I copied the wrong dll so here's the path I used dnpatch-1\dnpatch-1\dnpatch\bin\Debug\dnpatch.dll
If you load the initial file from C://Program Files (x86)/Steam/steamapps/common/Game/Game_Data/Managed/Assembly-CSharp.dll
you have to call p.Save(false)
. This will overwrite the original file (and won't create a backup of it), the other line isn't required. To keep the old max stack create your patcher instance like this:
Patcher patcher = new Patcher("Test.exe", true);
As for the second one: https://github.com/ioncodes/dnpatch/blob/v1/dnpatch/Processors/ILProcessor.cs#L97 this line contains the Append function. Here's an example from the unit tests:
Loader loader = new Loader();
loader.Initialize("crack", "Security.dll", "Security.default.dll", false, true, true); // crack the license mechanism
loader.Initialize("credits", "UI.dll", "UI.default.dll", false, true, true); // add credits to the UI window
Assembly security = loader.LoadAssembly("crack");
Assembly ui = loader.LoadAssembly("credits");
Console.WriteLine(security.AssemblyInfo.ToString());
Console.WriteLine(ui.AssemblyInfo.ToString());
security.Model.SetNamespace("Security");
security.Model.SetType("Security");
security.Model.SetMethod("IsLicensed");
ui.Model.SetNamespace("UI");
ui.Model.SetType("UI");
ui.Model.SetMethod("GetCredits");
// The IL class should be of type ILProcessor and you should be able to call Append here
security.IL.Overwrite(instructions: new Instruction[] // return true
{
Instruction.Create(OpCodes.Ldc_I4_1),
Instruction.Create(OpCodes.Ret)
});
ui.IL.Write(Instruction.Create(OpCodes.Ldstr, "Cracked By Evil-Corp"), 1);
loader.Save();
Loader loader = new Loader();
loader.Initialize("game", "C://Program Files (x86)/Steam/steamapps/common/Game/Game_Data/Managed/Assembly-CSharp.dll", "C://Program Files (x86)/Steam/steamapps/common/Game/Game_Data/Managed/Assembly-CSharp.dll", true, false, true);
Assembly game = loader.LoadAssembly("game");
Console.WriteLine(game.AssemblyInfo.ToString());
game.Model.SetNamespace("");
game.Model.SetType("Player");
game.Model.SetMethod("Spawn");
game.IL.Overwrite(instructions: new Instruction[]
{
Instruction.Create(OpCodes.Ldc_I4_8), //is this the index?
Instruction.Create(OpCodes.Ldstr, "InputDataManager.inst.players[this.playerIndex].health = " + health + ";")
});
Is the first line of the instructions the index?
And when I set the type it says
System.Exception: 'Type '.Player' does not exist.'
Even though I check through dnSpy and it's correct.
I've also tried replacing
loader.Initialize("game", "C://Program Files (x86)/Steam/steamapps/common/Project Arrhythmia/Project Arrhythmia_Data/Managed/Assembly-CSharp.dll", "C://Program Files (x86)/Steam/steamapps/common/Project Arrhythmia/Project Arrhythmia_Data/Managed/Assembly-CSharp.dll", true, false, true);
with
loader.Initialize("game", "C://Program Files (x86)/Steam/steamapps/common/Project Arrhythmia/Project Arrhythmia_Data/Managed/Assembly-CSharp.dll", "C://Program Files (x86)/Steam/steamapps/common/Project Arrhythmia/Project Arrhythmia_Data/Managed/Assembly-CSharp.default.dll", true, false, true);
Also is this how I would use the Append method?
Instruction[] SpeedCodes = {
Instruction.Create(OpCodes.Ldstr, "this.idleSpeed = 20f * " + speed +";"),
Instruction.Create(OpCodes.Ldstr, "this.boostSpeed = 85f * " + speed +";"),
Instruction.Create(OpCodes.Ldstr, "Vector3 temp = base.transform.localScale;"),
Instruction.Create(OpCodes.Ldstr, "temp = new Vector3(" + xscale + "f, " + yscale + "f, 1f);"),
Instruction.Create(OpCodes.Ldstr, "this.render.gameObject.transform.localScale = temp;"),
Instruction.Create(OpCodes.Ldstr, "this.rb.gameObject.transform.localScale = temp;")
};
game.Model.SetNamespace("");
game.Model.SetType("Player");
game.Model.SetMethod("Start");
game.IL.Append(SpeedCodes);
loader.Save();
Thank you so much for all of the help you've given me so far!
I can't recall it entirely but I believe you are missing either the Namespace or the full path to the type. What's the fully qualified type path for Player? Also, I believe Append() expect's the type InstructionSet which is this: https://github.com/ioncodes/dnpatch/blob/v1/dnpatch/Types/InstructionSet.cs
No need to thank me, I should really finish up the project and write proper documentation to make the experience less painful!
Instruction [] SpeedCodes = {
Instruction.Create(OpCodes.Ldstr, "this.idleSpeed = 20f * " + speed +";"),
Instruction.Create(OpCodes.Ldstr, "this.boostSpeed = 85f * " + speed +";"),
Instruction.Create(OpCodes.Ldstr, "Vector3 temp = base.transform.localScale;"),
Instruction.Create(OpCodes.Ldstr, "temp = new Vector3(" + xscale + "f, " + yscale,
Instruction.Create(OpCodes.Ldstr, "this.render.gameObject.transform.localScale = temp;"),
Instruction.Create(OpCodes.Ldstr, "this.rb.gameObject.transform.localScale = temp;")
};
InstructionSet codes = new InstructionSet();
codes.Instructions = SpeedCodes;
game.Model.SetNamespace("");
game.Model.SetType("Player");
game.Model.SetMethod("Start");
game.IL.Append(codes);
loader.Save();
So like this?
Also sorry but what does the fully qualified type path mean? I can show picture of where the type is in dnspy's assembly explorer if that helps. edit: tried editing other types but it didn't work
I've have seemed to fix the type problem by doing this instead
string path = $"Player";
TypeDef type = game.AssemblyData.Module.FindReflection(path);
game.Model.SetType(type);
Also when overwriting instructions is the index Instruction.Create(OpCodes.Ldc_I4_8)
?
game.IL.Overwrite(instructions: new Instruction[]
{
Instruction.Create(OpCodes.Ldc_I4_8),
Instruction.Create(OpCodes.Ldstr, "InputDataManager.inst.players[this.playerIndex].health = " + health + ";")
});
Ok so I just realized that I can just do this
game.IL.Write(Instruction.Create(OpCodes.Ldstr, "InputDataManager.inst.players[this.playerIndex].health = " + health + ";"), 20);
to overwrite one line
But now I get this error when I try to save
dnlib.DotNet.Writer.ModuleWriterException: 'Error calculating max stack value. If the method's obfuscated, set CilBody.KeepOldMaxStack or MetaDataOptions.Flags (KeepOldMaxStack, global option) to ignore this error. Otherwise fix your generated CIL code so it conforms to the ECMA standard.'
And when I try to Append it doesn't actually add anything
Instruction[] SpeedCodes = {
Instruction.Create(OpCodes.Ldstr, "this.idleSpeed = 20f * " + speed +";"),
Instruction.Create(OpCodes.Ldstr, "this.boostSpeed = 85f * " + speed +";"),
Instruction.Create(OpCodes.Ldstr, "Vector3 temp = base.transform.localScale;"),
Instruction.Create(OpCodes.Ldstr, "temp = new Vector3(" + xscale + "f, " + yscale),
Instruction.Create(OpCodes.Ldstr, "this.render.gameObject.transform.localScale = temp;"),
Instruction.Create(OpCodes.Ldstr, "this.rb.gameObject.transform.localScale = temp;")
};
InstructionSet codes = new InstructionSet();
codes.Instructions = SpeedCodes;
game.Model.SetNamespace("");
game.Model.SetType(type);
game.Model.SetMethod("Start");
game.IL.Append(codes);
Console.WriteLine("The game has now been modded. Please relaunch game to see changes");
loader.Save();
I don't think the v1 branch has the functionality to keep the old max stack but you can add that yourself (check master or the dnlib docs). As for the Append function, the code itself doesn't look wrong to me but it is a branch that is in development so there may be a few bugs hiding somewhere.
That being said, what exactly is your use-case you are trying to achieve, maybe there is another way that doesn't involve appending instructions. If it's an absolute must I can update the master branch with a new feature for that tomorrow.
Basically what I'm trying to do is a make a program that allows someone to change the values of player attributes. And what I need is to add a couple of lines of code below line 23 here I thought just adding them at the end will be simplest but as long as it's after line 23 it should be fine.
Okay, that's fair, I'll try to implement an Append function into master as soon as I can.
When I try overwriting the blue circled line, it instead overwrote a different line and as a string for some reason. Is the index the same as the line number in dnspy?
Patcher p = new Patcher("C://Program Files (x86)/Steam/steamapps/common/Game/Game_Data/Managed/Assembly-CSharp.dll", true);
Instruction HealthCode = Instruction.Create(OpCodes.Ldstr, "InputDataManager.inst.players[this.playerIndex].health = " + health);
Instruction HealthFind = Instruction.Create(OpCodes.Ldstr, "InputDataManager.inst.players[this.playerIndex].player.trail.UpdateTail(InputDataManager.inst.players[this.playerIndex].health, this.rb.position);");
Target target = new Target()
{
Namespace = "",
Class = "Player",
Method = "Spawn",
Instruction = HealthCode
};
target.Index = p.FindInstruction(target, HealthFind) + 4;
p.Patch(target);
p.Save(false);
You are in the source code view of dnSpy, switch to IL view. Keep in mind that we are patching IL code with dnlib/dnpatch. Source code patching isn't supported but the docs here and other docs on MSIL should suffice to get an overview of it.
int num = int.Parse(health);
Patcher p = new Patcher("C://Program Files (x86)/Steam/steamapps/common/Game/Game_Data/Managed/Assembly-CSharp.dll", true);
Instruction HealthCode = Instruction.Create(OpCodes.Ldc_I4, num);
Instruction HealthFind = Instruction.Create(OpCodes.Stfld, ???);
Target target = new Target()
{
Namespace = "",
Class = "Player",
Method = "Spawn",
Instruction = HealthCode
};
target.Index = p.FindInstruction(target, HealthFind)-1;
p.Patch(target);
p.Save(false);
I got it to work when I set the index manually but I have now idea what to enter for an Stfld opcode.
Also is there anyway to edit constructors with dnpatch?
IIRC constructors are called .ctor
in .NET, it's a method as any other.
How would I find an instruction in a method if FindInstructionsByOperand will only take integers and not a single string?
There's an overload for FindInstructionsByOperand which takes a string[]
, you can't mix them up tho. I used to use multiple patterns for that, then matched for the function that has most matches.
Also apologies for taking so long with the update, I have my hands full with a few projects that have a high priority.
Is there a way to edit locals with dnpatch, and when I try to do to use the string[] overload I only get dnpatch.Target[] as an output.
Don't worry about updating immediately! I'm in no rush to finish this and you've been a big help already!
Unfortunately, there is not a way to patch locals.
I'm very confused on how to edit instructions that aren't just strings or numbers.
If you treat the binary as obfuscated module you get access to advanced functionality. Target becomes ObfuscatedTarget, etc. This type has a field called ModuleDef which is the raw representation of a method body. With that type you can insert arbitrary instructions.
Sorry, but would you mind giving an example?
Very sorry for asking, but could you give me a general time frame of when you can add append to the master branch?
You should probably check this out while I'm busy: https://github.com/ioncodes/dnpatch/pull/56
Ok so I finally figured out how to insert arbitrary instructions with #56, but whenever I try to use a HasThis flag it gives a decompiling error when I check the method in dnSpy. Am I doing something wrong?
var mod = new ModuleDefUser(path);
var typeref2 = new TypeRefUser(mod, "UnityEngine", "Component", mod.CorLibTypes.AssemblyRef);
var scaleMember2 = new MemberRefUser(mod, "get_gameObject", new MethodSig(CallingConvention.HasThis, 0, mod.ImportAsTypeSig(typeof(UnityEngine.GameObject))), typeref2);
var typeref4 = new TypeRefUser(mod, "UnityEngine", "Vector3", mod.CorLibTypes.AssemblyRef);
var scaleMember4 = new MemberRefUser(mod, ".ctor", new MethodSig(CallingConvention.HasThis, 3, mod.CorLibTypes.Void, mod.CorLibTypes.Single, mod.CorLibTypes.Single, mod.CorLibTypes.Single), typeref4);
Instruction[] scaleInstructions =
{
Instruction.Create(OpCodes.Callvirt, scaleMember2),
Instruction.Create(OpCodes.Newobj, scaleMember4),
};
Target target= new Target()
{
Namespace = "",
Class = "Player",
Method = "Start",
Indices = new[] { 68, 69, 70, 71, 72, 73, 74, 75, 76},
InsertInstructions = true,
Instructions = scaleInstructions
};
Wait nvm I just realized that I did the wrong return type for one of them.
For some reason when I try to save the patch it gives me this error at the last line
System.IO.IOException: 'The requested operation cannot be performed on a file with a user-mapped section open. :
I've checked process explorer and nothing else has the dll open. Is p.Save only used for saving a new version of the assembly and not for saving changes to the current one or something?Also does setting an index after the last line of a method just makes the the instructions afterward?
Thanks for any help you can give! edit: after awhile I realized it's probably the path to the dll but I don't know what's wrong