Usage¶
A walkthrough of the workflow that actually gets used: find a value, confirm it, change it, keep it changed, and make the address survive a relaunch.
The panel¶
Tap the snowflake to open it. Drag the title bar to move it, tap ✕ to close.
┌──────────────────────────────┐
│ ❄️ Shirayuki ✕ │ ← drag here
├──────────────────────────────┤
│ 🔍 🔧 🔒 👁 🌿 💾 🧵 📦 │ ← tabs, scrollable
├──────────────────────────────┤
│ [i32] [ value or pattern ] ▶ │ ← type · input · action
├──────────────────────────────┤
│ Changed Unchanged Inc Dec ⟲ │ ← narrow bar (Search, after a scan)
├──────────────────────────────┤
│ 0x104C2A100 = 100 (0x64) │
│ 0x104C2A180 = 100 (0x64) │ ← tap a row to act on it
│ … │
└──────────────────────────────┘
Three controls drive everything:
- Type button (
i32) — tap to cycle the value type. - Input field — the value, pattern or address. Tap it on the Search tab to see recent entries.
- Action button (▶) — runs the tab's action. Long-press it for extra actions: export, batch modify, undo/redo, remove all.
Long-pressing a row usually copies its address.
Finding a value you can see¶
Say the game shows 100 HP.
1. Scan for it. Search tab, type i32, enter 100, tap ▶.
Thousands of matches is normal — 100 is a common number. The message tells you
when the result cap was hit, so you know the set is truncated.
2. Change the value in-game. Take damage. Now HP is 87.
3. Narrow. Enter 87 and tap ▶ — or tap Changed if you do not know the new
value.
4. Repeat until one or two remain. Take damage again, narrow again. Two or three rounds is usually enough.
Wrong type is the most common reason a scan finds nothing
A displayed 100 is very often a float. If i32 finds nothing, cycle to
f32 and try again. Health and currency are frequently floats; counters and
levels are usually integers.
5. Write to it. Tap the row. The dialog shows the current value and lets you write a new one.
┌────────────────────────────┐
│ 0x104C2A100 │
│ Current: 87 (0x57) (int32) │
│ ┌────────────────────────┐ │
│ │ 87 │ │
│ └────────────────────────┘ │
│ [Write] [Freeze] [Watch] │
│ [Cancel] │
└────────────────────────────┘
- Write — set it once.
- Freeze — hand it to the Freeze tab so it is held there.
- Watch — hand it to the Watch tab to observe changes.
The field is prefilled with a re-parseable form of the current value, and accepts
negative numbers, decimals and 0x hex.
An unreadable address is reported, not shown as 0
If the address has been freed since the scan, the dialog refuses to open rather
than showing 0 — accepting that prefill would have written a real zero.
When you cannot see the value¶
For a value with no on-screen number — a hidden cooldown, an invisible flag —
scan for ? on a numeric type. That seeds every aligned slot in writable memory
as a candidate, and you then narrow purely by behaviour:
- Enter
?, tap ▶. - Do the thing that changes the value.
- Tap Changed.
- Do something that does not change it, tap Unchanged.
- Alternate until the set is small.
Inc and Dec are the useful ones for values that drift in one direction — a timer counting down, score going up. Comparisons are numeric, so this works correctly for negative and floating point values.
Keeping a value fixed¶
Freeze tab. Input is 0xADDR VALUE [type]:
A background worker rewrites it every 16 ms.
- Tap a row to pause/resume it (
PAUSEDmeans still tracked, not writing). - Long-press to toggle auto-increment, which adds 1 each tick instead of holding a constant.
- Swipe to delete.
- Long-press the action button → Remove All.
Type tags accept both short and long forms — i32 and int32 are the same, as are
f32/float.
Watching a value change¶
Watch tab, input 0xADDR [type]:
Polls every 100 ms and shows previous → current with a change count. Useful for working out when something is written, and for telling a real match from a coincidence.
Patching code¶
Dump tab first, to see what you are changing:
0x104C31200 FF 43 01 D1 STP x29, x30, [sp, #-0x10]!
0x104C31204 FD 03 00 91 MOV x29, sp
0x104C31208 E0 03 00 AA MOV x0, x1
Then Patch tab, 0xADDR HEXBYTES [label]:
Original bytes are captured before the write, so:
- Tap to toggle the patch off and back on.
- Swipe to delete — the original bytes are restored first.
- Long-press the action button for Undo / Redo / Restore All.
1F 20 03 D5 is ARM64 NOP. The Dump tab also has a long-press → NOP shortcut,
but that one is not tracked and cannot be undone — prefer the Patch tab if you
might want it back.
Making an address survive a relaunch¶
Heap addresses change every launch. A pointer chain does not.
Pointer tab, input the address you found, optionally a depth:
Each chain starts at a module-relative offset, so it is stable across launches and ASLR. Shorter chains are usually more reliable. Tap a row to copy the notation.
Chains are saved in the session file, so they come back next launch.
Finding code by pattern¶
When you want the same routine in a future build, search for its bytes rather than its address.
- Modules tab — long-press the module to copy its full path.
- Dump tab — disassemble around the interesting code.
- Search tab, type
hex, enter an IDA-style pattern with??for bytes that will move:
Wildcards let the pattern survive recompilation.
Sessions¶
Bookmarks, patches, freezes, pointer chains and search history are written to
~/Documents/Shirayuki/<bundleid>_autosave.json when the app goes to the
background, and reloaded on the next launch.
Practical notes¶
- Scans are capped at 2000 results and skip regions over 100 MB. If you see
limit reached, narrow rather than trusting the list to be complete. - A scan runs off the main thread, so the app keeps running. Tapping the action button again while one is in flight is refused rather than starting a second.
- Freezing a stale address does nothing visible. If a freeze stops working after a relaunch, the address moved — find it again, or use a pointer chain.
- Injecting into every app includes system apps. If SpringBoard misbehaves, restrict the injection filter.
- Nothing here is verified on device by CI. The build and the C++ core are tested automatically; panel behaviour is not.