Created
March 6, 2023 07:50
-
-
Save taotao54321/1bf4cd801f8de4cddaa4d1e94b99d7d2 to your computer and use it in GitHub Desktop.
Plays a NesHawk movie on SubNesHawk
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
--[[ | |
Plays a NesHawk movie on SubNesHawk. | |
Set INPUT_PATH, and launch this script (at frame 0). | |
Power/Reset is not supported. | |
--]] | |
-- extract "Input Log.txt" from your NesHawk movie, and put it to the script directory. | |
-- (or specify an absolute path) | |
local INPUT_PATH = "Input Log.txt" | |
local function str_at(s, idx) | |
return string.sub(s, idx, idx) | |
end | |
local function str_starts_with(s, prefix) | |
return string.find(s, prefix, 1, true) == 1 | |
end | |
local function panic(msg) | |
error(msg) | |
end | |
-- parses a player input. (e.g. "UDLRSsBA") | |
local function parse_input_player(s) | |
local CHARS = { "U", "D", "L", "R", "S", "s", "B", "A" } | |
local NAMES = { "Up", "Down", "Left", "Right", "Start", "Select", "B", "A" } | |
if string.len(s) ~= 8 then | |
return nil | |
end | |
local input = {} | |
for i = 1, 8 do | |
local ch = str_at(s, i) | |
if ch == "." then | |
-- do nothing | |
elseif ch == CHARS[i] then | |
input[NAMES[i]] = true | |
else | |
return nil | |
end | |
end | |
return input | |
end | |
-- parses a frame line. (e.g. "|..|UDLRSsBA|") | |
local function parse_input_frame(line) | |
local invalid = function () | |
panic(string.format("invalid input frame: %s", line)) | |
end | |
if not str_starts_with(line, "|") then | |
invalid() | |
end | |
-- current position in the line. | |
local pos = 2 | |
-- skip power/reset field. | |
do | |
local pos_sep = string.find(line, "|", pos, true) | |
if pos_sep == nil then | |
invalid() | |
end | |
pos = pos_sep + 1 | |
end | |
-- array for each player's input. | |
local inputs = {} | |
-- parse inputs, up to 4 players. | |
for _i = 1, 4 do | |
local pos_sep = string.find(line, "|", pos, true) | |
if pos_sep == nil then | |
break | |
end | |
local s = string.sub(line, pos, pos_sep - 1) | |
local input = parse_input_player(s) | |
if input == nil then | |
invalid() | |
end | |
table.insert(inputs, input) | |
pos = pos_sep + 1 | |
end | |
-- at least, player 1's input must exist. | |
if #inputs == 0 then | |
invalid() | |
end | |
return inputs | |
end | |
-- parses "Input Log.txt". | |
local function parse_input(path) | |
local rdr = io.open(path, "r") | |
if rdr == nil then | |
panic(string.format("can't open file '%s'", path)) | |
end | |
local lines = rdr:lines() | |
-- check for header. | |
do | |
local line_first = lines() | |
if line_first ~= "[Input]" then | |
panic("invalid input: '[Input]' not found") | |
end | |
local line_second = lines() | |
if not str_starts_with(line_second, "LogKey:") then | |
panic("invalid input: 'LogKey:' not found") | |
end | |
end | |
-- array for each frame. | |
local frames = {} | |
-- parse each frames. | |
for line in rdr:lines() do | |
if line == "[/Input]" then | |
break | |
end | |
local inputs = parse_input_frame(line) | |
table.insert(frames, inputs) | |
end | |
rdr:close() | |
return frames | |
end | |
local function play(frames) | |
-- NOTE: you can't use emu.islagged() to detect VSYNC. | |
-- use emu.lagcount(). | |
local frame = emu.lagcount() | |
for _i, inputs in ipairs(frames) do | |
repeat | |
for j, input in ipairs(inputs) do | |
joypad.set(input, j) | |
end | |
emu.frameadvance() | |
until frame ~= emu.lagcount() | |
frame = emu.lagcount() | |
end | |
end | |
local function main() | |
local frames = parse_input(INPUT_PATH) | |
play(frames) | |
end | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment