def detectShiftJIS(f, encoding="shift_jis"): ret = "" sjis = 0 while True: b1 = f.readByte() if b1 == 0: return ret if ret != "" and b1 in bincodes: ret += "<" + common.toHex(b1) + ">" continue elif b1 >= 28 and b1 <= 126 and (sjis > 0 or chr(b1) == "#" or (b1 >= 48 and b1 <= 57) or ret.startswith("#")): ret += chr(b1) continue b2 = f.readByte() if b1 == 0x0D and b2 == 0x0A: ret += "|" elif common.checkShiftJIS(b1, b2): f.seek(-2, 1) try: ret += f.read(2).decode(encoding).replace("〜", "~") sjis += 1 except UnicodeDecodeError: if ret.count("UNK(") >= 5: return "" ret += "UNK(" + common.toHex(b1) + common.toHex(b2) + ")" elif len(ret) > 1 and ret.count("UNK(") < 5: ret += "UNK(" + common.toHex(b1) + common.toHex(b2) + ")" else: return ""
def readGIM(file, start=0): gim = GIM() with common.Stream(file, "rb") as f: f.seek(start) if f.readString(3) == 'MIG': f.seek(start + 16) gim.rootoff = f.tell() id = f.readUShort() if id != 0x02: common.logError("Unexpected id in block 0:", common.toHex(id), common.toHex(f.tell() - 2)) return None f.seek(2, 1) gim.rootsize = f.readUInt() nextblock = gim.rootoff + f.readUInt() image = None while nextblock > 0 and nextblock < start + gim.rootsize + 16: f.seek(nextblock) nextblock, image = readGIMBlock(f, gim, image) else: # This is a TGA file, assuming 32bit RGBA image = TGAImage() image.rootoff = f.tell() f.seek(start + 2) image.format = f.readByte() f.seek(9, 1) image.width = f.readUShort() image.height = f.readUShort() f.seek(2, 1) image.imgoff = f.tell() for i in range(image.height): for j in range(image.width): image.colors.append(readColor(f, 0x03)) gim = image return gim
def readShiftJIS(f, encoding="shift_jis"): strlen = f.readUInt() sjis = "" i = 0 while i < strlen: b1 = f.readByte() if b1 == 0x0A: sjis += "|" i += 1 else: b2 = f.readByte() if (b1, b2) in fixchars: sjis += fixchars[(b1, b2)] i += 2 elif not common.checkShiftJIS(b1, b2): if b2 == 0x01: sjis += "UNK(" + common.toHex(b1) + common.toHex(b2) + ")" i += 2 else: f.seek(-1, 1) sjis += chr(b1) i += 1 else: f.seek(-2, 1) try: sjis += f.read(2).decode(encoding).replace("〜", "~") except UnicodeDecodeError: common.logError("[ERROR] UnicodeDecodeError") sjis += "[ERROR" + str(f.tell() - 2) + "]" i += 2 return sjis
def getFontGlyphs(file): glyphs = {} with common.Stream(file, "rb", False) as f: # Header f.seek(36) hdwcoffset = f.readUInt() pamcoffset = f.readUInt() common.logDebug("hdwcoffset:", hdwcoffset, "pamcoffset:", pamcoffset) # HDWC f.seek(hdwcoffset - 4) hdwclen = f.readUInt() tilenum = (hdwclen - 16) // 3 firstcode = f.readUShort() lastcode = f.readUShort() f.seek(4, 1) common.logDebug("firstcode:", firstcode, "lastcode:", lastcode, "tilenum", tilenum) hdwc = [] for i in range(tilenum): hdwcstart = f.readSByte() hdwcwidth = f.readByte() hdwclength = f.readByte() hdwc.append((hdwcstart, hdwcwidth, hdwclength)) # PAMC nextoffset = pamcoffset while nextoffset != 0x00: f.seek(nextoffset) firstchar = f.readUShort() lastchar = f.readUShort() sectiontype = f.readUShort() f.seek(2, 1) nextoffset = f.readUInt() common.logDebug("firstchar:", common.toHex(firstchar), "lastchar:", common.toHex(lastchar), "sectiontype:", sectiontype, "nextoffset:", nextoffset) if sectiontype == 0: firstcode = f.readUShort() for i in range(lastchar - firstchar + 1): c = common.codeToChar(firstchar + i) glyphs[c] = common.FontGlyph(hdwc[firstcode + i][0], hdwc[firstcode + i][1], hdwc[firstcode + i][2], c, firstchar + i, firstcode + i) elif sectiontype == 1: for i in range(lastchar - firstchar + 1): charcode = f.readUShort() if charcode == 0xFFFF or charcode >= len(hdwc): continue c = common.codeToChar(firstchar + i) glyphs[c] = common.FontGlyph(hdwc[charcode][0], hdwc[charcode][1], hdwc[charcode][2], c, firstchar + i, charcode) else: common.logWarning("Unknown section type", sectiontype) return glyphs
def readGMOChunk(f, gmo, maxsize, nesting=""): offset = f.tell() id = f.readUShort() headerlen = f.readUShort() blocklen = f.readUInt() common.logDebug(nesting + "GMO ID", common.toHex(id), "at", common.toHex(offset), "len", common.toHex(headerlen), common.toHex(blocklen)) if id == 0xa: # Texture name f.seek(8, 1) texname = f.readEncodedString().replace(":", "") while texname in gmo.names: texname += "_" common.logDebug(nesting + "0x0A at", common.toHex(offset), common.toHex(offset + blocklen), texname) gmo.names.append(texname) elif id == 0x8013: # Texture data f.seek(4, 1) gmo.offsets.append(f.tell()) common.logDebug(nesting + "0x8013 at", common.toHex(f.tell()), common.toHex(offset), common.toHex(offset + blocklen)) if id != 0x7 and id != 0xc and headerlen > 0: f.seek(offset + headerlen) common.logDebug(nesting + "Raeding nested blocks:") while f.tell() < offset + blocklen - 1 and f.tell() < maxsize: readGMOChunk(f, gmo, maxsize, nesting + " ") common.logDebug(nesting + "Done") f.seek(offset + blocklen) else: f.seek(offset + blocklen)
def readShiftJIS(f, len2=False, untilZero=False, encoding="shift_jis"): if untilZero: strlen2 = 999 else: if len2: strlen = f.readUShort() strlen2 = f.readUShort() else: strlen = f.readByte() strlen2 = f.readByte() sjis = "" i = j = 0 padding = 0 while i < strlen2: b1 = f.readByte() if b1 == 0x00: i += 1 j += 1 padding += 1 if untilZero: return sjis, i else: b2 = f.readByte() if b1 == 0x0d and b2 == 0x0a: sjis += "|" i += 2 j += 2 elif b1 == 0x81 and b2 == 0xa5: sjis += ">>" i += 2 j += 1 elif not common.checkShiftJIS(b1, b2): f.seek(-1, 1) sjis += chr(b1) i += 1 j += 1 else: f.seek(-2, 1) try: sjis += f.read(2).decode(encoding).replace("〜", "~") except UnicodeDecodeError: common.logDebug("UnicodeDecodeError at", f.tell() - 2) sjis += "UNK(" + common.toHex(b1) + common.toHex(b2) + ")" i += 2 j += 1 if not untilZero and j != strlen: common.logWarning("Wrong strlen", strlen, j) return sjis, i
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 writeLine(out, pos, byte, line, functions): pos -= 16 function = "" if pos in functions: function = functions[pos] + " " del functions[pos] out.write(str(pos).zfill(5) + " 0x" + common.toHex(byte) + ": " + line + " " + function + "\n")
def readMappedImage(f, outfile, mapstart=0, num=1, bpp=2, width=0, height=0): f.seek(mapstart) maps = [] for j in range(num): map = TileMap() if num > 1: map.name = outfile.replace(".png", "_" + str(j + 1).zfill(2) + ".png") else: map.name = outfile map.offset = f.tell() map.width = width if map.width == 0: map.width = f.readByte() map.height = height if map.height == 0: map.height = f.readByte() map.bpp = bpp common.logDebug(" ", mapstart, vars(map)) map.map = [] for i in range(map.width * map.height): tilemap = TileMap() tilemap.data = f.readUShort() tilemap.tile = tilemap.data & 0x1ff tilemap.pal = (tilemap.data >> 9) & 0xf tilemap.bank = (tilemap.data >> 13) & 1 if tilemap.bank != 0 and bpp == 2: common.logError("Bank is not 0") tilemap.hflip = ((tilemap.data >> 14) & 1) == 1 tilemap.vflip = ((tilemap.data >> 15) & 1) == 1 map.map.append(tilemap) maps.append(map) common.logDebug("Map data ended at", common.toHex(f.tell())) return maps
def repackBinaryStrings(elf, section, infile, outfile, readfunc, writefunc, encoding="shift_jis", elfsections=[".rodata"]): with common.Stream(infile, "rb") as fi: with common.Stream(outfile, "r+b") as fo: for sectionname in elfsections: rodata = elf.sectionsdict[sectionname] fi.seek(rodata.offset) while fi.tell() < rodata.offset + rodata.size: pos = fi.tell() check = readfunc(fi, encoding) if check != "": if check in section and section[check][0] != "": common.logDebug("Replacing string", check, "at", common.toHex(pos), "with", section[check][0]) fo.seek(pos) endpos = fi.tell() - 1 newlen = writefunc(fo, section[check][0], endpos - pos + 1) if newlen < 0: fo.writeZero(1) common.logError("String", section[check][0], "is too long.") else: fo.writeZero(endpos - fo.tell()) else: pos = fi.tell() - 1 fi.seek(pos + 1)
def readShiftJIS(f): len = f.readShort() pos = f.tell() # Check if the string is all ascii ascii = True for i in range(len - 1): byte = f.readByte() if byte != 0x0A and (byte < 32 or byte > 122): ascii = False break if not ascii: f.seek(pos) sjis = "" i = 0 while i < len - 1: byte = f.readByte() if byte in codes: sjis += "<" + common.toHex(byte) + ">" i += 1 else: f.seek(-1, 1) try: sjis += f.read(2).decode("shift-jis").replace("〜", "~") except UnicodeDecodeError: common.logError("UnicodeDecodeError") sjis += "|" i += 2 return sjis return ""
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 mpstopmf(infile, outfile, duration): with common.Stream(infile, "rb", False) as fin: # Check header check1 = fin.readUInt() check2 = fin.readByte() if check1 != 0x1ba or check2 != 0x44: common.logError("Input header is wrong", common.toHex(check1), common.toHeck(check2)) return fin.seek(0) mpsdata = fin.read() # https://github.com/TeamPBCN/pmftools/blob/main/mps2pmf/mps2pmf.cpp with common.Stream(outfile, "wb", False) as f: # Magic f.writeString("PSMF") f.writeString("0012") # Header size f.writeUInt(0x800) # MPS size f.writeUInt(len(mpsdata)) f.seek(0x50) # Other header values f.writeUInt(0x4e) f.writeUInt(1) f.writeUShort(0x5f90) f.writeUShort(0) f.writeUInt(duration) f.writeUInt(0x61a8) f.writeUShort(1) f.writeUShort(0x5f90) f.writeUShort(0x201) f.writeUShort(0) f.writeUShort(0x34) f.writeUShort(0) f.writeUShort(1) f.writeUShort(0x5f90) f.writeUShort(0) f.writeUInt(duration) f.writeUShort(1) f.writeUInt(0x22) f.writeUShort(0x2) f.writeUShort(0xe000) f.writeUShort(0x21ef) f.writeUShort(0) f.writeUInt(0x0) f.writeUInt(0x1e11) f.writeUInt(0xbd00) f.writeUShort(0x2004) f.seek(0xa0) f.writeUShort(0x202) # Everything else is 0, write the MPS data f.seek(0x800) f.write(mpsdata)
def decompress(f, complength): header = f.readUInt() type = header & 0xFF decomplength = ((header & 0xFFFFFF00) >> 8) common.logDebug("Compression header:", common.toHex(header), "type:", common.toHex(type), "length:", decomplength) with common.Stream() as data: data.write(f.read(complength)) data.seek(0) if type == CompressionType.LZ10: return compression.decompressLZ10(data, complength, decomplength) elif type == CompressionType.LZ11: return compression.decompressLZ11(data, complength, decomplength) elif type == CompressionType.Huff4: return compression.decompressHuffman(data, complength, decomplength, 4) elif type == CompressionType.Huff8: return compression.decompressHuffman(data, complength, decomplength, 8) elif type == CompressionType.RLE: return compression.decompressRLE(data, complength, decomplength) else: common.logError("Unsupported compression type", common.toHex(type)) return data.read()
def writeMappedImage(f, tilestart, maps, palettes, num=1, skipzero=False): maxtile = tilesize = 0 for i in range(num): mapdata = maps[i] if mapdata.width == 0: common.logError("Width is 0") continue if mapdata.height == 0: common.logError("Height is 0") continue imgwidth = mapdata.width * 8 imgheight = mapdata.height * 8 pali = 0 if mapdata.bpp == 4 and palettes != colpalette: imgwidth += 40 for palette in palettes: if palette.count((0x0, 0x0, 0x0, 0xff)) == 16: break pali += 1 imgheight = max(imgheight, pali * 10) img = Image.new("RGB", (imgwidth, imgheight), (0x0, 0x0, 0x0)) pixels = img.load() x = y = 0 for map in mapdata.map: tilesize = (16 if mapdata.bpp == 2 else 32) if map.tile > maxtile: maxtile = map.tile if (map.tile > 0 or not skipzero) and (mapdata.bpp != 2 or map.bank == 0): f.seek(tilestart + map.bank * 0x4000 + map.tile * tilesize) try: readTile( f, pixels, x * 8, y * 8, palettes[map.pal] if map.pal < len(palettes) else palettes[0], map.hflip, map.vflip, mapdata.bpp) except struct.error: pass except IndexError: pass x += 1 if x == mapdata.width: y += 1 x = 0 if pali > 0: palstart = 0 for i in range(pali): pixels = common.drawPalette(pixels, palettes[i], imgwidth - 40, palstart * 10) palstart += 1 img.save(mapdata.name, "PNG") common.logDebug("Tile data ended at", common.toHex(tilestart + maxtile * tilesize + tilesize))
def detectShiftJIS(f): ret = "" while True: b1 = f.readByte() if ret != "" and b1 == 0: return ret if ret != "" and b1 in bincodes: ret += "<" + common.toHex(b1) + ">" continue b2 = f.readByte() if common.checkShiftJIS(b1, b2): f.seek(-2, 1) try: ret += f.read(2).decode("cp932").replace("〜", "~") except UnicodeDecodeError: if ret.count("UNK(") >= 5: return "" ret += "UNK(" + common.toHex(b1) + common.toHex(b2) + ")" elif len(ret) > 0 and ret.count("UNK(") < 5: ret += "UNK(" + common.toHex(b1) + common.toHex(b2) + ")" else: return ""
def readGMO(file): gmo = GMO() with common.Stream(file, "rb") as f: f.seek(16 + 4) gmo.size = f.readUInt() f.seek(8, 1) while f.tell() < gmo.size + 16: readGMOChunk(f, gmo, gmo.size + 16) for gimoffset in gmo.offsets: common.logDebug("Reading GIM at", common.toHex(gimoffset)) gim = readGIM(file, gimoffset) gmo.gims.append(gim) return gmo
def extractBIN(binrange, readfunc=common.detectEncodedString, encoding="shift_jis", binin="data/extract/arm9.bin", binfile="data/bin_output.txt", writepos=False, writedupes=False): common.logMessage("Extracting BIN to", binfile, "...") if type(binrange) == tuple: binrange = [binrange] strings, positions = common.extractBinaryStrings(binin, binrange, readfunc, encoding) with codecs.open(binfile, "w", "utf-8") as out: for i in range(len(strings)): if writepos: allpositions = [] for strpos in positions[i]: allpositions.append(common.toHex(strpos)) out.write(str(allpositions) + "!") for j in range(1 if writedupes is False else len(positions[i])): out.write(strings[i] + "=\n") common.logMessage("Done! Extracted", len(strings), "lines")
def readTIM(f, forcesize=0): tim = TIM() # Read header header = f.readUInt() if header != 0x10: return None type = f.readUInt() if type == 0x08: tim.bpp = 4 elif type == 0x09: tim.bpp = 8 elif type == 0x02: tim.bpp = 16 elif type == 0x03: tim.bpp = 24 else: common.logError("Unknown TIM type", common.toHex(type)) return None # Read palettes if tim.bpp == 4 or tim.bpp == 8: tim.clutsize = f.readUInt() tim.clutposx = f.readUShort() tim.clutposy = f.readUShort() tim.clutwidth = f.readUShort() tim.clutheight = f.readUShort() tim.clutoff = f.tell() for i in range(tim.clutheight): clut = readCLUTData(f, tim.clutwidth) tim.cluts.append(clut) # Read size tim.size = f.readUInt() tim.posx = f.readUShort() tim.posy = f.readUShort() tim.width = f.readUShort() tim.height = f.readUShort() if tim.bpp == 4: tim.width *= 4 elif tim.bpp == 8: tim.width *= 2 elif tim.bpp == 24: tim.width //= 1.5 tim.dataoff = f.tell() common.logDebug("TIM bpp", tim.bpp, "width", tim.width, "height", tim.height, "size", tim.size) pixelnum = forcesize if forcesize != 0 else (((tim.size - 12) * 8) // tim.bpp) readTIMData(f, tim, pixelnum) return tim
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 readSampledCurve(f, startoff, type, samplefunc, add=0): startframe = f.readUShort() other = f.readUShort() endframe = other & 0b111111111111 width = (other >> 12) & 0b11 lograte = (other >> 14) & 0b11 numsamples = 31 # int((endframe - startframe) / math.pow(2, lograte)) samplesoff = f.readUInt() + startoff common.logDebug(" sampled curve", type, "startframe", startframe, "endframe", endframe, "width", width, "lograte", lograte, "numsamples", numsamples, "samplesoff", common.toHex(samplesoff)) savepos = f.tell() f.seek(samplesoff) for i in range(numsamples): samplefunc(f, width, add) f.seek(savepos)
def extractEXE(binrange, readfunc=common.detectEncodedString, encoding="shift_jis", exein="", exefile="data/exe_output.txt", writepos=False): common.logMessage("Extracting EXE to", exefile, "...") if type(binrange) == tuple: binrange = [binrange] strings, positions = common.extractBinaryStrings(exein, binrange, readfunc, encoding) with codecs.open(exefile, "w", "utf-8") as out: for i in range(len(strings)): if writepos: out.write(common.toHex(positions[i][0]) + "!") out.write(strings[i] + "=\n") common.logMessage("Done! Extracted", len(strings), "lines")
def compress(data, type): with common.Stream() as out: length = len(data) out.writeByte(type.value) out.writeByte(length & 0xFF) out.writeByte(length >> 8 & 0xFF) out.writeByte(length >> 16 & 0xFF) if type == CompressionType.LZ10: out.write(compression.compressLZ10(data)) elif type == CompressionType.LZ11: out.write(compression.compressLZ11(data)) elif type == CompressionType.Huff4: out.write(compression.compressHuffman(data, 4)) elif type == CompressionType.Huff8: out.write(compression.compressHuffman(data, 8)) else: common.logError("Unsupported compression type", common.toHex(type)) out.write(data) out.seek(0) return out.read()
def extractBinaryStrings(elf, foundstrings, infile, func, encoding="shift_jis", elfsections=[".rodata"]): with common.Stream(infile, "rb") as f: for sectionname in elfsections: rodata = elf.sectionsdict[sectionname] f.seek(rodata.offset) while f.tell() < rodata.offset + rodata.size: pos = f.tell() check = func(f, encoding) if check != "": if check not in foundstrings: common.logDebug("Found string at", common.toHex(pos), check) foundstrings.append(check) pos = f.tell() - 1 f.seek(pos + 1) return foundstrings
def run(): infolder = "data/extract_NFP/SPC.NFP/" outfile = "data/spc_output.txt" common.logMessage("Extracting SPC to", outfile, "...") with codecs.open(outfile, "w", "utf-8") as out: files = common.getFiles(infolder, ".SPC") for file in common.showProgress(files): common.logDebug("Processing", file, "...") first = True with common.Stream(infolder + file, "rb") as f: f.seek(12) # "SCRP" + filesize + "CODE" codesize = f.readUInt() if codesize > 10: f.seek(6, 1) while f.tell() < 16 + codesize - 2: pos = f.tell() byte = f.readByte() if byte == 0x10: try: sjis = game.readShiftJIS(f) if sjis != "": common.logDebug("Found string at", pos, "with length", len(sjis)) if first: first = False out.write("!FILE:" + file + "\n") out.write(sjis + "=\n") f.seek(9, 1) except UnicodeDecodeError: common.logError("UnicodeDecodeError") elif byte == 0x15: f.seek(1, 1) bytelen = f.readByte() f.seek(8 * bytelen, 1) elif byte in game.spccodes: f.seek(game.spccodes[byte], 1) else: common.logDebug("Unknown byte", common.toHex(byte), "at", pos) common.logMessage("Done! Extracted", len(files), "files")
def run(): infolder = "data/extract_NFP/NFP2D.NFP/" altinfolder = "data/work_YCE/" outfolder = "data/out_YCE/" outfile = "data/yce_data.txt" common.makeFolder(outfolder) common.logMessage("Extracting YCE to", outfolder, "...") with open(outfile, "w") as yce: files = common.getFiles(infolder, ".YCE") for file in common.showProgress(files): common.logDebug("Processing", file, "...") filepath = infolder + file if os.path.isfile(altinfolder + file): filepath = altinfolder + file with common.Stream(filepath, "rb") as f: # Read header f.seek(8) size = f.readUInt() # size - header (7) f.seek(4, 1) # Always 0 f.seek(4, 1) # Always 24 f.readUInt() # Animation data offset f.readUInt() # ? num = f.readUInt() # Number of images images = [] for i in range(num): img = game.YCETexture() img.offset = f.readUInt() + 24 # Image data offset images.append(img) for img in images: common.logDebug("Reading image at offset", img.offset, "...") f.seek(img.offset) img.size = f.readUInt() # Image data size constant = f.readUInt() # 0x1C if constant != 0x1C: common.logDebug("Constant is not 0x1C!", common.toHex(constant)) img.oamnum = f.readUInt() # Number of OAMs img.oamsize = f.readUInt() # OAM data size img.tilesize = f.readUInt() # Tile data size img.paloffset = f.readUInt( ) + img.offset # Palette data offset (relative to offset) constant = f.readUInt() # 0x01 if constant != 0x01: common.logDebug("Constant 2 is not 0x01!", common.toHex(constant)) common.logDebug("size:", img.size, "oamnum:", img.oamnum, "oamsize:", img.oamsize) common.logDebug("tilesize:", img.tilesize, "paloffset:", img.paloffset) img.oams = [] for j in range(img.oamnum): oam = game.OAM() oam.x = f.readShort( ) # X position of the cell (-128 to 127) oam.y = f.readShort( ) # Y position of the cell (-256 to 255) for x in range(8): unkbyte = f.readByte() if unkbyte != 0x00: common.logDebug("unkbyte", x, "is not 0x00!", common.toHex(unkbyte)) shape = f.readByte() # NCER OBJ Shape size = f.readByte() # NCER OBJ Size for x in range(2): unkbyte = f.readByte() if unkbyte != 0x00: common.logDebug("unkbyte2", x, "is not 0x00!", common.toHex(unkbyte)) oam.offset = f.readUInt() # Table from http://www.romhacking.net/documents/%5B469%5Dnds_formats.htm#NCER if shape == 0: if size == 0: tilesize = (8, 8) elif size == 1: tilesize = (16, 16) elif size == 2: tilesize = (32, 32) elif size == 3: tilesize = (64, 64) elif shape == 1: if size == 0: tilesize = (16, 8) elif size == 1: tilesize = (32, 8) elif size == 2: tilesize = (32, 16) elif size == 3: tilesize = (64, 32) elif shape == 2: if size == 0: tilesize = (8, 16) elif size == 1: tilesize = (8, 32) elif size == 2: tilesize = (16, 32) elif size == 3: tilesize = (32, 64) oam.width = tilesize[0] oam.height = tilesize[1] img.oams.append(oam) # Calculate width and height minx = miny = 512 maxx = maxy = -512 for oam in img.oams: minx = min(minx, oam.x) miny = min(miny, oam.y) maxx = max(maxx, oam.x + oam.width) maxy = max(maxy, oam.y + oam.height) img.width = maxx - minx img.height = maxy - miny for oam in img.oams: oam.x -= minx oam.y -= miny common.logDebug("width:", img.width, "height:", img.height) common.logDebug("oams:", img.oams) # Create image width = height = 0 for img in images: width = max(width, img.width + 40) height += max(img.height, 10) outimg = Image.new("RGBA", (width, height), (0, 0, 0, 0)) pixels = outimg.load() # Read images currheight = 0 for img in images: # Load palette palette = [] f.seek(img.paloffset) paldata = f.read(32) for i in range(0, 32, 2): p = struct.unpack("<H", paldata[i:i + 2])[0] palette.append(common.readPalette(p)) # Read tile data f.seek(img.offset + img.oamsize) tiledata = f.read(img.tilesize) for oam in img.oams: x = oam.offset * 64 for i in range(oam.height // 8): for j in range(oam.width // 8): for i2 in range(8): for j2 in range(8): index = (tiledata[x // 2] >> ((x % 2) << 2)) & 0x0f pixels[oam.x + j * 8 + j2, currheight + oam.y + i * 8 + i2] = palette[index] x += 1 # Draw palette pixels = common.drawPalette(pixels, palette, width - 40, currheight) currheight += max(img.height, 10) outimg.save(outfolder + file.replace(".YCE", ".png"), "PNG") yce.write( file + "=" + base64.standard_b64encode(pickle.dumps(images)).decode() + "\n") common.logMessage("Done! Extracted", len(files), "files")
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 readCPK(file): with common.Stream(file, "rb") as f: magic = f.readString(4) if magic != 'CPK ': common.logError("Wrong magic:", magic) return None cpk = CPK() utfoffset = f.tell() utfpacket, utfsize, encrypted = readUTFData(f) cpak = CPKFileEntry() cpak.filename = "CPK_HDR" cpak.fileoffsetpos = f.tell() + 0x10 cpak.filesize = utfsize cpak.encrypted = encrypted cpak.filetype = "CPK" utf = readUTF(utfpacket, utfoffset, True) if utf is None: common.logError("Error reading first UTF") return None cpak.utf = utf cpk.filetable.append(cpak) tocoffset, tocoffsetpos = utf.getColumnData(0, "TocOffset", UTFStructTypes.DATA_TYPE_UINT64) etocoffset, etocoffsetpos = utf.getColumnData(0, "EtocOffset", UTFStructTypes.DATA_TYPE_UINT64) itocoffset, itocoffsetpos = utf.getColumnData(0, "ItocOffset", UTFStructTypes.DATA_TYPE_UINT64) gtocoffset, gtocoffsetpos = utf.getColumnData(0, "GtocOffset", UTFStructTypes.DATA_TYPE_UINT64) contentoffset, contentoffsetpos = utf.getColumnData(0, "ContentOffset", UTFStructTypes.DATA_TYPE_UINT64) cpk.filetable.append(CPKFileEntry.createEntry("CONTENT_OFFSET", contentoffset, UTFStructTypes.DATA_TYPE_UINT64, contentoffsetpos, "CPK", "CONTENT", False)) files, _ = utf.getColumnData(0, "Files", UTFStructTypes.DATA_TYPE_UINT32) cpk.align, _ = utf.getColumnData(0, "Align", UTFStructTypes.DATA_TYPE_UINT16) common.logDebug("tocoffset", common.toHex(tocoffset), "tocoffsetpos", common.toHex(tocoffsetpos)) common.logDebug("etocoffset", common.toHex(etocoffset), "etocoffsetpos", common.toHex(etocoffsetpos)) common.logDebug("itocoffset", common.toHex(itocoffset), "itocoffsetpos", common.toHex(itocoffsetpos)) common.logDebug("gtocoffset", common.toHex(gtocoffset), "gtocoffsetpos", common.toHex(gtocoffsetpos)) common.logDebug("contentoffset", common.toHex(contentoffset), "contentoffsetpos", common.toHex(contentoffsetpos)) common.logDebug("files", common.toHex(files), "align", common.toHex(cpk.align)) if tocoffset != 0xffffffffffffffff: cpk.filetable.append(CPKFileEntry.createEntry("TOC_HDR", tocoffset, UTFStructTypes.DATA_TYPE_UINT64, tocoffsetpos, "CPK", "HDR", False)) readTOC(f, cpk, tocoffset, contentoffset) if etocoffset != 0xffffffffffffffff: cpk.filetable.append(CPKFileEntry.createEntry("ETOC_HDR", etocoffset, UTFStructTypes.DATA_TYPE_UINT64, etocoffsetpos, "CPK", "HDR", False)) readETOC(f, cpk, etocoffset) if itocoffset != 0xffffffffffffffff: cpk.filetable.append(CPKFileEntry.createEntry("ITOC_HDR", itocoffset, UTFStructTypes.DATA_TYPE_UINT64, itocoffsetpos, "CPK", "HDR", False)) readITOC(f, cpk, itocoffset, contentoffset, cpk.align) if gtocoffset != 0xffffffffffffffff: cpk.filetable.append(CPKFileEntry.createEntry("GTOC_HDR", gtocoffset, UTFStructTypes.DATA_TYPE_UINT64, gtocoffsetpos, "CPK", "HDR", False)) readGTOC(f, cpk, gtocoffset) return cpk
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(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))