-
Notifications
You must be signed in to change notification settings - Fork 18
Assembly Modification
This guide shows how to do assembly modifications to redirect the games control-flow or change the game state without externally writing any data.
Keep in mind that this is a long guide as I will be covering both the plugin part and the Cheat Engine part.
- Assembly Modification
To follow this guide you should know/understand
- what assembly is
- how to read x86-64 assembly (to some extent)
- x64 CPU registers
- how to use the memory viewer in Cheat Engine
- the concept of a function call and a return statement
- basic C/C++
- how to write a plugin
Tools Used:
- Cheat Engine
- Visual Studio 2019
Here is a good resource for general information about x64 registers and the x86-64 instruction set.
The goal for this plugin will be to modify the Palico Coral Orchestra to always play the Attack Up L (or any song of your choice) song regardless of state.
First and foremost, if you are reading this you should already know how to find values in Cheat Engine. For this to work we will need to find the address of a Coral Orchestra buff timer. This could be any buff, like Attack Up L, Defense Up L, Regen Up, etc. This guide will be going with Attack Up L.
Now we could do this the "hard" way and manually look for the value, or simply use Marcus101RR's cheat table which already has all of the buffs listed.
Go ahead and open the table and find the Palico Buffs section. Next up load into an expedition and make sure you have the coral orchestra equipped.
To find how the game decides which buff to give to the player, we will need to look at the code. To do that however, we will first need to find that piece of code. Cheat Engine happens to have a handy feature called "Data Breakpoints" to help with this.
Before we get started, make sure you have Use VEH Debugger
selected under Edit > Settings > Debugger Options > Debugger method
.
To use these Data Breakpoints, simply right-click on any of the buffs in Cheat Engine and select Find out what writes to this address
.
This will open a prompt asking you if you wish to attach the debugger to the game, to this click yes. The next prompt will ask you if you wish to see what writes the pointer itself, or what writes to the address pointed to by the pointer, select the latter. Doing so will open a new window, for now don't click anything in there.
Now go ahead and activate coral orchestra until you get the buff that you selected. Once you get the buff you will see some instructions pop up in the window that opened earlier.
You should see 2 instructions, one that was executed once and one that is continuously being executed. You can now click the Stop
button in the bottom right of the window.
The bottom instruction, the one that was counting up, is the one that decreases the buff timer steadily. However the one that we're interested in is the top one, which initially writes the duration of the buff. The instruction being executed is
movss [rsi+rax*4+10],xmm7
Which roughly means "move the floating point value in the register xmm7
into the address pointed to by rsi + rax * 0x4 + 0x10
". Which means our buff time is stored in xmm7
. However we are not interested in the buff time, but which buff specifically. This is given by rax
in this case. Looking at the instruction we can tell that it is indexing into an array of floats.
Which means that the value we're interested in is rax
. If we click the Show Disassembler
button at the right side of the window the memory viewer will open showing us the piece of code we just inspected. With some further inspection (for which I will not go into detail), we can deduce that rax
is the second parameter passed to this function.
A quick rundown of this deduction: Scroll up and find the place where rax
is assigned to, where we find mov eax,edi
. Next we scroll up further to find where edi
is assigned to, which happens to be at the top of the function with mov edi,edx
. In a function that takes 2 parameters, the second one is stored in rdx
, or rather edx
for 32-bit integers.
Now that we know that we're actually interested in edx
, we scroll down until we find a ret
instruction. The ret
instruction returns from a function. Now right-click this instruction and select Break and trace instructions
.
In the new window that opens leave everything at default and hit Ok
. This will open another empty window. Now we need to activate another palico buff, which will fill the newly opened Tracer window with instructions. You will see a short lag in your game, which is how you'll know that the breakpoint was hit.
Now right-click anywhere in the window and click Expand all
.
At the very top of the window you should see the ret
instruction we looked at earlier, and one below that is the return address of this function. It should be mov rcx,[rsi+28]
, but we don't actually care about what the instruction says, simply double-click it and it will bring you to that location in the memory viewer. After doing so you can close the Tracer window.
Now looking at this piece of code, we can make a couple of observations:
What is marked in yellow is the function call that we just returned from. And what is marked in blue, is the instruction that moves our value of interest into edx
:
mov edx,r15d
So now our value of interest is in r15
. If we scroll up a bit we should see how r15d
is assigned:
The instruction mov r15d,[rcx+0xA2A0]
tells us that our value of interest is at the memory location pointed to by rcx + 0xA2A0
. Now we need to know what rcx
actually is. Doing so is actually quite simple, simply right-click the instruction and select Set breakpoint
.
Doing so will highlight that instruction in green (or red when we deselect it). Now we need to activate yet another coral orchestra buff. When you do this, your game will freeze, but don't panic. This is intended!
Looking at the memory viewer again, a couple things have changed. At the top there should be some new buttons labelled "Run", "Step Into", "Step Over", etc. However the more interesting part is on the right side of the window. This is a snapshot of all of the cpu registers at this point in time.
Note: Your values will not be the same as mine!
Now we see what rcx
actually is. Clicking on it will open a small window allowing us to copy the value. Now you may have noticed that the memory viewer is actually divided into 2 (or 3) sections. The top part is the code viewer, and the bottom part (the one with the many numbers) is the actual memory viewer. You can resize both of these sections.
Now we need to use the actual memory viewer, so go ahead and make that section bigger. Right-click anywhere in that section and select Goto address
.
In the small popup, paste in the value copied from rcx
. And at the end of this, add a +a2a0
. Your address might look something like this: 00000001C3C99D70+a2a0
. Now hit OK
. This will bring you to the address where our value of interest is stored.
You can now remove the breakpoint in the top section that we set earlier, and then click the green "Run
" button at the top left of the window. This will make your game resume like normal.
Now in the top left of the bottom section of the window, you should see a value ##
, followed by four 00
s. For example: 41 00 00 00
. Highlight those 4 bytes and right-click.
In the context menu select Data breakpoint > Find out what writes to this address
.
Doing so will highlight those bytes in green/red and will open a small window like the at the start of the guide. Now as usual, we need to activate another coral orchestra buff.
Now every time you activate a buff, you should see exactly one instruction count up by 1 in this window. That instruction being:
mov [rbx+0000A2A0],eax
Once again go ahead and click Show disassembler
. You can then close the small window.
The newly found section of code should look something like this:
As you can see, one instruction above the one we selected is a function call. We can also see that our value of interest is stored in eax
. In the Microsoft x64 calling convention, function return values are always stored in rax/eax/ax/al
. Which means that the value is returned from the function called above. Which means we need to follow that function call.
To follow a function call simply select the call
instruction and hit Space. Or, you right-click:
Note: This also works for jmp
, jne
, jnz
, ja
, etc.
Now if you follow this call, and then scroll down quite a bit you should see some constructs like this:
This type of code formatting is an obvious indication of a switch case
. Each construct
mov edx,VALUE
mov eax,edx
movaps xmm6,[rsp+30]
...
is one case
label.
Above all of the cases, there is a piece of code that calculates the jmp
needed to go to the correct case. In this case it should look something like so:
Now once again we see that the value we're looking for is decided based on what rax
is in this instance.
The jmp
is calculated with
mov ecx,[r8+rax*4+019A40A4]
which means we need to modify eax
. Right above this instruction is a mov eax,r15d
, which is 3 bytes and looks perfect to replace. If we set a breakpoint here and activate the coral orchestra a couple of times, we see that the value in eax/rax
ranges from 0-7 based on buff type. Fortunately for you I already mapped all of those out a long time ago:
0: Attack Up S
1: Defense Up S
2: Affinity Up
3: Recovery Speed Up
4: Health Recovery
5: Stamina Use Decreased
6: Attack Up L
7: Defense Up L
This means that all we have to do is to move the respective value into eax
before the jmp
is calculated.
Now we need an instruction that is 3 bytes or less. The first thought might be mov eax,n
(where n is 0-7), however that instruction is 5 bytes long. If you want to know how the bytes of an instruction look like you can use this site. Make sure to select the x64
mode and enter your instruction in the Assemble
field.
Now if you take a look at the cheatsheet I linked at the start of the guide, you can see that there are ways of accessing only the lower bytes of a register. For rax
those are:
rax: full register
eax: lower 4 bytes (32 bits)
ax: lowest 2 bytes (16 bits)
al: lowest byte (8 bits)
We know that our value can only range fromn 0-7, which doesn't take more than one byte, which means we can write to al
instead of eax
. I will be going with Attack Up L, aka 6
. This will make our new instruction:
mov al,6
Which evaluates to the bytes: b6 06
. Only two bytes! Now having less than 3 bytes is not an issue, we can simply replace the last byte with a nop
instruction, which stands for "no operation" and just does nothing.
We can test this out directly in Cheat Engine. Simply right-click the mov eax,r15d
instruction and select Assemble
.
In the small window enter the instruction mov al,6
like decided earlier, and hit OK
. It will open another pop-up asking you if you want to replace the unused bytes with nop
s, hit Yes
.
Now go back to the game and use the coral orchestra, you will see that you will only receive the buff that you chose.
One last thing to do, is to select the newly inserted instruction, and right-click, select Goto address
, and copy the address there and save it somewhere. For me this address is: 0x1419A3FFE
. Note that this address won't change until an update makes changes to the exe.
Whew! That was a lot to take in. But what we did here will only persist until we close game. Once we restart our edit will be gone. However we now have the address at which we need to replace the instruction. We could of course manually replace it each time we launch the game, but that would be kind of annoying wouldn't it.
Instead we now write the actual plugin to do this for us. (The entire purpose of this guide)
Now we get to the actual plugin writing of the guide which, ironically, is much shorter than the part before. However this will almost always be the case with Memory modding. The reverse engineering is the part that takes time, while making a tool out of the acquired information only takes a short time for the most part.
First, go ahead and create a new dll project in Visual Studio. This will be a very barebones plugin, all it will do is overwrite the instruction and then immediately exit.
Which means our plugin will do this:
- Enter
DllMain
- Check if
DLL_PROCESS_ATTACH
- Patch instruction
- Exit
Skeleton code of DllMain
looks like this:
BOOL APIENTRY DllMain(HMODULE hDll, DWORD dwReason, LPVOID lpReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
// patch instruction
return true;
}
Simple right? We now need a function that does the patching for us. Let's write one! The function we're gonna write is gonna take an address, and a list of bytes to patch.
Internal vs. External differ here as usual, so pick the way you want to go.
First we declare the function (make sure it's above DllMain
)
Internal:
void PatchBytes(uintptr_t address, const std::vector<BYTE>& bytes)
External:
void PatchBytes(HANDLE hProc, uintptr_t address, const std::vector<BYTE>& bytes)
As usual, external needs the extra HANDLE
parameter.
We also need to pay attention that we can't simply write to executable memory. We first have to modify the page protection to allow writing to it. We can do this with the VirtualProtect
or VirtualProtectEx
functions.
Internal:
DWORD oldProt = 0;
VirtualProtect(address, bytes.size(), PAGE_EXECUTE_READWRITE, &oldProt);
External:
DWORD oldProt = 0;
VirtualProtectEx(hProc, address, bytes.size(), PAGE_EXECUTE_READWRITE, &oldProt);
The additional oldProt
parameter is where the current page protection type will be stored. We will use this to restore page protection after we're done replacing the instruction.
Now we need to actually write the bytes to the exe. Internally, we can use the function memcpy
to copy an entire set of bytes to an address, while externally we use WriteProcessMemory
as usual.
Internal:
memcpy((void*)address, bytes.data(), bytes.size());
External:
WriteProcessMemory(hProc, (LPVOID)address, bytes.data(), bytes.size(), nullptr);
And finally, as mentioned before, we restore the page protection.
Internal:
VirtualProtect(address, bytes.size(), oldProt, &oldProt);
External:
VirtualProtectEx(hProc, address, bytes.size(), oldProt, &oldProt);
Now we pass oldProt
as the new protection argument, which restores the page protection to what it was before.
Done!
Now in our DllMain
we can call the function to apply our modifications.
External needs to do the usual pre-setup work to gain the process handle. To see how to do this view the previous guides.
Internal:
if (dwReason == DLL_PROCESS_ATTACH)
{
PatchBytes(0x1419A3FFE, { 0xB6, 0x06, 0x90 });
}
External:
if (dwReason == DLL_PROCESS_ATTACH)
{
HANDLE hProc = ...; // Get process handle here
PatchBytes(hProc, 0x1419A3FFE, { 0xB6, 0x06, 0x90 });
}
A quick explanation of what the bytes in the function call mean:
0xB6, 0x06
: These are the bytes that evaluate to mov al,6
. Replace the 0x06
with whatever buff you chose.
0x90
: This is the byte for a nop
instruction. It is needed otherwise the last byte that we're replacing will not be changed and will throw off the CPU and possibly crash the game.
Now all we need to do is compile our program and put the dll in our nativePC/plugins
folder, or launch the exe if external.
That's it. Now each time you launch the game with this plugin active you will only get this specific buff.
General Tutorials
Animation Tutorials
Audio Tutorials
File & In Game IDs
- Accessory and Specialized Tool IDs (Asset)
- Armor IDs (Asset)
- Decorations IDs
- EFX IDs
- Endemic Critter IDs (Asset)
- Face IDs (Asset)
- Furniture IDs (Asset)
- Gimmick IDs (Asset)
- Hairstyle IDs (Asset)
- Item IDs
- LMT IDs
- Material IDs
- Medal IDs
- Model Bone Function IDs
- Monster IDs
- Monster Shell IDs (A-P)
- Monster Shell IDs (Q-Z)
- NPC IDs (Asset)
- NPC Base Model (Asset)
- Palico Equipment IDs (Asset)
- Pendant IDs (Asset)
- Poogie Clothes IDs
- Quest IDs
- Skill IDs
- Stage IDs (Asset)
- Player Weapon Action IDs
- Weapon IDs (Asset)
- Weapon ID Dump (GS,SnS,DB)
- Weapon ID Dump (LS,Ham,HH)
- Weapon ID Dump (Lan,GL)
- Weapon ID Dump (SA,CB)
- Weapon ID Dump (Bow,HBG,LBG)
Model Tutorials
- Quick Guide to Importing Models (Blender 2.79)
- Walkthrough of a Weapon Import
- Basics of Exporting Into the .mod3 Format
- How To Fix UVs Sharing a Seam
- How To Separate Mesh Parts in Blender
- Rotating, Moving and Resizing in Blender
- How to Split a Single Mesh Outfit into Player Equippable Parts
- Jigglebone Chains (.ctc) and Colliders (.ccl)
- Axial CTC Rotations
- Editing Hair Models and Materials
- Useful Blender Scripts
- [external page] How to Make All Polygons Into Triangles (Required for MHW models)
- [external page] How to Convert Triangles Back Into Quads
- [external page] How to Change The View-port clipping Parameters For Large Models
- [external page] How to Set Origin to Vertex in Edit Mode
- [external page] Shortcut to repeat the last operation in Blender
- [external page] Transferring Rig Weights From One Mesh To Another
- [external page] How to Copy Paint Weights From One Object to Another
- [external page] How to Remove All Zero-Weight Vertex Groups
- [external page] How to Weight Paint Against Current Pose
- [external page] Making a Hair Rig
- [external page] Physics Transfer
EFX Tutorials
FSM Editing
MRL3 Tutorials
NPC Editing
Plugins and Memory Editing
Monster AI Editing
General Texture Tutorials
- Obtaining, Converting and Replacing Textures
- Textures with Paint NET
- How To Open DDS Files
- Editing Textures
- Understanding Alpha Channels
- Exporting Textures with Transparency
- How To Achieve Glass Texture
- How to create Crystal materials with Alpha Textures
- Working Around the Mip Map Loading Bug
- [external page] Extracting a UV Layout
Specific Texture Tutorials
Asterisk's Plugin Notes
Miscellaneous Tutorials
Outdated Tutorials
- How to Make an NPC Skin Material Support Skin Color Customization
- Plugin Comparison
- A Theoretical Proposal on Skeleton Extension
- Monster Hunter World Save Editor Tutorial
- Making Copies of a Save Slot
- Making a Green Screen
- Notes on CTC Physics Jiggle Files
- Opening MRL3 file with a template
- Transferring MRL3 Files Between Models
- Expanding mrl3 Reference List
- How to Edit Eye Color in mrl3 Files