mirror of
https://github.com/TiagoVal97/cheatspecter.git
synced 2026-10-21 03:36:29 +02:00
A quality pass after 0.4.0 was tried on a real console. - Fix the Learn page crashing in current browsers when changing lesson steps - Clear feedback for every search outcome; real progress with time left and Cancel - Dialogs scroll inside the screen with Save and Cancel always in view - Fix a console error on every closed connection to the payload - Copy buttons work when the page is served from the PS5 - The practice game sits beside the lesson's tools and lasts between steps - Damaged cheat files, vanishing error messages, unreadable memory shown as zeros - Phone layout: one-line header, larger switches, readable rows - Browser tests under test/ui and a guard against effects that return values
284 lines
10 KiB
JSON
284 lines
10 KiB
JSON
{
|
|
"format": "cheatspecter-lesson",
|
|
"version": 1,
|
|
"id": "find-the-code",
|
|
"title": "Find the code that changes it, and switch it off",
|
|
"summary": "Ask the debugger which instruction lowers your ammo, then replace it with instructions that do nothing.",
|
|
"minutes": 10,
|
|
"games": {
|
|
"range": {
|
|
"name": "Specter Range",
|
|
"objects": [
|
|
{
|
|
"id": "player",
|
|
"fields": [
|
|
{
|
|
"id": "ammo",
|
|
"type": "u32",
|
|
"value": 20,
|
|
"offset": 40
|
|
},
|
|
{
|
|
"id": "shots",
|
|
"type": "u32",
|
|
"value": 0,
|
|
"offset": 64
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"actions": [
|
|
{
|
|
"id": "fire",
|
|
"label": "Fire",
|
|
"effects": [
|
|
{
|
|
"field": "player.ammo",
|
|
"op": "sub",
|
|
"by": 1,
|
|
"min": 0,
|
|
"refill": 20
|
|
},
|
|
{
|
|
"field": "player.shots",
|
|
"op": "add",
|
|
"by": 1
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"hud": [
|
|
{
|
|
"label": "Ammo",
|
|
"field": "player.ammo",
|
|
"show": "number"
|
|
}
|
|
],
|
|
"decoys": [
|
|
{
|
|
"type": "u32",
|
|
"value": 20,
|
|
"count": 12
|
|
},
|
|
{
|
|
"type": "u32",
|
|
"value": 19,
|
|
"count": 5
|
|
}
|
|
],
|
|
"copies": [
|
|
{
|
|
"of": "player.ammo",
|
|
"count": 2,
|
|
"follow": "fade"
|
|
}
|
|
]
|
|
},
|
|
"shop": {
|
|
"name": "Specter Market",
|
|
"objects": [
|
|
{
|
|
"id": "wallet",
|
|
"fields": [
|
|
{
|
|
"id": "coins",
|
|
"type": "u32",
|
|
"value": 600,
|
|
"offset": 24
|
|
},
|
|
{
|
|
"id": "items",
|
|
"type": "u32",
|
|
"value": 0,
|
|
"offset": 28
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"actions": [
|
|
{
|
|
"id": "buy",
|
|
"label": "Buy a potion (45 coins)",
|
|
"effects": [
|
|
{
|
|
"field": "wallet.coins",
|
|
"op": "sub",
|
|
"by": 45,
|
|
"min": 0,
|
|
"refill": 600
|
|
},
|
|
{
|
|
"field": "wallet.items",
|
|
"op": "add",
|
|
"by": 1
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"hud": [
|
|
{
|
|
"label": "Coins",
|
|
"field": "wallet.coins",
|
|
"show": "number"
|
|
},
|
|
{
|
|
"label": "Potions",
|
|
"field": "wallet.items",
|
|
"show": "number"
|
|
}
|
|
],
|
|
"decoys": [
|
|
{
|
|
"type": "u32",
|
|
"value": 600,
|
|
"count": 9
|
|
},
|
|
{
|
|
"type": "u32",
|
|
"value": 555,
|
|
"count": 4
|
|
}
|
|
],
|
|
"copies": [
|
|
{
|
|
"of": "wallet.coins",
|
|
"count": 2,
|
|
"follow": "fade"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"steps": [
|
|
{
|
|
"kind": "read",
|
|
"title": "Go after the cause",
|
|
"text": "Freezing has two weaknesses. It fights the game four times a second rather than stopping it, and it is lost the moment the game restarts, because the value moves.\n\nThere is something that does not move: the game's **code**. Somewhere in it is one small **instruction** that says, in effect, \"take 1 away from the ammo\". It runs every time you fire. It is in the same place every time the game starts.\n\nIf that instruction did nothing, firing would cost nothing. No tug of war, no flicker, and the fix can be switched on again tomorrow.\n\n# How do you find one instruction among millions?\n\nYou ask the **debugger**. Give it the address of your ammo and it watches that spot. The moment any code writes there, the game is paused for an instant, the debugger notes which instruction did it, and the game carries on. You fire once, and it hands you the culprit.\n\n# How do you make an instruction do nothing?\n\nEvery instruction is a few bytes. One special byte, written `90`, is an instruction that means \"do nothing and move on\". It is called a **no-op**. Replace the bytes of \"take 1 away\" with the same number of no-ops and the game steps over the spot where the subtraction used to be.\n\n> note: You replace an instruction with exactly as many no-op bytes as it had. Remove bytes or add them and everything after would shift, and the game would crash. The tool takes care of the count for you."
|
|
},
|
|
{
|
|
"kind": "practice",
|
|
"title": "Catch it in the act",
|
|
"text": "1. Find your ammo, as in lesson 2, until one match is left.\n2. On that row, open the **…** menu and choose **Find what writes to this**.\n\nThe tool moves to the **Code** tab and says it is watching. Now give it something to see: **Fire** once or twice.",
|
|
"game": "range",
|
|
"tool": "find",
|
|
"goal": {
|
|
"check": "caught",
|
|
"label": "Find out which code writes to your ammo",
|
|
"field": "player.ammo"
|
|
},
|
|
"hints": [
|
|
"First the usual: Find tab, type the Ammo number, Search, Fire, type the new number, Narrow down.",
|
|
"The … button is at the very end of the row, after the padlock. Its menu has \"Find what writes to this\".",
|
|
"While the Code tab says \"Watching\", press Fire on the practice game. An instruction appears in the list, with a count of how often it ran."
|
|
],
|
|
"done": "One line, and a count that goes up with each shot. It reads something like \"sub dword ptr [rbx+0x28], 1\": subtract 1 from a four-byte number that sits 0x28 past wherever \"rbx\" points. You do not need to read it fluently. It ran when you fired, and it wrote to your ammo. That is your instruction."
|
|
},
|
|
{
|
|
"kind": "practice",
|
|
"title": "Switch it off",
|
|
"text": "Next to the instruction is a button: **Disable it**. Press it.\n\nThe tool stops watching, overwrites the instruction with no-ops, saves the whole thing as a cheat with an on/off switch under **Your cheats**, and takes you to that tab.",
|
|
"game": "range",
|
|
"tool": "code",
|
|
"goal": {
|
|
"check": "off",
|
|
"label": "Replace that instruction with no-ops",
|
|
"action": "fire"
|
|
},
|
|
"hints": [
|
|
"The list of instructions is in the Code tab, under \"What touches a value\". Each line has a Show button and a Disable it button.",
|
|
"If the list is empty, the watching was stopped before you fired. Go back to the Find tab, open the … menu on your ammo row and choose \"Find what writes to this\" again, then fire."
|
|
],
|
|
"done": "It is now a cheat with a switch. Flip the switch off and the original bytes go back, exactly as they were."
|
|
},
|
|
{
|
|
"kind": "practice",
|
|
"title": "Try it",
|
|
"text": "Fire three times and watch the Ammo number.\n\nThen, if you are curious, flip the switch off, fire, and flip it on again.",
|
|
"game": "range",
|
|
"tool": "cheats",
|
|
"goal": {
|
|
"check": "held",
|
|
"label": "Fire three times without your ammo going down",
|
|
"field": "player.ammo",
|
|
"action": "fire",
|
|
"times": 3
|
|
},
|
|
"hints": [
|
|
"The cheat needs to be switched on: in Your cheats, its switch should be yellow.",
|
|
"Leave a moment between shots."
|
|
],
|
|
"done": "No flicker this time. Nothing is putting the number back, because nothing takes it away any more."
|
|
},
|
|
{
|
|
"kind": "practice",
|
|
"title": "Restart, and switch it back on",
|
|
"text": "Here is the part freezing could not do. Press **Restart game**.\n\nThe game loads its code fresh from disc, so the instruction is back and your cheat's switch is off. Your ammo is at a new address, and you do not care: the cheat never needed it.\n\nFlip the switch on. No searching.",
|
|
"game": "range",
|
|
"tool": "cheats",
|
|
"goal": {
|
|
"check": "all",
|
|
"of": [
|
|
{
|
|
"check": "restarted",
|
|
"label": "Restart the practice game"
|
|
},
|
|
{
|
|
"check": "off",
|
|
"label": "Switch your cheat on again",
|
|
"action": "fire"
|
|
}
|
|
]
|
|
},
|
|
"hints": [
|
|
"After the restart, open the Your cheats tab. Your cheat is still listed.",
|
|
"Turn its switch on. Then fire to check."
|
|
],
|
|
"done": "One switch, and it works again, in a game you have not searched at all. This is what a cheat that lasts looks like."
|
|
},
|
|
{
|
|
"kind": "choice",
|
|
"title": "Check yourself",
|
|
"text": "",
|
|
"question": "Why does the switched-off instruction still work as a cheat after a restart, when a frozen value does not?",
|
|
"options": [
|
|
"The game's code is loaded in the same place every time; values are not.",
|
|
"No-ops are stronger than freezes.",
|
|
"The debugger keeps running inside the game."
|
|
],
|
|
"answer": 0,
|
|
"explain": "The code comes off the disc the same way on every start. Values are created as the game goes, wherever there is room. A cheat tied to the code can be found again; a cheat tied to a value's address cannot."
|
|
},
|
|
{
|
|
"kind": "practice",
|
|
"title": "On your own: free potions",
|
|
"text": "A shop that takes your coins. Make potions free, with a cheat that would still be there after a restart.",
|
|
"game": "shop",
|
|
"tool": "find",
|
|
"goal": {
|
|
"check": "all",
|
|
"of": [
|
|
{
|
|
"check": "off",
|
|
"label": "Switch off the code that takes your coins",
|
|
"action": "buy"
|
|
},
|
|
{
|
|
"check": "held",
|
|
"label": "Buy three potions without paying",
|
|
"field": "wallet.coins",
|
|
"action": "buy",
|
|
"times": 3
|
|
}
|
|
]
|
|
},
|
|
"hints": [
|
|
"Find the coins first. Then use the … menu on the row: \"Find what writes to this\".",
|
|
"Buy a potion while the Code tab is watching, then press \"Disable it\" on the instruction that showed up.",
|
|
"Buy three potions to prove it. Notice that the potion count still goes up: you only removed the paying."
|
|
],
|
|
"done": "You still get the potions, because the instruction that adds a potion is a different one, and you left it alone. Choosing which instruction to remove is most of the skill.\n\nOn a real game, the Cheats tab has **Guide me through my first cheat**. It walks you through these same steps one screen at a time, and it is a good way to make your first real one.",
|
|
"fresh": true,
|
|
"challenge": true
|
|
}
|
|
]
|
|
}
|