Files
LibretroAdmin 581abd98e0 serialize: no function addresses in the save state
register_serialize() wrote data32 out for every register. For one that
is read through a function that field is the low half of the function's
address, which is not state - register_unserialize() reads it into a
dummy - and is different in every process. About 1,300 of them went into
each state, so two states of the same machine at the same moment were
never the same file.

A register read through a function is now written as zero. The format
does not change and older states load as before.

With this and the read-only registers no longer holding an address
either, two runs of the same content for the same number of frames
leave states that differ in the real-time clock and nothing else: 4
places instead of 2,500 bytes. tools/threads/live_same.py now requires
that - more than a few bytes of difference between two identical runs
fails - and still compares the threaded state with the non-threaded one
everywhere but the clock.
2026-10-06 01:29:26 +02:00

79 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""Compare the save states tools/threads/live.sh collects.
Usage: live_same.py threaded.state plain.state plain-again.state
The first comes from a run with Threaded Rendering on, the other two from
two runs with it off; all three ran the same frames from the same start.
The threaded state has to be the non-threaded one.
A save state holds the real-time clock, which is the clock's, so two runs
do not leave quite the same state: the clock's value, the copy the BIOS
keeps in flash and its checksum. The two non-threaded runs show where
those are, and every byte within seven of one that differs between them
is left out of the comparison. Everywhere else the threaded state must
match byte for byte.
The clock is all that may differ between the two non-threaded runs. More
than a few bytes means something that is not state, such as a clock
address, is being written into the state again, and that fails too.
"""
import sys
def main():
if len(sys.argv) != 4:
sys.stderr.write(__doc__)
return 2
threaded, plain, again = (open(p, 'rb').read() for p in sys.argv[1:])
if not (len(threaded) == len(plain) == len(again)):
print('the states differ in size: %d, %d, %d'
% (len(threaded), len(plain), len(again)))
return 1
def differing(a, b):
"""Offsets of the bytes in which a and b differ."""
out = []
if a == b:
return out
step = 1 << 16
for base in range(0, len(a), step):
ca, cb = a[base:base + step], b[base:base + step]
if ca != cb:
out.extend(base + i for i in range(len(ca)) if ca[i] != cb[i])
return out
# Runs of bytes that hold the clock, as [first, last] pairs.
clock = []
for i in differing(plain, again):
if clock and i - 7 <= clock[-1][1]:
clock[-1][1] = i + 7
else:
clock.append([i - 7, i + 7])
# Between two runs of the same thing, only the clock may differ: the
# RTC and what the BIOS writes of it into flash.
noise = sum(1 for _ in differing(plain, again))
if noise > 32:
print('%d bytes differ between the two non-threaded runs; only the '
'clock should' % noise)
return 1
bad = []
run = 0
for i in differing(threaded, plain):
while run < len(clock) and clock[run][1] < i:
run += 1
if run == len(clock) or i < clock[run][0]:
bad.append(i)
if bad:
print('%d bytes differ outside the %d places the clock is kept; '
'first at offset 0x%x' % (len(bad), len(clock), bad[0]))
return 1
print('same state (%d places holding the clock left out)' % len(clock))
return 0
if __name__ == '__main__':
sys.exit(main())