PSP / PPSSPP Modifying Code at Runtime - Is it a bad idea?

Started by flame, February 03, 2018, 10:39:16 AM

Previous topic - Next topic

flame

PPSSPP has a setting called dynarec, dynamic recompiler, and it gets a lot of its fast emulation speed from that.

dynarec on is the default. You can turn it off. The only way I know how to get to settings when the game is running is by pushing the PS button or Xbox button on your controller, you can also get there from the PPSSPP screen when no game is loaded. From there it's System -> Developer Tools -> Dynarec (JIT).

I am working on Ao no Kiseki and to solve a certain problem my strategy was to write code that changes existing game code - a function parameter coded by an li instruction. But with Dynarec on, PPSSPP will never see it. A real PSP just runs whatever code is there so it will work fine; PPSSPP reads the code when the game boots and never again after that when dynarec is on. With it off, PPSSPP works like a PSP does.

So is it a good idea to replace that hard coded function parameter with an lw instruction? If I do that, it will work even with dynarec on.

Gemini

Woudln't automodifying code need a cache flush before execution? I believe such case would be emulated correctly even on dynarec.

flame

I saw sceKernelDcacheWritebackAll syscall. The game calls it once every frame already. I don't know how else to flush the cache.

With dynarec off it does work; with it on it doesn't.

As an additional test I changed those li instructions to lw's and it did work even with dynarec on.

[Unknown]

I know this is an older topic (sorry), but just wanted to clarify and figured someone might find this via search.

PPSSPP should detect code modifications (and it worked on the PSP also), but only in certain situations (the way games usually do it.)

Beware the instruction cache.  Your change on the PSP will only work if the modified instruction is not in the cache or gets purged out before the next run.  The data cache (Dcache) is separate.  You *should* use sceKernelIcacheInvalidateRange to write proper code.

You can see here:
https://github.com/hrydgard/pspautotests/blob/master/tests/cpu/vfpu/vregs.cpp#L28

(in this case, I'm dynamically generating code for a test run, so I flush the dcache to make sure my write is in RAM, and flush the icache to make sure the new instructions are seen from RAM.)

Even if you can't call sceKernelIcacheInvalidateRange, there's actually a `cache` instruction.  Operation 8 on the PSP's processor will zap the icache for a cache line (I assume it's what that syscall does anyway.)  `cache 8,0x1234(at)` should do the trick.

If you do this, PPSSPP should detect the modification and everything should be fine.

Without that, PPSSPP still tries to detect code modifications, but not exhaustively.  First, it won't detect a partial write (i.e. sb, sh, swl, swr type writes), only a full word write.  Second, the write needs to touch the start of the basic block.  This is a limitation currently for performance reasons, so like I said it may change (there's a couple games that fall into annoying gaps...)

Just to explain the basic block part, here's some example MIPS we might want to modify:


.func SomeFunc
08004000 12200003  beq s1,zero,0x08004010
08004004 00000000  nop
08004008 3C021234  lui v0,0x1234
0800400C 34445678  ori a0,v0,0x5678
...
.endfunc


Let's say that we wanted to change this to load 0x1234567C instead of 0x12345678.  A tempting way might be to just write the byte at 0800400F, but PPSSPP definitely won't detect that.

But even writing a full word there won't work.  You'd have to write to 08004008 as well, because that starts a "basic block".  Basically, a new basic block starts after every possible branch, and at every branch target.  It's a compiler thing.  PPSSPP only checks the first instruction of each basic block.

In practice, if you can't clear cache for some reason (but really, do that please), the next best thing to do is what games typically do: overwrite the entire function.  Swapping in a new function will work fine.  Replacing the first instruction with a jump to your code will also work.

-[Unknown]