Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import argparse, sys
- import re
- import struct
- from PIL import Image
- EXAMPLE_FILENAMES = (
- 'KANSA_00.CGX',
- 'KAN00_CE.CEL',
- 'WAKABA.MAP'
- )
- EXAMPLE_MAP_WIDTH = 10
- EXAMPLE_COMPRESSION = '-c lz'
- EXAMPLE_GUIDE = '''
- mkdir out
- python3 conv_tool.py dat {0} out/{0}.bin
- python3 conv_tool.py pic {4} -w 16 out/{0}.bin out/{0}.png
- python3 conv_tool.py dat {1} out/{1}.bin
- python3 conv_tool.py cel -w 4 out/{1}.bin out/{0}.png out/{1}.png
- python3 conv_tool.py dat {2} out/{2}.bin
- python3 conv_tool.py map out/{2}.bin out/{1}.png {3} out/{2}.png
- '''[1:-1].format(*EXAMPLE_FILENAMES, EXAMPLE_MAP_WIDTH, EXAMPLE_COMPRESSION)
- DEFAULT_HELP_STRING = '''
- usage: {0} TOOL ...
- Tool for converting Gen 1/2 Pokemon source files
- positional arguments:
- TOOL tool to use
- optional arguments:
- -h, --help show this help message and exit
- valid tools are:
- dat convert text file into binary data
- pic convert file into png image (typically used for .CGX and .DAT)
- cel convert file into cel image (typically used for .CEL)
- map convert file into map image (typically used for .MAP and .FLD)
- clz add color to a png image (NOT YET IMPLEMENTED)
- for tool-specific help: {0} TOOL -h
- '''[1:-1].format(sys.argv[0])
- class Decompressor:
- LZ_END = 0b11111111
- LZ_CMD = 0b11100000
- LZ_LEN = 0b00011111
- LZ_LITERAL = 0b00000000
- LZ_ITERATE = 0b00100000
- LZ_ALTERNATE = 0b01000000
- LZ_ZERO = 0b01100000
- LZ19_INCREASE = 0b01100000
- LZ_RW = 0b10000000
- LZ_REPEAT = 0b10000000
- LZ_FLIP = 0b10100000
- LZ_REVERSE = 0b11000000
- LZ_LONG = 0b11100000
- LZ_LONG_C_HI = 0b00011100
- LZ_LONG_L_HI = 0b00000011
- def __init__(self, raw, mode='lz'):
- self.buff = raw
- self.ind = 0
- assert mode in ('lz', 'lz19')
- self.mode = mode
- def decompress(self):
- out = b''
- while True:
- cmd = self.read_byte()
- if cmd == self.LZ_END:
- break
- if cmd & self.LZ_CMD == self.LZ_LONG:
- # long
- ext = self.read_byte()
- cmd_id = (cmd & self.LZ_LONG_C_HI) << 3
- cmd_len = ext + 1 + ((cmd & self.LZ_LONG_L_HI) << 8)
- else:
- # short
- cmd_id = cmd & self.LZ_CMD
- cmd_len = (cmd & self.LZ_LEN) + 1
- if cmd_id & self.LZ_RW:
- byt = self.read_byte()
- if byt >= 0x80:
- # negative
- rel = len(out) + 0x7f - byt
- else:
- # positive
- rel = byt << 8
- rel += self.read_byte()
- else:
- rel = None
- if cmd_id == self.LZ_LITERAL:
- for _ in range(cmd_len):
- out += self.read_byte(1)
- elif cmd_id == self.LZ_ITERATE:
- byt = self.read_byte(1)
- out += byt * cmd_len
- elif cmd_id == self.LZ_ALTERNATE:
- byt1 = self.read_byte(1)
- byt2 = self.read_byte(1)
- if self.mode == 'lz19':
- out += (byt1 + byt2) * cmd_len
- else:
- for i in range(cmd_len):
- if i % 2 == 0:
- out += byt1
- else:
- out += byt2
- elif cmd_id == self.LZ_ZERO:
- if self.mode == 'lz19':
- byt = self.read_byte()
- for _ in range(cmd_len):
- out += struct.pack('B', byt)
- byt += 1
- byt & 0xff
- else:
- out += b'\x00' * cmd_len
- elif cmd_id == self.LZ_REPEAT:
- for _ in range(cmd_len):
- out += out[rel:rel+1]
- rel += 1
- elif cmd_id == self.LZ_FLIP:
- for _ in range(cmd_len):
- byt = out[rel]
- flp = int(bin(byt)[2:].zfill(8)[::-1], 2)
- out += struct.pack('B', flp)
- rel += 1
- elif cmd_id == self.LZ_REVERSE:
- for _ in range(cmd_len):
- out += out[rel:rel+1]
- rel -= 1
- else:
- raise Exception
- return out, self.ind # data, compressed size
- def read_byte(self, asraw=0):
- if asraw:
- cur = self.buff[self.ind:self.ind+1]
- else:
- cur = self.buff[self.ind]
- self.ind += 1
- return cur
- class main_dat:
- REGEX_DEC = re.compile(r'^ d[bw] ((?:[0-9]{3},? ?)+)\s*(?:;.*)?$')
- REGEX_HEX = re.compile(r'^ d[bw] ((?:0(?:[A-Fa-f0-9]{2})+h,? ?)+)\s*(?:;.*)?$')
- def __init__(self, args):
- txt = open(args.filein).read()
- v = not args.quiet
- lins = txt.split('\n')
- if args.decimal:
- reg = self.REGEX_DEC
- else:
- reg = self.REGEX_HEX
- datout = b''
- uuct = 0
- for i,l in enumerate(lins):
- mm = reg.match(l)
- if mm:
- cur_dats = mm.groups()[0].split(',')
- if args.decimal:
- cur_dats_2 = [hex(int(x.strip()[1:-1]))[2:].zfill(2) for x in cur_dats]
- else:
- cur_dats_2 = [x.strip()[1:-1] for x in cur_dats]
- datout += self.divhxl(cur_dats_2)
- else:
- if v:
- print('Line does not match pattern:')
- print(i+1, l)
- uuct += 1
- if uuct > 10:
- input()
- uuct = 0
- open(args.fileout, 'wb').write(datout)
- def divhx(self, x):
- pcs = []
- for i in range(len(x) // 2):
- pcs.append(x[i*2:i*2+2])
- return [int(x,16) for x in pcs[::-1]]
- def divhxl(self, x):
- out = b''
- for k in x:
- cur = self.divhx(k)
- out += struct.pack('B'*len(cur), *cur)
- return out
- def Main_dat():
- parser = argparse.ArgumentParser(description='Tool for converting Gen 1/2 pokemon source files')
- parser.add_argument('mode', type=str, choices={'dat'})
- parser.add_argument('filein', type=str, help='file to convert')
- parser.add_argument('fileout', type=str, help='output')
- parser.add_argument('-q', dest='quiet', action='store_true', help='suppress output')
- parser.add_argument('-d', dest='decimal', action='store_true', help='decimal data rather than hex')
- args = parser.parse_args()
- if args.fileout == '-':
- args.fileout = args.filein + '.bin'
- main_dat(args)
- class main_pic:
- gbgray = ((255,255,255),(176,176,176),(104,104,104),(0,0,0))
- def __init__(self, args):
- raw = open(args.filein, 'rb').read()
- if args.c:
- decomp = Decompressor(raw, args.c)
- raw, _ = decomp.decompress()
- if args.width:
- width = args.width
- else:
- width = 1
- numtiles = len(raw) // 0x10
- height = numtiles // width + (numtiles % width != 0)
- imgout = self.make_pic(raw, width, height)
- imgout.save(args.fileout)
- def make_pic(self, raw, width, height):
- numtiles = len(raw) // 0x10
- imgout = Image.new('RGBA', (width*8, height*8))
- pxl = imgout.load()
- i = 0
- for y in range(height):
- for x in range(width):
- for ty in range(8):
- byt1 = raw[i]
- byt2 = raw[i+1]
- ry = y * 8 + ty
- for bt in range(8):
- rx = x * 8 + (7-bt)
- c_0 = bool(byt1 & (1 << bt))
- c_1 = bool(byt2 & (1 << bt))
- c_i = c_1 * 2 + c_0
- pxl[rx,ry] = self.gbgray[c_i]
- i += 2
- assert i == len(raw)
- return imgout
- def Main_pic():
- parser = argparse.ArgumentParser(description='Tool for converting Gen 1/2 pokemon source files')
- parser.add_argument('mode', type=str, choices={'pic'})
- parser.add_argument('filein', type=str, help='file to convert')
- parser.add_argument('fileout', type=str, help='output')
- parser.add_argument('-w', '--width', type=int, help='width of image, in 8px x 8px tiles')
- parser.add_argument('-c', type=str, choices={'lz', 'lz19'}, help='compression')
- args = parser.parse_args()
- if args.fileout == '-':
- args.fileout = args.filein[:-4] + '.png'
- main_pic(args)
- class main_cel:
- def __init__(self, args):
- self.img = Image.open(args.tilin)
- self.img_w_t = self.img.size[0] // 8
- raw = open(args.celin, 'rb').read()
- numcel_x = args.width
- numcel = len(raw) // 0x10
- numcel_y = numcel // numcel_x + (numcel % numcel_x != 0)
- imgout = Image.new('RGBA', (numcel_x * 32, numcel_y * 32))
- for y in range(numcel_y):
- for x in range(numcel_x):
- celi = y * numcel_x + x
- curcel = raw[celi*16:celi*16+16]
- for yy in range(4):
- for xx in range(4):
- imgy = y * 32 + yy * 8
- imgx = x * 32 + xx * 8
- imgout.paste(self.gettile(curcel[yy*4+xx]), (imgx, imgy))
- imgout.save(args.fileout)
- def gettile(self, i):
- imgx = (i % self.img_w_t) * 8
- imgy = (i // self.img_w_t) * 8
- return self.img.crop((imgx, imgy, imgx+8, imgy+8))
- def Main_cel():
- parser = argparse.ArgumentParser(description='Tool for converting Gen 1/2 pokemon source files')
- parser.add_argument('mode', type=str, choices={'cel'})
- parser.add_argument('celin', type=str, help='cel file to convert')
- parser.add_argument('tilin', type=str, help='png-cgx/dat file to use')
- parser.add_argument('fileout', type=str, help='output')
- parser.add_argument('-w', '--width', type=int, default=1, help='width (in cells)')
- args = parser.parse_args()
- if args.fileout == '-':
- args.fileout = args.celin[:-4] + '.png'
- main_cel(args)
- class main_map:
- def __init__(self, args):
- self.img = Image.open(args.celin)
- self.img_w_c = self.img.size[0] // 32
- raw = open(args.mapin, 'rb').read()
- w = args.width
- assert len(raw) % w == 0
- h = len(raw) // w
- imgout = Image.new('RGBA', (w * 32, h * 32))
- for y in range(h):
- for x in range(w):
- celi = raw[y*w+x]
- cel = self.getcel(celi)
- imgout.paste(cel, (x*32, y*32))
- imgout.save(args.fileout)
- def getcel(self, i):
- imgx = (i % self.img_w_c) * 32
- imgy = (i // self.img_w_c) * 32
- return self.img.crop((imgx, imgy, imgx+32, imgy+32))
- def Main_map():
- parser = argparse.ArgumentParser(description='Tool for converting Gen 1/2 pokemon source files')
- parser.add_argument('mode', type=str, choices={'map'})
- parser.add_argument('mapin', type=str, help='map file to convert')
- parser.add_argument('celin', type=str, help='png-cel file to use')
- parser.add_argument('width', type=int, help='width of map (in cells)')
- parser.add_argument('fileout', type=str, help='output')
- args = parser.parse_args()
- if args.fileout == '-':
- args.fileout = args.mapin[:-4] + '.png'
- main_map(args)
- class main_clz:
- def __init__(self, args):
- pass
- def Main_clz():
- print('NOT YET IMPLEMENTED')
- if __name__ == '__main__':
- if len(sys.argv) == 2 and sys.argv[1] in ('-h', '--help'):
- print(DEFAULT_HELP_STRING)
- exit()
- elif len(sys.argv) == 2 and sys.argv[1] == 'example':
- print(EXAMPLE_GUIDE)
- exit()
- parser = argparse.ArgumentParser(description='Tool for converting Gen 1/2 pokemon source files', add_help=False)
- parser.add_argument('mode', type=str, choices={'dat','pic','cel','map','clz'})
- args, _ = parser.parse_known_args()
- if args.mode == 'dat':
- Main_dat()
- elif args.mode == 'pic':
- Main_pic()
- elif args.mode == 'cel':
- Main_cel()
- elif args.mode == 'map':
- Main_map()
- elif args.mode == 'clz':
- Main_clz()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement