Thank you, patjenova. Your code helped me understand Khuong’s old crafting recipe code more correctly.Here it is for 1.23.0
[00# Mastercode Get Craft 0xB4BFF00 (Select First Item)]
040A0000 08CB4F04 F0014053
040A0000 08CB4F08 F9078262
040A0000 08CB4F0C AA0203F3
040A0000 08CB4F10 1706704E
040A0000 04E51044 14F98FB0
And this is an example code you could try.
[(ZL) Change Selected Crafting Recipe To DreamSnaps Decor Reward]
580F0000 0B4BFF00
580F1000 00000040
780F0000 00000018
640F1000 00000000 01DF2085
I need to correct my earlier understanding. I was mainly looking at the pointer-code part of Khuong’s old code, and I missed that the Master Code also had the part that saves the recipe pointer into a scratch slot. After reading your v1.23.0 code, I realized that Khuong’s old code was not simply a normal pointer chain. It was also using a hook-assisted saved pointer mechanism. So I think your code is best understood as a proper v1.23.0 update of Khuong’s old crafting recipe code.
Both versions use the same basic idea:
Code:
hook Meta.CraftWithRecipe$$GetRelevantInventory
save CraftingRecipeItemData* to a scratch slot
read that saved pointer with Atmosphère pointer code
edit recipe->result_->itemID_
The v1.23.0 master code is:
Each `040A0000` line writes one 32-bit ARM64 instruction to `main + offset`.
So these lines:
write this small code cave:
And this line:
patches the original function at:
The original instruction there is:
After the patch, it becomes:
So the game jumps to the code cave.
The hooked function is:
In v1.23.0, the relevant argument is `recipeData`, which is a `CraftingRecipeItemData*`.
For an IL2CPP instance method on ARM64, the native register layout is usually like this:
So in this case, `X2` appears to be:
This also matches the original function. The game does:
and later reads:
`CraftingRecipeItemData + 0x48` is `ingredients_`, so `X19` is being used as the recipe object.
Now the code cave:
`ADRP` loads a 4KB page address into a register. Here it loads:
ARM64 often builds an address as `page + offset`, because a full 64-bit absolute address usually cannot be used directly in a normal load/store instruction.
This stores the 64-bit value in `X2` to:
In main-offset form:
So this line saves the current `CraftingRecipeItemData*` pointer to:
Then:
restores the original instruction that was replaced by the hook.
Finally:
returns to the original function, immediately after the replaced instruction.
So the code cave is basically:
The segment table is important here:
The code cave is placed at:
This is inside the `.text` segment and before `.rodata` starts at:
Since `.text` has execute permission, unused padding near the end of `.text` can be used as an ASM code cave.
The saved pointer slot is:
This is after `.bss` ends:
and before Imports starts:
So it is in the padding area after `.bss` and before Imports. More importantly, it is still in the same 4KB page as the end of `.bss`:
Since `.bss` is writable, this page is likely writable at runtime. That makes `0x710B4BFF00` suitable as a small 8-byte scratch slot for saving a pointer.
So the address usage is:
After the master code saves the recipe pointer, the example code can use it:
Line by line:
This loads a 64-bit pointer from:
into Atmosphère Cheat VM register `R15`.
Because the master code saved `recipeData` there, this gives:
Next:
This dereferences:
`CraftingRecipeItemData + 0x40` is `result_`, so now:
Next:
This adds `0x18`:
`ResultInstance + 0x18` is `itemID_`, so now `R15` points to:
Finally:
writes the 32-bit value `0x01DF2085` to the address in `R15`.
So the pointer code is basically:
The object path is:
This is the same basic design as Khuong’s old v1.8.6 code.
Khuong’s old code saved the recipe pointer to:
and then read it from the crafting recipe code.
Your v1.23.0 code saves the recipe pointer to:
and then reads it from the new pointer code.
So the mechanism is:
One small note: the posted example title says `(ZL)`, but the shown pointer-code lines do not include the usual button wrapper. If it should only run while holding ZL, it would normally need:
Code:
[00# Mastercode Get Craft 0xB4BFF00 (Select First Item)]
040A0000 08CB4F04 F0014053
040A0000 08CB4F08 F9078262
040A0000 08CB4F0C AA0203F3
040A0000 08CB4F10 1706704E
040A0000 04E51044 14F98FB0
Each `040A0000` line writes one 32-bit ARM64 instruction to `main + offset`.
So these lines:
Code:
040A0000 08CB4F04 F0014053
040A0000 08CB4F08 F9078262
040A0000 08CB4F0C AA0203F3
040A0000 08CB4F10 1706704E
write this small code cave:
Code:
0x7108CB4F04: ADRP X19, #0x710B4BF000
0x7108CB4F08: STR X2, [X19,#0xF00]
0x7108CB4F0C: MOV X19, X2
0x7108CB4F10: B 0x7104E51048
And this line:
Code:
040A0000 04E51044 14F98FB0
patches the original function at:
Code:
0x7104E51044
The original instruction there is:
Code:
0x7104E51044: MOV X19, X2
After the patch, it becomes:
Code:
0x7104E51044: B 0x7108CB4F04
So the game jumps to the code cave.
The hooked function is:
Code:
Meta.CraftWithRecipe$$GetRelevantInventory
In v1.23.0, the relevant argument is `recipeData`, which is a `CraftingRecipeItemData*`.
For an IL2CPP instance method on ARM64, the native register layout is usually like this:
Code:
X0 = this
X1 = first normal argument
X2 = second normal argument
X3 = MethodInfo
So in this case, `X2` appears to be:
Code:
CraftingRecipeItemData* recipeData
This also matches the original function. The game does:
Code:
MOV X19, X2
and later reads:
Code:
LDR X8, [X19,#0x48]
`CraftingRecipeItemData + 0x48` is `ingredients_`, so `X19` is being used as the recipe object.
Now the code cave:
Code:
ADRP X19, #0x710B4BF000
`ADRP` loads a 4KB page address into a register. Here it loads:
Code:
X19 = 0x710B4BF000
ARM64 often builds an address as `page + offset`, because a full 64-bit absolute address usually cannot be used directly in a normal load/store instruction.
Code:
STR X2, [X19,#0xF00]
This stores the 64-bit value in `X2` to:
Code:
0x710B4BF000 + 0xF00 = 0x710B4BFF00
In main-offset form:
Code:
0x710B4BFF00 = main + 0x0B4BFF00
So this line saves the current `CraftingRecipeItemData*` pointer to:
Code:
main + 0x0B4BFF00
Then:
Code:
MOV X19, X2
restores the original instruction that was replaced by the hook.
Finally:
Code:
B 0x7104E51048
returns to the original function, immediately after the replaced instruction.
So the code cave is basically:
Code:
*(u64 *)(main + 0x0B4BFF00) = recipeData;
X19 = recipeData;
return to the original function;
The segment table is important here:
Code:
Name Start End Permission
.text 0x7100000000 0x7108CB5000 R-X
.rodata 0x7108CB5000 0x710966E958 R--
.data 0x710A4FC000 0x710B1014C0 RW-
.bss 0x710B1014C0 0x710B4BF138 RW-
Imports 0x710B4C0008 0x710B4C17A8 R--
The code cave is placed at:
Code:
0x7108CB4F04
This is inside the `.text` segment and before `.rodata` starts at:
Code:
0x7108CB5000
Since `.text` has execute permission, unused padding near the end of `.text` can be used as an ASM code cave.
The saved pointer slot is:
Code:
0x710B4BFF00
This is after `.bss` ends:
Code:
0x710B4BF138
and before Imports starts:
Code:
0x710B4C0008
So it is in the padding area after `.bss` and before Imports. More importantly, it is still in the same 4KB page as the end of `.bss`:
Code:
0x710B4BF000 - 0x710B4BFFFF
Since `.bss` is writable, this page is likely writable at runtime. That makes `0x710B4BFF00` suitable as a small 8-byte scratch slot for saving a pointer.
So the address usage is:
Code:
0x7108CB4F04 -> executable .text padding, used as ASM code cave
0x710B4BFF00 -> writable .bss-end padding, used as saved pointer storage
After the master code saves the recipe pointer, the example code can use it:
Code:
[(ZL) Change Selected Crafting Recipe To DreamSnaps Decor Reward]
580F0000 0B4BFF00
580F1000 00000040
780F0000 00000018
640F1000 00000000 01DF2085
Line by line:
Code:
580F0000 0B4BFF00
This loads a 64-bit pointer from:
Code:
main + 0x0B4BFF00
into Atmosphère Cheat VM register `R15`.
Because the master code saved `recipeData` there, this gives:
Code:
R15 = CraftingRecipeItemData*
Next:
Code:
580F1000 00000040
This dereferences:
Code:
R15 = *(u64 *)(R15 + 0x40)
`CraftingRecipeItemData + 0x40` is `result_`, so now:
Code:
R15 = ResultInstance*
Next:
Code:
780F0000 00000018
This adds `0x18`:
Code:
R15 = R15 + 0x18
`ResultInstance + 0x18` is `itemID_`, so now `R15` points to:
Code:
&result_->itemID_
Finally:
Code:
640F1000 00000000 01DF2085
writes the 32-bit value `0x01DF2085` to the address in `R15`.
So the pointer code is basically:
Code:
recipe = *(CraftingRecipeItemData **)(main + 0x0B4BFF00);
result = recipe->result_; // +0x40
result->itemID_ = 0x01DF2085; // +0x18
The object path is:
Code:
CraftingRecipeItemData + 0x40 -> result_
ResultInstance + 0x18 -> itemID_
This is the same basic design as Khuong’s old v1.8.6 code.
Khuong’s old code saved the recipe pointer to:
Code:
main + 0x0B9B0F08
and then read it from the crafting recipe code.
Your v1.23.0 code saves the recipe pointer to:
Code:
main + 0x0B4BFF00
and then reads it from the new pointer code.
So the mechanism is:
Code:
hook GetRelevantInventory
save CraftingRecipeItemData* to scratch slot
read saved pointer with Atmosphère pointer code
edit recipe->result_->itemID_
One small note: the posted example title says `(ZL)`, but the shown pointer-code lines do not include the usual button wrapper. If it should only run while holding ZL, it would normally need:
Code:
80000100
...
20000000
As far as I can tell, patjenova is one of the most active cheat-code contributors on GBAtemp. His codes often show techniques that seem to come from many years of studying, updating, and reverse engineering different cheat-code patterns. In that sense, his codes are probably much better learning material than my own codes, since I usually only make and share practical, game-specific codes for the games I personally play.







