Thurler

Nerfing maintenance and prince shotoku's oop artifact

Apr 13th, 2023 (edited)
160
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 32.12 KB | None | 0 0
  1. 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:
  2.  
  3. 01. How Maintenance works in the code
  4. 02. Plan A - make a new function
  5. 03. 3peso magic
  6. 04. Plan B - patch the existing function
  7. 05. Plan C - make our own magic
  8. 06. The aftermath
  9. 07. Patch instructions
  10. 08. The actual code
  11.  
  12. ==================================================================
  13.  
  14. 01. How Maintenance works in the code
  15.  
  16. 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:
  17.  
  18. void apply_bonus(character, equip, bonus):
  19. for each stat in the game, except hp-rec and mp-rec:
  20. character.stat = character.stat + (character.stat_raw * (bonus.stat + 100.0) / 100.0);
  21. if equip increases hp-rec:
  22. character.hp_rec = character.hp_rec + bonus.hp_rec
  23. if equip increases mp-rec:
  24. character.mp_rec = character.mp_rec + bonus.mp_rec
  25.  
  26. int skill_level(character, skill_id):
  27. if character cannot learn skill_id:
  28. return 0;
  29. else:
  30. return character.skills.find(skill_id).level;
  31.  
  32. int maintenance_id = 0x101;
  33. int prince_shotoku_oop_art_id = 0x167;
  34.  
  35. for each equip_slot of the 4 equip slots:
  36. if equip_slot is not empty:
  37. apply_bonus(character, equip_slot.equip, equip_slot.equip.bonus);
  38. if skill_level(character, maintenance_id) > 0:
  39. apply_bonus(character, equip_slot.equip, equip_slot.equip.bonus);
  40. if skill_level(character, prince_shotoku_oop_art_id) > 0:
  41. apply_bonus(character, equip_slot.equip, equip_slot.equip.bonus);
  42.  
  43. 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.
  44.  
  45. 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!
  46.  
  47. 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.
  48.  
  49. ==================================================================
  50.  
  51. 02. Plan A - make a new function
  52.  
  53. 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:
  54.  
  55. for each equip_slot of the 4 equip slots:
  56. if equip_slot is not empty:
  57. apply_bonus(character, equip_slot.equip, equip_slot.equip.bonus);
  58. if skill_level(character, maintenance_id) > 0:
  59. apply_bonus_clone_a(character, equip_slot.equip, equip_slot.equip.bonus);
  60. if skill_level(character, prince_shotoku_oop_art_id) > 0:
  61. apply_bonus_clone_b(character, equip_slot.equip, equip_slot.equip.bonus);
  62.  
  63. 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:
  64.  
  65. Inside apply_bonus:
  66. (0x8000) e8 00 f0 ff ff - CALL -0x1000
  67. (0x7005) [beginning of function]
  68.  
  69. Inside apply_bonus_clone_a:
  70. (0x8800) e8 00 f0 ff ff - CALL -0x1000
  71. (0x7805) [some random code]
  72.  
  73. 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.
  74.  
  75. 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:
  76.  
  77. Inside apply_bonus_clone_a:
  78. (0x8800) e8 00 e8 ff ff - CALL -0x1800
  79. (0x7005) [beginning of function]
  80.  
  81. 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.
  82.  
  83. ==================================================================
  84.  
  85. 03. 3peso magic
  86.  
  87. 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:
  88.  
  89. f2 0f 2a 84 82 70 98 13 02 cvtsi2sd xmm0,DWORD PTR [edx+eax*4+0x2139870]
  90. 8b 55 0c mov edx,DWORD PTR [ebp+0xc]
  91. f2 0f 2a 0c 95 f0 59 13 02 cvtsi2sd xmm1,DWORD PTR [edx*4+0x21359f0]
  92.  
  93. 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.
  94.  
  95. 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?
  96.  
  97. 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:
  98.  
  99. f2 0f 2a 84 82 70 98 56 02 cvtsi2sd xmm0,DWORD PTR [edx+eax*4+0x2569870]
  100. 8b 55 0c mov edx,DWORD PTR [ebp+0xc]
  101. f2 0f 2a 0c 95 f0 59 56 02 cvtsi2sd xmm1,DWORD PTR [edx*4+0x25659f0]
  102.  
  103. 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?
  104.  
  105. 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:
  106.  
  107. f2 0f 2a 84 82 70 98 59 02 cvtsi2sd xmm0,DWORD PTR [edx+eax*4+0x2599870]
  108. 8b 55 0c mov edx,DWORD PTR [ebp+0xc]
  109. f2 0f 2a 0c 95 f0 59 59 02 cvtsi2sd xmm1,DWORD PTR [edx*4+0x25959f0]
  110.  
  111. 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.
  112.  
  113. 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.
  114.  
  115. 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.
  116.  
  117. ==================================================================
  118.  
  119. 04. Plan B - patch the existing function
  120.  
  121. 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.
  122.  
  123. 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.
  124.  
  125. 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:
  126.  
  127. void apply_bonus(character, equip, bonus):
  128. boost = 100.0;
  129. if skill_level(character, maintenance_id) > 0:
  130. boost = boost + 100.0;
  131. if skill_level(character, prince_shotoku_oop_art_id) > 0:
  132. boost = boost + 100.0;
  133. for each stat in the game, except hp-rec and mp-rec:
  134. character.stat = character.stat + (character.stat_raw * (bonus.stat + boost) / 100.0);
  135. if equip increases hp-rec:
  136. character.hp_rec = character.hp_rec + bonus.hp_rec
  137. if equip increases mp-rec:
  138. character.mp_rec = character.mp_rec + bonus.mp_rec
  139.  
  140. 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.
  141.  
  142. 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!
  143.  
  144. 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:
  145.  
  146. 8b 45 f8 mov eax,DWORD PTR [ebp-0x8]
  147. a3 08 8b 55 08 mov ds:0x8558b08,eax
  148.  
  149. 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:
  150.  
  151. 8b 45 f8 mov eax,DWORD PTR [ebp-0x8]
  152. 8b 08 mov ecx,DWORD PTR [eax]
  153. 8b 55 08 mov edx,DWORD PTR [ebp+0x8]
  154.  
  155. 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?
  156.  
  157. 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.
  158.  
  159. 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.
  160.  
  161. So what is there left that we can do?
  162.  
  163. ==================================================================
  164.  
  165. 05. Plan C - make our own magic
  166.  
  167. 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:
  168.  
  169. f2 0f 58 0d 80 e7 d1 01 ADDSD XMM1,qword ptr ds:0x01d1e780
  170. f2 0f 5e 05 80 e7 d1 01 DIVSD XMM1,qword ptr ds:0x01d1e780
  171.  
  172. 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.
  173.  
  174. 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!
  175.  
  176. 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:
  177.  
  178. mov eax,0x64 // load 100 into EAX
  179. cvtsi2sd xmm2,eax // convert 100 in EAX to 100.0 in XMM2
  180. addsd xmm1,xmm2 // add XMM2 into XMM1 (which already has equipment bonus value)
  181. mulsd xmm0,xmm1 // multiply XMM1 by XMM0 (which already has the current stat value)
  182. cvtsi2sd xmm1,eax // convert 100 in EAX to 100.0 in XMM1
  183. divsd xmm0,xmm1 // divide the previous result by 100.0
  184.  
  185. 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:
  186.  
  187. cvtsi2sd xmm1,DWORD PTR [edx*4+0x21359f0]
  188. nop
  189. nop
  190. ...
  191. nop
  192. nop
  193. cvttsd2si eax,xmm0
  194.  
  195. 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:
  196.  
  197. cvtsi2sd xmm1,DWORD PTR [edx*4+0x21359f0]
  198. jmp VERY_FAR_AFTER_RET
  199. nop
  200. ...
  201. nop
  202. nop
  203. MOVE_BACK:
  204. cvttsd2si eax,xmm0
  205. ...
  206. ...
  207. ...
  208. ret 0xc
  209. VERY_FAR_AFTER_RET:
  210. cvtsi2sd xmm2,DWORD PTR [ebp+0x10]
  211. mulsd xmm1,xmm2
  212. mov eax,0x64
  213. cvtsi2sd xmm2,eax
  214. addsd xmm1,xmm2
  215. mulsd xmm0,xmm1
  216. cvtsi2sd xmm1,eax
  217. divsd xmm0,xmm1
  218. jmp MOVE_BACK
  219.  
  220. 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).
  221.  
  222. 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:
  223.  
  224. VERY_FAR_AFTER_RET:
  225. cvtsi2sd xmm2,DWORD PTR [ebp+0x10]
  226. mulsd xmm1,xmm2
  227. mov eax,0x64
  228. cvtsi2sd xmm2,eax
  229. addsd xmm1,xmm2
  230. push 0x101
  231. mov ecx,DWORD PTR [ebp+0x8]
  232. push ecx
  233. mov edx,DWORD PTR [ebp-0x8]
  234. mov ecx,DWORD PTR [edx]
  235. call SKILL_LEVEL
  236. cmp eax,0x1
  237. jl LABEL_NO_MAINTENANCE
  238. mov eax,0x64
  239. cvtsi2sd xmm2,eax
  240. addsd xmm1,xmm2
  241. LABEL_NO_MAINTENANCE:
  242. push 0x167
  243. mov ecx,DWORD PTR [ebp+0x8]
  244. push ecx
  245. mov edx,DWORD PTR [ebp-0x8]
  246. mov ecx,DWORD PTR [edx]
  247. call SKILL_LEVEL
  248. cmp eax,0x1
  249. jl LABEL_NO_OOP_ARTIFACT
  250. mov eax,0x64
  251. cvtsi2sd xmm2,eax
  252. addsd xmm1,xmm2
  253. LABEL_NO_OOP_ARTIFACT:
  254. mulsd xmm0,xmm1
  255. mov eax,0x64
  256. cvtsi2sd xmm1,eax
  257. divsd xmm0,xmm1
  258. jmp MOVE_BACK
  259.  
  260. 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:
  261.  
  262. 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.
  263.  
  264. 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.
  265.  
  266. 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:
  267.  
  268. mov eax,DWORD PTR [ebp-0x8]
  269. mov ecx,DWORD PTR [eax]
  270. mov edx,DWORD PTR [ebp+0x8]
  271. mov ecx,DWORD PTR [ecx+edx*4+0x14]
  272.  
  273. 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!
  274.  
  275. ==================================================================
  276.  
  277. 06. The aftermath
  278.  
  279. 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.
  280.  
  281. 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.
  282.  
  283. 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.
  284.  
  285. 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.
  286.  
  287. ==================================================================
  288.  
  289. 07. Patch instructions
  290.  
  291. 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.
  292.  
  293. >>> THIS DOES NOT APPLY TO THE STEAM EXE <<<<
  294.  
  295. 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:
  296.  
  297. 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
  298.  
  299. 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:
  300.  
  301. 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.
  302.  
  303. Do the same thing for the hex string E8 DC 1B A2 FF 83 F8 01 7C 48.
  304. Do the same thing for the hex string E8 61 49 F4 FF 83 F8 01 7C 3A.
  305. Do the same thing for the hex string E8 09 49 F4 FF 83 F8 01 7C 3A.
  306.  
  307. 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:
  308.  
  309. CALL SKILL_LEVEL
  310. CMP EAX, 0x1
  311. JL some offset
  312.  
  313. 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.
  314.  
  315. 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:
  316.  
  317. 1st (A) - Base multiplier for all equips for all characters
  318. 2nd (B) - Extra multiplier for all equips only if character has Maintenance
  319. 3rd (C) - Extra multiplier for all equips only if character has Shotoku's OOP artifact
  320. 4th (D) - The divider for all equips for all characters
  321.  
  322. In math terms:
  323.  
  324. stat = (stat + (equip * (A + slv*B + slv*C) / D))
  325.  
  326. 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:
  327.  
  328. 64 00 00 00 = 100 decimal
  329. 32 00 00 00 = 50 decimal
  330. 00 01 00 00 = 256 decimal
  331.  
  332. Be careful of negative numbers in two's complement if you go over FF FF FF 7F:
  333.  
  334. FF FF FF 7F = 2147483647 decimal
  335. 00 00 00 80 = -2147483648 decimal
  336. FF FF FF FF = -1 decimal
  337.  
  338. ==================================================================
  339.  
  340. 08. The actual code
  341.  
  342. 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:
  343.  
  344. jmp LABEL_A
  345. LABEL_F:
  346. mov eax,DWORD PTR [ebp-0x14]
  347. add eax,0x1
  348. mov DWORD PTR [ebp-0x14],eax
  349. LABEL_A:
  350. cmp DWORD PTR [ebp-0x14],0x1b
  351. jge LABEL_B
  352. imul eax,DWORD PTR [ebp+0xc],0x6c
  353. mov ecx,DWORD PTR [ebp-0x14]
  354. cmp DWORD PTR [eax+ecx*4+0x2139870],0x0
  355. je LABEL_C
  356. mov eax,DWORD PTR [ebp-0x8]
  357. mov ecx,DWORD PTR [eax]
  358. mov edx,DWORD PTR [ebp+0x8]
  359. mov ecx,DWORD PTR [ecx+edx*4+0x14]
  360. imul edx,DWORD PTR [ebp+0xc],0x6c
  361. mov eax,DWORD PTR [ebp-0x14]
  362. cvtsi2sd xmm0,DWORD PTR [edx+eax*4+0x2139870]
  363. mov edx,DWORD PTR [ebp+0xc]
  364. cvtsi2sd xmm1,DWORD PTR [edx*4+0x21359f0]
  365. jmp LABEL_FARFAR1
  366. nop
  367. nop
  368. nop
  369. nop
  370. nop
  371. nop
  372. nop
  373. nop
  374. nop
  375. nop
  376. nop
  377. nop
  378. nop
  379. nop
  380. nop
  381. nop
  382. nop
  383. nop
  384. nop
  385. nop
  386. nop
  387. nop
  388. nop
  389. nop
  390. LABEL_BACK1:
  391. cvttsd2si eax,xmm0
  392. cdq
  393. mov esi,DWORD PTR [ebp-0x14]
  394. add eax,DWORD PTR [ecx+esi*8+0x220]
  395. adc edx,DWORD PTR [ecx+esi*8+0x224]
  396. mov ecx,DWORD PTR [ebp-0x8]
  397. mov ecx,DWORD PTR [ecx]
  398. mov esi,DWORD PTR [ebp+0x8]
  399. mov ecx,DWORD PTR [ecx+esi*4+0x14]
  400. mov esi,DWORD PTR [ebp-0x14]
  401. mov DWORD PTR [ecx+esi*8+0x220],eax
  402. mov DWORD PTR [ecx+esi*8+0x224],edx
  403. LABEL_C:
  404. imul eax,DWORD PTR [ebp+0xc],0x6c
  405. mov ecx,DWORD PTR [ebp-0x14]
  406. cmp DWORD PTR [eax+ecx*4+0x216e430],0x1
  407. jl LABEL_D
  408. mov eax,DWORD PTR [ebp-0x8]
  409. mov ecx,DWORD PTR [eax]
  410. mov edx,DWORD PTR [ebp+0x8]
  411. mov ecx,DWORD PTR [ecx+edx*4+0x14]
  412. imul edx,DWORD PTR [ebp+0xc],0x6c
  413. mov eax,DWORD PTR [ebp-0x14]
  414. cvtsi2sd xmm0,DWORD PTR [edx+eax*4+0x216e430]
  415. mov edx,DWORD PTR [ebp+0xc]
  416. cvtsi2sd xmm1,DWORD PTR [edx*4+0x21359f0]
  417. jmp LABEL_FARFAR2
  418. nop
  419. nop
  420. nop
  421. nop
  422. nop
  423. nop
  424. nop
  425. nop
  426. nop
  427. nop
  428. nop
  429. nop
  430. nop
  431. nop
  432. nop
  433. nop
  434. nop
  435. nop
  436. nop
  437. nop
  438. nop
  439. nop
  440. nop
  441. nop
  442. LABEL_BACK2:
  443. cvttsd2si eax,xmm0
  444. cdq
  445. mov esi,DWORD PTR [ebp-0x14]
  446. add eax,DWORD PTR [ecx+esi*8+0x2f8]
  447. adc edx,DWORD PTR [ecx+esi*8+0x2fc]
  448. mov ecx,DWORD PTR [ebp-0x8]
  449. mov ecx,DWORD PTR [ecx]
  450. mov esi,DWORD PTR [ebp+0x8]
  451. mov ecx,DWORD PTR [ecx+esi*4+0x14]
  452. mov esi,DWORD PTR [ebp-0x14]
  453. mov DWORD PTR [ecx+esi*8+0x2f8],eax
  454. mov DWORD PTR [ecx+esi*8+0x2fc],edx
  455. LABEL_D:
  456. imul eax,DWORD PTR [ebp+0xc],0x6c
  457. mov ecx,DWORD PTR [ebp-0x14]
  458. cmp DWORD PTR [eax+ecx*4+0x21a2ff0],0x0
  459. je LABEL_E
  460. mov eax,DWORD PTR [ebp-0x8]
  461. mov ecx,DWORD PTR [eax]
  462. mov edx,DWORD PTR [ebp+0x8]
  463. mov ecx,DWORD PTR [ecx+edx*4+0x14]
  464. imul edx,DWORD PTR [ebp+0xc],0x6c
  465. mov eax,DWORD PTR [ebp-0x14]
  466. cvtsi2sd xmm0,DWORD PTR [edx+eax*4+0x21a2ff0]
  467. mov edx,DWORD PTR [ebp+0xc]
  468. cvtsi2sd xmm1,DWORD PTR [edx*4+0x21359f0]
  469. jmp LABEL_FARFAR3
  470. nop
  471. nop
  472. nop
  473. nop
  474. nop
  475. nop
  476. nop
  477. nop
  478. nop
  479. nop
  480. nop
  481. nop
  482. nop
  483. nop
  484. nop
  485. nop
  486. nop
  487. nop
  488. nop
  489. nop
  490. nop
  491. nop
  492. nop
  493. nop
  494. LABEL_BACK3:
  495. cvttsd2si eax,xmm0
  496. cdq
  497. mov esi,DWORD PTR [ebp-0x14]
  498. add eax,DWORD PTR [ecx+esi*8+0x3d0]
  499. adc edx,DWORD PTR [ecx+esi*8+0x3d4]
  500. mov ecx,DWORD PTR [ebp-0x8]
  501. mov ecx,DWORD PTR [ecx]
  502. mov esi,DWORD PTR [ebp+0x8]
  503. mov ecx,DWORD PTR [ecx+esi*4+0x14]
  504. mov esi,DWORD PTR [ebp-0x14]
  505. mov DWORD PTR [ecx+esi*8+0x3d0],eax
  506. mov DWORD PTR [ecx+esi*8+0x3d4],edx
  507. LABEL_E:
  508. jmp LABEL_F
  509. LABEL_B:
  510. // the rest of the function
  511. ...
  512. ret 0xc
  513. LABEL_FARFAR1:
  514. call MFUNC
  515. jmp LABEL_BACK1
  516. LABEL_FARFAR2:
  517. call MFUNC
  518. jmp LABEL_BACK2
  519. LABEL_FARFAR3:
  520. call MFUNC
  521. jmp LABEL_BACK3
  522. MFUNC:
  523. cvtsi2sd xmm2,DWORD PTR [ebp+0x10]
  524. mulsd xmm1,xmm2
  525. mov eax,0x64
  526. cvtsi2sd xmm2,eax
  527. addsd xmm1,xmm2
  528. push 0x101
  529. mov ecx,DWORD PTR [ebp+0x8]
  530. push ecx
  531. mov edx,DWORD PTR [ebp-0x8]
  532. mov ecx,DWORD PTR [edx]
  533. call SKILL_LEVEL
  534. cmp eax,0x1
  535. jl LABEL_NO_MAINTENANCE
  536. imul eax,eax,0x64
  537. cvtsi2sd xmm2,eax
  538. addsd xmm1,xmm2
  539. LABEL_NO_MAINTENANCE:
  540. push 0x167
  541. mov ecx,DWORD PTR [ebp+0x8]
  542. push ecx
  543. mov edx,DWORD PTR [ebp-0x8]
  544. mov ecx,DWORD PTR [edx]
  545. call SKILL_LEVEL
  546. cmp eax,0x1
  547. jl LABEL_NO_OOP_ARTIFACT
  548. imul eax,eax,0x64
  549. cvtsi2sd xmm2,eax
  550. addsd xmm1,xmm2
  551. LABEL_NO_OOP_ARTIFACT:
  552. mulsd xmm0,xmm1
  553. mov eax,0x64
  554. cvtsi2sd xmm1,eax
  555. divsd xmm0,xmm1
  556. mov eax,DWORD PTR [ebp-0x8]
  557. mov ecx,DWORD PTR [eax]
  558. mov edx,DWORD PTR [ebp+0x8]
  559. mov ecx,DWORD PTR [ecx+edx*4+0x14]
  560. ret
  561.  
Add Comment
Please, Sign In to add comment