def drawTIM(outfile, tim, transp=False, forcepal=-1, allpalettes=False, nopal=False): if tim.width == 0 or tim.height == 0: return clutwidth = clutheight = 0 if tim.bpp == 4 or tim.bpp == 8: clut = forcepal if forcepal != -1 else getUniqueCLUT(tim, transp) if not nopal: clutwidth = 40 clutheight = 5 * (len(tim.cluts[clut]) // 8) if allpalettes: clutheight *= len(tim.cluts) img = Image.new("RGBA", (tim.width + clutwidth, max(tim.height, clutheight)), (0, 0, 0, 0)) pixels = img.load() x = 0 for i in range(tim.height): for j in range(tim.width): if x >= len(tim.data): common.logWarning("Out of TIM data") break if tim.bpp == 4 or tim.bpp == 8: if len(tim.cluts[clut]) > tim.data[x]: color = tim.cluts[clut][tim.data[x]] else: common.logWarning("Index", tim.data[x], "not in CLUT") color = (0, 0, 0, 0) else: color = tim.data[x] if not transp: color = (color[0], color[1], color[2], 255) pixels[j, i] = color x += 1 if (tim.bpp == 4 or tim.bpp == 8) and not nopal: if allpalettes: for i in range(len(tim.cluts)): pixels = common.drawPalette(pixels, tim.cluts[i], tim.width, i * (clutheight // len(tim.cluts)), transp) else: pixels = common.drawPalette(pixels, tim.cluts[clut], tim.width, 0, transp) if outfile == "": return img img.save(outfile, "PNG")
def drawGIM(outfile, gim): width = 0 height = 0 palette = False if isinstance(gim, GIM): for image in gim.images: width = max(width, image.width) if len(image.palette) > 0: palette = True palsize = 5 * (len(image.palette) // 8) height += max(image.height, palsize) else: height += image.height else: width = gim.width height = gim.height img = Image.new("RGBA", (width + (40 if palette else 0), height), (0, 0, 0, 0)) pixels = img.load() currheight = 0 if isinstance(gim, GIM): for image in gim.images: i = 0 if image.tiled == 0x00: for y in range(image.height): for x in range(image.width): drawGIMPixel(image, pixels, x, currheight + y, i) i += 1 else: for blocky in range(image.blockedheight // image.tileheight): for blockx in range(image.blockedwidth // image.tilewidth): for y in range(image.tileheight): for x in range(image.tilewidth): pixelx = blockx * image.tilewidth + x pixely = currheight + blocky * image.tileheight + y if pixelx >= image.width or pixely >= currheight + image.height: i += 1 continue drawGIMPixel(image, pixels, pixelx, pixely, i) i += 1 if len(image.palette) > 0: pixels = common.drawPalette(pixels, image.palette, image.width, currheight) palsize = 5 * (len(image.palette) // 8) currheight += max(image.height, palsize) else: currheight += image.height else: i = 0 for y in range(gim.height): for x in range(gim.width): pixels[x, gim.height - 1 - y] = gim.colors[i] i += 1 img.save(outfile, "PNG")
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 drawMappedImage(width, height, mapdata, tiledata, paldata, tilesize=8, bpp=4): palnum = len(paldata) // 32 img = Image.new("RGBA", (width + 40, max(height, palnum * 10)), (0, 0, 0, 0)) pixels = img.load() # Maps maps = [] for i in range(0, len(mapdata), 2): map = struct.unpack("<h", mapdata[i:i+2])[0] pal = (map >> 12) & 0xF xflip = (map >> 10) & 1 yflip = (map >> 11) & 1 tile = map & 0x3FF maps.append((pal, xflip, yflip, tile)) common.logDebug("Loaded", len(maps), "maps") # Tiles tiles = [] for i in range(len(tiledata) // (32 if bpp == 4 else 64)): singletile = [] for j in range(tilesize * tilesize): x = i * (tilesize * tilesize) + j if bpp == 4: index = (tiledata[x // 2] >> ((x % 2) << 2)) & 0x0f else: index = tiledata[x] singletile.append(index) tiles.append(singletile) common.logDebug("Loaded", len(tiles), "tiles") # Palette palettes = readPaletteData(paldata) pals = [] for palette in palettes: pals += palette # Draw the image i = j = 0 for map in maps: try: pal = map[0] xflip = map[1] yflip = map[2] tile = tiles[map[3]] for i2 in range(tilesize): for j2 in range(tilesize): pixels[j + j2, i + i2] = pals[16 * pal + tile[i2 * tilesize + j2]] # Very inefficient way to flip pixels if xflip or yflip: sub = img.crop(box=(j, i, j + tilesize, i + tilesize)) if yflip: sub = ImageOps.flip(sub) if xflip: sub = ImageOps.mirror(sub) img.paste(sub, box=(j, i)) except (KeyError, IndexError): common.logWarning("Tile or palette", str(map), "not found") j += tilesize if j >= width: j = 0 i += tilesize # Draw palette if len(palettes) > 0: for i in range(len(palettes)): pixels = common.drawPalette(pixels, palettes[i], width, i * 10) return img
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")