Created
August 29, 2021 19:14
-
-
Save Zinfidel/047a31b5fb0e3d86854e0cf8e7b00c80 to your computer and use it in GitHub Desktop.
Scripts for force-syncing movies in BizHawk (particularly N64 movies with different plug-in settings). See http://tasvideos.org/Zinfidel/SyncScripts.html for usage.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package.path = package.path .. ';luasocket/lua/?.lua;luasocket/lua/socket/?.lua' | |
package.cpath = package.cpath .. ';luasocket/?.dll;luasocket/mime/?.dll;luasocket/socket/?.dll' | |
local socket = require 'socket' | |
local socClient | |
local server | |
local onExitEvent = event.onexit( | |
function() | |
socClient:close() | |
server:close() | |
console.log("Client closed.") | |
end | |
, "Exit Event" ); | |
local function endsWith(str, ending) | |
return ending == "" or str:sub(-#ending) == ending | |
end | |
local function strChop(str, ending) | |
return str:sub(1, -(#ending + 1)) | |
end | |
-- Pause the client and establish a socket listener. | |
client.pause() | |
server = assert(socket.bind("127.0.0.1", 45678), "Socket server failed to bind to ip/port. Script aborted.") | |
console.write("Waiting on connection...") | |
emu.yield() | |
server:settimeout(5) | |
server:setoption('keepalive', true) | |
-- Wait for the other instance to connect. | |
while true do | |
local err = nil | |
socClient, err = server:accept() | |
if not err then | |
console.log(" Connected!") | |
emu.yield() | |
break | |
else | |
console.write('.') | |
end | |
emu.yield() | |
end | |
socClient:settimeout(10) | |
console.write("Awaiting a message ...") | |
while true do | |
emu.yield() | |
local line, err = socClient:receive() | |
if not err then | |
if line == "quit" then | |
console.log("\nReceived exit message. Exiting now.") | |
break; | |
elseif endsWith(line, ".State") then | |
local frameNum = tonumber(strChop(line, ".State")) | |
console.write("\nReceived request for a savestate at frame " .. tostring(frameNum) .. ". Seeking...") | |
emu.yield() | |
client.seekframe(frameNum) | |
savestate.save(line) | |
console.log("\nCreated " .. line .. ". Sending to the client.") | |
emu.yield() | |
socClient:send(line .. "\n") | |
console.write("\nAwaiting a message ...") | |
else | |
console.log("\nUnknown command " .. line .. " received.") | |
end | |
elseif err == "timeout" then | |
console.write('.') | |
elseif err == "closed" then | |
console.log("\nConnection with client closed. Exiting now.") | |
break; | |
else | |
console.log("\nNo message received. Error: " .. err) | |
end | |
emu.yield() | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- This script is for collecting "sync data" for a game that consists of some sort of canary data | |
-- that is very likely to indicate a desync happening. Good choices for this kind of data are transformation | |
-- matrices for models, or manually collected position data like x,y,z,pitch,yaw,roll,etc. | |
-- Change this to some amount of frames on which data will be flushed. 200 means flush to file every 200 frames. | |
FLUSH_FRAMES = 200 | |
-- Check if a file exists on the filesystem | |
local function file_exists(name) | |
local f=io.open(name,"r") | |
if f~=nil then io.close(f) return true else return false end | |
end | |
-- Create a "log.txt" file for the data, but add a number so we don't accidentally overwrite previous results. | |
local name = "log.txt" | |
local i = 1 | |
while (file_exists(name)) do | |
name = "log" .. tostring(i) .. ".txt" | |
i = i + 1 | |
end | |
LogFile = io.open(name, "w") | |
-- Flush and close the log file when the script closes. | |
local onExitEvent = event.onexit( | |
function() | |
LogFile:close() | |
console.log("") | |
console.log("Script stopping. Closing log file.") | |
end | |
, "Exit Event" ); | |
-- Collect sync data | |
console.write("Flushing to file ") | |
while true do | |
-- Custom data collection for the game goes in here. The example code below is for Star Wars Episode 1: Racer, | |
-- it finds the player's position matrix and records each matrix entry as a float to 5 decimals. It writes these | |
-- data to a comma-separated line. | |
--------------------------------------------------------------------------------------------------------------- | |
Racedata = 0x118F90 | |
Statedata = mainmemory.read_u24_be(Racedata+0x84+0x1) | |
MatrixStart = Statedata + 0x1C | |
for j=0, 15, 1 do | |
LogFile:write(string.format("%.5f",mainmemory.readfloat(MatrixStart + 4*j, true))) | |
if (j ~= 15) then LogFile:write(',') end | |
end | |
--------------------------------------------------------------------------------------------------------------- | |
-- End custom collection code | |
-- New line, and if we are at a flush boundary, flush the file. | |
LogFile:write('\n') | |
if (emu.framecount() % FLUSH_FRAMES) == 0 then | |
LogFile:flush() | |
console.write('.') | |
end | |
emu.frameadvance() | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- feos, 2017 | |
-- DK64 resync workflow | |
-- props to thecoreyburton | |
t = {} -- array of state names found | |
count = 0 -- total states found, just for print() | |
av_paused = false -- pretend we have that info from the emu | |
offset = 10 -- av delay after stated frame | |
-- scan the script dir for states and store their names to array | |
function scandir() | |
local popen = io.popen -- use cmd | |
local pfile = popen('dir . /b') -- cmd arguments | |
local i = 0 | |
local index = 0 | |
local name = "" | |
for filename in pfile:lines() do | |
i = i + 1 | |
-- skip all but state files | |
if filename:find(".State") then | |
-- strip frame numbers | |
name = string.gsub(filename, ".State", "") | |
index = tonumber(name) | |
t[index] = filename | |
count = count + 1 | |
end | |
end | |
pfile:close() | |
end | |
scandir() | |
print("States found: " .. count) -- #t doesn't work for some reason (gaps in the table?) | |
last_frame = 0 | |
while true do | |
if movie.isloaded() then | |
-- check if there's a state of "offset" frames back | |
local name = t[emu.framecount() - offset] | |
-- suspend av and load the state, with a delay | |
if name and not av_paused then | |
client.pause_av() | |
av_paused = true | |
savestate.load(name) | |
print("Loaded " .. name) | |
end | |
-- basic report you'd barely see | |
if av_paused then | |
gui.text(0, 40, "AV paused") | |
end | |
-- resume av when that frame occurs again | |
if t[emu.framecount() - offset] and av_paused then | |
client.unpause_av() | |
av_paused = false | |
end | |
end | |
emu.frameadvance() | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package.path = package.path .. ';luasocket/lua/?.lua;luasocket/lua/socket/?.lua' | |
package.cpath = package.cpath .. ';luasocket/?.dll;luasocket/mime/?.dll;luasocket/socket/?.dll' | |
local socket = require 'socket' | |
local tab = {} -- entire parsed file broken into lines | |
local gap_minimum = 10 -- Set the minimum number of frames between savestates. This can help prevent thrashing in bad sections. | |
function ParseFile() | |
print("Parsing file...") | |
local logfile = io.open("log.txt", "r") | |
local char | |
repeat | |
char = logfile:read("*l") | |
table.insert(tab, char) | |
until char == nil | |
logfile:close() | |
print("File parsed! Line count: "..#tab.."\n") | |
end | |
-- Customize this function to retrieve your sync data and format it the same way as it appears in log.txt. An example of how | |
-- to do this for N64 Star Wars Episode 1: Racer, matching the example in sync-collect.lua, is the default implementation. this | |
-- value will get compared, as a string, to the corresponding line in log.txt to verify sync. | |
function GetData() | |
Racedata = 0x118F90 | |
Statedata = mainmemory.read_u24_be(Racedata+0x84+0x1) | |
SyncData = {} | |
MatrixStart = Statedata + 0x1C | |
for i=0, 15, 1 do | |
table.insert(SyncData, string.format("%.5f",mainmemory.readfloat(MatrixStart + 4*i, true))) | |
end | |
return table.concat(SyncData, ",") | |
end | |
-- Pause the client and establish a tcp connection to the server. | |
client.pause() | |
local tcp = assert(socket.connect("127.0.0.1", 45678), "Socket client failed to establish tcp connection with server. Script aborted.") | |
console.write("Connection to server established!\n") | |
tcp:settimeout(2) | |
emu.yield() | |
ParseFile() | |
local onExitEvent = event.onexit( | |
function() | |
console.log("\nScript exiting. Sending quit command to server." ); | |
tcp:send("quit\n") | |
end | |
, "OnExit" ); | |
local last_ss = 0 | |
while true do | |
local frame = emu.framecount() | |
local original = tab[frame+1] | |
local yours = GetData() | |
gui.pixelText(0, 40, string.format( "%s - original\n%s - yours", original, yours)) | |
-- mismatch | |
if (yours ~= original) and ((frame - last_ss) > gap_minimum) then | |
last_ss = frame | |
client.pause() | |
-- screen and console alarm | |
gui.pixelText(0, 40, string.format( "%s - original\n%s - yours\ndesync at frame %d", original, yours, frame), "red") | |
print(string.format( "desync at frame %d\n%s\n%s\n", frame, yours, original)) | |
emu.yield() | |
local response = "" | |
tcp:send(frame .. ".State\n") | |
console.write("Sent packet asking for frame " .. tostring(frame) .. " ...") | |
emu.yield() | |
while true do | |
local line, err, partial = tcp:receive() | |
if not err then | |
response = line | |
console.log("\nReceived message that savestate " .. tostring(frame) .. " is available. Loading ...") | |
emu.yield() | |
savestate.load(response) | |
client.unpause() | |
break | |
elseif err == "timeout" then | |
response = response .. partial | |
console.write('.') | |
elseif err == "closed" then | |
console.log("\nConnection with server closed. Exiting now.") | |
break | |
else | |
console.log("\nUnknown communication error.") | |
end | |
emu.yield() | |
end | |
end | |
-- so far so good | |
emu.frameadvance() | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment