Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alexbevi
GitHub Repository: alexbevi/BizHawk
Path: blob/master/Assets/Lua/N64/M64_reader.lua
2 views
1
-- M64 reader script
2
-- Translates M64 file movies into button presses for Bizhawk, accounting for lag frames.
3
-- This script will automatically pause at the end of the movie
4
5
-- This script will not clear the saveram. If a movie requires empty saveram it must be cleared before using this script.
6
7
-- If you are trying to convert a M64 into BKM format, you can try pausing the emulator and starting recording a movie, then loading this script and letting it run. Beginning a new movie will clear the saveram for you.
8
9
10
local m64_filename = forms.openfile(nil,nil,"Mupen Movie Files (*.M64)|*.M64|All Files (*.*)|*.*")
11
12
console.clear()
13
if m64_filename == "" then
14
console.log("No movie selected. Exiting.")
15
return
16
end
17
18
console.log("Opening movie for playback: " .. m64_filename)
19
20
-- Open the file and read past the header data
21
local input_file = assert(io.open(m64_filename, "rb"))
22
local header = input_file:read(0x400)
23
24
-- Check the file and display some info
25
if string.sub(header,1,3) ~= "M64" or string.byte(header,4) ~= 0x1A then
26
console.log("File signature is not M64\\x1A. This might not be an .m64 movie, but I'll try to play it anyway")
27
end
28
29
function remove_nulls(s)
30
if string.len(s) == 0 then
31
return s
32
end
33
34
local i = 1
35
while string.byte(s,i) ~= 0 and i <= string.len(s) do
36
i = i + 1
37
end
38
39
return string.sub(s,1,i-1)
40
end
41
42
local movie_rom_name = string.sub(header,0x0C5,0x0E4)
43
movie_rom_name = remove_nulls(movie_rom_name)
44
console.log("Rom name: " .. movie_rom_name)
45
46
local rerecords = string.byte(header,0x11) + string.byte(header,0x12) * 0x100 + string.byte(header,0x13) * 0x10000 + string.byte(header,0x14) * 0x1000000
47
console.log("# of rerecords: " .. rerecords)
48
49
local rerecords = string.byte(header,0x0D) + string.byte(header,0x0E) * 0x100 + string.byte(header,0x0F) * 0x10000 + string.byte(header,0x10) * 0x1000000
50
console.log("# of frames: " .. rerecords)
51
52
local author_info = string.sub(header,0x223,0x300)
53
author_info = remove_nulls(author_info)
54
console.log("Author: " .. author_info)
55
56
local description = string.sub(header,0x301,0x400)
57
description = remove_nulls(description)
58
console.log("Description: " .. description)
59
60
local video_plugin = string.sub(header,0x123,0x162)
61
video_plugin = remove_nulls(video_plugin)
62
console.log("Video Plugin: " .. video_plugin)
63
64
local audio_plugin = string.sub(header,0x163,0x1A2)
65
audio_plugin = remove_nulls(audio_plugin)
66
console.log("Audio Plugin: " .. audio_plugin)
67
68
local input_plugin = string.sub(header,0x1A3,0x1E2)
69
input_plugin = remove_nulls(input_plugin)
70
console.log("Input Plugin: " .. input_plugin)
71
72
local rsp_plugin = string.sub(header,0x1E3,0x222)
73
rsp_plugin = remove_nulls(rsp_plugin)
74
console.log("RSP Plugin: " .. rsp_plugin)
75
76
-- Flag to note that we've reached the end of the movie
77
local finished = false
78
79
-- Since m64 movies do not record on lag frames, we need to know if the input was actually used for the current frame
80
local input_was_used = false
81
function input_used()
82
if not finished then
83
input_was_used = true
84
end
85
end
86
event.oninputpoll(input_used)
87
88
local buttons = { }
89
local X
90
local Y
91
-- Reads in the next frame of data from the movie, or sets the finished flag if no frames are left
92
function read_next_frame()
93
local data = input_file:read(4)
94
if not data or string.len(data) ~= 4 then
95
finished = true
96
return
97
end
98
local byte = string.byte(string.sub(data,1,1))
99
if bit.band(byte,0x01) ~= 0 then
100
buttons["DPad R"] = true
101
else
102
buttons["DPad R"] = false
103
end
104
if bit.band(byte,0x02) ~= 0 then
105
buttons["DPad L"] = true
106
else
107
buttons["DPad L"] = false
108
end
109
if bit.band(byte,0x04) ~= 0 then
110
buttons["DPad D"] = true
111
else
112
buttons["DPad D"] = false
113
end
114
if bit.band(byte,0x08) ~= 0 then
115
buttons["DPad U"] = true
116
else
117
buttons["DPad U"] = false
118
end
119
if bit.band(byte,0x10) ~= 0 then
120
buttons["Start"] = true
121
else
122
buttons["Start"] = false
123
end
124
if bit.band(byte,0x20) ~= 0 then
125
buttons["Z"] = true
126
else
127
buttons["Z"] = false
128
end
129
if bit.band(byte,0x40) ~= 0 then
130
buttons["B"] = true
131
else
132
buttons["B"] = false
133
end
134
if bit.band(byte,0x80) ~= 0 then
135
buttons["A"] = true
136
else
137
buttons["A"] = false
138
end
139
140
byte = string.byte(string.sub(data,2,2))
141
if bit.band(byte,0x01) ~= 0 then
142
buttons["C Right"] = true
143
else
144
buttons["C Right"] = false
145
end
146
if bit.band(byte,0x02) ~= 0 then
147
buttons["C Left"] = true
148
else
149
buttons["C Left"] = false
150
end
151
if bit.band(byte,0x04) ~= 0 then
152
buttons["C Down"] = true
153
else
154
buttons["C Down"] = false
155
end
156
if bit.band(byte,0x08) ~= 0 then
157
buttons["C Up"] = true
158
else
159
buttons["C Up"] = false
160
end
161
if bit.band(byte,0x10) ~= 0 then
162
buttons["R"] = true
163
else
164
buttons["R"] = false
165
end
166
if bit.band(byte,0x20) ~= 0 then
167
buttons["L"] = true
168
else
169
buttons["L"] = false
170
end
171
172
X = string.byte(string.sub(data,3,3))
173
if X > 127 then
174
X = X - 256
175
end
176
177
Y = string.byte(string.sub(data,4,4))
178
if Y > 127 then
179
Y = Y - 256
180
end
181
end
182
183
while true do
184
-- Only read the next frame of data if the last one was used
185
if input_was_used and not finished then
186
read_next_frame()
187
input_was_used = false
188
end
189
190
if not finished then
191
joypad.set(buttons, 1)
192
local analogs = { ["X Axis"] = X, ["Y Axis"] = Y }
193
joypad.setanalog(analogs, 1)
194
end
195
196
if finished then
197
console.log("Movie finished")
198
client.pause()
199
return
200
end
201
202
emu.frameadvance()
203
end
204
205