Guest User

Untitled

a guest
Apr 12th, 2020
588
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 21.79 KB | None | 0 0
  1. #!/usr/bin/env python3.1
  2. """rby-sprite-extract.py - extract Pokemon sprites from a ROM image.
  3.  
  4. BUGS
  5.  
  6. * --color=gray doesn't work
  7. * formats don't work very well with extract_all
  8.  
  9.  
  10. """
  11.  
  12. import struct
  13. import io
  14. import sys
  15. import os, os.path
  16. import itertools
  17. import argparse
  18. from os import SEEK_CUR
  19. from struct import pack, unpack
  20. from array import array
  21. from subprocess import Popen, PIPE
  22. from collections import namedtuple, OrderedDict as odict
  23.  
  24. def bitflip(x, n):
  25.     r = 0
  26.     while n:
  27.         r = (r << 1) | (x & 1)
  28.         x >>= 1
  29.         n -= 1
  30.     return r
  31.  
  32. class Decompressor:
  33.     """ Pokemon sprite decompression for Gen I.
  34.  
  35.    The overall structure of this decompressor was guided by
  36.    <http://www.magicstone.de/rhwiki/article/Grafikkomprimierung_PKMN_RGBY>,
  37.    while the nitty-gritty was filled in by
  38.    <http://www.upokecenter.com/projects/rbgfx.c>.
  39.  
  40.    """
  41.     table1 = [(2 << i) - 1 for i in range(16)]
  42.  
  43.     #table2 = [
  44.     #    [0x01, 0x32, 0x76, 0x45, 0xfe, 0xcd, 0x89, 0xba],
  45.     #    [0xfe, 0xcd, 0x89, 0xba, 0x01, 0x32, 0x76, 0x45],
  46.     #    [0x08, 0xc4, 0xe6, 0x2a, 0xf7, 0x3b, 0x19, 0xd5],
  47.     #    [0xf7, 0x3b, 0x19, 0xd5, 0x08, 0xc4, 0xe6, 0x2a],
  48.     #]
  49.     table2 = [
  50.         [0, 1, 3, 2, 7, 6, 4, 5, 0xf, 0xe, 0xc, 0xd, 8, 9, 0xb, 0xa],
  51.         [0xf, 0xe, 0xc, 0xd, 8, 9, 0xb, 0xa, 0, 1, 3, 2, 7, 6, 4, 5],
  52.     ]
  53.  
  54.     table3 = [bitflip(i, 4) for i in range(16)]
  55.  
  56.     tilesize = 8
  57.  
  58.     def __init__(self, f, mirror=False):
  59.         self.bs = fbitstream(f)
  60.  
  61.         self.sizex = self._readint(4) * self.tilesize
  62.         self.sizey = self._readint(4)
  63.  
  64.         self.size = self.sizex * self.sizey
  65.  
  66.         self.ramorder = next(self.bs)
  67.  
  68.         self.mirror = mirror
  69.  
  70.         self.data = None
  71.  
  72.     def decompress(self):
  73.         rams = [[], []]
  74.  
  75.         r1 = self.ramorder
  76.         r2 = self.ramorder ^ 1
  77.  
  78.         self._fillram(rams[r1])
  79.         mode = self._readbit()
  80.         if mode == 1:
  81.             mode = 1 + self._readbit()
  82.         self._fillram(rams[r2])
  83.  
  84.         rams[0] = bytearray(bitgroups_to_bytes(rams[0]))
  85.         rams[1] = bytearray(bitgroups_to_bytes(rams[1]))
  86.  
  87.         if mode == 0:
  88.             self._thing1(rams[0])
  89.             self._thing1(rams[1])
  90.         elif mode == 1:
  91.             self._thing1(rams[r1])
  92.             self._thing2(rams[r1], rams[r2])
  93.         elif mode == 2:
  94.             self._thing1(rams[r2], mirror=False)
  95.             self._thing1(rams[r1])
  96.             self._thing2(rams[r1], rams[r2])
  97.  
  98.         data = []
  99.         for a, b in zip(bitstream(rams[0]), bitstream(rams[1])):
  100.             data.append(a | (b << 1))
  101.  
  102.         self.data = bitgroups_to_bytes(data)
  103.  
  104.     def untile(self, mirror=None):
  105.         if mirror is None:
  106.             mirror = self.mirror
  107.  
  108.         ram = self.data
  109.         out = []
  110.         sizey = self.sizey * self.tilesize
  111.         sizex = self.sizex // self.tilesize
  112.         if not mirror:
  113.             for y in range(sizey):
  114.                 for x in range(sizex):
  115.                     k = (y + sizey * x) * 2
  116.                     out.append(ram[k])
  117.                     out.append(ram[k+1])
  118.         else:
  119.             for y in range(sizey):
  120.                 for x in reversed(range(sizex)):
  121.                     k = (y + sizey * x) * 2
  122.                     out.append(ram[k+1])
  123.                     out.append(ram[k])
  124.         return bytes(out)
  125.  
  126.     def to_image(self):
  127.         ram = self.untile()
  128.         bs = bitstream(ram)
  129.         data = []
  130.         try:
  131.             while 1:
  132.                 data.append(readint(bs, 2))
  133.         except StopIteration:
  134.             pass
  135.  
  136.         img = Image((self.sizex, self.sizey * self.tilesize), data)
  137.         return img
  138.  
  139.     def _fillram(self, ram):
  140.         mode = ['rle', 'data'][self._readbit()]
  141.         size = self.size * 4
  142.         while len(ram) < size:
  143.             if mode == 'rle':
  144.                 self._read_rle_chunk(ram)
  145.                 mode = 'data'
  146.             elif mode == 'data':
  147.                 self._read_data_chunk(ram, size)
  148.                 mode = 'rle'
  149.             else:
  150.                 assert False
  151.  
  152.         if len(ram) > size:
  153.             raise ValueError(size, len(ram))
  154.         ram[:] = self._deinterlace_bitgroups(ram)
  155.  
  156.     def _read_rle_chunk(self, ram):
  157.         """read a run-length-encoded chunk of zeros from self.bs into `ram`"""
  158.  
  159.         # count bits until we find a 0
  160.         i = 0
  161.         while self._readbit():
  162.             i += 1
  163.  
  164.         n = self.table1[i]
  165.         a = self._readint(i+1)
  166.         n += a
  167.  
  168.         #print(i, a, n)
  169.  
  170.         for i in range(n):
  171.             ram.append(0)
  172.  
  173.     def _read_data_chunk(self, ram, size):
  174.         """Read pairs of bits into `ram`"""
  175.         while 1:
  176.             bitgroup = self._readint(2)
  177.             # if we encounter a pair of 0 bits, we're done
  178.             if bitgroup == 0:
  179.                 break
  180.             ram.append(bitgroup)
  181.  
  182.             # stop once we have enough data.
  183.             # only matters for a handful of pokemon.
  184.             if size <= len(ram):
  185.                 break
  186.  
  187.     def _thing1(self, ram, mirror=None):
  188.         if mirror is None:
  189.             mirror = self.mirror
  190.  
  191.         for x in range(self.sizex):
  192.             bit = 0
  193.             for y in range(self.sizey):
  194.                 i = y*self.sizex + x
  195.                 a = ram[i] >> 4 & 0xf
  196.                 b = ram[i] & 0xf
  197.  
  198.                 a = self.table2[bit][a]
  199.                 bit = a & 1
  200.                 if mirror:
  201.                     a = self.table3[a]
  202.  
  203.                 b = self.table2[bit][b]
  204.                 bit = b & 1
  205.                 if mirror:
  206.                     b = self.table3[b]
  207.  
  208.                 ram[i] = (a << 4) | b
  209.  
  210.     def _thing2(self, ram1, ram2, mirror=None):
  211.         if mirror is None:
  212.             mirror = self.mirror
  213.  
  214.         for i in range(len(ram2)):
  215.             if mirror:
  216.                 a = ram2[i] >> 4
  217.                 b = ram2[i] & 0xf
  218.                 a = self.table3[a]
  219.                 b = self.table3[b]
  220.                 ram2[i] = a << 4 | b
  221.  
  222.             ram2[i] ^= ram1[i]
  223.  
  224.     def _deinterlace_bitgroups(self, bits):
  225.         l = []
  226.         for y in range(self.sizey):
  227.             for x in range(self.sizex):
  228.                 i = 4 * y * self.sizex + x
  229.                 for j in range(4):
  230.                     l.append(bits[i])
  231.                     i += self.sizex
  232.         return l
  233.  
  234.     def _readbit(self):
  235.         """Read a single bit."""
  236.         return next(self.bs)
  237.  
  238.     def _readint(self, count):
  239.         """Read an integer `count` bits in length."""
  240.         return readint(self.bs, count)
  241.  
  242. def fbitstream(f):
  243.     while 1:
  244.         char = f.read(1)
  245.         if not char:
  246.             break
  247.         byte = char[0]
  248.  
  249.         yield (byte >> 7) & 1
  250.         yield (byte >> 6) & 1
  251.         yield (byte >> 5) & 1
  252.         yield (byte >> 4) & 1
  253.         yield (byte >> 3) & 1
  254.         yield (byte >> 2) & 1
  255.         yield (byte >> 1) & 1
  256.         yield byte & 1
  257.  
  258. def bitstream(b):
  259.     for byte in b:
  260.         yield (byte >> 7) & 1
  261.         yield (byte >> 6) & 1
  262.         yield (byte >> 5) & 1
  263.         yield (byte >> 4) & 1
  264.         yield (byte >> 3) & 1
  265.         yield (byte >> 2) & 1
  266.         yield (byte >> 1) & 1
  267.         yield byte & 1
  268.  
  269. def readint(bs, count):
  270.     """Read an integer `count` bits long from `bs`."""
  271.     n = 0
  272.     while count:
  273.         n <<= 1
  274.         n |= next(bs)
  275.         count -= 1
  276.     return n
  277.  
  278. def bitgroups_to_bytes(bits):
  279.     l = []
  280.     for i in range(0, len(bits)-3, 4):
  281.         n = ((bits[i] << 6)
  282.              | (bits[i+1] << 4)
  283.              | (bits[i+2] << 2)
  284.              | (bits[i+3]))
  285.         l.append(n)
  286.     return bytes(l)
  287.  
  288. def decompress(f, offset=None, mirror=False):
  289.     if offset is not None:
  290.         f.seek(offset)
  291.  
  292.     dcmp = Decompressor(f, mirror=mirror)
  293.     dcmp.decompress()
  294.     img = dcmp.to_image()
  295.     return img
  296.  
  297.  
  298. class Image:
  299.     def __init__(self, size, data=None):
  300.         self.sizex, self.sizey = size
  301.         self.data = data
  302.         self.palette = None
  303.  
  304.     def save_format(self, format, *args, **kw):
  305.         formats = {
  306.             'png': self.save_png,
  307.             'pnm': self.save_pnm,
  308.             'ppm': self.save_ppm,
  309.             'pgm': self.save_pgm,
  310.             'boxes': self.save_boxes,
  311.             'xterm': self.save_xterm,
  312.         }
  313.         return formats[format](*args, **kw)
  314.  
  315.     # PIL is not yet available for python 3, so we'll write out a ppm(5) or
  316.     # pgm(5) file and let netpbm(1) sort it out.
  317.     def save_pam(self, out):
  318.         def write(*args, **kw):
  319.             print(*args, file=out, **kw)
  320.         write("P7")
  321.         write("HEIGHT", self.sizey)
  322.         write("WIDTH", self.sizex)
  323.         write("MAXVAL", 3)
  324.         write("DEPTH", 1)
  325.         write("TUPLTYPE", "GRAYSCALE")
  326.         write("ENDHDR")
  327.         i = 0
  328.         out.flush()
  329.         out.buffer.write(bytes(self.data))
  330.  
  331.     def save_pgm(self, out):
  332.         if 'b' in out.mode:
  333.             return self._save_pgm(out)
  334.         else:
  335.             return self._save_plain_pgm(out)
  336.  
  337.     def _save_pgm(self, out):
  338.         out.write("P5\n{:d} {:d}\n{:d}\n"
  339.                       .format(self.sizex, self.sizey, 3)
  340.                       .encode())
  341.         for byte in self.data:
  342.             out.write(pack("<B", (3 - byte)))
  343.  
  344.     def _save_plain_pgm(self, out):
  345.         def write(*args, **kw):
  346.             print(*args, file=out, **kw)
  347.         write("P2")
  348.         write(self.sizex, self.sizey) # width, height
  349.         write(3) # maxval
  350.         i = 0
  351.         width = self.sizex
  352.         for i in range(len(self.data)):
  353.             write(3 - self.data[i], end=" ")
  354.             i += 1
  355.             if i % width == 0:
  356.                 write()
  357.  
  358.     def save_ppm(self, out, *args, **kw):
  359.         if 'b' in out.mode:
  360.             return self._save_ppm(out, *args, **kw)
  361.         else:
  362.             return self._save_plain_ppm(out, *args, **kw)
  363.  
  364.     def _save_ppm(self, out, palette=None):
  365.         if palette is None:
  366.             palette = self.palette
  367.         out.write("P6\n{:d} {:d}\n{:d}\n"
  368.                       .format(self.sizex, self.sizey, 31)
  369.                       .encode())
  370.         for byte in self.data:
  371.             out.write(pack("<BBB", *palette[byte]))
  372.  
  373.     def _save_plain_ppm(self, out, palette=None):
  374.         if palette is None:
  375.             palette = self.palette
  376.  
  377.         def write(*args, **kw):
  378.             print(*args, file=out, **kw)
  379.         write("P3") # magic number
  380.         write(self.sizex, self.sizey) # width, height
  381.         write(31) # maxval. XXX don't hardcode this
  382.  
  383.         width = self.sizex
  384.         for i, byte in enumerate(self.data):
  385.             write("{:2d} {:2d} {:2d}".format(*palette[byte]), end="  ")
  386.             if (i + 1) % width == 0:
  387.                 write()
  388.  
  389.     def save_pnm(self, *args, palette=None, **kw):
  390.         if palette is None:
  391.             palette = self.palette
  392.  
  393.         if palette:
  394.             return self.save_ppm(*args, palette=palette, **kw)
  395.         else:
  396.             return self.save_pgm(*args, **kw)
  397.  
  398.     def save_png(self, out, palette=None):
  399.         if palette is None:
  400.             palette = self.palette
  401.         p = Popen("pnmtopng", stdin=PIPE, stdout=out)
  402.         self.save_pnm(p.stdin, palette=palette)
  403.         p.stdin.close()
  404.         p.wait()
  405.  
  406.     def save_boxes(self, out, palette=None):
  407.         if palette is None:
  408.             palette = self.palette
  409.         char_palette = "\u00a0\u2591\u2592\u2593\u2588"
  410.  
  411.         def write(*args, **kw):
  412.             print(*args, file=out, **kw)
  413.  
  414.         width = self.sizex
  415.         for i, byte in enumerate(self.data):
  416.             write(char_palette[byte] * 2, end="")
  417.             if (i + 1) % width == 0:
  418.                 write()
  419.  
  420.     def save_xterm(self, out, palette=None):
  421.         if palette is None:
  422.             palette = self.palette
  423.  
  424.         def write(*args, **kw):
  425.             print(*args, file=out, **kw)
  426.  
  427.         if palette:
  428.             colors = []
  429.             for i, (r, g, b) in enumerate(palette):
  430.                 r = round(r / 31 * 5)
  431.                 g = round(g / 31 * 5)
  432.                 b = round(b / 31 * 5)
  433.                 color = 16 + r * 36 + g * 6 + b
  434.                 colors.append(color)
  435.  
  436.                 #r = round(r / 31 * 255)
  437.                 #g = round(g / 31 * 255)
  438.                 #b = round(b / 31 * 255)
  439.                 #write("\x1b]4;%d;rgb:%2.2x/%2.2x/%2.2x\x1b\\" % (i + 16, r, g, b), end="")
  440.             palette = colors
  441.         else:
  442.             palette = [231, 248, 240, 232]
  443.  
  444.         width = self.sizex
  445.         for i, byte in enumerate(self.data):
  446.             # xterm color escapes
  447.             # set background color
  448.             write("\033[48;5;{color}m".format(color=palette[byte]), end="")
  449.             #write("\033[48;5;{color}m".format(color=byte + 16), end="")
  450.             write("\N{NO-BREAK SPACE}" * 2, end="")
  451.             if (i + 1) % width == 0:
  452.                 write()
  453.         write("\033[0m")
  454.  
  455. class Palette:
  456.     def __init__(self, colors):
  457.         self.colors = colors
  458.  
  459.     @classmethod
  460.     def fromfile(cls, f):
  461.         colors = unpack("<HHHH", f.read(8))
  462.         colors = [cls.rgb15_to_rgb(x) for x in colors]
  463.         return cls(colors)
  464.  
  465.     @staticmethod
  466.     def rgb15_to_rgb(v):
  467.         r = v & 31
  468.         g = (v >> 5) & 31
  469.         b = (v >> 10) & 31
  470.         return (r, g, b)
  471.  
  472.     def __getitem__(self, i):
  473.         return self.colors[i]
  474.  
  475.  
  476. Offsets = namedtuple("Offsets",
  477.     ("base_stats base_stats_mew "
  478.      "pokedex_order pokedex_order_length "
  479.      "palette_map palettes "))
  480.  
  481. class Game:
  482.     def __init__(self, rom, munge=None):
  483.         self.rom = rom
  484.         self._read_info()
  485.         self._find_offsets()
  486.  
  487.         self.internal_ids = {}
  488.         self._read_internal_ids()
  489.  
  490.         self._read_palette_map()
  491.         if munge is not None:
  492.             self._read_palettes(munge)
  493.         else:
  494.             self._read_palettes()
  495.  
  496.     def _read_info(self):
  497.         rom = self.rom
  498.  
  499.         rom.seek(0x134)
  500.         title = rom.read(15).rstrip(b"\x00")
  501.  
  502.         rom.seek(0x134 + 22)
  503.         country = rom.read(1)[0]
  504.  
  505.         rom.seek(0x134 + 18)
  506.         self.has_sgb = rom.read(1) == b"\x03"
  507.  
  508.         rom.seek(0x134 + 15)
  509.         self.has_gbc = rom.read(1) == b"\x80"
  510.  
  511.         if title == b"POKEMON RED":
  512.             if country == 0:
  513.                 self.version = 'red.jp'
  514.             else:
  515.                 self.version = 'red'
  516.         elif title == b"POKEMON GREEN":
  517.             self.version = 'green.jp'
  518.         elif title == b"POKEMON BLUE":
  519.             self.version = 'blue'
  520.         elif title == b"POKEMON YELLOW":
  521.             self.version = 'yellow'
  522.         else:
  523.             raise ValueError("Unknown game", title)
  524.  
  525.         self.title = title
  526.         self.country = country
  527.  
  528.         self.colors = ['gray']
  529.         if self.has_sgb:
  530.             self.colors.append('sgb')
  531.         if self.has_gbc:
  532.             self.colors.append('gbc')
  533.  
  534.     def _find(self, pat):
  535.         self.rom.seek(0)
  536.         for bank in itertools.count():
  537.             data = self.rom.read(0x4000)
  538.             if not data:
  539.                 break
  540.             index = data.find(pat)
  541.             if index != -1:
  542.                 return bank * 0x4000 + index
  543.  
  544.         raise IndexError
  545.  
  546.     def _find_offsets(self):
  547.         # search strings
  548.         bulbasaur_stats = pack("<BBBBBB", 1, 0x2d, 0x31, 0x31, 0x2d, 0x41)
  549.         mew_stats = pack("<BBBBBB", 151, 100, 100, 100, 100, 100)
  550.         palette_map = b"\x10\x16\x16\x16\x12\x12\x12\x13\x13\x13"
  551.         pokedex_order = b"\x70\x73\x20\x23\x15\x64\x22\x50"
  552.  
  553.         offsets = {}
  554.         offsets['base_stats'] = self._find(bulbasaur_stats)
  555.         offsets['base_stats_mew'] = self._find(mew_stats)
  556.         offsets['pokedex_order'] = self._find(pokedex_order)
  557.         offsets['palette_map'] = self._find(palette_map)
  558.         offsets['palettes'] = offsets['palette_map'] + 152
  559.  
  560.         if offsets['base_stats_mew'] > 0x8000:
  561.             offsets['base_stats_mew'] = None
  562.  
  563.         self.offsets = Offsets(pokedex_order_length=0xbe, **offsets)
  564.  
  565.     def _read_internal_ids(self):
  566.         self.rom.seek(self.offsets.pokedex_order)
  567.         order = array("B", self.rom.read(self.offsets.pokedex_order_length))
  568.         for i in range(1, 151+1):
  569.             self.internal_ids[i] = order.index(i) + 1
  570.  
  571.     def _read_palette_map(self):
  572.         self.rom.seek(self.offsets.palette_map)
  573.         self.palette_map = list(self.rom.read(152))
  574.  
  575.     def _read_palettes(self, munge=True):
  576.         self.palettes = odict()
  577.  
  578.         self.rom.seek(self.offsets.palettes)
  579.         for color in self.colors:
  580.             if color == 'gray':
  581.                 continue
  582.             palettes = self.palettes[color] = []
  583.             for i in range(40):
  584.                 palettes.append(Palette.fromfile(self.rom))
  585.                 if color == 'sgb' and munge:
  586.                     palettes[i].colors[0] = (31, 31, 31)
  587.  
  588.     def get_palette(self, poke, color):
  589.         return self.palettes[color][self.palette_map[poke]]
  590.  
  591.     def get_bank(self, poke):
  592.         internal_id = self.internal_ids[poke]
  593.         if self.offsets.base_stats_mew is not None and internal_id == 0x15:
  594.             return 0x1
  595.         elif internal_id == 0xb6:
  596.             return 0xb
  597.         elif internal_id < 0x1f:
  598.             return 0x9
  599.         elif internal_id < 0x4a:
  600.             return 0xa
  601.         elif self.version in ('red.jp', 'green.jp') and internal_id < 0x75:
  602.             return 0xb
  603.         elif internal_id < 0x74:
  604.             return 0xb
  605.         elif self.version in ('red.jp', 'green.jp') and internal_id < 0x9a:
  606.             return 0xc
  607.         elif internal_id < 0x99:
  608.             return 0xc
  609.         else:
  610.             return 0xd
  611.  
  612.     def get_sprite_offset(self, poke, sprite='front'):
  613.         if poke == 151 and self.offsets.base_stats_mew is not None:
  614.             self.rom.seek(self.offsets.base_stats_mew)
  615.         else:
  616.             self.rom.seek(self.offsets.base_stats)
  617.             self.rom.seek((poke - 1) * 28, SEEK_CUR)
  618.  
  619.         self.rom.seek(10, SEEK_CUR)
  620.         size = self.rom.read(1)
  621.  
  622.         pointers = unpack("<HH", self.rom.read(4))
  623.  
  624.         if sprite == 'front':
  625.             offset = pointers[0]
  626.         elif sprite == 'back':
  627.             offset = pointers[1]
  628.         else:
  629.             raise ValueError(sprite)
  630.  
  631.         bank = self.get_bank(poke)
  632.         #print(poke, get_internal_id(poke), hex(bank), list(map(hex, find_bank(rom, offset, size))))
  633.         return ((bank - 1) << 14) + offset
  634.  
  635. def find_banks(rom, pointer, size):
  636.     banks = []
  637.     rom.seek(pointer - 0x4000)
  638.     for bank in range(0x3f):
  639.         byte = rom.read(1)
  640.         if byte == size:
  641.             banks.append(bank)
  642.         rom.seek(0x4000-1, SEEK_CUR)
  643.     return banks
  644.  
  645.  
  646. def extract_sprite(game, poke, color=None, sprite='front', mirror=False):
  647.     if color is None:
  648.         color = game.colors[-1]
  649.     offset = game.get_sprite_offset(poke, sprite=sprite)
  650.     #print(hex(offset), file=sys.stderr)
  651.     img = decompress(game.rom, offset, mirror=mirror)
  652.     img.palette = game.get_palette(poke, color)
  653.     return img
  654.  
  655. def extract_all(game, directory, format="png"):
  656.     basedir = directory
  657.     ext = "." + format
  658.     for facing in ('front', 'back'):
  659.         for color in game.colors:
  660.             path = construct_path(basedir, facing=facing, palette=color)
  661.             xmakedirs(path)
  662.  
  663.         for poke in range(1, 151+1):
  664.             offset = game.get_sprite_offset(poke, facing)
  665.  
  666.             img = decompress(game.rom, offset)
  667.             for color in game.colors:
  668.                 path = construct_path(basedir, facing=facing, palette=color)
  669.                 path = os.path.join(path, str(poke)+ext)
  670.                 print(path)
  671.  
  672.                 if color == 'gray':
  673.                     img.palette = False
  674.                 else:
  675.                     img.palette = game.get_palette(poke, color)
  676.  
  677.                 img.save_format(format, open(path, 'wb'))
  678.  
  679.             #img.save_png(open(path, 'wb'), palette=False)
  680.  
  681. def construct_path(base, version="", facing="", palette=""):
  682.     if facing == "front":
  683.         facing = ""
  684.     if palette == "sgb":
  685.         palette = ""
  686.     return os.path.join(base, version, facing, palette)
  687.  
  688. def xmakedirs(path):
  689.     if not os.path.exists(path):
  690.         os.makedirs(path)
  691.  
  692.  
  693.  
  694. def print_help(full=True):
  695.     prog = os.path.basename(sys.argv[0])
  696.     print("""\
  697. Usage: {prog} rompath pokemon {{front|back}} [options]
  698.       {prog} rompath -d directory [options]
  699.       {prog} --help
  700. """.format(prog=prog))
  701.  
  702.     if not full:
  703.         sys.exit()
  704.  
  705.     print("""\
  706. First form:
  707.    Extract a single sprite to stdout
  708.  
  709.    rompath       - the path to the ROM
  710.    pokemon       - the id of the pokemon to extract
  711.    front | back  - frontsprite or backsprite?
  712.  
  713. Second form:
  714.    Extract all sprites into a directory
  715.  
  716.    rompath       - the path to the ROM
  717.    -d outdir
  718.    --directory=  - where to put the sprites
  719.  
  720.  
  721. Options:
  722.    -c, --color=   - which palette to use (gray/sgb/gbc)
  723.    -f, --format=  - which output format to use
  724.  
  725.    --munge
  726.    --no-munge     - if munge is set, set the first palette entry of the
  727.                     SGB palettes to white.  (default: munge)
  728.  
  729. Formats:
  730.    ppm     - the ppm(1) format from netpbm(1)
  731.    png     - the Portable Network Graphics format
  732.    boxes   - render using unicode boxes
  733.    xterm   - output color codes for a 256-color terminal
  734.  
  735. """.format(prog=prog))
  736.  
  737.     sys.exit()
  738.  
  739. def parse_args():
  740.     # this is sort of a hack
  741.     # and basically kills introspection for --help
  742.     parser = argparse.ArgumentParser(add_help=False)
  743.     parser.add_argument("-i", "--input")
  744.     parser.add_argument("-o", "--output")
  745.  
  746.     return parser.parse_args()
  747.  
  748. def main():
  749.     args = parse_args()
  750.  
  751.     f = open(args.input, 'rb')
  752.     img = decompress(f, 0)
  753.     img.palette = False
  754.  
  755.     img.save_format("png", open(args.output, 'wb'))
  756.  
  757.  
  758. main()
Advertisement
Add Comment
Please, Sign In to add comment