def getFontData(data): fontconfig = data + "fontconfig.txt" with codecs.open(data + "table_input.txt", "r", "utf-8") as tablef: table = common.getSection(tablef, "") invtable = {} for c in table.keys(): invtable[table[c][0]] = c with codecs.open(data + "table.txt", "r", "utf-8") as tablef: ccodes = common.getSection(tablef, "") glyphs = readFontGlyphs(fontconfig) return table, invtable, ccodes, glyphs
def run(data, allfile=False): infolder = data + "extract/" outfile = data + "bin_output.txt" with codecs.open(data + "table_input.txt", "r", "utf-8") as tablef: table = common.getSection(tablef, "") if allfile: game.fileranges = {"bank_1d.bin": [(0x0, 0xfff0)]} with codecs.open(outfile, "w", "utf-8") as out: common.logMessage("Extracting bin to", outfile, "...") for file in common.showProgress(game.fileranges): out.write("!FILE:" + file + "\n") with common.Stream(infolder + file, "rb") as f: for range in game.fileranges[file]: f.seek(range[0]) while f.tell() < range[1]: if (len(range) >= 3): f.seek(range[2], 1) pos = f.tell() binstr = game.readString(f, table, True) if allfile and len(binstr) > 50: f.seek(pos + 2) continue if binstr.startswith("|"): f.seek(pos + 2) continue if binstr != "": common.logDebug("Found string at", common.toHex(pos), binstr) out.write(binstr + "=\n") common.logMessage("Done!")
def repackFontData(infile, outfile, datafile): common.logMessage("Repacking font data from", datafile, "...") common.copyFile(infile, outfile) glyphs = getFontGlyphs(infile) with codecs.open(datafile, "r", "utf-8") as f: section = common.getSection(f, "") if len(section) == 0: return with common.Stream(outfile, "rb+", False) as f: # Header f.seek(36) hdwcoffset = f.readUInt() # HDWC f.seek(hdwcoffset - 4) hdwclen = f.readUInt() tilenum = (hdwclen - 16) // 3 f.seek(8, 1) for i in range(tilenum): found = False for glyph in glyphs.values(): if glyph.index == i: sectionglyph = glyph.char if glyph.char != "=" else "<3D>" if sectionglyph in section: common.logDebug("Writing", section[sectionglyph][0], "at", f.tell()) fontdata = section[sectionglyph][0].split(",") f.writeSByte(int(fontdata[0])) f.writeByte(int(fontdata[1])) f.writeByte(int(fontdata[2])) found = True break if not found: f.seek(3, 1) common.logMessage("Done!")
def readFontGlyphs(file): glyphs = {} with codecs.open(file, "r", "utf-8") as f: fontconfig = common.getSection(f, "") for c in fontconfig: charlen = 0 if fontconfig[c][0] == "" else int(fontconfig[c][0]) glyphs[c] = common.FontGlyph(0, charlen, charlen + 1) return glyphs
def getFixChars(): fixchars = [] if not os.path.isfile("data/fontconfig.txt"): return fixchars with codecs.open("data/fontconfig.txt", "r", "utf-8") as f: chars = common.getSection(f, "", "|") for char in chars: fixchars.append((char, chars[char][0].replace("<3D>", "="))) return fixchars
def translate(text): with codecs.open(data + "table_input.txt", "r", "utf-8") as tablef: table = common.getSection(tablef, "") invtable = {} for c in table.keys(): invtable[table[c][0]] = c ret = "" for c in text: ret += invtable[c][-2:] + invtable[c][:2] common.logMessage(ret)
def repackEXE(binrange, freeranges=None, manualptrs=None, readfunc=common.detectEncodedString, writefunc=common.writeEncodedString, encoding="shift_jis", comments="#", exein="", exeout="", ptrfile="data/manualptrs.asm", exefile="data/exe_input.txt"): if not os.path.isfile(exefile): common.logError("Input file", exefile, "not found") return False common.copyFile(exein, exeout) common.logMessage("Repacking EXE from", exefile, "...") section = {} with codecs.open(exefile, "r", "utf-8") as bin: section = common.getSection(bin, "", comments) chartot, transtot = common.getSectionPercentage(section) if type(binrange) == tuple: binrange = [binrange] notfound = common.repackBinaryStrings(section, exein, exeout, binrange, freeranges, readfunc, writefunc, encoding, 0x8000F800) # Handle not found pointers by manually replacing the opcodes if len(notfound) > 0 and manualptrs is not None: with open(ptrfile, "w") as f: for ptr in notfound: if ptr.old not in manualptrs: common.logError("Manual pointer", common.toHex(ptr.old), "->", common.toHex(ptr.new), "not found for string", ptr.str) continue for manualptr in manualptrs[ptr.old]: ptrloc = manualptr[0] ptrreg = manualptr[1] common.logDebug("Reassembling manual pointer", common.toHex(ptr.old), "->", common.toHex(ptr.new), "at", common.toHex(ptrloc), ptrreg) f.write(".org 0x" + common.toHex(ptrloc) + "\n") f.write(".area 0x8,0x0\n") f.write(" li " + ptrreg + ",0x" + common.toHex(ptr.new) + "\n") f.write(".endarea\n\n") common.logMessage("Done! Translation is at {0:.2f}%".format( (100 * transtot) / chartot)) return True
def run(data): infolder = data + "extract/" outfile = data + "credits_output.txt" with codecs.open(data + "table_input.txt", "r", "utf-8") as tablef: table = common.getSection(tablef, "") with codecs.open(outfile, "w", "utf-8") as out: common.logMessage("Extracting credits to", outfile, "...") with common.Stream(infolder + "bank_09.bin", "rb") as f: f.seek(0xf120) while f.tell() < 0xf9e2: b2 = f.readByte() b1 = f.readByte() utfc, _ = game.convertChar(b1, b2, table) if utfc == "<ffff>": out.write("\n") else: out.write(utfc) common.logMessage("Done!")
def repackBIN(binrange, freeranges=None, readfunc=common.detectEncodedString, writefunc=common.writeEncodedString, encoding="shift_jis", comments="#", binin="data/extract/arm9.bin", binout="data/repack/arm9.bin", binfile="data/bin_input.txt", fixchars=[]): if not os.path.isfile(binfile): common.logError("Input file", binfile, "not found") return False common.copyFile(binin, binout) common.logMessage("Repacking BIN from", binfile, "...") section = {} with codecs.open(binfile, "r", "utf-8") as bin: section = common.getSection(bin, "", comments, fixchars=fixchars) chartot, transtot = common.getSectionPercentage(section) if type(binrange) == tuple: binrange = [binrange] notfound = common.repackBinaryStrings(section, binin, binout, binrange, freeranges, readfunc, writefunc, encoding, 0x02000000) for pointer in notfound: common.logError("Pointer", common.toHex(pointer.old), "->", common.toHex(pointer.new), "not found for string", pointer.str) common.logMessage("Done! Translation is at {0:.2f}%".format((100 * transtot) / chartot)) return True
def run(data): infolder = data + "extract/" outfolder = data + "out_IMG/" common.logMessage("Extracting images to", outfolder, "...") common.makeFolder(outfolder) files = common.getFiles(infolder) extracted = 0 with codecs.open(common.bundledFile("images.txt"), "r", "utf-8") as imagef: for file in files: section = common.getSection(imagef, file) with common.Stream(infolder + file, "rb") as f: for imgname in section.keys(): imgdata = section[imgname][0].split(",") mapstart = int(imgdata[1], 16) imgnum = int(imgdata[2]) if len(imgdata) >= 3 else 1 readpal = len(imgdata) >= 4 and imgdata[3] == "1" extracted += imgnum if "-" in imgdata[0]: tilestart = int(imgdata[0].split("-")[0], 16) tileend = int(imgdata[0].split("-")[1], 16) for i in common.showProgress(range(tilestart, tileend + 1, 1)): ws.extractMappedImage(f, outfolder + imgname + "_" + hex(i) + ".png", i, mapstart, imgnum, readpal) else: tilestart = int(imgdata[0], 16) ws.extractMappedImage(f, outfolder + imgname + ".png", tilestart, mapstart, imgnum, readpal) if file == "bank_09.bin": map = game.getBerserkMap(outfolder) ws.writeMappedImage(f, 0xf080, [map], ws.bwpalette) extracted += 1 # Extract ramen stand image with common.Stream(infolder + "bank_03.bin", "rb") as f: map = game.getRamenMap(outfolder) ws.writeMappedImage(f, 0x3748, [map], ws.bwpalette) map = game.getLanternMap(outfolder) ws.writeMappedImage(f, 0x3748, [map], ws.bwpalette) common.logMessage("Done! Extracted", extracted, "files")
def run(data): workfolder = data + "work_IMG/" infolder = data + "extract/" outfolder = data + "repack/" common.logMessage("Repacking images from", workfolder, "...") files = common.getFiles(infolder) repacked = 0 with codecs.open(common.bundledFile("images.txt"), "r", "utf-8") as imagef: for file in files: section = common.getSection(imagef, file) if len(section) > 0: with common.Stream(outfolder + file, "rb+") as f: for imgname in section.keys(): imgdata = section[imgname][0].split(",") mapstart = int(imgdata[1], 16) imgnum = int(imgdata[2]) if len(imgdata) >= 3 else 1 readpal = len(imgdata) >= 4 and imgdata[3] == "1" writepal = len(imgdata) >= 5 and imgdata[4] == "1" tilestart = int(imgdata[0], 16) ws.repackMappedImage(f, workfolder + imgname + ".png", tilestart, mapstart, imgnum, readpal, writepal) repacked += imgnum if file == "bank_09.bin": map = game.getBerserkMap(workfolder) ws.repackMappedTiles(f, 0xf080, map, ws.bwpalette) repacked += 1 # Repack ramen stand images with common.Stream(outfolder + "bank_03.bin", "rb+") as f: map = game.getRamenMap(workfolder) ws.repackMappedTiles(f, 0x3748, map, ws.bwpalette) map = game.getLanternMap(workfolder) ws.repackMappedTiles(f, 0x3748, map, ws.bwpalette) repacked += 2 common.logMessage("Done! Repacked", repacked, "files")
def run(data, processed): infolder = data + ("extract/" if not processed else "repack/") files = ["bank_11.bin", "bank_12.bin"] outfile = data + "analyze_output.txt" with codecs.open(data + "table_input.txt", "r", "utf-8") as tablef: table = common.getSection(tablef, "") invtable = {} with codecs.open(data + "table.txt", "r", "utf-8") as tablef: convtable = common.getSection(tablef, "") for char in convtable: invtable[int(convtable[char][0], 16)] = char with codecs.open(outfile, "w", "utf-8") as out: for file in common.showProgress(files): common.logMessage("Processing", file, "...") out.write("!FILE:" + file + "\n") size = os.path.getsize(infolder + file) with common.Stream(infolder + file, "rb") as f: while f.tell() < size - 1: pos = f.tell() opcode = f.readByte() if opcode in game.opcodes and game.opcodes[opcode] != -1: addline = "" if opcode in game.ptropcodes: addline = " Pointer: " + repr( game.ptropcodes[opcode]) if opcode == 0x0a: if processed: common.logDebug("Failing at", common.toHex(pos)) writeLine( out, pos, opcode, game.readString(f, table, processed=invtable)) else: writeLine(out, pos, opcode, game.readString(f, table)) elif opcode in game.repopcodes: readbytes = "" replen = 0 while True: byte = f.readBytes(1) readbytes += byte replen += 1 if byte == "FF " and (opcode not in game.ptropcodes or replen > 2): break writeLine(out, pos, opcode, readbytes + addline) else: writeLine( out, pos, opcode, f.readBytes(game.opcodes[opcode]) + addline) if opcode == 0xff: out.write("\n") check = f.readUInt() check2 = f.readUInt() f.seek(-8, 1) if check == 0xffffffff and check2 == 0xffffffff: break else: common.logError("Uknown opcode", common.toHex(opcode), "at", common.toHex(pos)) writeLine(out, pos, opcode, "Unknown") out.write("\n\n") common.logMessage("Done! Extracted", len(files), "files")
def run(firstgame, analyzefile): infolder = "data/extract/data/script/" outfile = "data/wsb_output.txt" commonfile = "data/common.txt" analyzeout = "data/wsb_analysis.txt" commonstr = {} # Read common strings from another file if os.path.isfile(commonfile): with codecs.open(commonfile, "r", "utf-8") as commonf: commonstr = common.getSection(commonf, "COMMON") encoding = "shift_jis" if firstgame else "shift_jisx0213" common.logMessage("Extracting WSB to", outfile, "...") with codecs.open(analyzeout, "w", "utf-8") as a: with codecs.open(outfile, "w", "utf-8") as out: if len(commonstr) > 0: out.write("!FILE:COMMON\n") for s in commonstr: out.write(s + "=\n") files = common.getFiles(infolder, ".wsb") for file in common.showProgress(files): analyze = analyzefile != "" and file.endswith(analyzefile) if analyzefile != "" and not analyze: continue first = True common.logDebug("Processing", file, "...") with common.Stream(infolder + file, "rb") as f: f.seek(4) # 0x10 codeoffset = f.readUInt() f.seek(8, 1) # all 0xFF unk = f.readUInt() textoffset = f.readUInt() codeoffset2 = f.readUInt() common.logDebug("codeoffset:", codeoffset, "unk:", unk, "textoffset:", textoffset, "codeoffset2:", codeoffset2) f.seek(4, 1) # Parse the various code blocks while looking for strings while f.tell() < codeoffset: pos = f.tell() b1 = f.readByte() b2 = f.readByte() if (b1 == 0x55 and b2 == 0x08) or (b1 == 0x95 and b2 == 0x10): # Found a string pointer if analyze: lenline = f.readBytes(4 if b1 == 0x95 else 2) f.seek(-(4 if b1 == 0x95 else 2), 1) sjis, strlen = game.readShiftJIS( f, b1 == 0x95, False, encoding) if sjis != "" and sjis != ">>" and sjis != " ": sjissplit = sjis.split(">>") for sjisline in sjissplit: if sjisline != "" and sjisline != " " and sjisline not in commonstr: if first: out.write("!FILE:" + file + "\n") first = False out.write(sjisline + "=\n") if analyze: # Try to calculate the length to check if the calculation is correct addlen = "" with common.Stream() as test: game.writeShiftJIS(test, sjis, b1 == 0x95, False, 0, encoding, firstgame) testlen = test.tell() test.seek(0) addlen = test.readBytes(4 if b1 == 0x95 else 2) if lenline != addlen: test.seek(0) addlen += "\nDIFF\n" + test.readBytes( testlen) + "\n" fpos = f.tell() f.seek(pos + 2) addlen += f.readBytes(fpos - f.tell()) writeLine(a, pos, b1, b2, lenline + sjis + " " + addlen) elif (b1, b2) in game.wsbcodes: if analyze: ptrstr = "" if (b1, b2) in game.wsbpointers: ptrstr += "ptr" writeLine( a, pos, b1, b2, f.readBytes(game.wsbcodes[(b1, b2)]) + ptrstr) else: f.seek(game.wsbcodes[(b1, b2)], 1) else: if analyze: writeLine(a, pos, b1, b2, "Unknown!") if codeoffset > 0: codenum = f.readUInt() for i in range(codenum): f.seek(codeoffset + 4 + 4 * i) codepointer = f.readUInt() f.seek(codeoffset + codepointer) sjis, codelen = game.readShiftJIS( f, False, True, encoding) # Ignore ASCII strings and a particular debug line found in every file if not common.isAscii(sjis) and sjis.find( "%d, %d") < 0 and sjis not in commonstr: if first: out.write("!FILE:" + file + "\n") first = False out.write(sjis + "=\n") if analyze: writeLine(a, i, 0, 0, str(codepointer) + " " + sjis) common.logMessage("Done! Extracted", len(files), "files")
def run(firstgame): infolder = "data/extract/data/script/" outfolder = "data/repack/data/script/" infile = "data/wsb_input.txt" fontfile = "data/replace/data/font/lcfont12.NFTR" if not os.path.isfile(infile): common.logError("Input file", infile, "not found") return encoding = "shift_jis" if firstgame else "shift_jisx0213" common.logMessage("Repacking WSB from", infile, "...") # Read the glyph size from the font if not os.path.isfile(fontfile): fontfile = fontfile.replace("replace/", "extract/") glyphs = nitro.readNFTR(fontfile).glyphs wordwrap = game.wordwrap[0] if firstgame else game.wordwrap[1] fixchars = game.getFixChars() with codecs.open(infile, "r", "utf-8") as wsb: commonsection = common.getSection(wsb, "COMMON", fixchars=fixchars) chartot, transtot = common.getSectionPercentage(commonsection) files = common.getFiles(infolder, ".wsb") for file in common.showProgress(files): section = common.getSection(wsb, file, fixchars=fixchars) chartot, transtot = common.getSectionPercentage( section, chartot, transtot) # Repack the file pointerdiff = {} pointers = {} common.logDebug(" Processing", file, "...") insize = os.path.getsize(infolder + file) with common.Stream(infolder + file, "rb") as fin: with common.Stream(outfolder + file, "wb") as f: # Copy header fin.seek(4) # 0x10 codeoffset = fin.readUInt() if codeoffset == 0 and not firstgame: fin.seek(0) f.write(fin.read()) continue fin.seek(8, 1) # all 0xFF unk = fin.readUInt() textoffset = fin.readUInt() codeoffset2 = fin.readUInt() fin.seek(0) f.write(fin.read(32)) # Write new strings while fin.tell() < codeoffset: pos = fin.tell() fpos = f.tell() b1 = fin.readByte() b2 = fin.readByte() f.writeByte(b1) f.writeByte(b2) if (b1 == 0x55 and b2 == 0x08) or (b1 == 0x95 and b2 == 0x10): sjis, oldlen = game.readShiftJIS( fin, b1 == 0x95, False, encoding) # Fix a bugged line with wrong speaker code if file == "event/ev_mou/mou_10.wsb" and sjis == "そうじゃな。|わっちの直感が申すには……>>": f.seek(fpos - 12) f.writeByte(0x53) f.seek(fpos + 2) strreplaced = False if sjis != "" and sjis != ">>": sjissplit = sjis.split(">>") for i in range(len(sjissplit)): newsjis = sjisline = sjissplit[i] if sjisline in commonsection: newsjis = commonsection[sjisline][0] elif sjisline in section: newsjis = section[sjisline].pop(0) if len(section[sjisline]) == 0: del section[sjisline] if newsjis != "": # Disable wordwrap for strings that contain replace codes if newsjis.count("@<") > 0: sjissplit[i] = newsjis # Check for automatic centering elif newsjis.count("<<") > 0: sjissplit[i] = common.centerLines( newsjis, glyphs, wordwrap, centercode="<<") else: sjissplit[i] = common.wordwrap( newsjis, glyphs, wordwrap) if sjissplit[i].count("|") > 2: common.logError( "Sub-line too long:", sjissplit[i]) cutsplit = sjissplit[i].split( "|") sjissplit[i] = cutsplit[ 0] + "|" + cutsplit[ 1] + "|" + cutsplit[2] newsjis = ">>".join(sjissplit) if newsjis != sjis and newsjis != "" and newsjis != ">>": common.logDebug("Repacking", newsjis, "at", common.toHex(pos)) strreplaced = True if newsjis == "!": newsjis = "" newlen = game.writeShiftJIS( f, newsjis, b1 == 0x95, False, 0, encoding, firstgame) lendiff = newlen - oldlen if newlen > (0x80 if firstgame else 0x70) and b1 == 0x55: common.logDebug( "String is too long", newlen, "changing to 0x95") f.seek(fpos) f.writeByte(0x95) f.writeByte(0x10) game.writeShiftJIS( f, newsjis, True, False, 0, encoding, firstgame) lendiff += 2 if lendiff != 0: common.logDebug( "Adding", lendiff, "at", pos) pointerdiff[pos - 16] = lendiff if not strreplaced: fin.seek(pos + 2) f.write( fin.read(oldlen + (4 if b1 == 0x95 else 2))) elif (b1, b2) in game.wsbcodes: if (b1, b2) in game.wsbpointers: if b1 == 0x81 and b2 == 0xB9: f.write(fin.read(2)) pointer = fin.readUInt() pointers[f.tell()] = pointer f.writeUInt(pointer) else: f.write(fin.read(game.wsbcodes[(b1, b2)])) # Write code section if codeoffset > 0: newcodeoffset = f.tell() codediff = 0 codenum = fin.readUInt() f.writeUInt(codenum) for i in range(codenum): fin.seek(codeoffset + 4 + 4 * i) f.seek(newcodeoffset + 4 + 4 * i) codepointer = fin.readUInt() f.writeUInt(codepointer + codediff) fin.seek(codeoffset + codepointer) f.seek(newcodeoffset + codepointer + codediff) sjis, codelen = game.readShiftJIS( fin, False, True, encoding) strreplaced = False if sjis in section or sjis in commonsection: if sjis in commonsection: newsjis = commonsection[sjis][0] else: newsjis = section[sjis].pop(0) if len(section[sjis]) == 0: del section[sjis] if newsjis != "": strreplaced = True newcodelen = game.writeShiftJIS( f, newsjis, False, True, 0, encoding, firstgame) if codelen != newcodelen: codediff += newcodelen - codelen if not strreplaced: fin.seek(codeoffset + codepointer) f.write(fin.read(codelen)) f.writeZero(insize - fin.tell()) # Write new header offsets f.seek(4) f.writeUInt(common.shiftPointer(codeoffset, pointerdiff)) f.seek(8, 1) f.writeUInt(common.shiftPointer(unk, pointerdiff)) f.writeUInt(common.shiftPointer(textoffset, pointerdiff)) f.writeUInt(common.shiftPointer(codeoffset2, pointerdiff)) # Shift pointers for k, v in pointers.items(): f.seek(k) f.writeUInt(common.shiftPointer(v, pointerdiff)) common.logMessage("Done! Translation is at {0:.2f}%".format( (100 * transtot) / chartot))
def repackPGFData(fontin, fontout, configfile, bitmapin=""): pgf = readPGFData(fontin) section = {} if os.path.isfile(configfile): with codecs.open(configfile, "r", "utf-8") as f: section = common.getSection(f, "", "##") with common.Stream(fontin, "rb") as fin: # Set the new glyphs information for char in section: jsondata = json.loads(section[char][0]) char = char.replace("<3D>", "=") for glyphindex in pgf.reversetable[char]: glyph = pgf.glyphs[glyphindex] newsize = 8 glyph.width = int(jsondata["width"]) glyph.height = int(jsondata["height"]) glyph.left = int(jsondata["left"]) glyph.top = int(jsondata["top"]) glyph.dimension["x"] = float(jsondata["dimension"]["x"]) glyph.dimension["y"] = float(jsondata["dimension"]["y"]) glyph.dimensionid = checkPGFDataMap(pgf.dimensionmap, glyph.dimension) newsize += 1 if glyph.dimensionid >= 0 else 8 glyph.bearingx["x"] = float(jsondata["bearingx"]["x"]) glyph.bearingx["y"] = float(jsondata["bearingx"]["y"]) glyph.bearingxid = checkPGFDataMap(pgf.bearingxmap, glyph.bearingx) newsize += 1 if glyph.bearingxid >= 0 else 8 glyph.bearingy["x"] = float(jsondata["bearingy"]["x"]) glyph.bearingy["y"] = float(jsondata["bearingy"]["y"]) glyph.bearingyid = checkPGFDataMap(pgf.bearingymap, glyph.bearingy) newsize += 1 if glyph.bearingyid >= 0 else 8 glyph.advance["x"] = float(jsondata["advance"]["x"]) glyph.advance["y"] = float(jsondata["advance"]["y"]) glyph.advanceid = checkPGFDataMap(pgf.advancemap, glyph.advance) newsize += 1 if glyph.advanceid >= 0 else 8 bitmapfile = bitmapin + str(glyph.index).zfill(4) + ".png" if not os.path.isfile(bitmapfile): fin.seek(pgf.glyphpos + pgf.charptr[glyph.index] + glyph.totlen // 8) glyph.bitmap = fin.read(glyph.size - glyph.totlen // 8) rleflag = glyph.flag & 0b11 else: glyph.bitmap, rleflag, glyph.width, glyph.height = repackPGFBitmap( glyph, bitmapfile) glyph.oldsize = glyph.size glyph.size = newsize + len(glyph.bitmap) if not glyph.shadow: glyph.shadowid = 0 glyph.shadowflag = 21 else: # TODO: shadow support pass glyph.flag = rleflag if glyph.dimensionid >= 0: glyph.flag |= 0b000100 if glyph.bearingxid >= 0: glyph.flag |= 0b001000 if glyph.bearingyid >= 0: glyph.flag |= 0b010000 if glyph.advanceid >= 0: glyph.flag |= 0b100000 pgf.dimensionlen = len(pgf.dimensionmap) pgf.bearingxlen = len(pgf.bearingxmap) pgf.bearingylen = len(pgf.bearingymap) pgf.advancelen = len(pgf.advancemap) # Write the file with common.Stream(fontout, "wb") as f: # Copy the header fin.seek(0) f.write(fin.read(pgf.headerlen)) # Write the new lengths f.seek(0x102) f.writeByte(pgf.dimensionlen) f.writeByte(pgf.bearingxlen) f.writeByte(pgf.bearingylen) f.writeByte(pgf.advancelen) # Write the maps f.seek(pgf.headerlen) for i in range(pgf.dimensionlen): f.writeInt(int(pgf.dimensionmap[i]["x"] * 64)) f.writeInt(int(pgf.dimensionmap[i]["y"] * 64)) for i in range(pgf.bearingxlen): f.writeInt(int(pgf.bearingxmap[i]["x"] * 64)) f.writeInt(int(pgf.bearingxmap[i]["y"] * 64)) for i in range(pgf.bearingylen): f.writeInt(int(pgf.bearingymap[i]["x"] * 64)) f.writeInt(int(pgf.bearingymap[i]["y"] * 64)) for i in range(pgf.advancelen): f.writeInt(int(pgf.advancemap[i]["x"] * 64)) f.writeInt(int(pgf.advancemap[i]["y"] * 64)) # Copy other tables fin.seek(pgf.mapend) if pgf.shadowmaplen > 0: f.write( fin.read( ((pgf.shadowmaplen * pgf.shadowmapbpe + 31) // 32) * 4)) f.write( fin.read(((pgf.charmaplen * pgf.charmapbpe + 31) // 32) * 4)) charptrpos = f.tell() f.write( fin.read(((pgf.charptrlen * pgf.charptrbpe + 31) // 32) * 4)) # Write the characters and store the pointers charptrs = [] glyphpos = f.tell() for i in range(len(pgf.glyphs)): glyph = pgf.glyphs[i] glyphptr = f.tell() - glyphpos charptrs.append(glyphptr // pgf.charptrscale) data = bytearray(8) pos = 0 pos = setBPEValue(14, data, pos, glyph.size) pos = setBPEValue(7, data, pos, glyph.width) pos = setBPEValue(7, data, pos, glyph.height) pos = setBPEValue(7, data, pos, glyph.left) pos = setBPEValue(7, data, pos, glyph.top) pos = setBPEValue(6, data, pos, glyph.flag) pos = setBPEValue(7, data, pos, glyph.shadowflag) pos = setBPEValue(9, data, pos, glyph.shadowid) f.write(data) if glyph.dimensionid >= 0: f.writeByte(glyph.dimensionid) else: f.writeInt(int(glyph.dimension["x"] * 64)) f.writeInt(int(glyph.dimension["y"] * 64)) if glyph.bearingxid >= 0: f.writeByte(glyph.bearingxid) else: f.writeInt(int(glyph.bearingx["x"] * 64)) f.writeInt(int(glyph.bearingx["y"] * 64)) if glyph.bearingyid >= 0: f.writeByte(glyph.bearingyid) else: f.writeInt(int(glyph.bearingy["x"] * 64)) f.writeInt(int(glyph.bearingy["y"] * 64)) if glyph.advanceid >= 0: f.writeByte(glyph.advanceid) else: f.writeInt(int(glyph.advance["x"] * 64)) f.writeInt(int(glyph.advance["y"] * 64)) if glyph.width > 0 and glyph.height > 0: if glyph.bitmap is None: fin.seek(pgf.glyphpos + pgf.charptr[glyph.index] + glyph.totlen // 8) glyph.bitmap = fin.read((glyph.oldsize if glyph. oldsize > 0 else glyph.size) - glyph.totlen // 8) f.write(glyph.bitmap) if (f.tell() - glyphpos) % pgf.charptrscale > 0: f.writeZero(pgf.charptrscale - ((f.tell() - pgf.glyphpos) % pgf.charptrscale)) # Write the new char ptr table f.seek(charptrpos) setBPETable(f, pgf.charptrlen, pgf.charptrbpe, charptrs)
def run(firstgame, no_redirect): infolder = "data/extract/data/data/" outfolder = "data/repack/data/data/" infile = "data/dat_input.txt" redfile = "data/redirects.asm" fontfile = "data/replace/data/font/lcfont12.NFTR" if not os.path.isfile(infile): common.logError("Input file", infile, "not found") return common.makeFolder(outfolder) chartot = transtot = 0 monthsection, skipsection = game.monthsection, game.skipsection game.monthsection = game.skipsection = None encoding = "shift_jis" if firstgame else "shift_jisx0213" common.logMessage("Repacking DAT from", infile, "...") # Read the glyph size from the font if not os.path.isfile(fontfile): fontfile = fontfile.replace("replace/", "extract/") glyphs = nitro.readNFTR(fontfile).glyphs fixchars = game.getFixChars() # Copy this txt file if not firstgame and os.path.isfile(infolder + "facilityhelp.txt"): common.copyFile(infolder + "facilityhelp.txt", outfolder + "facilityhelp.txt") redirects = [] with codecs.open(infile, "r", "utf-8") as dat: files = common.getFiles(infolder, ".dat") for file in common.showProgress(files): section = common.getSection(dat, file, fixchars=fixchars) # If there are no lines, just copy the file if len(section) == 0: common.copyFile(infolder + file, outfolder + file) # Part of the AP patch if not firstgame and file == "route.dat": with common.Stream(outfolder + file, "rb+") as f: f.seek(0x5ee8) f.writeByte(0x0) continue i = 0 chartot, transtot = common.getSectionPercentage( section, chartot, transtot) common.logDebug("Processing", file, "...") size = os.path.getsize(infolder + file) with common.Stream(infolder + file, "rb") as fin: with common.Stream(outfolder + file, "wb") as f: f.write(fin.read()) fin.seek(0) # Loop the file and replace strings as needed while fin.tell() < size - 2: pos = fin.tell() check = game.detectShiftJIS(fin, encoding) if check != "": if file == "entrance_icon.dat": # For entrance_icon, just write the string and update the pointer if check in section: # For the first one, seek to the correct position in the output file if i == 0: f.seek(pos) # Write the string newsjis = check if check in section and section[check][ 0] != "": common.logDebug( "Replacing string at", pos) newsjis = section[check][0] startpos = f.tell() game.writeShiftJIS(f, newsjis, False, True, 0, encoding) endpos = f.tell() # Update the pointer f.seek(0x1c98 + 4 * i) f.writeUInt(startpos - 0x1c98) f.seek(endpos) i += 1 else: # Found a SJIS string, check if we have to replace it if check in section and section[check][0] != "": common.logDebug("Replacing string at", pos) f.seek(pos) newsjis = section[check][0] maxlen = 0 if file == "goods.dat": newsjis = common.wordwrap( newsjis, glyphs, 170) maxlen = 60 elif file == "gossip.dat": newsjis = common.wordwrap( newsjis, glyphs, 190) if newsjis.count("<<") > 0: newsjis = common.centerLines( newsjis, glyphs, 190, centercode="<<") if fin.tell() - pos < 35: maxlen = 35 else: maxlen = 160 elif file == "scenarioguide.dat": newsjis = common.wordwrap( newsjis, glyphs, 165) maxlen = 60 if newsjis.count("|") > 1: common.logError( "scenarioguide line", newsjis, "too long") newlen = game.writeShiftJIS( f, newsjis, False, True, maxlen, encoding) if newlen < 0: if file != "gossip.dat" or no_redirect or maxlen != 160: common.logError( "String {} is too long ({}/{})." .format( newsjis, len(newsjis), maxlen)) else: common.logWarning( "String {} is too long ({}/{})." .format( newsjis, len(newsjis), maxlen)) # Doesn't fit, write it shorter f.seek(pos) cutat = 155 if firstgame else 150 while ord(newsjis[cutat]) > 127: cutat -= 1 stringfit = newsjis[:cutat] stringrest = newsjis[cutat:] game.writeShiftJIS( f, stringfit, False, True, maxlen, encoding) f.seek(-1, 1) f.writeByte(0x1f) f.writeByte(len(redirects)) redirects.append(stringrest) # Pad with 0s if the line is shorter while f.tell() < fin.tell(): f.writeByte(0x00) pos = fin.tell() - 1 fin.seek(pos + 1) with codecs.open(redfile, "w", "utf-8") as f: f.write(".ascii \"NDSC\"\n\n") f.write("REDIRECT_START:\n\n") for i in range(len(redirects)): f.write(".dh REDIRECT_{} - REDIRECT_START\n".format(i)) for i in range(len(redirects)): f.write("\nREDIRECT_{}:\n".format(i)) redirect = redirects[i].replace("\"", "\\\"") redirect = redirect.replace("|", "\" :: .db 0xa :: .ascii \"") redirectascii = "" for c in redirect: if ord(c) > 127: sjisc = common.toHex( int.from_bytes(c.encode(encoding), "big")) redirectascii += "\" :: .db 0x" + sjisc[:2] + " :: .db 0x" + sjisc[ 2:] + " :: .ascii \"" else: redirectascii += c f.write(".ascii \"{}\" :: .db 0\n".format(redirectascii)) game.monthsection, game.skipsection = monthsection, skipsection common.logMessage("Done! Translation is at {0:.2f}%".format( (100 * transtot) / chartot))
def run(): infolder = "data/extract_NFP/SPC.NFP/" outfolder = "data/work_NFP/SPC.NFP/" infile = "data/spc_input.txt" infixfile = "data/sprite_fix.txt" tablefile = "data/table.txt" chartot = transtot = 0 if not os.path.isfile(infile): common.logError("Input file", infile, "not found.") return common.makeFolder(outfolder) common.logMessage("Repacking SPC from", infile, "...") common.loadTable(tablefile) spritefixf = codecs.open(infixfile, "r", "utf-8") with codecs.open(infile, "r", "utf-8") as spc: files = common.getFiles(infolder, [".SPC", ".SET"]) for file in common.showProgress(files): section = common.getSection(spc, file, "#", game.fixchars) spritefix = common.getSection(spritefixf, file, "#") if len(section) == 0 and len(spritefix) == 0: common.copyFile(infolder + file, outfolder + file) continue chartot, transtot = common.getSectionPercentage( section, chartot, transtot) common.logDebug("Repacking", file, "...") codepointers = [] pointerdiff = {} funcpointers = {"MswMess": [], "MswHit": []} nextstr = None addstr = "" last29 = [] oldstrpos = 0 f = open(outfolder + file, "wb") f.close() with common.Stream(outfolder + file, "r+b") as f: with common.Stream(infolder + file, "rb") as fin: # Write the header f.writeString("SCRP") fin.seek(4) f.writeUInt(fin.readUInt()) f.writeString("CODE") fin.seek(4, 1) codesize = fin.readUInt() f.writeUInt(codesize) f.write(fin.read(6)) # Loop the file and shift pointers while fin.tell() < 16 + codesize - 2: pos = fin.tell() byte = fin.readByte() f.writeByte(byte) if byte == 0x10: oldlen = fin.readUShort() fin.seek(-2, 1) strpos = fin.tell() strposf = f.tell() sjis = game.readShiftJIS(fin) if (sjis != "" and sjis in section) or nextstr is not None: common.logDebug("Found SJIS string at", strpos + 16) # Check if we have a nextstr to inject instead of using the section if nextstr is None: newsjis = section[sjis].pop(0) if len(section[sjis]) == 0: del section[sjis] if newsjis == "!": newsjis = "" # Center the line savestrpos = f.tell() f.seek(oldstrpos - 28) checkbyte = f.readByte() if checkbyte == 0x02: f.seek(-1, 1) f.writeByte(1) f.seek(savestrpos) elif newsjis == "": newsjis = sjis else: newsjis = nextstr nextstr = None # If the string starts with <<, pad it with spaces if newsjis.startswith("<<"): newsjis = newsjis[2:] pad = " " * ((20 - len(newsjis)) // 2) newsjis = pad + newsjis + pad # If the string starts with "[xx]", add mouth flapping if newsjis.startswith( "[") and newsjis[3] == "]": flapbyte = newsjis[1:3] newsjis = newsjis[4:] flappos = f.tell() f.seek(strposf - 5) f.writeByte(int(flapbyte, 16)) f.seek(flappos) # If the string contains a >>, split it and save it for later if newsjis.find(">>") > 0: splitstr = newsjis.split(">>", 1) newsjis = splitstr[0] addstr = splitstr[1] # Check if we have a string after savepos = fin.tell() fin.seek(9, 1) b1 = fin.readByte() b2 = fin.readByte() fin.seek(savepos) if b1 == 0x10 and b2 == 0x01: nextstr = "" # If the string contains a |, try to turn the string into a 2-lines message if newsjis.find("|") > 0: splitstr = newsjis.split("|", 1) newsjis = splitstr[0] nextstr = splitstr[1] newsjis = newsjis.replace("|", "<0A>") # Change the byte 0x1C bytes before the string to 2 if it's 1 checkpos = 28 if newsjis.startswith("FIX("): splitstr = newsjis.split(")", 1) newsjis = splitstr[1] checkpos = int(splitstr[0].replace( "FIX(", "")) f.seek(-checkpos, 1) checkbyte = f.readByte() if checkbyte == 0x01: f.seek(-1, 1) f.writeByte(2) f.seek(checkpos - 1, 1) # Write the SJIS string newlen = game.writeShiftJIS(f, newsjis) lendiff = newlen - oldlen if lendiff != 0: common.logDebug("Adding", lendiff, "at", strpos) pointerdiff[strpos - 16] = lendiff fin.seek(1, 1) else: common.logDebug( "Found ASCII or unaltered string at", strpos + 16) fixPos = pos - 16 # Patch RITT_02 to add Dayakka's missing text if file == "RITT_02.SPC" and fixPos == 1775: fin.seek(strpos + oldlen + 2) f.writeUShort(0x09) f.writeString("APP_DAYA") f.writeByte(0x00) pointerdiff[strpos - 16] = 8 elif file == "RITT_02.SPC" and fixPos == 1810: fin.seek(strpos + oldlen + 2) f.writeUShort(0x09) f.writeString("DAYA_004") f.writeByte(0x00) pointerdiff[strpos - 16] = 8 elif file == "RITT_02.SPC" and fixPos == 1845: fin.seek(strpos + oldlen + 2) f.writeUShort(0x05) f.writeString("AWAY") f.writeByte(0x00) pointerdiff[strpos - 16] = 4 elif file == "SYS_054.SPC" and fixPos == 4233: fin.seek(strpos + oldlen + 2) f.writeUShort(0x09) f.writeString("MSW_C083") f.writeByte(0x00) else: fin.seek(strpos + 2) asciistr = fin.readNullString() if asciistr in spritefix: fin.seek(strpos + oldlen + 2) f.writeUShort(0x08) f.writeString(spritefix[asciistr][0]) f.writeByte(0x00) else: fin.seek(strpos) f.write(fin.read(oldlen + 2)) f.write(fin.read(2)) pointer = fin.readUInt() f.writeUInt( common.shiftPointer(pointer, pointerdiff)) # Check if we have an addstr if addstr != "" and nextstr is None: addstrsplit = addstr.split(">>") for addstr in addstrsplit: strsplit = addstr.split("|") startpointer = f.tell() startpointeri = fin.tell() f.writeByte(0x28) f.writeByte(0x00) funcpointers["MswMess"].append(f.tell() - 16) f.writeByte(0x29) f.writeUInt(0x03) f.writeByte(0x80) f.writeUInt(0x00) f.writeByte(0x2A) f.writeByte(0x00) f.writeByte(0x31) f.writeByte(0x0F) f.writeUInt(0x0C) f.writeByte(0x29) f.writeUInt(0x00) funcpointers["MswHit"].append(f.tell() - 16) f.writeByte(0x29) f.writeUInt(0x01) f.writeByte(0x80) f.writeUInt(0x00) f.writeByte(0x2A) f.writeByte(0x00) f.writeByte(0x31) f.writeByte(0x0F) f.writeUInt(0x04) f.writeByte(0x29) f.writeUInt(last29[len(last29) - 1]) f.writeByte(0x10) strpointer = f.tell() game.writeShiftJIS(f, strsplit[0]) f.writeByte(0x22) f.writeByte(0x00) f.writeUInt(strpointer - 16 - 4) f.writeByte(0x28) f.writeByte(0x00) f.writeByte(0x10) strpointer2 = f.tell() game.writeShiftJIS( f, strsplit[1] if len(strsplit) == 2 else "") f.writeByte(0x22) f.writeByte(0x00) f.writeUInt(strpointer2 - 16 - 4) endpointer = f.tell() common.logDebug("Adding new str", endpointer - startpointer, "at", startpointeri) if startpointeri - 16 not in pointerdiff: pointerdiff[startpointeri - 16] = 0 pointerdiff[ startpointeri - 16] += endpointer - startpointer addstr = "" oldstrpos = strposf elif byte == 0x15: f.write(fin.read(1)) bytelen = fin.readByte() f.writeByte(bytelen) for i in range(bytelen): f.write(fin.read(4)) codepointers.append(f.tell()) f.write(fin.read(4)) elif byte in game.spccodes: if byte == 0x11: codepointers.append(f.tell()) elif byte == 0x12: codepointers.append(f.tell() + 1) elif byte == 0x29: last29.append(fin.readUInt()) fin.seek(-4, 1) # Patch SYS_046/7 and fix the disappearing cut-in sprites fixPos = pos - 16 if (file == "SYS_046.SPC" and byte == 0x29 and fixPos in [9660, 11356, 11915, 13108, 13646]) or ( file == "SYS_047.SPC" and byte == 0x29 and fixPos == 1607): f.writeUInt(0x0A) fin.seek(4, 1) else: f.write(fin.read(game.spccodes[byte])) common.logDebug("Unknown byte", common.toHex(byte), "at", pos) f.writeByte(0x8F) f.writeByte(0x00) f.writeByte(0x00) endpos = f.tell() # Shift the other code pointers for codepointer in codepointers: f.seek(codepointer) pointer = f.readUInt() f.seek(-4, 1) f.writeUInt(common.shiftPointer(pointer, pointerdiff)) # Write the code section size in the header f.seek(12) f.writeUInt(endpos - 16) f.seek(endpos) # Function section fin.seek(codesize + 16) f.writeString("FUNC") fin.seek(4, 1) funcsize = fin.readUInt() f.writeUInt(funcsize) # Copy the function section while shifting pointers common.logDebug(str(funcpointers)) while True: # Read the function name function = fin.readNullString() if function == "": break f.writeString(function) f.writeZero(1) common.logDebug("Found function:", function) # Read the pointers until we find 0 while True: pointer = fin.readUInt() if pointer == 0: f.writeUInt(0) break else: pointer = common.shiftPointer( pointer, pointerdiff) if function in funcpointers and len( funcpointers[function]) > 0: for newpointer in funcpointers[function]: common.logDebug( function, "new:", newpointer, "poi:", pointer) if pointer > newpointer: f.writeUInt(newpointer) funcpointers[function].remove( newpointer) f.writeUInt(pointer) f.writeZero(1) # Write the file size in the header pos = f.tell() f.seek(4) f.writeUInt(pos - 4) f.seek(pos) # Write TERM and pad with 0s f.writeString("TERM") f.writeZero(16 - (f.tell() % 16)) common.logMessage("Done! Translation is at {0:.2f}%".format( (100 * transtot) / chartot))
def run(data): fontfile = data + "font.png" fontconfigfile = data + "fontconfig.txt" infont = data + "font_output.png" outfont = data + "font_input.png" outtable = data + "table.txt" bankfile = data + "repack/bank_10.bin" common.logMessage("Repacking font ...") # List of characters and positions in the font.png file chars = {} positions = {} bigrams = [] with codecs.open(fontconfigfile, "r", "utf-8") as f: fontconfig = common.getSection(f, "") x = 0 for c in fontconfig: if fontconfig[c][0] == "": if c in bigrams: common.logError("Duplicate bigram", c) continue bigrams.append(c) chars[c] = chars[c[0]] + 1 + chars[c[1]] continue chars[c] = int(fontconfig[c][0]) positions[c] = x if chars[c] > 7: x += 15 else: x += 7 glyphs = game.readFontGlyphs(fontconfigfile) skipcodes = [0x0, 0x40, 0x80, 0xc0] # Open the images img = Image.open(infont).convert("RGB") pixels = img.load() font = Image.open(fontfile).convert("RGB") fontpixels = font.load() # Generate the image and table fontx = 0 fonty = 0xa3 * 16 + 1 fontwidths = [] x = 0x20 tablestr = "" for item in glyphs: while x in skipcodes: fontx += 16 if fontx == 16 * 4: fontx = 0 fonty += 16 x += 1 fontwidths.append(0) if item in bigrams: for i2 in range(7): for j2 in range(15): pixels[fontx + i2, fonty + j2] = fontpixels[positions[item[0]] + i2, j2] for i2 in range(7): for j2 in range(15): pixels[fontx + chars[item[0]] + 1 + i2, fonty + j2] = fontpixels[positions[item[1]] + i2, j2] else: for i2 in range(15 if chars[item] > 7 else 7): for j2 in range(15): pixels[fontx + i2, fonty + j2] = fontpixels[positions[item] + i2, j2] if chars[item] < 15: for i2 in range(15 - chars[item]): for j2 in range(15): pixels[fontx + chars[item] + i2, fonty + j2] = fontpixels[positions[" "], j2] fontwidths.append(chars[item] + 1) fontx += 16 if fontx == 16 * 4: fontx = 0 fonty += 16 tablestr += (item + "=" + common.toHex(x) + "\n") x += 1 if fonty >= img.height: break with codecs.open(outtable, "w", "utf-8") as f: f.write(tablestr) # Replace the original ASCII character as well fontx = 0 fonty = 1 asciiglyphs = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" asciiwidths = [] for asciiglyph in asciiglyphs: for i2 in range(7): for j2 in range(15): pixels[fontx + i2, fonty + j2] = fontpixels[positions[asciiglyph] + i2, j2] for i2 in range(15 - chars[asciiglyph]): for j2 in range(15): pixels[fontx + chars[asciiglyph] + i2, fonty + j2] = fontpixels[positions[" "], j2] asciiwidths.append(chars[asciiglyph] + 1) fontx += 16 if fontx == 16 * 4: fontx = 0 fonty += 16 img.save(outfont, "PNG") # Put the font back in the bank and set the font widths with common.Stream(bankfile, "rb+") as f: ws.repackTiledImage(f, outfont, 16 * 4, 16 * 244) f.seek(0) for i in range(len(asciiwidths)): f.writeByte(asciiwidths[i]) f.seek(63, 1) f.seek((0xa3 * 4) * 64) for i in range(len(fontwidths)): if 0x20 + i not in skipcodes: f.writeByte(fontwidths[i]) else: f.seek(1, 1) f.seek(63, 1) common.logMessage("Done!")
def run(data, allfile=False): infolder = data + "extract/" outfolder = data + "repack/" infile = data + "bin_input.txt" infilename = data + "name_input.txt" chartot = transtot = 0 table, invtable, ccodes, glyphs = game.getFontData(data) with codecs.open(infile, "r", "utf-8") as bin: common.logMessage("Repacking bin from", infile, "...") for file in common.showProgress(game.fileranges): section = common.getSection(bin, file) if len(section) == 0: continue chartot, transtot = common.getSectionPercentage( section, chartot, transtot) # Repack the file common.logMessage("Processing", file, "...") common.copyFile(infolder + file, outfolder + file) with common.Stream(outfolder + file, "rb+") as f: if file != "bank_1d.bin": for binrange in game.fileranges[file]: f.seek(binrange[0]) while f.tell() < binrange[1]: if (len(binrange) >= 3): f.seek(binrange[2], 1) strpos = f.tell() readstr = game.readString(f, table, True) if allfile and len(readstr) > 50: f.seek(strpos + 2) continue if readstr.startswith("|"): f.seek(strpos + 2) continue if readstr != "": newstr = "" if readstr in section: newstr = section[readstr].pop(0) if len(section[readstr]) == 0: del section[readstr] strend = f.tell() if newstr != "": if newstr == "!": newstr = "" common.logDebug("Repacking", newstr, "at", common.toHex(strpos)) f.seek(strpos) game.writeString(f, newstr, invtable, ccodes, strend - strpos - 2, True) while f.tell() < strend: f.writeByte(int(ccodes[" "][0], 16)) f.seek(strend - 2) f.writeUShort(0xffff) else: # String pointers are stored starting at 0xcd00 f.seek(0xcd00) ptrs = [] for i in range(23): ptrs.append(f.readUShort()) f.seek(14, 1) strings = [] for ptr in ptrs: f.seek(ptr) strings.append(game.readString(f, table, True)) newptrs = [] f.seek(0xce70) for i in range(len(strings)): if strings[i] in section: newstr = section[strings[i]].pop(0) else: newstr = strings[i] newstr = common.wordwrap(newstr, glyphs, game.wordwrap_angel, game.detectTextCode) common.logDebug("Repacking", newstr, "at", common.toHex(f.tell())) newstr = newstr.split("|") if len(newstr) > 8: common.logError("Line too long", str(len(newstr)) + "/8", newstr[0]) newstr = newstr[:8] while len(newstr) < 8: newstr.append("") for binstr in newstr: if not binstr.startswith("▼"): binstr = " " + binstr newptrs.append(f.tell()) game.writeString(f, binstr, invtable, ccodes, -1, True) f.writeUShort(0xffff) f.seek(0xcd00) for newptr in newptrs: f.writeUShort(newptr) # Set the name input selection glyphs in bank 14 newglyphs = {} with codecs.open(infilename, "r", "utf-8") as name: nameglyphs = name.read().replace("\r", "").replace("\n", "").replace("#", "") with common.Stream(outfolder + "bank_14.bin", "rb+") as f: # Write the new name input values f.seek(0xc250) for nameglyph in nameglyphs: if nameglyph in invtable and invtable[nameglyph][:2] != 'ff': f.writeUShort(int(invtable[nameglyph], 16)) else: glyphcode = int(ccodes[nameglyph][0], 16) - 0x20 glyphcode <<= 6 glyphcode += 0xa300 newglyphs[nameglyph] = glyphcode f.writeUShort(glyphcode) # Write "Adam", but using the long glyphs f.seek(0x296c + 3) f.writeUShort(int(invtable["A"], 16)) f.seek(3, 1) f.writeUShort(newglyphs["d"]) f.seek(3, 1) f.writeUShort(newglyphs["a"]) f.seek(3, 1) f.writeUShort(newglyphs["m"]) common.logMessage("Done! Translation is at {0:.2f}%".format( (100 * transtot) / chartot)) nasm.run(common.bundledFile("bin_patch.asm"))
def run(data, speaker=True, scene=False, merge=False): infolder = data + "extract/" files = ["bank_11.bin", "bank_12.bin"] outfile = data + "script_output.txt" with codecs.open(data + "table_input.txt", "r", "utf-8") as tablef: table = common.getSection(tablef, "") if merge: mergescript = codecs.open(data + "script_input.txt", "r", "utf-8") # Get the scenes list from the debug menu if scene: scenenames = {"bank_11.bin": [], "bank_12.bin": []} with common.Stream(infolder + "bank_1e.bin", "rb") as f: f.seek(0xcd90) while True: unk = f.readUShort() if unk == 0xffff: break scenepos = f.readUShort() scenebank = "bank_11.bin" if f.readUShort( ) == 0x00f1 else "bank_12.bin" scenename = f.readNullString() scenenames[scenebank].append({ 'pos': scenepos, 'name': scenename }) f.seek(1, 1) # Always 0x90 for bankname in scenenames: scenenames[bankname] = sorted(scenenames[bankname], key=lambda i: i['pos']) # Extract the scripts with codecs.open(outfile, "w", "utf-8") as out: common.logMessage("Extracting script to", outfile, "...") for file in common.showProgress(files): common.logMessage("Processing", file, "...") out.write("!FILE:" + file + "\n") size = os.path.getsize(infolder + file) lastspeaker = -1 if scene: nextscene = scenenames[file].pop(0) if merge: mergesection = common.getSection(mergescript, file) with common.Stream(infolder + file, "rb") as f: while f.tell() < size - 1: opcode = f.readByte() if opcode == 0x0a: strpos = f.tell() # Print the scene name if scene: if (nextscene is not None and strpos >= nextscene['pos']): out.write("\n\n##SCENE:" + nextscene['name'] + "\n") nextscene = scenenames[file].pop(0) if len( scenenames[file]) > 0 else None # Add the speaker name if speaker: # Usually 7 bytes before there's a 0x10 byte, followed by the speaker expression and then the speaker sprite speakercode = -1 f.seek(-7, 1) checkspeaker = f.readByte() if checkspeaker == 0x10: f.seek(1, 1) speakercode = f.readByte() else: # Sometimes, it's 6 bytes before instead so check that one too checkspeaker = f.readByte() if checkspeaker == 0x10: f.seek(1, 1) speakercode = f.readByte() if speakercode != -1: if speakercode not in game.speakercodes: common.logDebug("Unknown speaker code", speakercode) else: if speakercode != lastspeaker: out.write( "#" + game.speakercodes[speakercode] + "\n") lastspeaker = speakercode # Read the string f.seek(strpos) readstr = game.readString(f, table) if readstr != "": if readstr[-2:] == ">>": readstr = readstr[:-2] out.write(readstr) out.write("=") if merge and readstr in mergesection: out.write(mergesection[readstr].pop(0)) if len(mergesection[readstr]) == 0: del mergesection[readstr] out.write("\n") common.logDebug("Read string:", readstr) elif opcode in game.repopcodes: while f.readByte() != 0xff: pass else: f.seek(game.opcodes[opcode], 1) out.write("\n\n") common.logMessage("Done! Extracted", len(files), "files")
def run(data): infolder = data + "extract/" outfolder = data + "repack/" files = ["bank_11.bin", "bank_12.bin"] binfile = outfolder + "bank_14.bin" debugfile = outfolder + "bank_1e.bin" infile = data + "script_input.txt" chartot = transtot = 0 table, invtable, ccodes, glyphs = game.getFontData(data) if not os.path.isfile(infile): common.logError("Input file", infile, "not found") return pointers = {} pointerdiff = {} common.logMessage("Repacking script from", infile, "...") with codecs.open(infile, "r", "utf-8") as script: for file in common.showProgress(files): section = common.getSection(script, file) pointers[file] = [] pointerdiff[file] = {} if len(section) == 0: continue chartot, transtot = common.getSectionPercentage( section, chartot, transtot) # Repack the file common.logMessage("Processing", file, "...") size = os.path.getsize(infolder + file) with common.Stream(infolder + file, "rb") as fin: with common.Stream(outfolder + file, "wb") as f: while fin.tell() < size: opcode = fin.readByte() if opcode == 0x0a: f.writeByte(opcode) strpos2 = f.tell() strpos = fin.tell() readstr = game.readString(fin, table) newstr = "" if readstr != "": addend = "" if readstr[-2:] == ">>": addend = ">>" readstr = readstr[:-2] if readstr in section: newstr = section[readstr].pop(0) if len(section[readstr]) == 0: del section[readstr] strend = fin.tell() if newstr != "": newstr = common.wordwrap( newstr, glyphs, game.wordwrap, game.detectTextCode) if newstr == "!": newstr = "" newstr += addend common.logDebug("Writing string at", common.toHex(strpos), common.toHex(strpos2), newstr) game.writeString(f, newstr, invtable, ccodes) f.writeUShort(0xffff) strend2 = f.tell() lendiff = (strend2 - strpos2) - (strend - strpos) if lendiff != 0: common.logDebug("Adding", lendiff, "at", common.toHex(strpos)) pointerdiff[file][strpos + 1] = lendiff else: fin.seek(strpos) f.write(fin.read(strend - strpos)) elif opcode in game.repopcodes: if opcode in game.ptropcodes: pointers[file].append(f.tell()) f.writeByte(opcode) replen = 0 while True: loopbyte = fin.readByte() f.writeByte(loopbyte) replen += 1 if loopbyte == 0xff and (opcode not in game.ptropcodes or replen > 2): break else: if opcode in game.ptropcodes: pointers[file].append(f.tell()) f.writeByte(opcode) f.write(fin.read(game.opcodes[opcode])) if opcode == 0xff: check = fin.readUInt() check2 = fin.readUInt() fin.seek(-8, 1) if check == 0xffffffff and check2 == 0xffffffff: break # Pad with 0xffff f.writeBytes(0xff, 0xffff - f.tell() + 1) common.logMessage("Shifting script pointers ...") for file in common.showProgress(files): with common.Stream(outfolder + file, "rb+") as f: for pointerpos in pointers[file]: f.seek(pointerpos) opcode = f.readByte() for pointeroff in game.ptropcodes[opcode]: pointerfile = file if type(pointeroff) == tuple: f.seek(pointerpos + 1 + pointeroff[1]) bankid = f.readUShort() pointerfile = files[0] if bankid == 0x00f1 else files[1] pointeroff = pointeroff[0] f.seek(pointerpos + 1 + pointeroff) pointer = f.readUShort() if pointer != 0xffff: newpointer = common.shiftPointer( pointer, pointerdiff[pointerfile]) else: newpointer = pointer f.seek(-2, 1) f.writeUShort(newpointer) common.logMessage("Shifting bin pointers ...") # Read pointer tables in the bin file with common.Stream(binfile.replace("/repack/", "/extract/"), "rb+") as f: f.seek(0x23b0) while f.tell() < 0x2740: pos1 = f.tell() ptr1 = f.readUShort() bank1 = f.readUShort() pos2 = f.tell() ptr2 = f.readUShort() bank2 = f.readUShort() game.binptrs.append((pos1, ptr1, bank1)) game.binptrs.append((pos2, ptr2, bank2)) f.seek(8, 1) for binrange in [(0x2ff7, 0x3027), (0x3063, 0x309b), (0x480f, 0x481f), (0x496b, 0x499b), (0x4a01, 0x4a39)]: f.seek(binrange[0]) while f.tell() < binrange[1]: pos = f.tell() ptr = f.readUShort() bank = f.readUShort() game.binptrs.append((pos, ptr, bank)) f.seek(0x902e) while f.tell() < 0x9106: pos = f.tell() ptr = f.readUShort() game.binptrs.append((pos, ptr, 0xf2)) game.binptrs.append((0x26b8, 0x3d7b, 0xf2)) shiftPointers(binfile, game.binptrs, pointerdiff) # Shift debug pointers debugptrs = [] with common.Stream(debugfile.replace("/repack/", "/extract/"), "rb+") as f: f.seek(0xcd92) while f.tell() < 0xd420: pos1 = f.tell() ptr1 = f.readUShort() bank1 = f.readUShort() debugptrs.append((pos1, ptr1, bank1)) f.seek(12, 1) shiftPointers(debugfile, debugptrs, pointerdiff) common.logMessage("Done! Translation is at {0:.2f}%".format( (100 * transtot) / chartot))
def run(): infolder = "data/extract/data/Rom/event/script/" outfolder = "data/repack/data/Rom/event/script/" infile = "data/cnut_input.txt" fontfile = "data/repack/data/Rom/font/font0.NFTR" replacefontfile = "data/replace/data/Rom/font/font0.NFTR" chartot = transtot = 0 if not os.path.isfile(infile): common.logError("Input file", infile, "not found") return common.logMessage("Repacking CNUT from", infile, "...") if os.path.isfile(replacefontfile): glyphs = nitro.readNFTR(replacefontfile).glyphs else: glyphs = nitro.readNFTR(fontfile).glyphs with codecs.open(infile, "r", "utf-8") as cnut: files = common.getFiles(infolder, ".cnut") for file in common.showProgress(files): section = common.getSection(cnut, file, "//") if len(section) == 0: common.makeFolders(outfolder + file) common.copyFile(infolder + file, outfolder + file) continue chartot, transtot = common.getSectionPercentage( section, chartot, transtot) # Repack the file common.logDebug("Processing", file, "...") size = os.path.getsize(infolder + file) lastpos = 0 with common.Stream(infolder + file, "rb") as fin: common.makeFolders(infolder + file) with common.Stream(outfolder + file, "wb") as f: while fin.tell() < size - 4: pos = fin.tell() b1 = fin.readByte() b2 = fin.readByte() b3 = fin.readByte() b4 = fin.readByte() if b1 == 0x10 and b2 == 0x00 and b3 == 0x00 and b4 == 0x08: # Found a string check = game.readShiftJIS(fin) pre = post = "" # Add back some codes that are removed from extracted lines if check.startswith("#CLR()"): pre = "#CLR()" check = check[6:] if check.startswith("#ARW("): pre += check[:7] check = check[7:] if check.endswith("#INP()"): post = "#INP()" check = check[:-6] # Check if the line is translated and replace it if check in section: newsjis = section[check].pop(0) if len(section[check]) == 0: del section[check] if newsjis != "": newsjis = pre + newsjis + post if newsjis != check: newsjis = common.wordwrap( newsjis, glyphs, 205, game.detectTextCode) newsjis = newsjis.replace( ">>", "#INP()" + pre).replace( "|", "<0A>") # Copy data up to here endpos = fin.tell() fin.seek(lastpos) f.write(fin.read(pos + 4 - lastpos)) lastpos = endpos common.logDebug(" Repacking at", pos) game.writeShiftJIS(f, newsjis) else: fin.seek(pos + 1) fin.seek(lastpos) f.write(fin.read(size - lastpos)) common.logMessage("Done! Translation is at {0:.2f}%".format( (100 * transtot) / chartot))
def run(): binin = "data/extract/arm9.bin" binout = "data/repack/arm9.bin" binfile = "data/bin_input.txt" tablefile = "data/table.txt" if not os.path.isfile(binfile): common.logError("Input file", binfile, "not found.") return common.copyFile(binin, binout) freeranges = [(0xEA810, 0xEEC00)] currentrange = 0 rangepos = 0 section = {} with codecs.open(binfile, "r", "utf-8") as bin: section = common.getSection(bin, "", "#", game.fixchars) chartot, transtot = common.getSectionPercentage(section) common.logMessage("Repacking BIN from", binfile, "...") common.loadTable(tablefile) rangepos = freeranges[currentrange][0] with common.Stream(binin, "rb") as fi: allbin = fi.read() strpointers = {} with common.Stream(binout, "r+b") as fo: # Skip the beginning and end of the file to avoid false-positives fi.seek(992000) while fi.tell() < 1180000: pos = fi.tell() if pos < 1010000 or pos > 1107700: check = game.detectShiftJIS(fi) if check in section and section[check][0] != "": common.logDebug("Replacing string at", pos) newstr = section[check][0] # Check how much padding space we have padding = 0 while True: if fi.readByte() == 0x00: padding += 1 else: fi.seek(-1, 1) break fo.seek(pos) endpos = fi.tell() - 1 newlen = game.writeShiftJIS(fo, newstr, False, endpos - pos) if newlen < 0: if rangepos >= freeranges[currentrange][ 1] and newstr not in strpointers: common.logWarning("No more room! Skipping ...") else: # Write the string in a new portion of the rom if newstr in strpointers: newpointer = strpointers[newstr] else: common.logDebug( "No room for the string, redirecting to", rangepos) fo.seek(rangepos) game.writeShiftJIS(fo, newstr, False) fo.writeZero(1) newpointer = 0x02000000 + rangepos rangepos = fo.tell() strpointers[newstr] = newpointer if rangepos >= freeranges[currentrange][1]: if currentrange + 1 < len(freeranges): currentrange += 1 rangepos = freeranges[ currentrange][0] # Search and replace the old pointer pointer = 0x02000000 + pos pointersearch = struct.pack("<I", pointer) index = 0 common.logDebug("Searching for pointer", pointersearch.hex().upper()) while index < len(allbin): index = allbin.find(pointersearch, index) if index < 0: break common.logDebug(" Replaced pointer at", str(index)) fo.seek(index) fo.writeUInt(newpointer) index += 4 else: fo.writeZero(endpos - fo.tell()) if check != "": pos = fi.tell() - 1 fi.seek(pos + 1) common.logMessage("Done! Translation is at {0:.2f}%".format( (100 * transtot) / chartot))
def run(): xmlfile = "data/fontdump.xml" imgfile = "data/fontdump.png" fontfile = "font.png" fontconfigfile = "fontconfig.txt" outfile = "data/fontout.png" infont = "data/extract_NFP/ETC.NFP/GL_12FNT.NFT" tempfont = "data/GL_12FNT.NFTR" outfont = "data/work_NFP/ETC.NFP/GL_12FNT.NFT" binin = "data/bin_input.txt" spcin = "data/spc_input.txt" table = "data/table.txt" common.logMessage("Repacking font ...") fontexe = common.bundledFile("NerdFontTerminatoR.exe") if not os.path.isfile(fontexe): common.logError("NerdFontTerminatoR not found") return # List of characters with codecs.open(fontconfigfile, "r", "utf-8") as f: fontconfig = common.getSection(f, "") upperchars = fontconfig["upperchars"][0].split("|") lowerchars = fontconfig["lowerchars"][0].split("|") numbers = fontconfig["numbers"][0].split("|") punctuation = fontconfig["punctuation"][0].split("|") customs = fontconfig["customs"][0].split("|") all = upperchars + lowerchars + numbers + punctuation + customs # X Position in the font.png file positions = {} for i in range(len(upperchars)): positions[upperchars[i]] = i * 12 positions[lowerchars[i]] = (i * 12) + 6 for i in range(len(numbers)): positions[numbers[i]] = (len(upperchars) * 12) + (i * 6) for i in range(len(punctuation)): positions[punctuation[i]] = (len(upperchars) * 12) + (len(numbers) * 6) + (i * 6) for i in range(len(customs)): positions[customs[i]] = (len(upperchars) * 12) + (len(numbers) * 6) + (len(punctuation) * 6) + (i * 12) # Fix the font size before dumping it with common.Stream(infont, "rb") as font: with common.Stream(tempfont, "wb") as temp: font.seek(8) size = font.readUInt() font.seek(0) temp.write(font.read(size)) # Dump the font common.execute(fontexe + " -e " + tempfont + " " + xmlfile + " " + imgfile, False) # Generate the code range coderanges = [(0x89, 0x9F), (0xE0, 0xEA)] skipcodes = [0x7F] charrange = (0x40, 0xFC) codes = [] for coderange in coderanges: for i in range(coderange[0], coderange[1] + 1): first = charrange[0] if i == 0x88: first = 0x9F last = charrange[1] if i == 0xEA: last = 0xA4 for j in range(first, last + 1): if j in skipcodes: continue hexcode = i * 0x100 + j if hexcode > 0x9872 and hexcode < 0x989F: continue codes.append(hexcode) # Generate a basic bigrams list items = [" "] for char1 in upperchars: for char2 in lowerchars: items.append(char1 + char2) for char1 in upperchars: items.append(" " + char1) items.append(char1 + " ") for char2 in upperchars: if char1 + char2 not in items: items.append(char1 + char2) for char1 in lowerchars: items.append(" " + char1) items.append(char1 + " ") for char2 in lowerchars: if char1 + char2 not in items: items.append(char1 + char2) for custom in customs: items.append(custom) # And a complete one from all the bigrams with codecs.open(spcin, "r", "utf-8") as spc: inputs = common.getSection(spc, "", "#", game.fixchars) with codecs.open(binin, "r", "utf-8") as bin: inputs.update(common.getSection(bin, "", "#", game.fixchars)) for k, input in inputs.items(): for str in input: str = "<0A>".join(str.replace("|", "<0A>").split(">>")) if str.startswith("<<"): str = str[2:] pad = " " * ((20 - len(str)) // 2) str = pad + str + pad if str.startswith("[") and str[3] == "]": str = str[4:] i = 0 while i < len(str): if i < len(str) - 1 and str[i+1] == "<": str = str[:i+1] + " " + str[i+1:] elif i < len(str) - 4 and (str[i+1:i+5] == "UNK(" or str[i+1:i+5] == "CUS("): str = str[:i+1] + " " + str[i+1:] char = str[i] if char == "<" and i < len(str) - 3 and str[i+3] == ">": i += 4 elif char == "U" and i < len(str) - 4 and str[i:i+4] == "UNK(": i += 9 elif char == "C" and i < len(str) - 4 and str[i:i+4] == "CUS(": i += 9 else: if i + 1 == len(str): bigram = char + " " else: bigram = char + str[i+1] i += 2 if bigram not in items: if bigram[0] not in all or bigram[1] not in all: common.logError("Invalid bigram", bigram, "from phrase", str) else: items.append(bigram) # Open the images img = Image.open(imgfile) pixels = img.load() font = Image.open(fontfile) fontpixels = font.load() # Generate the image and table fontx = 106 fonty = 5644 x = len(codes) - 1 tablestr = "" for item in items: if item in customs: for i2 in range(11): for j2 in range(11): pixels[fontx + i2, fonty + j2] = fontpixels[positions[item] + i2, j2] else: for i2 in range(5): for j2 in range(11): pixels[fontx + i2, fonty + j2] = fontpixels[positions[item[0]] + i2, j2] for j2 in range(11): pixels[fontx + 5, fonty + j2] = fontpixels[positions[" "], j2] for i2 in range(5): for j2 in range(11): pixels[fontx + i2 + 6, fonty + j2] = fontpixels[positions[item[1]] + i2, j2] fontx -= 13 if fontx < 0: fontx = 197 fonty -= 13 tablestr = (item + "=" + common.toHex(codes[x]) + "\n") + tablestr x -= 1 with codecs.open(table, "w", "utf-8") as f: f.write(tablestr) img.save(outfile, "PNG") # Generate the new font common.execute(fontexe + " -i " + xmlfile + " " + outfile + " " + tempfont, False) common.copyFile(tempfont, outfont) # Clean up the temp files os.remove(xmlfile) os.remove(imgfile) os.remove(outfile) os.remove(tempfont) if x < len(items): common.logMessage("Done! Couldn't fit", len(items) - x, "bigrams") else: common.logMessage("Done! Room for", x - len(items), "more bigrams")