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:wseek(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 if window.x and window.x ~= "var" then psh5 = orB(andB(psh5, 0xff800000), window.x) end if window.y and window.y ~= "var" then psh4 = orB(andB(psh4, 0xff800000), window.y) end if window.width and window.width ~= "var" then psh3 = orB(andB(psh3, 0xff800000), window.width) end if window.height and window.height ~= "var" then psh2 = orB(andB(psh2, 0xff800000), window.height) end 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] if not window then return end if window.wtype == "fixed" then return end 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 and window.height ~= "var" then psh3 = orB(andB(psh3, 0xff800000), window.height) end if andB(psh4, 0xff000000) ~= 0x0B000000 and window.width and window.width ~= "var" then psh4 = orB(andB(psh4, 0xff800000), window.width) end if andB(psh5, 0xff000000) ~= 0x0B000000 and window.y and window.y ~= "var" then psh5 = orB(andB(psh5, 0xff800000), window.y) end if andB(psh6, 0xff000000) ~= 0x0B000000 and window.x and window.x ~= "var" 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_room_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_logic) 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