Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Have you ever thought to yourself - "man I wish I could nerf Maintenance so Nitori doesn't feel so insane" or even "what if Maintenance tripled all bonus instead of doubling it". With the power of wasting several hours on a whim to patch Maintenance's effect, now you too can play around with its multiplier. Here's a brief summary of my journey:
- 01. How Maintenance works in the code
- 02. Plan A - make a new function
- 03. 3peso magic
- 04. Plan B - patch the existing function
- 05. Plan C - make our own magic
- 06. The aftermath
- 07. Patch instructions
- 08. The actual code
- ==================================================================
- 01. How Maintenance works in the code
- Using ghidra and some cheat engine, we can help isolate the piece of code that checks if the character has Maintenance learned, and what to do when that is the case. The basic of it looks like this, in some C-like pseudo-code:
- void apply_bonus(character, equip, bonus):
- for each stat in the game, except hp-rec and mp-rec:
- character.stat = character.stat + (character.stat_raw * (bonus.stat + 100.0) / 100.0);
- if equip increases hp-rec:
- character.hp_rec = character.hp_rec + bonus.hp_rec
- if equip increases mp-rec:
- character.mp_rec = character.mp_rec + bonus.mp_rec
- int skill_level(character, skill_id):
- if character cannot learn skill_id:
- return 0;
- else:
- return character.skills.find(skill_id).level;
- int maintenance_id = 0x101;
- int prince_shotoku_oop_art_id = 0x167;
- for each equip_slot of the 4 equip slots:
- if equip_slot is not empty:
- apply_bonus(character, equip_slot.equip, equip_slot.equip.bonus);
- if skill_level(character, maintenance_id) > 0:
- apply_bonus(character, equip_slot.equip, equip_slot.equip.bonus);
- if skill_level(character, prince_shotoku_oop_art_id) > 0:
- apply_bonus(character, equip_slot.equip, equip_slot.equip.bonus);
- So as you can see, the function apply_bonus is just called twice, hence why Maintenance just doubles your stat gains, and why it doesn't affect special effects like Quartz Charm's damage increase/decrease. Note that if a character learned both Maintenance and Shotoku's OOP Artifact, the equip bonus would triple, NOT quadruple.
- Note also that stat calculation is performed using floating point numbers (doubles, in this case). So at very, very high values, some imprecision can occur due to the magic that makes decimals work - rounding errors can happen with values above 9,007,199,254,740,993 (9 quadrillion and some change, or 2^53 + 1). The stat cap in the game is 10 quadrillion, so if you do get close to the cap you could actually see these rounding errors!
- The question now becomes: how can we nerf Maintenance, without affecting how regular equipment works? The most naive solution is to reduce one of those 100.0 values in the apply_bonus function to something like 50.0 to try and make Maintenance 50%, but now you've just halved the equipment bonus for everyone else, while Maintenance gets the original value.
- ==================================================================
- 02. Plan A - make a new function
- If we duplicte the original apply_bonus function, and change the function calls to use this new function instead, we can change by how much Maintenance increases stats. The entire function is 0x6AF bytes long, so we look for empty spaces in the EXE that have at least that many bytes empty. There are plenty of candidates, so we pick whatever is closest to the original function and clone it away:
- for each equip_slot of the 4 equip slots:
- if equip_slot is not empty:
- apply_bonus(character, equip_slot.equip, equip_slot.equip.bonus);
- if skill_level(character, maintenance_id) > 0:
- apply_bonus_clone_a(character, equip_slot.equip, equip_slot.equip.bonus);
- if skill_level(character, prince_shotoku_oop_art_id) > 0:
- apply_bonus_clone_b(character, equip_slot.equip, equip_slot.equip.bonus);
- Simple enough. After loading the game, though, you get a game crash when loading the save file. Looking into the debugger, it seems the 2 minor function calls made inside the apply_bonus function are jumping off to the wrong places in memory. This is expected, since we cloned the original function byte by byte into the new address, and the call functions use relative offsets to tell the CPU where to go next:
- Inside apply_bonus:
- (0x8000) e8 00 f0 ff ff - CALL -0x1000
- (0x7005) [beginning of function]
- Inside apply_bonus_clone_a:
- (0x8800) e8 00 f0 ff ff - CALL -0x1000
- (0x7805) [some random code]
- Note the original function properly jumps into the beginning of the function, located at 0x7005, but the new function jumps 0x800 bytes ahead of the intended address, because the new function is offset from the original function's address by 0x800 bytes.
- In order to correct this, we must adjust the CALL instruction inside the clone function to jump to the proper address, by changing its offset:
- Inside apply_bonus_clone_a:
- (0x8800) e8 00 e8 ff ff - CALL -0x1800
- (0x7005) [beginning of function]
- That was an easy enough fix. After loading the game, though, you find out that your Nitori now has negative 3 trillion HP. Unequipping her main equip brings her to 2 billion HP. Something is clearly wrong here, but how? We cloned the function byte by byte, and adjusted the offsets, it's even avoiding any crashes. Other characters have their proper stats, so it must be something with our cloned function.
- ==================================================================
- 03. 3peso magic
- Loading up the game on a debugger, we can add breakpoints and step through the code to make sure everything is being loaded and being processed correctly. After some work to find the exact place in memory to add a breakpoint, we see the following snippet of code when computing by how much to increase the character stats:
- f2 0f 2a 84 82 70 98 13 02 cvtsi2sd xmm0,DWORD PTR [edx+eax*4+0x2139870]
- 8b 55 0c mov edx,DWORD PTR [ebp+0xc]
- f2 0f 2a 0c 95 f0 59 13 02 cvtsi2sd xmm1,DWORD PTR [edx*4+0x21359f0]
- The first instruction converts the character's current stat into a double, and stores it in register XMM0. The second instruction loads the equipment ID into register EDX. The last instruction loads that ID's bonus to the current stat inside the register XMM1 as a double. The piece of heart, for example, would load a value of 10 for HP and 0 for everything else.
- However, when stepping through, we see that the game is loading negative 1 million as Nitori's HP stat, and positive 300 million as the equipment bonus. After doing the math, Nitori ends up with negative 3 trillion HP. How is this possible, when the exact same code is being run in the original function, and it's working as intended?
- Maybe we can confirm as a sanity check that everything is working fine in the original function. The exact same instructions show up as follows:
- f2 0f 2a 84 82 70 98 56 02 cvtsi2sd xmm0,DWORD PTR [edx+eax*4+0x2569870]
- 8b 55 0c mov edx,DWORD PTR [ebp+0xc]
- f2 0f 2a 0c 95 f0 59 56 02 cvtsi2sd xmm1,DWORD PTR [edx*4+0x25659f0]
- Did you notice the difference? It's very subtle, but the displacements are different. They are off by 0x430000 bytes. How did this happen? Did something change the original code? Maybe I edited the wrong offsets?
- Well that's weird, but fine, we can change the offsets like we did with the CALL instructions to make things like up, whatever it is that's messing things up. We load the game again, and everything is jumbled just like before, but the values change slightly. Checking the original, unchanged function, we see:
- f2 0f 2a 84 82 70 98 59 02 cvtsi2sd xmm0,DWORD PTR [edx+eax*4+0x2599870]
- 8b 55 0c mov edx,DWORD PTR [ebp+0xc]
- f2 0f 2a 0c 95 f0 59 59 02 cvtsi2sd xmm1,DWORD PTR [edx*4+0x25959f0]
- It's now off by an additional 0x30000 compared to the previous run? What exactly is going on here? Something has to be patching the exe when it's loaded to make sure these offsets align, but only for the original function and not for our clone.
- Maybe it's some leftover debugger feature to help with automatic debugging / crash reporting, maybe it's some insane 3peso magic to obfuscate the memory displacements, maybe it's some weird Windows thing, or maybe this is completely normal and I just don't know how computers work. I'm betting on the last option.
- Regardless, we need a new plan, since whatever is doing this seems to only apply this offset to instructions that are "previously known" to the exe, as our new code was left alone, to point to the wrong address in memory.
- ==================================================================
- 04. Plan B - patch the existing function
- Alright, so something is automatically changing the instructions with static displacements to point to the correct offset after the game is loaded in memory. So the next best course of action is to somehow patch the original function to do what we want instead. Something like changing the original 100.0 to 200.0 if Maintenance is learned.
- This means we will internalize the logic that was initially outside the function, and we're gonna need to make room in the EXE for this. Thankfully, the original function is padded with empty bytes with which we can play with. There aren't many - not enough to clone the entire function, but enough to hack something together.
- And so we change the original logic from having a hardcoded 100.0 boost to a variable one, as described. The new function looks somewhat like this:
- void apply_bonus(character, equip, bonus):
- boost = 100.0;
- if skill_level(character, maintenance_id) > 0:
- boost = boost + 100.0;
- if skill_level(character, prince_shotoku_oop_art_id) > 0:
- boost = boost + 100.0;
- for each stat in the game, except hp-rec and mp-rec:
- character.stat = character.stat + (character.stat_raw * (bonus.stat + boost) / 100.0);
- if equip increases hp-rec:
- character.hp_rec = character.hp_rec + bonus.hp_rec
- if equip increases mp-rec:
- character.mp_rec = character.mp_rec + bonus.mp_rec
- Simple enough, we just copy the logic that was outside the function to inside of it. In order to create this new variable, however, we're gonna need some scratch registers. We can't use the memory for that, since we don't know which addresses are being used by the game for other things, and overwriting them could be very, very bad.
- We could just use the stack as a register, by pushing / popping values, but because we're calling the skill_level function in the middle of this computation, and the functions share arguments, the CPU needs the stack to be perfectly neat and organized, which is doable, but hard to do when you must do it yourself without a compiler to help you out. Thankfully we can just use the XMM registers in the FPU, so problem solved!
- We load the game and... Crash. Stepping through the debugger, it seems our new logic ran perfectly fine, and it crashed somewhere between it and finalizing the new stat computation. Loading up the debugger, we see the cause of the crash are these instructions:
- 8b 45 f8 mov eax,DWORD PTR [ebp-0x8]
- a3 08 8b 55 08 mov ds:0x8558b08,eax
- So we load something into EAX, sure, this is normal. But then we write EAX into memory, at a position that is displaced sp far beyond what is reachable by the game, it causes a crash. Was this instruction really in the original code? Looking at the raw EXE hex data:
- 8b 45 f8 mov eax,DWORD PTR [ebp-0x8]
- 8b 08 mov ecx,DWORD PTR [eax]
- 8b 55 08 mov edx,DWORD PTR [ebp+0x8]
- So the instructions got corrupted? That second instruction became something entirely different, because 0x8b got changed into 0xa3 for apparently no reason. It's not an instruction that has a fixed displacement either, so whatever logic that was changing bytes earlier shouldn't really be touching this, right?
- Remember how we added code before the original function's code? Well that shifted the original code forward in memory, since we had to fit the new code in. This isn't really supposed to be a problem since there is empty space after the function ends, but clearly something went wrong here.
- Comparing the new code to what was written there originally, we notice that the 0x8b that got corrupted is in the exact same offset of a static displacement in the original code. Remember how the 0x13 got changed to 0x56? Well that byte occupied the exact same position as our current corrupted byte.
- So what is there left that we can do?
- ==================================================================
- 05. Plan C - make our own magic
- So we clearly need to leave the original code where it is. At least the instructions with static displacements. To make matters worse, the original code that adds 100.0 and divides by 100.0 both use static displacement:
- f2 0f 58 0d 80 e7 d1 01 ADDSD XMM1,qword ptr ds:0x01d1e780
- f2 0f 5e 05 80 e7 d1 01 DIVSD XMM1,qword ptr ds:0x01d1e780
- The memory position those instructions point to is just a static reference to the value 100.0. I suspect the compiler does this because there is no instruction to load a scalar value straight into the FPU registers, you gotta go through memory or a separate register. And it's faster to just load it from a static place in memory.
- Because there is so much coupling between the computing logic and these displacements, we're gonna need a smarter solution: rewrite portions of the code in our empty area after the function, and jump to it from inside the function, in between displacement instructions!
- First of all, we need to get rid of those ADDSD and DIVSD instructions, since they're at the core of our code and we can write them with new code that doesn't rely on displacements:
- mov eax,0x64 // load 100 into EAX
- cvtsi2sd xmm2,eax // convert 100 in EAX to 100.0 in XMM2
- addsd xmm1,xmm2 // add XMM2 into XMM1 (which already has equipment bonus value)
- mulsd xmm0,xmm1 // multiply XMM1 by XMM0 (which already has the current stat value)
- cvtsi2sd xmm1,eax // convert 100 in EAX to 100.0 in XMM1
- divsd xmm0,xmm1 // divide the previous result by 100.0
- Next, we need to leave the original code as is, until the last instruction that uses a displacement instruction, but before the original ADDSD. We then NOP everything between that and the instruction after the original DIVSD instruction, so that we simply skip over the problematic bytes that get corrupted:
- cvtsi2sd xmm1,DWORD PTR [edx*4+0x21359f0]
- nop
- nop
- ...
- nop
- nop
- cvttsd2si eax,xmm0
- But where does the code go into, you might ask. Well it goes right after the function's last instruction - RET 0xc. We now need to divert execution from the first NOP into our new block of code, then back to the CVTTSD2SI instruction:
- cvtsi2sd xmm1,DWORD PTR [edx*4+0x21359f0]
- jmp VERY_FAR_AFTER_RET
- nop
- ...
- nop
- nop
- MOVE_BACK:
- cvttsd2si eax,xmm0
- ...
- ...
- ...
- ret 0xc
- VERY_FAR_AFTER_RET:
- cvtsi2sd xmm2,DWORD PTR [ebp+0x10]
- mulsd xmm1,xmm2
- mov eax,0x64
- cvtsi2sd xmm2,eax
- addsd xmm1,xmm2
- mulsd xmm0,xmm1
- cvtsi2sd xmm1,eax
- divsd xmm0,xmm1
- jmp MOVE_BACK
- With this, we successfully execute all of our new logic without any corruption, and all the stats get loaded correctly when we load the game (even though we're still on the vanilla Maintenance numbers).
- Some of the NOP instructions will get corrupted, but that doesn't matter since they will never be executed in the first place, thanks to the jumping around. Now all that's left is to add the skill checking logic to this bottom part where we add 100.0 and divide by 100.0:
- VERY_FAR_AFTER_RET:
- cvtsi2sd xmm2,DWORD PTR [ebp+0x10]
- mulsd xmm1,xmm2
- mov eax,0x64
- cvtsi2sd xmm2,eax
- addsd xmm1,xmm2
- push 0x101
- mov ecx,DWORD PTR [ebp+0x8]
- push ecx
- mov edx,DWORD PTR [ebp-0x8]
- mov ecx,DWORD PTR [edx]
- call SKILL_LEVEL
- cmp eax,0x1
- jl LABEL_NO_MAINTENANCE
- mov eax,0x64
- cvtsi2sd xmm2,eax
- addsd xmm1,xmm2
- LABEL_NO_MAINTENANCE:
- push 0x167
- mov ecx,DWORD PTR [ebp+0x8]
- push ecx
- mov edx,DWORD PTR [ebp-0x8]
- mov ecx,DWORD PTR [edx]
- call SKILL_LEVEL
- cmp eax,0x1
- jl LABEL_NO_OOP_ARTIFACT
- mov eax,0x64
- cvtsi2sd xmm2,eax
- addsd xmm1,xmm2
- LABEL_NO_OOP_ARTIFACT:
- mulsd xmm0,xmm1
- mov eax,0x64
- cvtsi2sd xmm1,eax
- divsd xmm0,xmm1
- jmp MOVE_BACK
- And now when we load the game..... Another crash. Did the corruption find its way inside the new piece of code? Did the NOPs corrupt too much and are somehow affecting the instructions down here? As it turns out, a trip to the debugger will reveal that it's much simpler:
- In order to call the skill_level function, we had to carefully manipulate ECX so that its state is the same as it would normally be when calling the function (it actually matters because the leftover debugger will use ECX to validate that the stack is correct and the return addresses make sense). Not only that, skill_level makes use of ECX and leaves it dirty post-execution, so the original value that was in it was completely corrupted when we added this new code.
- All we need to do to fix this is to restore it to its previous value after the function runs. Simply push ECX into the stack before the function call, and pop it after... But now the game crashes because the stack size is "corrupted" when we called skill_level. Because the leftover debugger isn't aware of our new code, it sees an improper stack size when the function is called and causes an exception that 3peso obviously doesn't catch.
- So the last resort is to calculate its original value once again after the function. But the sad news is that the original value depends on a static displacement instruction... Would be funny if that were the case, but thank all the gods that's not reality. It's just some simple math and register manipulation:
- mov eax,DWORD PTR [ebp-0x8]
- mov ecx,DWORD PTR [eax]
- mov edx,DWORD PTR [ebp+0x8]
- mov ecx,DWORD PTR [ecx+edx*4+0x14]
- Load the game now and everything works properly! Tweaking the 0x64 values in the MOV instructions we added now correctly tweaks how Maintenance and Shotoku's OOP artifact behave!
- ==================================================================
- 06. The aftermath
- You might have noticed that the proposed solution changes the original 100.0 into 200.0 if either skill is learned, and because this function is called twice, an extra time if the skill is learned, we end up applying the 2x multiplier twice... Making Maintenance give 4x the equipment bonus. Oops.
- The fix for this is very simple, just NOP out the extra calls to apply_bonus that are done if the skills are learned. Now that it's only called once, not only is it more optimized, it also gives you full control over how strong Maintenance can be.
- Swap out the mov eax,0x64 with mov eax,0x32 and Maintenance now only gives 50% extra equipment bonus. Swap it with 0xc8 and it's now a 3x boost. Swap it with 0x9cffffff and it's now zero bonus, making it negate all equipment stat boosts.
- We can go even further beyond and multiply Maintenance's level to make the boost dynamic. Let's say Maintenance is now a skill that caps at level 10, costing 20 points per level up, granting you 10% bonus per level. Or maybe 5% per level if you want to cap it lower. We already have the skill level being returned from our function call, it's as simple as adding an IMUL instruction of that level and 0x64 or whatever value you choose, instead of loading a hard-coded 0x64.
- ==================================================================
- 07. Patch instructions
- ALWAYS BACKUP YOUR EXE BEFORE ATTEMPTING TO HEX EDIT IT. THE EXE SIZE SHOULD NEVER INCREASE OR DECREASE, IT SHOULD ALWAYS BE EXACTLY 23,677,006 BYES LONG. THESE PATCHES ONLY WORK FOR THE DLSITE VERSION 1.107 OF THE GAME.
- >>> THIS DOES NOT APPLY TO THE STEAM EXE <<<<
- At offset 0x342710, you should see the beginning of the apply_bonus function, it should start with the bytes 55 8B EC 81 EC D8 00 00 00 53 56 57 51 8D BD 28. Starting from 55, select 0x74d bytes and paste the byte sequence below. This is the patched apply_bonus function:
- 55 8B EC 81 EC D8 00 00 00 53 56 57 51 8D BD 28 FF FF FF B9 36 00 00 00 B8 CC CC CC CC F3 AB 59 89 4D F8 B9 C0 B0 7A 02 E8 F9 FB CB FF C7 45 EC 00 00 00 00 EB 09 8B 45 EC 83 C0 01 89 45 EC 83 7D EC 1B 0F 8D A0 01 00 00 6B 45 0C 6C 8B 4D EC 83 BC 88 70 98 13 02 00 74 78 8B 45 F8 8B 08 8B 55 08 8B 4C 91 14 6B 55 0C 6C 8B 45 EC F2 0F 2A 84 82 70 98 13 02 8B 55 0C F2 0F 2A 0C 95 F0 59 13 02 E9 28 06 00 00 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 F2 0F 2C C0 99 8B 75 EC 03 84 F1 20 02 00 00 13 94 F1 24 02 00 00 8B 4D F8 8B 09 8B 75 08 8B 4C B1 14 8B 75 EC 89 84 F1 20 02 00 00 89 94 F1 24 02 00 00 6B 45 0C 6C 8B 4D EC 83 BC 88 30 E4 16 02 01 7C 78 8B 45 F8 8B 08 8B 55 08 8B 4C 91 14 6B 55 0C 6C 8B 45 EC F2 0F 2A 84 82 30 E4 16 02 8B 55 0C F2 0F 2A 0C 95 F0 59 13 02 E9 A9 05 00 00 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 F2 0F 2C C0 99 8B 75 EC 03 84 F1 F8 02 00 00 13 94 F1 FC 02 00 00 8B 4D F8 8B 09 8B 75 08 8B 4C B1 14 8B 75 EC 89 84 F1 F8 02 00 00 89 94 F1 FC 02 00 00 6B 45 0C 6C 8B 4D EC 83 BC 88 F0 2F 1A 02 00 74 78 8B 45 F8 8B 08 8B 55 08 8B 4C 91 14 6B 55 0C 6C 8B 45 EC F2 0F 2A 84 82 F0 2F 1A 02 8B 55 0C F2 0F 2A 0C 95 F0 59 13 02 E9 2A 05 00 00 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 F2 0F 2C C0 99 8B 75 EC 03 84 F1 D0 03 00 00 13 94 F1 D4 03 00 00 8B 4D F8 8B 09 8B 75 08 8B 4C B1 14 8B 75 EC 89 84 F1 D0 03 00 00 89 94 F1 D4 03 00 00 E9 4D FE FF FF 83 7D 0C 0D 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 20 01 00 00 83 C1 03 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 20 01 00 00 E9 7E 04 00 00 83 7D 0C 0E 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 20 01 00 00 83 C1 04 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 20 01 00 00 E9 4C 04 00 00 83 7D 0C 0F 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 20 01 00 00 83 C1 06 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 20 01 00 00 E9 1A 04 00 00 83 7D 0C 10 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 24 01 00 00 83 C1 01 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 24 01 00 00 E9 E8 03 00 00 83 7D 0C 11 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 24 01 00 00 83 C1 02 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 24 01 00 00 E9 B6 03 00 00 83 7D 0C 1F 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 20 01 00 00 83 E9 08 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 20 01 00 00 E9 84 03 00 00 83 7D 0C 20 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 24 01 00 00 83 E9 02 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 24 01 00 00 E9 52 03 00 00 83 7D 0C 24 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 24 01 00 00 83 C1 01 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 24 01 00 00 E9 20 03 00 00 83 7D 0C 2A 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 24 01 00 00 83 C1 01 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 24 01 00 00 E9 EE 02 00 00 83 7D 0C 2B 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 24 01 00 00 83 C1 02 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 24 01 00 00 E9 BC 02 00 00 83 7D 0C 2D 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 20 01 00 00 83 C1 03 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 20 01 00 00 E9 8A 02 00 00 83 7D 0C 2E 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 20 01 00 00 83 C1 06 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 20 01 00 00 E9 58 02 00 00 81 7D 0C 26 01 00 00 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 20 01 00 00 83 C1 02 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 20 01 00 00 E9 23 02 00 00 81 7D 0C 29 01 00 00 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 24 01 00 00 83 C1 01 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 24 01 00 00 E9 EE 01 00 00 81 7D 0C 2B 01 00 00 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 20 01 00 00 83 C1 02 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 20 01 00 00 E9 B9 01 00 00 81 7D 0C 31 01 00 00 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 20 01 00 00 83 C1 01 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 20 01 00 00 E9 84 01 00 00 81 7D 0C 3D 01 00 00 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 20 01 00 00 83 C1 02 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 20 01 00 00 E9 4F 01 00 00 81 7D 0C 40 01 00 00 75 53 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 20 01 00 00 83 C1 02 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 20 01 00 00 8B 4D F8 8B 11 8B 45 08 8B 4C 82 14 8B 91 24 01 00 00 83 C2 01 8B 45 F8 8B 08 8B 45 08 8B 4C 81 14 89 91 24 01 00 00 E9 F3 00 00 00 81 7D 0C 7D 01 00 00 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 20 01 00 00 83 C1 01 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 20 01 00 00 E9 BE 00 00 00 81 7D 0C 83 01 00 00 75 2C 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 20 01 00 00 83 C1 02 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 20 01 00 00 E9 89 00 00 00 81 7D 0C 83 01 00 00 75 29 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 20 01 00 00 83 C1 01 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 20 01 00 00 EB 57 81 7D 0C B6 01 00 00 75 4E 8B 45 F8 8B 08 8B 55 08 8B 44 91 14 8B 88 20 01 00 00 83 C1 02 8B 55 F8 8B 02 8B 55 08 8B 44 90 14 89 88 20 01 00 00 8B 4D F8 8B 11 8B 45 08 8B 4C 82 14 8B 91 24 01 00 00 83 C2 01 8B 45 F8 8B 08 8B 45 08 8B 4C 81 14 89 91 24 01 00 00 5F 5E 5B 81 C4 D8 00 00 00 3B EC E8 95 F6 CB FF 8B E5 5D C2 0C 00 E8 19 00 00 00 E9 E6 F9 FF FF E8 0F 00 00 00 E9 65 FA FF FF E8 05 00 00 00 E9 E4 FA FF FF F2 0F 2A 55 10 F2 0F 59 CA B8 64 00 00 00 F2 0F 2A D0 F2 0F 58 CA 68 01 01 00 00 8B 4D 08 51 8B 55 F8 8B 0A E8 3C 75 CC FF 83 F8 01 7C 0E 69 C0 64 00 00 00 F2 0F 2A D0 F2 0F 58 CA 68 67 01 00 00 8B 4D 08 51 8B 55 F8 8B 0A E8 16 75 CC FF 83 F8 01 7C 0E 69 C0 64 00 00 00 F2 0F 2A D0 F2 0F 58 CA F2 0F 59 C1 B8 64 00 00 00 F2 0F 2A C8 F2 0F 5E C1 8B 45 F8 8B 08 8B 55 08 8B 4C 91 14 C3
- You can verify if things are working correctly so far if Maintenance and Shotoku's OOP Artifact now quadruple equip bonuses. To make it go back to normal, we need to remove some function calls:
- Look for the hex string E8 38 1C A2 FF 83 F8 01 7C 48 in the exe. There should be only one match. Replace the 7C with EB.
- Do the same thing for the hex string E8 DC 1B A2 FF 83 F8 01 7C 48.
- Do the same thing for the hex string E8 61 49 F4 FF 83 F8 01 7C 3A.
- Do the same thing for the hex string E8 09 49 F4 FF 83 F8 01 7C 3A.
- You can verify if things are working correctly if Maintenance and Shotoku's OOP Artifact are now back to working the same as the vanilla game. The hex strings you looked for were something like:
- CALL SKILL_LEVEL
- CMP EAX, 0x1
- JL some offset
- By changing from 7C to EB, we make the JL a JMP, so we unconditionally jump away from the extra function calls to apply_bonus. Another possible patch is to change the 0x1 in the CMP into something like 0x7F, to make the call only work if the skill level is above 127.
- Finally, in order to patch Maintenance / Shotoku's OOP artifact to give it more/less than 100% extra equip bonus, or just change the entire logic. There are four instances of the hex string 64 00 00 00 in the big hex string you pasted over the original function. Here's what each of them do:
- 1st (A) - Base multiplier for all equips for all characters
- 2nd (B) - Extra multiplier for all equips only if character has Maintenance
- 3rd (C) - Extra multiplier for all equips only if character has Shotoku's OOP artifact
- 4th (D) - The divider for all equips for all characters
- In math terms:
- stat = (stat + (equip * (A + slv*B + slv*C) / D))
- Where slv is the skill level, making B and C result in zero if the skill is unavailable or not learned yet. Changing that 64 00 00 00 hex string will change the value for each of these constants. Remember the values are in hexadecimal, and in little endian format:
- 64 00 00 00 = 100 decimal
- 32 00 00 00 = 50 decimal
- 00 01 00 00 = 256 decimal
- Be careful of negative numbers in two's complement if you go over FF FF FF 7F:
- FF FF FF 7F = 2147483647 decimal
- 00 00 00 80 = -2147483648 decimal
- FF FF FF FF = -1 decimal
- ==================================================================
- 08. The actual code
- This is what the relevant part of the apply_bonus function looks like after all the patching, without any simplifications for the explanations above. Only the part with the "for each stat..." is included:
- jmp LABEL_A
- LABEL_F:
- mov eax,DWORD PTR [ebp-0x14]
- add eax,0x1
- mov DWORD PTR [ebp-0x14],eax
- LABEL_A:
- cmp DWORD PTR [ebp-0x14],0x1b
- jge LABEL_B
- imul eax,DWORD PTR [ebp+0xc],0x6c
- mov ecx,DWORD PTR [ebp-0x14]
- cmp DWORD PTR [eax+ecx*4+0x2139870],0x0
- je LABEL_C
- mov eax,DWORD PTR [ebp-0x8]
- mov ecx,DWORD PTR [eax]
- mov edx,DWORD PTR [ebp+0x8]
- mov ecx,DWORD PTR [ecx+edx*4+0x14]
- imul edx,DWORD PTR [ebp+0xc],0x6c
- mov eax,DWORD PTR [ebp-0x14]
- cvtsi2sd xmm0,DWORD PTR [edx+eax*4+0x2139870]
- mov edx,DWORD PTR [ebp+0xc]
- cvtsi2sd xmm1,DWORD PTR [edx*4+0x21359f0]
- jmp LABEL_FARFAR1
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- LABEL_BACK1:
- cvttsd2si eax,xmm0
- cdq
- mov esi,DWORD PTR [ebp-0x14]
- add eax,DWORD PTR [ecx+esi*8+0x220]
- adc edx,DWORD PTR [ecx+esi*8+0x224]
- mov ecx,DWORD PTR [ebp-0x8]
- mov ecx,DWORD PTR [ecx]
- mov esi,DWORD PTR [ebp+0x8]
- mov ecx,DWORD PTR [ecx+esi*4+0x14]
- mov esi,DWORD PTR [ebp-0x14]
- mov DWORD PTR [ecx+esi*8+0x220],eax
- mov DWORD PTR [ecx+esi*8+0x224],edx
- LABEL_C:
- imul eax,DWORD PTR [ebp+0xc],0x6c
- mov ecx,DWORD PTR [ebp-0x14]
- cmp DWORD PTR [eax+ecx*4+0x216e430],0x1
- jl LABEL_D
- mov eax,DWORD PTR [ebp-0x8]
- mov ecx,DWORD PTR [eax]
- mov edx,DWORD PTR [ebp+0x8]
- mov ecx,DWORD PTR [ecx+edx*4+0x14]
- imul edx,DWORD PTR [ebp+0xc],0x6c
- mov eax,DWORD PTR [ebp-0x14]
- cvtsi2sd xmm0,DWORD PTR [edx+eax*4+0x216e430]
- mov edx,DWORD PTR [ebp+0xc]
- cvtsi2sd xmm1,DWORD PTR [edx*4+0x21359f0]
- jmp LABEL_FARFAR2
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- LABEL_BACK2:
- cvttsd2si eax,xmm0
- cdq
- mov esi,DWORD PTR [ebp-0x14]
- add eax,DWORD PTR [ecx+esi*8+0x2f8]
- adc edx,DWORD PTR [ecx+esi*8+0x2fc]
- mov ecx,DWORD PTR [ebp-0x8]
- mov ecx,DWORD PTR [ecx]
- mov esi,DWORD PTR [ebp+0x8]
- mov ecx,DWORD PTR [ecx+esi*4+0x14]
- mov esi,DWORD PTR [ebp-0x14]
- mov DWORD PTR [ecx+esi*8+0x2f8],eax
- mov DWORD PTR [ecx+esi*8+0x2fc],edx
- LABEL_D:
- imul eax,DWORD PTR [ebp+0xc],0x6c
- mov ecx,DWORD PTR [ebp-0x14]
- cmp DWORD PTR [eax+ecx*4+0x21a2ff0],0x0
- je LABEL_E
- mov eax,DWORD PTR [ebp-0x8]
- mov ecx,DWORD PTR [eax]
- mov edx,DWORD PTR [ebp+0x8]
- mov ecx,DWORD PTR [ecx+edx*4+0x14]
- imul edx,DWORD PTR [ebp+0xc],0x6c
- mov eax,DWORD PTR [ebp-0x14]
- cvtsi2sd xmm0,DWORD PTR [edx+eax*4+0x21a2ff0]
- mov edx,DWORD PTR [ebp+0xc]
- cvtsi2sd xmm1,DWORD PTR [edx*4+0x21359f0]
- jmp LABEL_FARFAR3
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- LABEL_BACK3:
- cvttsd2si eax,xmm0
- cdq
- mov esi,DWORD PTR [ebp-0x14]
- add eax,DWORD PTR [ecx+esi*8+0x3d0]
- adc edx,DWORD PTR [ecx+esi*8+0x3d4]
- mov ecx,DWORD PTR [ebp-0x8]
- mov ecx,DWORD PTR [ecx]
- mov esi,DWORD PTR [ebp+0x8]
- mov ecx,DWORD PTR [ecx+esi*4+0x14]
- mov esi,DWORD PTR [ebp-0x14]
- mov DWORD PTR [ecx+esi*8+0x3d0],eax
- mov DWORD PTR [ecx+esi*8+0x3d4],edx
- LABEL_E:
- jmp LABEL_F
- LABEL_B:
- // the rest of the function
- ...
- ret 0xc
- LABEL_FARFAR1:
- call MFUNC
- jmp LABEL_BACK1
- LABEL_FARFAR2:
- call MFUNC
- jmp LABEL_BACK2
- LABEL_FARFAR3:
- call MFUNC
- jmp LABEL_BACK3
- MFUNC:
- cvtsi2sd xmm2,DWORD PTR [ebp+0x10]
- mulsd xmm1,xmm2
- mov eax,0x64
- cvtsi2sd xmm2,eax
- addsd xmm1,xmm2
- push 0x101
- mov ecx,DWORD PTR [ebp+0x8]
- push ecx
- mov edx,DWORD PTR [ebp-0x8]
- mov ecx,DWORD PTR [edx]
- call SKILL_LEVEL
- cmp eax,0x1
- jl LABEL_NO_MAINTENANCE
- imul eax,eax,0x64
- cvtsi2sd xmm2,eax
- addsd xmm1,xmm2
- LABEL_NO_MAINTENANCE:
- push 0x167
- mov ecx,DWORD PTR [ebp+0x8]
- push ecx
- mov edx,DWORD PTR [ebp-0x8]
- mov ecx,DWORD PTR [edx]
- call SKILL_LEVEL
- cmp eax,0x1
- jl LABEL_NO_OOP_ARTIFACT
- imul eax,eax,0x64
- cvtsi2sd xmm2,eax
- addsd xmm1,xmm2
- LABEL_NO_OOP_ARTIFACT:
- mulsd xmm0,xmm1
- mov eax,0x64
- cvtsi2sd xmm1,eax
- divsd xmm0,xmm1
- mov eax,DWORD PTR [ebp-0x8]
- mov ecx,DWORD PTR [eax]
- mov edx,DWORD PTR [ebp+0x8]
- mov ecx,DWORD PTR [ecx+edx*4+0x14]
- ret
Add Comment
Please, Sign In to add comment