brad-lin / FreePSXBoot

Exploit to allow loading arbitrary code on the PSX using only a memory card (no game needed)
MIT License
574 stars 44 forks source link

Seizure-inducing orange screen #19

Closed socram8888 closed 3 years ago

socram8888 commented 3 years ago

While the payload is loading, the screen is painted orange using the 0x02 GP0 command. This works perfectly fine on an emulator, but I got a very annoying flickering on real hardware which I am not sure what could be causing it.

This is also affecting tonyhax, which uses the same command for clearing the screen.

nicolasnoble commented 3 years ago

The painting only affects the current drawing area.

https://github.com/grumpycoders/pcsx-redux/blob/93653ba5281487d3bed57371d7b64c32dfc669f0/src/gpu/soft/soft.cc#L1012-L1015

If you want to fill a larger section of the vram, you'd need to first enlarge the clipping area:

https://github.com/nicolasnoble/pcsx-redux/blob/tiny-shell/src/mips/common/hardware/gpu.h#L132-L140 https://github.com/nicolasnoble/pcsx-redux/blob/tiny-shell/src/mips/shell/gpu.c#L38-L40

It's not happening on emulators because typically, emulators will only flip buffers when reaching an emulated vsync, so only a single buffer stays on.

socram8888 commented 3 years ago

I am not sure about that. The BIOS at that point has a drawing area configured to cover the entire viewfield, as it does not use double buffering: imagen

E3 is X: 0 and Y: 0, while E4 is X: 639 Y: 479. Thus the fill would affect the entire image, not flicker like it does.

nicolasnoble commented 3 years ago

Oh, right. I've seen this actually. When the GPU is in 480i mode, and when it's not refreshing anymore properly, it'll still send the old frame on every other vsync.

nicolasnoble commented 3 years ago

One solution here would be to disable interlacing: https://psx-spx.consoledev.net/graphicsprocessingunitgpu/#gp108h-display-mode

nicolasnoble commented 3 years ago

Writing 0x08000000 to GP1 should suffice that is. We can do this in the second stage however, where we have more instructions to spare.

socram8888 commented 3 years ago

It's probably the best solution. I've spent since that message trying to come with solutions that would work in i480 and none has worked:

None has worked and the orange screen and tonyhax are totally bugged.

nicolasnoble commented 3 years ago

My current strategy in stage2 is just to reinitialize the GPU, really.

nicolasnoble commented 3 years ago

So in order to explain what's going on right now, I'm currently working on a 3-stage boot process:

stage 1 is the 128 bytes initial exploit payload stage 2 is a tiny bootstrap binary that's currently 8 frames long and would take about 300ms to load from stage 1, with the ability to reinitialize the GPU and has its own memory card routine that's much faster than the bios stage 3 is unirom that reboots the machine with the cop0 hook in order to properly reinitialize the kernel

socram8888 commented 3 years ago

For the record, I found the problem: the bit 10 of Texpage isn't set when the screen is painted orange. Setting texpage fixes the issue.

I'd recommend doing this instead of fully reinitializing the GPU issuing a command 0x00, as this will leave the GPU in the correct system's video standard (unless you remember this bit and set the correct standard also after reseting it).

nicolasnoble commented 3 years ago
    int isPAL = (*((char *)0xbfc7ff52) == 'E');
nicolasnoble commented 3 years ago

Oh by the way, bit 10 of texpage is for interlace painting. The GPU is always in double buffer mode, that is. In 480i mode, the double buffering is done by masking every other line when drawing.

socram8888 commented 3 years ago

Not sure if it's worth adding or not, but if you use the backwards loading scheme I proposed we have enough space in the gpuWords array to fit in the texpage required to properly paint the screen in first stage:

diff --git a/builder/builder.cc b/builder/builder.cc
index 2c217eb..b455976 100755
--- a/builder/builder.cc
+++ b/builder/builder.cc
@@ -365,7 +365,7 @@ int main(int argc, char** argv) {
     }

     constexpr unsigned firstUsableFrame = 64;
-    const bool backwards = args.get<bool>("bw", false);
+    const bool backwards = args.get<bool>("bw", true);

     if (sp == 0) sp = 0x801ffff0;
     unsigned frames = (tsize + 127) >> 7;
@@ -519,6 +519,7 @@ int main(int argc, char** argv) {
     };

     std::vector<uint32_t> gpuWords = {
+        0xe1000400,  // enable writing outside vsync
         0x0200a5ff,  // paint orange
         0,           // x, y
         0x01ff03ff,  // width, height

Tried it on the SCPH-102 and it works just fine.

nicolasnoble commented 3 years ago

So the stage2 now over in #14 is ready, and I'd say there is no longer any need for any visual feedback in stage1. On this branch, stage2 loads within ~400ms once stage1 starts its work, and stage2 will properly do a nice GPU reset and drawing, before loading stage3 using a custom method in ~7 seconds.

nicolasnoble commented 3 years ago

Alright, #11 has been merged, and all the fastload images have the new stage2 code. The non-fastload images are still there, but only in case something goes wrong. We'll probably just remove the non-fastload images eventually.

There's no longer any epilepsy-inducing flashes.