function patch_logic(logic, windows, activate_debug)
local ptr_start = 3
local rd_opcode = function(PC)
logic:seek(PC * 4)
return logic:readU32()
end
local patch_opcode = function(PC, newcode)
logic:bseek(PC * 4)
logic:writeU32(newcode)
end
local txt_process = function(PC)
local opcode = rd_opcode(PC)
local txtptr
local x, y, width, height
if andB(opcode, 0x00100000) ~= 0 then
local psh1 = rd_opcode(PC - 1)
local psh2 = rd_opcode(PC - 2)
local psh3 = rd_opcode(PC - 3)
local psh4 = rd_opcode(PC - 4)
local psh5 = rd_opcode(PC - 5)
local psh6 = rd_opcode(PC - 6)
if andB(psh1, 0xff000000) ~= 0x12000000 then return end
if andB(psh2, 0xff000000) ~= 0x12000000 then return end
if andB(psh3, 0xff000000) ~= 0x12000000 then return end
if andB(psh4, 0xff000000) ~= 0x12000000 then return end
if andB(psh5, 0xff000000) ~= 0x12000000 then return end
if andB(psh6, 0xff000000) ~= 0x12000000 then return end
local arg1 = andB(psh1, 0x7fffff)
local arg2 = andB(psh2, 0x7fffff)
local arg3 = andB(psh3, 0x7fffff)
local arg4 = andB(psh4, 0x7fffff)
local arg5 = andB(psh5, 0x7fffff)
local arg6 = andB(psh6, 0x7fffff)
txtptr = arg6 + 1 - ptr_start
if txtptr < 0 then return end
local window = windows[txtptr + ptr_start]
if not window then return end
psh5 = orB(andB(psh5, 0xff800000), window.x)
psh4 = orB(andB(psh4, 0xff800000), window.y)
psh3 = orB(andB(psh3, 0xff800000), window.width)
psh2 = orB(andB(psh2, 0xff800000), window.height)
patch_opcode(PC - 2, psh2)
patch_opcode(PC - 3, psh3)
patch_opcode(PC - 4, psh4)
patch_opcode(PC - 5, psh5)
else
txtptr = andB(opcode, 0xff) + 1 - ptr_start
if txtptr < 0 then return end
local window = windows[txtptr + ptr_start]
local arg1 = orB(window.x, shl(window.y, 16))
local arg2 = orB(window.width, shl(window.height, 16))
patch_opcode(PC + 1, arg1)
patch_opcode(PC + 2, arg2)
end
end
local process_subtxt = function(PC)
local psh1 = rd_opcode(PC - 1)
local psh2 = rd_opcode(PC - 2)
local psh3 = rd_opcode(PC - 3)
local psh4 = rd_opcode(PC - 4)
local psh5 = rd_opcode(PC - 5)
local psh6 = rd_opcode(PC - 6)
if andB(psh1, 0xff000000) ~= 0x12000000 and andB(psh1, 0xff000000) ~= 0x0B000000 then return end
if andB(psh2, 0xff000000) ~= 0x12000000 then return end
if andB(psh3, 0xff000000) ~= 0x12000000 and andB(psh3, 0xff000000) ~= 0x0B000000 then return end
if andB(psh4, 0xff000000) ~= 0x12000000 and andB(psh4, 0xff000000) ~= 0x0B000000 then return end
if andB(psh5, 0xff000000) ~= 0x12000000 and andB(psh5, 0xff000000) ~= 0x0B000000 then return end
if andB(psh6, 0xff000000) ~= 0x12000000 and andB(psh6, 0xff000000) ~= 0x0B000000 then return end
local arg2 = andB(psh2, 0x7fffff)
local txtptr = arg2 + 1 - ptr_start
if txtptr < 0 then return end
local window = windows[txtptr + ptr_start]
if not window then return end
if andB(psh3, 0xff000000) ~= 0x0B000000 and window.height then psh3 = orB(andB(psh3, 0xff800000), window.height) end
if andB(psh4, 0xff000000) ~= 0x0B000000 and window.width then psh4 = orB(andB(psh4, 0xff800000), window.width) end
if andB(psh5, 0xff000000) ~= 0x0B000000 and window.y then psh5 = orB(andB(psh5, 0xff800000), window.y) end
if andB(psh6, 0xff000000) ~= 0x0B000000 and window.x then psh6 = orB(andB(psh6, 0xff800000), window.x) end
patch_opcode(PC - 3, psh3)
patch_opcode(PC - 4, psh4)
patch_opcode(PC - 5, psh5)
patch_opcode(PC - 6, psh6)
end
local call_process = function(PC)
local opcode = rd_opcode(PC)
if opcode == 0x1400033C or opcode == 0x140003E2 or opcode == 0x140003F5 or opcode == 0x14000408 then process_subtxt(PC) end
end
check_logic(logic)
if activate_debug then patch_opcode(0x1158, 0x9e000000) end
logic_disasm(logic, nil, {
-- txtbox1
[0x92] = txt_process,
-- txtbox2
[0x93] = txt_process,
-- txtbox3
[0x94] = txt_process,
-- txtbox4
[0x98] = txt_process,
-- *sigh*
[0x14] = call_process,
} )
end
function process_current_room_script()
local script, font, scriptfile, fontfile, nptrs, windows
room_idx = current_file - 3610
if not rooms_lookup[room_idx] then return nil, nil end
script = Buffer(true)
scriptfile = Buffer(true)
fontfile = Buffer(true)
font = { idx = 1, ilookup = {}, clookup = {} }
for k, v in ipairs(script_types.room.translated) do
add_glyph(font, v)
end
for i = 1, script_types.room.size do
script:writeU8(script_types.room.header[i])
end
scriptfile:writeU16(0)
scriptfile:writeU16(3)
nptrs = 0
windows = {}
for pidx, pval in ipairs(rooms_lookup[room_idx]) do
nptrs = nptrs + 1
local ptr = rooms_txts[pval]
windows[nptrs] = windows_data[pval]
scriptfile:writeU16(script:getsize())
for k, v in ipairs(ptr) do
if type(v) == "string" then
local str = v
local char
local gidx
while str:len() ~= 0 do
char, str = get_next_utf8(str)
gidx = font.clookup[char] or add_glyph(font, char)
if gidx < 0x80 then
script:writeU8(gidx)
else
local high = shl(gidx, 7)
local low = andB(gidx, 127) + 0x80
script:writeU8(low)
script:writeU8(high)
end
end
elseif type(v) == "table" then
local code = v[1]
local high = 0x80
local low = 0x80 + code
script:writeU8(low)
script:writeU8(high)
if v[2] then script:writeU8(v[2]) end
if v[3] then script:writeU8(v[3]) end
if v[4] then error "Wrong script..." end
else
error("Wrong script...")
end
end
script:writeU8(0)
end
scriptfile:copyfrom(script)
script:destroy()
local npadding = alignment(#font.ilookup, 4)
fontfile:writeU32(#font.ilookup + npadding)
fontfile:writeU32(12)
for _, glyph in ipairs(font.ilookup) do
if not glyphes[glyph] then error("No glyph in database for " .. glyph) end
fontfile:writeU8(glyphes[glyph].attr.Width)
end
for i = 1, npadding do
fontfile:writeU8(0)
end
for _, glyph in ipairs(font.ilookup) do
Base64DecodeBin(glyphes[glyph].attr.Data, fontfile)
end
for i = 1, npadding do
for j = 1, 6 do fontfile:writeU32(0) end
end
return scriptfile, fontfile, nptrs + 2, windows
end
function insert_script(old, new, nptrs, windows)
local size_logic = old:readU32()
local u1 = old:readU32()
local u2 = old:readU32()
local n_ptrs = old:readU32()
local u3 = old:readU32()
local u4 = old:readU32()
local u5 = old:readU32()
local ret = Buffer(true)
ret:writeU32(size_1)
ret:writeU32(u1)
ret:writeU32(u2)
if nptrs ~= n_ptrs then error("Script inconsistancy! Got " .. nptrs .. " instead of " .. n_ptrs) end
ret:writeU32(nptrs)
ret:writeU32(u3)
ret:writeU32(u4)
ret:writeU32(u5)
local logic = Buffer(true)
logic:copyfrom(old, size_logic)
patch_logic(logic, windows, got_us and current_file == (3610 + 125) and activate_debug_room)
logic:seek(0)
ret:copyfrom(logic)
ret:copyfrom(new)
return ret
end
function process_arcroom(fname, h, size, ext)
log("Processing " .. fname .. "." .. ext)
local o
if dump_mode then
o = Output(fname .. "." .. ext)
o:copyfrom(h, size)
o:destroy()
h:seek(-size, SEEK_CUR)
end
log("Processing " .. fname .. " - format 'arcroom' - " .. ext)
dump_mkdir(fname)
local nfiles = h:readU32()
local offset = h:readU32()
if nfiles * 8 + 8 ~= offset then
error("Bad archive format.")
end
local ret
if not dump_mode then
ret = Buffer(true)
ret:writeU32(nfiles)
ret:writeU32(offset)
end
index = {}
for i = 1, nfiles do
index[i] = {}
index[i].ftype = h:readU32()
index[i].size = h:readU32()
end
local tell
local outfile = {}
local script = nil
local font = nil
local nptrs
local windows = {}
if not dump_mode then
script, font, nptrs, windows = process_current_room_script()
end
for i = 1, nfiles do
tell = h:tell()
local handler = nil
local counter = 1
if dump_mode and ext == "arm" and index[i].ftype == 4 then
log("Preparing to dump script...")
handler = function(fname, h, size, ext)
if counter == 1 then
log("Taking script...")
script = Buffer(true)
script:copyfrom(h)
elseif counter == 2 then
log("Taking font...")
font = Buffer(true)
font:copyfrom(h)
else
error("Too many files.")
end
counter = counter + 1
end
elseif not dump_mode and ext == "arm" and index[i].ftype == 4 and script and font then
old_script = Buffer(true)
handler = function(fname, h, size, ext)
local ret
if counter == 1 then
log("Putting script...")
ret = insert_script(h, script, nptrs, windows)
elseif counter == 2 then
log("Putting font...")
ret = Buffer(true)
ret:copyfrom(font)
else
error("Too many files.")
end
counter = counter + 1
return ret
end
elseif dump_mode and ext == "sarc" and index[i].ftype == 4 then
handler = function(fname, h, size, ext)
if counter == 1 then
if script then error "Can't have two scripts in these..." end
log("Taking text...")
script = Buffer(true)
script:copyfrom(h)
elseif counter == 2 then
if font then error "Can't have two fonts in these..." end
log("Taking font...")
font = Buffer(true)
font:copyfrom(h)
else
error("Too many files")
end
counter = counter + 1
end
elseif dump_mode and ext == "sarc" and index[i].ftype == 7 then
if font then error "Can't have two fonts in these..." end
handler = function(fname, h, size, ext)
if counter == 1 then
log("Taking font...")
font = Buffer(true)
font:copyfrom(h)
else
error("Too many files")
end
counter = counter + 1
end
elseif not dump_mode and ext == "sarc" and index[i].ftype == 4 then
-- error "Not written yet"
handler = nil
elseif not dump_mode and ext == "sarc" and index[i].ftype == 7 then
-- error "Not written yet"
handler = nil
end
outfile[i] = process_single_file(fname .. string.format("/%04i-%08X", i, index[i].ftype), h, index[i].size, "room", handler)
if handler and script and font then
if dump_mode and ext == "arm" then
extract_room_script(fname .. string.format("/%04i", i), script, font)
elseif dump_mode then
extract_simple_script(fname .. string.format("/%04i", i), script, font)
end
script:destroy()
font:destroy()
script = nil
font = nil
end
h:seek(tell + index[i].size)
end
if not dump_mode then
for i = 1, nfiles do
local alignment_bytes = alignment(outfile[i]:getsize(), 4)
for j = 1, alignment_bytes do
outfile[i]:writeU8(0)
end
ret:writeU32(index[i].ftype)
ret:writeU32(outfile[i]:getsize())
end
for i = 1, nfiles do
ret:copyfrom(outfile[i])
outfile[i]:destroy()
end
end
return ret
end
room_logic_check = {
[0x0090] = { 0x18000000, 0x00000008 }, [0x009B] = { 0x98100000, 0x17000000 },
[0x033C] = { 0x18000000, 0x0000002C }, [0x03BC] = { 0x92100000, 0x150003E1 }, [0x03C7] = { 0x94100000, 0x150003E1 }, [0x03D3] = { 0x93100000, 0x150003E1 }, [0x03DF] = { 0x92100000, 0x150003E1 },
[0x03E2] = { 0x18000000, 0x00000018 }, [0x03F0] = { 0x1400033C, 0x7E000000 },
[0x03F5] = { 0x18000000, 0x00000018 }, [0x0403] = { 0x1400033C, 0x7E000000 },
[0x0408] = { 0x18000000, 0x00000018 }, [0x0416] = { 0x1400033C, 0x7E000000 },
-- [0x041B] = { 0x18000000, 0x00000020 }, [0x042B] = { 0x1400033C, 0x7E000000 },
-- [0x0432] = { 0x18000000, 0x00000020 }, [0x0442] = { 0x1400033C, 0x7E000000 },
-- [0x0449] = { 0x18000000, 0x00000020 }, [0x0459] = { 0x1400033C, 0x7E000000 },
}
function check_opcode(script, PC, supposed)
script:seek(PC * 4)
local r = script:readU32()
if r ~= supposed then error(string.format("Error for script: opcode %04X is %08X but was supposed to be %08X", PC, r, supposed)) end
end
function check_room_logic(script)
for offset, v in pairs(room_logic_check) do
local PC = offset - 1
for suboffset, supposed in ipairs(v) do
check_opcode(script, PC + suboffset, supposed)
end
end
end
function extract_room_script(fname, script, font)
log("Processing script " .. fname)
dump_mkdir(fname)
dump_mkdir(fname .. "/EXTRA")
local font = extract_font(fname .. "/FONT", font)
local lookup = resolve_font(font)
local size_1 = script:readU32()
local logic_offset = script:readU32()
local u1 = script:readU32()
local n_ptrs = script:readU32()
local u3 = script:readU32()
local u4 = script:readU32()
local u5 = script:readU32()
if logic_offset ~= 0 then error("Unhandled logic offset " .. logic_offset) end
local o
o = Output(fname .. "/EXTRA/00-head1-" .. string.format("%08X", u1) .. ".bin")
o:writeU32(u1)
o:destroy()
o = Output(fname .. "/EXTRA/00-head2-" .. string.format("%08X-%08X-%08X", u3, u4, u5) .. ".bin")
o:writeU32(u3)
o:writeU32(u4)
o:writeU32(u5)
o:destroy()
o = Output(fname .. "/EXTRA/01-head1.bin")
o:writeU32(u1)
o:destroy()
o = Output(fname .. "/EXTRA/01-head2.bin")
o:writeU32(u3)
o:writeU32(u4)
o:writeU32(u5)
o:destroy()
o = Output(fname .. "/EXTRA/02-part1.bin")
o:copyfrom(script, size_1)
o:destroy()
script:seek(-size_1, SEEK_CUR)
local logic = Buffer(true)
logic:copyfrom(script, size_1)
local ptrs = {}
local ptr_start = 1
for i = 1, n_ptrs do
ptrs[i] = script:readU16()
end
local ptrs_contents, ptrs_raws = {}, {}
local tell = script:tell()
ptrs_contents[1] = ""
ptrs_contents[2] = ""
ptrs_raws[1] = ""
ptrs_raws[2] = ""
ptr_start = 3
local header = script_types.room.header
for i = 1, script_types.room.size do
if script:readU8() ~= header[i] then error("Script header file of type 'room' not according to its header") end
end
for i = ptr_start, n_ptrs do
ptrs_contents[i] = ""
ptrs_raws[i] = ""
if tell + ptrs[i] ~= script:tell() then error("Script consistancy failure for pointer " .. i .. " - we're at " .. script:tell() .. " and " .. (tell + ptrs[i]) .. " was expected.") end
while true do
local r, rr = extract_char(script, lookup)
if not r then break end
ptrs_contents[i] = ptrs_contents[i] .. r
ptrs_raws[i] = ptrs_raws[i] .. rr
end
end
o = Output(fname .. "/EXTRA/logic.txt")
check_room_logic(logic)
local textboxes = { }
local rd_opcode = function(PC)
logic:seek(PC * 4)
return logic:readU32()
end
local add_textbox = function(txtptr, txtbox)
if txtptr < 0 then return end
o:write("\n")
if ptrs_raws[txtptr + ptr_start] then
o:write(ptrs_raws[txtptr + ptr_start])
if textboxes[txtptr + ptr_start] then
o:write("\n****SUSPECT****PTR ALREADY RECORDED****: " .. (txtptr + ptr_start))
end
textboxes[txtptr + ptr_start] = txtbox
else
error("****SUSPECT****PTR TOO LARGE****: " .. (txtptr + ptr_start))
end
end
local txt_process = function(PC)
local opcode = rd_opcode(PC)
local txtptr
local x, y, width, height
if andB(opcode, 0x00100000) ~= 0 then
local psh1 = rd_opcode(PC - 1)
local psh2 = rd_opcode(PC - 2)
local psh3 = rd_opcode(PC - 3)
local psh4 = rd_opcode(PC - 4)
local psh5 = rd_opcode(PC - 5)
local psh6 = rd_opcode(PC - 6)
if andB(psh1, 0xff000000) ~= 0x12000000 then return end
if andB(psh2, 0xff000000) ~= 0x12000000 then return end
if andB(psh3, 0xff000000) ~= 0x12000000 then return end
if andB(psh4, 0xff000000) ~= 0x12000000 then return end
if andB(psh5, 0xff000000) ~= 0x12000000 then return end
if andB(psh6, 0xff000000) ~= 0x12000000 then return end
local arg1 = andB(psh1, 0x7fffff)
local arg2 = andB(psh2, 0x7fffff)
local arg3 = andB(psh3, 0x7fffff)
local arg4 = andB(psh4, 0x7fffff)
local arg5 = andB(psh5, 0x7fffff)
local arg6 = andB(psh6, 0x7fffff)
txtptr = arg6 + 1 - ptr_start
x = arg5
y = arg4
width = arg3
height = arg2
else
txtptr = andB(opcode, 0xff) + 1 - ptr_start
local arg1 = rd_opcode(PC + 1)
local arg2 = rd_opcode(PC + 2)
x = andB(arg1, 0xffff)
y = shr(arg1, 16)
width = andB(arg2, 0xffff)
height = shr(arg2, 16)
end
add_textbox(txtptr, {
x = x,
y = y,
width = width,
height = height,
})
end
local process_subtxt = function(PC)
local psh1 = rd_opcode(PC - 1)
local psh2 = rd_opcode(PC - 2)
local psh3 = rd_opcode(PC - 3)
local psh4 = rd_opcode(PC - 4)
local psh5 = rd_opcode(PC - 5)
local psh6 = rd_opcode(PC - 6)
if andB(psh1, 0xff000000) ~= 0x12000000 and andB(psh1, 0xff000000) ~= 0x0B000000 then return end
if andB(psh2, 0xff000000) ~= 0x12000000 then return end
if andB(psh3, 0xff000000) ~= 0x12000000 and andB(psh3, 0xff000000) ~= 0x0B000000 then return end
if andB(psh4, 0xff000000) ~= 0x12000000 and andB(psh4, 0xff000000) ~= 0x0B000000 then return end
if andB(psh5, 0xff000000) ~= 0x12000000 and andB(psh5, 0xff000000) ~= 0x0B000000 then return end
if andB(psh6, 0xff000000) ~= 0x12000000 and andB(psh6, 0xff000000) ~= 0x0B000000 then return end
local arg1 = andB(psh1, 0x7fffff)
local arg2 = andB(psh2, 0x7fffff)
local arg3 = andB(psh3, 0x7fffff)
local arg4 = andB(psh4, 0x7fffff)
local arg5 = andB(psh5, 0x7fffff)
local arg6 = andB(psh6, 0x7fffff)
if andB(psh1, 0xff000000) == 0x0B000000 then arg1 = nil end
if andB(psh3, 0xff000000) == 0x0B000000 then arg3 = nil end
if andB(psh4, 0xff000000) == 0x0B000000 then arg4 = nil end
if andB(psh5, 0xff000000) == 0x0B000000 then arg5 = nil end
if andB(psh6, 0xff000000) == 0x0B000000 then arg6 = nil end
local txtptr = arg2 + 1 - ptr_start
add_textbox(txtptr, {
x = arg6 or "var",
y = arg5 or "var",
width = arg4 or "var",
height = arg3 or "var",
})
end
local process_subtxt2 = function(PC)
local psh1 = rd_opcode(PC - 1)
local psh2 = rd_opcode(PC - 2)
local psh3 = rd_opcode(PC - 3)
local psh4 = rd_opcode(PC - 4)
local psh5 = rd_opcode(PC - 5)
if andB(psh1, 0xff000000) ~= 0x12000000 then return end
if andB(psh2, 0xff000000) ~= 0x0B000000 then return end
if andB(psh3, 0xff000000) ~= 0x22000000 then return end
if andB(psh4, 0xff000000) ~= 0x0B000000 then return end
if andB(psh5, 0xff000000) ~= 0xCB000000 then return end
local txtptr = andB(psh5, 0xff) + 1 - ptr_start
add_textbox(txtptr, {
typ = "fixed",
})
end
local call_process = function(PC)
local opcode = rd_opcode(PC)
if opcode == 0x1400033C or opcode == 0x140003E2 or opcode == 0x140003F5 or opcode == 0x14000408 then process_subtxt(PC) end
if opcode == 0x14000090 then process_subtxt2(PC) end
end
logic_disasm(logic, o, {
-- ret
[0x17] = function(PC) o:write("\n-----------------------------------------------------\n") end,
-- txtbox1
[0x92] = txt_process,
-- txtbox2
[0x93] = txt_process,
-- txtbox3
[0x94] = txt_process,
-- txtbox4
[0x98] = txt_process,
-- *sigh*
[0x14] = call_process,
} )
o:destroy()
logic:destroy()
for i = ptr_start, n_ptrs do
if textboxes[i] then
local tb = textboxes[i]
if tb.typ == "fixed" then
ptrs_contents[i] = '\n' .. ptrs_contents[i]
elseif tb.typ == "auto" then
ptrs_contents[i] = '\n' .. ptrs_contents[i]
else
ptrs_contents[i] = '\n' .. ptrs_contents[i]
end
else
ptrs_contents[i] = '\n' .. ptrs_contents[i]
end
end
-- WARNING -- only for rooms
local ptrs_idx = process_ptrs(ptrs_contents, ptr_start, n_ptrs)
local o = Output(fname .. "-script.txt")
o:write("template:room\n")
for i = ptr_start, n_ptrs do
o:write(ptrs_idx[i] .. "\n")
end
lookup_rooms:write(" [" .. current_file - 3610 .. "] = { ")
for i = ptr_start, n_ptrs do
lookup_rooms:write(ptrs_idx[i] .. ", ")
end
lookup_rooms:write "},\n"
o:destroy()
end
function extract_simple_script(fname, script, font)
log("Processing script " .. fname)
dump_mkdir(fname)
local font = extract_font(fname .. "/FONT", font)
local lookup = resolve_font(font)
local n_ptrs = 1
local script_begin = script:readU32()
local ptrs = {}
local unks = {}
local ptr_start = 1
local tell = script:tell()
while tell < script_begin do
unks[n_ptrs] = script:readU32()
ptrs[n_ptrs] = script:readU32()
tell = script:tell()
n_ptrs = n_ptrs + 1
end
ptrs[n_ptrs] = script:getsize() - script_begin
n_ptrs = n_ptrs - 1
local ptrs_contents, ptrs_raws = {}, {}
ptr_start = 1
for i = ptr_start, n_ptrs do
ptrs_contents[i] = ""
ptrs_raws[i] = ""
-- if tell + ptrs[i] ~= script:tell() then print("Script consistancy failure for pointer " .. i .. " - we're at " .. script:tell() .. " and " .. (tell + ptrs[i]) .. " was expected.") end
script:seek(script_begin + ptrs[i])
while script:tell() ~= (script_begin + ptrs[i + 1]) do
local r, rr = extract_char(script, lookup)
if r then
ptrs_contents[i] = ptrs_contents[i] .. r
ptrs_raws[i] = ptrs_raws[i] .. rr
elseif script:tell() ~= (script_begin + ptrs[i + 1]) then
ptrs_contents[i] = ptrs_contents[i] .. ""
end
end
end
local o = Output(fname .. "-script.txt")
for i = ptr_start, n_ptrs do
o:write("\n")
o:write(ptrs_contents[i])
o:write("\n")
end
o:destroy()
end