Closed ITotalJustice closed 2 years ago
quick follow-up for what loops should be skipped (imo)
once you have detected it is a read-only waitloop, next you need to check the address that it is polling. if the address is ram, then the only way for that value can change is via dma or irq handler. otherwise, it's polling IO.
when polling IO, there's lots of edge cases to be aware of. for example, if the game is polling the timer data, then you need to further check what the value it expects is. this can be more complex the bigger the loop as it may involve shifts and adds to the read value, all of this needs to be calculated. finally, when this is done, you can schedule an event at that specific point when the timer will be that value. however there's an edge case. maybe the game fires a dma which disables the timer and then manually writes the reload value. maybe the game writes to the timer from irq handler. maybe the game is polling the timer that is not yet enabled!
as you can see, lots of edge case that add a lot of complexity, and i haven't even covered them all for the timer. this was just 1 IO register as well!
because of this, i think its best to keep the list of loops to skip very very small. this will make analysing the loop itself faster and simple. ideally, you shouldn't need to calculate the value the game expects, you should only calculate the address it is polling. this again saves on time spent alayiseng the loop.
here is my current list of easy addresses to skip:
and thats it. very small list, but it's what the vast majority of games poll.
if you think about it, polling a timer is pointless, they can simply set the timer to 0x10000-timeout
, set the timer irq handler and then wait until the timer irq handler is fired.
Note: i haven't yet compiled a list of common IO polls. I plan to test ~50 games and compile a list of all IO polls and put that data into a table. if it turns out that 90% of games do poll X io register, then we know it is worth optimising for.
if the value the game is polling changes in the middle of the poll loop after the load, then skipping the loop has undesirable effects.
here's an example for if the game is polling vcount.
loop:
ld r2, [VCOUNT] ;@ vcount == 158
cmp r2, #159 ;@ vcount == 159
bne loop ;@ i now skip this loop until vcount changes
this would result in skipping an entire frame because vcount will next change to 160, which this loop will still continue until its finally back on line 159. the intended result is actually to have the loop run twice like this:
ld r2, [VCOUNT] ;@ vcount == 158
cmp r2, #159 ;@ vcount == 159
bne loop ;@ no equal so jumps back
ld r2, [VCOUNT] ;@ vcount == 159
cmp r2, #159 ;@ vcount == 159
bne loop ;@ is equal so loop exits
there's 2 potential workarounds:
thanks to @calc84maniac for pointing this out to me!
when evaluating the loop, it can be quite expensive to check every single instruction and ensure that it only is reading a location in memory and nothing else. you'd have to ensure that any add, sub, mov
is just part of checking if the read back value is what's desired.
an example can be found in yugioh the scared cards
where it starts reading from an address in memory, then increments the register that is used to index memory, then it does a compare before branching. i assume this code is trying to find the first value that equals the wanted value.
evaluate the loop in a 2-pass. on the first pass, do everything like normal, but save a copy of the cpu registers. on the second pass, simply do a memcmp on the current and stored registers. if they're equal, then it's reasonable to assume that the loop is read only.
intro
its pretty trivial to detect waitloops. what's slightly more difficult to detect is what the loop is exactly doing.
often, a loop will read values from ram and do a compare then jump back if it doesn't get the result it wants, like:
detecting a loop
everytime a conditional branch that jumps backwards happens, keep track of the new
pc
. if another conditional branch happens, check if thenew_pc == previous_jump_pc
. if true, we have found a loop!detecting read only loop
detecting a read only loop is more involved. you can use elimination by the loop size.
if (current_pc - new_pc == 0x8)
then only 3 instructions executed. you know the last instruction is the conditional branch, so the first instruction has to be a load and second instruction is going to be a compare.of course the loop can be much bigger in size. the bigger the loop, the more instructions to analyse, which is more expensive. i have a cutoff off 6 instructions, which is the biggest loops ive found so far. its important that when you come to a conclusion as to whether of not the loop can be skipped, save this result. this makes your loop analyse function not really matter that much in terms of performance (within reason, dont be silly about it).
i also only check for loops if the executing code is within rom. i have not yet encountered a rom that wait loops from ram (in thumb), and i couldnt consider it safe to do otherwise. if wait looping in ram, the game dma the ram and overwrite it or that code can be replaced later on.
i have only checked for loops when a cond branch happens in thumb. i have not bothered with arm, and havent needed to.
ways of changing data in a read only loop
when in a read only loop (doesn't write), the only way the data can change is via:
that's it. knowing this, some safe and big speed-ups can be made.
io waitloops
common io registers to poll are:
therefore, reading any of these registers should store the pc. this will make waitloop detection (and its type) far simpler.
other waitloops
however, many games prefer to have their vblank irq handler set a variable in ram. these games will then poll that variable in ram. pokemon emerald does exactly this, so does firered, leafgreen, kirby, etc...
this is slightly more involved however can easily be checked, just ensure that the loop is read only. as long as the loop is read only, then we can safely assume that it waiting for one of 1-4 to occur (see above).
known loops
i spent some time detecting loops in various games, sadly i did not document the loops early on so i cant know if theyre polling io or waiting on ram.
i'd recommend trying to implement a waitloop detection system to try and find all these loops.
unknown loops
some games clearly must be waitlooping, but i have not detected it with my set-up. if you do detect these loops, please tell me know and where the loop is!