PS1 I Tricked the PS1 Hardware to Ignore Spyro 3’s "Uncrackable" Security

Unicorngoulash

Forum Noob
I went on a journey to bring the legendary double-jump glitch from Spyro 2: Ripto's Rage back into Spyro 3: Year of the Dragon. Not on an emulator. On real PlayStation 1 hardware first.

Here's the problem.
Spyro 3 did not merely remove the glitch. It actively fights you if you try to restore it.
The game contains deliberate integrity checks designed to detect code modification and respond with punishment. Touch the executable and the game notices. This is not accidental behavior, this is intentional anti-tamper logic. The usual approach is brute force: hunt down every checksum across the main executable and overlays, then adjust them one by one. That method is fragile, and extremely easy to get wrong.

There had to be a better way.
So I stopped thinking like a modder.
Instead of hacking the game, I hacked the console and became the ghost in the machine...
When real hardware breaks emulator assumptions

The method sat so far outside normal PS1 usage that it exposed incorrect assumptions in emulation.
Pushed the PlayStation's COP0 debug logic in ways that, to my knowledge, hadn't been meaningfully exercised before. Not by games. Not by existing mods.

DuckStation's COP0 emulation got a reality check, real retail PS1 silicon exposed the gaps. Their commit "Inhibit debug dispatcher when COP0 BP is invalid" came from testing this edge case. I helped debug it with the main dev, contributing to the emulator's accuracy.

That is the tell.
Emulators usually define the ceiling of what people think is possible. This time, real hardware forced the emulator to catch up. For the record, pcsx-redux and no$psx were already closer to reality and reflected this behavior more accurately, that being said, most PS1 emulators don't even attempt to emulate the COP0 at all.

This was not just restoring a glitch. It was mapping undocumented silicon behavior on a retail console by force, and proving it by breaking assumptions in one of the most accurate PS1 emulators available.
The angle nobody takes

Deep inside the PS1 CPU is dormant debug circuitry. Its exception vector lives at 0x80000040. On retail systems, the handler is completely nopped out. Most people assume this means the feature does not exist.
That assumption is wrong. The R3000A dictates behavior, not a nop sitting in memory meant to discourage use. I restored that vector to jump into my own exception handler. Its job was simple: observe execution state in real time and redirect behavior when needed. Sony expected this pathway to be used only by official devkits. Never by consumers. Never by games. And certainly never against the game itself.

That was the opening.
The move

I revived a part of my old and dusty PlayStation that had not been meaningfully used for 31 years, because almost everyone assumed it was unused or impossible on retail hardware anyway.
The system could now:
  • Catch execution at an exact instruction
  • Intercept it while it is running
  • Redirect behavior without modifying code at rest
  • Allow Spyro's protection systems to execute normally
  • Step out again without the game detecting anything
From the game's point of view, nothing changed, because nothing about the game actually changed.
From the console's point of view, everything changed.

A custom handler gets installed before the game boots. Execution is intercepted at runtime using COP0 execution breakpoints. The moment a point in execution of interest is reached, it goes to my own exception handler and behavior is redirected for a few cycles by adjusting the EPC; we're basically doing invisible jumps from anywhere, in this new space anything can be done, and then control is returned cleanly with JMP + RFE.

Spyro's protection logic runs normally and never sees modification. From the game's point of view, nothing changed; it still believes it owns the machine. The COP0_BPCM/COP_BPC/COP_DCIC system is re-armed through the standard exception vector at 0x80000080 whenever all matches in execution have been reached and altered.

Conclusion: the Spyro 2 double-jump restored in Spyro 3, on real PS1 hardware, checksum‑clean, fingerprint‑free. The CPU was no longer blindly executing instructions. It was being observed, intercepted, and momentarily redirected through its own dormant pathways. For a microscopic window of time, the PlayStation itself became the arbiter of execution flow. Not the game. Not the protection code. Not the assumptions baked into retail software.

The technique was rigorously tested on a real retail PlayStation 1 console. No emulators. No devkits. Just raw retail silicon. Full 117% Completion was achieved in Spyro 3: Year of the Dragon with the Spyro 2 double-jump fully functional. Every egg collected. Every gem grabbed. Every level unlocked. The checksums passed clean every single time, no detection. Spyro's anti-tamper logic never triggered across dozens of hours of playtesting. The invisible jumps worked flawlessly from any point in the game. It was a true rewrite at the silicon level.
The result

For a fraction of a frame, I slipped between the CPU and the game. Long enough to alter behavior. Short enough to leave no fingerprints.
The integrity checks passed.
The anti-tamper logic remained satisfied.
The engine believed it was still in control.
And the double-jump came back to life.
What this actually proves

This was never just about restoring a glitch.
It proves that Spyro 3's protection can be bypassed without touching the game itself.
It proves that retail PS1 hardware still contains dormant capabilities people wrote off decades ago.
It proves that real hardware can still outpace emulation when pushed beyond assumed limits.
A 31-year-old console still had something left to say.

The game thought it was in charge.
The emulator thought it knew the limits.
Neither one did.


Unicorngoulash
[COLOR=oklch(0.9296 0.007 106.53)]

[/COLOR]
 
absolute-cinema-v0-ga7pdod2xwhf1.jpg

Nah, but seriously that's pretty cool. For being a hack it seems you found a very clean way of going about injecting code.
 
Haha, yes, it is a bit cinematic. I gave it some flair, not too technical for the average reader, technical enough for the reverse engineers to sorta get what it is that I'm doing.

The logic is basically a ghost story.
Is it even a 'hack' at this point? I'm using the CPU's own hardware to change the laws of physics.

One second the CPU 'teleports' 8 bytes forward where no jump instruction exists because I manually bumped the EPC to skip the trap. The next second, a real jump happens but I intercept it and lie about the destination.
Since I'm hiding in a memory mirror, the game doesn't audit, I can rewrite reality without changing a single byte of code where the game would normally do its checks. No prints, no altered values to scan, just a silent vanish.
 
Last edited by a moderator:
I believe that writeup will be interesting for @alexfree ^^
The UnCRackAble ProTECtion gets cracked again in another way! I will need to take time to understand this later, but here's what I can say about this game's protection rn.

The way me and @mottzila figured out with pure software (actually gameshark codes, but the first ever to really fix without a patch to the actual disc) was there is a "stepper" counter. it counts up while checking the protection and if it gets to a certain amount it fails the check and anti-piracy kicks in. The only reason that worked because while many areas of RAM are protected/checked/verified for tampering, the actual stepper counter is NOT one of those areas. Absolutely absurd but it worked, I wrote about it here: https://gbatemp.net/threads/spyro-y...with-gsmeshark-codes-thx-to-mottzilla.633548/. This is how Tonyhax International bypasses protections for this game for USA releases anyways.

The only problem was is when you have like 14 different gameshark codes checking one address every exception trigger it can cause some lag during these 'checks' that happen throughout gameplay, because basically we are bruteforcing it to zero no matter what val it is trying to write and then compare for (should AP be triggered?). This could probably be improved even with this method but I have been busy with other things (like a NotEqual0 then instead of 14 or whatever codes checking if it is some val not zero).

Mottzilla also wrote a real patch for CD images much before that, I unfortunately can not recall his website atm.
 
The UnCRackAble ProTECtion gets cracked again in another way! I will need to take time to understand this later, but here's what I can say about this game's protection rn.

I had always wondered about the rolling loop code you made, perhaps it was intentional. But in the world of GameShark, there are simpler ways to go about it.

This is what I'd do (mind, without full context of why you did it the way you did):

#If not 0000, turn to 0000 (Option 1)
D107F23C 0000
8007F23C 0000

#If above 0000, turn to 0000 (Option 2)
D307F23C 0000
8007F23C 0000

#If less than 000F, turn to 0000 (Option 3)
D207F23C 000F
8007F23C 0000

Perhaps there was some mad science behind the rolling loop, so I'm taking it at face value :)

However, with the way I do things now, one could write an assembly code for this instead and never have the risk of triggering any anti-piracy or anti-tamper checks. That being said, my code and your code serve completely different purposes.

I target the in-game assembly to restore Spyro's double jump glitch from Spyro 2 back into Spyro 3. The regions responsible for the double jump behavior are constantly checked throughout the entire game. This means that if someone wanted to bypass this the traditional way, they'd need to re-calculate every checksum for the two instructions I've targeted, often very risky, messy, and time-intensive (praying you actually caught them all).

I bypass the checks completely by never modifying the actual game. Instead, I redirect where the CPU goes in execution by enabling certain debug functions on the PS1's COP0, which allows me to set watchpoints and perform invisible jumps to my own exception handler, making it completely invisible to the game. During this frame, I can do whatever I like, then essentially jump into the future by changing the EPC. These COP0 instructions are often completely neglected by emulators, and the fact I was able to re-enable them on a retail console was a miracle by itself. Sony NOPed out the debug exception vector at address 80000040, but my theory was that it should still exist regardless, since the R3000A dictates its presence even if Sony disabled it in software. Writing to that vector at runtime before the game even boots allowed me to set up the perfect storm, the normal exception vector at 80000080 keeps re-arming my watchpoints automatically. Only three emulators I know of emulate it properly, and I actually helped the main DuckStation dev debug it so it'd work on my favorite emulator. Goes to show, the real hardware still had a few secrets left to flex.

I'll add a text file as an attachment that goes more in-depth, along with a GameShark/DuckStation code (in DuckStation format, which allows 4-byte writes and reads). I can't post the full ROM here due to legal reasons, but the DuckStation code should work just as well.

[Spyro 3 V1.1][NTSC-U][Duckstation code]
#AP & AT Bypass Double Jump

A400B080 800594DC
9000B080 8000B0BC
9000B0BC 27BDFFE8
9000B0C0 AFBA0010
9000B0C4 AFBB0014
9000B0C8 00000000
9000B0CC 3C1AA000
9000B0D0 375A0040
9000B0D4 3C1B0800
9000B0D8 377B2C58
9000B0DC AF5B0000
9000B0E0 00000000
9000B0E4 277BFFEA
9000B0E8 AF5B0040
9000B0EC AF400044
9000B0F0 00000000
9000B0F4 8FBA0010
9000B0F8 8FBB0014
9000B0FC 27BD0018
9000B100 08016537
9000B108 3C1AFFFF
9000B10C 375AFFFF
9000B110 409A5800
9000B120 3C1A8004
9000B124 375ACA98
9000B128 409A1800
9000B138 3C1AE180
9000B13C 375A0000
9000B140 409A3800
9000B150 3C1A0000
9000B154 375A0C80
9000B158 03400008
9000B160 40803800
9000B170 3C1AFFFF
9000B174 375AFFFF
9000B178 409A5800
9000B188 3C1A8004
9000B18C 375ACB30
9000B190 409A1800
9000B1A0 3C1AE180
9000B1A4 375A0000
9000B1A8 409A3800
9000B1B8 401A7000
9000B1C8 3C1B8004
9000B1CC 377BCA98
9000B1D0 137A0002
9000B1D8 00021043
9000B1DC 275A0008
9000B1E0 409A7000
9000B1F0 03400008
9000B1F4 42000010
 

Attachments

Last edited:
Back
Top