Skip to content

Assembly Modification

Psymon edited this page Oct 16, 2023 · 6 revisions

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.

Table of contents

Requirements

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.

Setting a goal

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.

Finding the code that decides the buff

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.

Pointer

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.

Data Breakpoints

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.

image

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.

image2

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.

The Memory Viewer

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.

Break and Trace

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.

image3

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.

image4

Now right-click anywhere in the window and click Expand all.

image5

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:

image6

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:

image7

Breakpoints

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.

image8

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.

image9

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.

image10

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.

Data Breakpoints: Part 2

Now in the top left of the bottom section of the window, you should see a value ##, followed by four 00s. 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.

image11

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:

image12

Return Values and Function Calls

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:

image13

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:

image14

The Switch Case

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:

image15

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.

Replacing the Instruction

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.

image16

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 nops, 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)

Writing the Plugin

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:

  1. Enter DllMain
  2. Check if DLL_PROCESS_ATTACH
  3. Patch instruction
  4. 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.

Writing the Patching Function

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!

Calling the Function

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.

Finishing Up

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

General Tutorials

Animation Tutorials

Animation Tutorials

Audio Tutorials:

Audio Tutorials

IDs:

File & In Game IDs

Model Tutorials:

Model Tutorials

Effects Tutorials:

EFX Tutorials

FSM Tutorials

FSM Editing

MRL3 Tutorials:

MRL3 Tutorials

NPC Editing:

NPC Editing

Map Editing:

Map Editing

Plugins and Memory Editing:

Plugins and Memory Editing

Quest Editing:

Quest Editing

Monster AI Editing:

Monster AI Editing

Texture Tutorials:

General Texture Tutorials
Specific Texture Tutorials

TIML Editing

TIML Editing

Asterisk's Plugin Notes:

Asterisk's Plugin Notes

Miscellaneous Tutorials:

Miscellaneous Tutorials

Outdated Tutorials:

Outdated Tutorials
Clone this wiki locally
  翻译: