From 3a0e80932ec11494407ea5a3aa7724007b71502b Mon Sep 17 00:00:00 2001 From: yenatch Date: Fri, 17 May 2013 01:58:42 -0400 Subject: [PATCH 1/9] fix data-handling errors in gfx.py --- extras/gfx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extras/gfx.py b/extras/gfx.py index 2c98a5ab2..e3081e256 100644 --- a/extras/gfx.py +++ b/extras/gfx.py @@ -189,6 +189,7 @@ class Compressed: def __init__(self, image = None, mode = 'horiz', size = None): assert image, 'need something to compress!' + image = list(image) self.image = image self.pic = [] self.animtiles = [] @@ -1503,7 +1504,7 @@ def lz_to_png_by_file(filename): """ assert filename[-3:] == ".lz" lz_data = open(filename, "rb").read() - bpp = Decompressed(lz).output + bpp = Decompressed(lz_data).output bpp_filename = filename.replace(".lz", ".2bpp") to_file(bpp_filename, bpp) to_png(bpp_filename) @@ -1619,5 +1620,4 @@ if __name__ == "__main__": elif args.cmd == 'mass-decompress': mass_decompress() if debug: print 'decompressed known gfx to pokecrystal/gfx/!' - From abaed2145fbf982a4873e186e964ce0435fb3ce0 Mon Sep 17 00:00:00 2001 From: yenatch Date: Fri, 17 May 2013 02:01:37 -0400 Subject: [PATCH 2/9] gfx.py: get rid of argparse --- extras/gfx.py | 76 +++++++++++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 42 deletions(-) diff --git a/extras/gfx.py b/extras/gfx.py index e3081e256..15402ddb0 100644 --- a/extras/gfx.py +++ b/extras/gfx.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- import os +import sys import png -import argparse from math import sqrt, floor, ceil from crystal import load_rom @@ -1519,43 +1519,35 @@ def dump_tileset_pngs(): lz_to_png_by_file(tileset_filename) if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument('cmd', nargs='?', metavar='cmd', type=str) - parser.add_argument('arg1', nargs='?', metavar='arg1', type=str) - parser.add_argument('arg2', nargs='?', metavar='arg2', type=str) - parser.add_argument('arg3', nargs='?', metavar='arg3', type=str) - parser.add_argument('arg4', nargs='?', metavar='arg4', type=str) - parser.add_argument('arg5', nargs='?', metavar='arg5', type=str) - args = parser.parse_args() debug = False - if args.cmd == 'dump-pngs': + if sys.argv[1] == 'dump-pngs': mass_to_colored_png() - elif args.cmd == 'png-to-lz': + elif sys.argv[1] == 'png-to-lz': # python gfx.py png-to-lz [--front anim(2bpp) | --vert] [png] # python gfx.py png-to-lz --front [anim(2bpp)] [png] - if args.arg1 == '--front': + if sys.argv[2] == '--front': # front.png and tiles.png are combined before compression, # so we have to pass in things like anim file and pic size - name = os.path.splitext(args.arg3)[0] + name = os.path.splitext(sys.argv[4])[0] to_2bpp(name+'.png', name+'.2bpp') pic = open(name+'.2bpp', 'rb').read() - anim = open(args.arg2, 'rb').read() + anim = open(sys.argv[3], 'rb').read() size = int(sqrt(len(pic)/16)) # assume square pic to_file(name+'.lz', Compressed(pic + anim, 'vert', size).output) # python gfx.py png-to-lz --vert [png] - elif args.arg1 == '--vert': + elif sys.argv[2] == '--vert': # others are vertically oriented (frontpics are always vertical) - name = os.path.splitext(args.arg2)[0] + name = os.path.splitext(sys.argv[3])[0] to_2bpp(name+'.png', name+'.2bpp') pic = open(name+'.2bpp', 'rb').read() @@ -1567,57 +1559,57 @@ if __name__ == "__main__": # standard usage - png_to_lz(args.arg1) + png_to_lz(sys.argv[2]) - elif args.cmd == 'png-to-2bpp': - to_2bpp(args.arg1) + elif sys.argv[1] == 'png-to-2bpp': + to_2bpp(sys.argv[2]) - elif args.cmd == 'de': + elif sys.argv[1] == 'de': # python gfx.py de [addr] [fileout] [mode] rom = load_rom() - addr = int(args.arg1,16) - fileout = args.arg2 - mode = args.arg3 + addr = int(sys.argv[2],16) + fileout = sys.argv[3] + mode = sys.argv[4] decompress_from_address(addr, fileout, mode) - if debug: print 'decompressed to ' + args.arg2 + ' from ' + hex(int(args.arg1,16)) + '!' + if debug: print 'decompressed to ' + sys.argv[3] + ' from ' + hex(int(sys.argv[2],16)) + '!' - elif args.cmd == 'lz': + elif sys.argv[1] == 'lz': # python gfx.py lz [filein] [fileout] [mode] - filein = args.arg1 - fileout = args.arg2 - mode = args.arg3 + filein = sys.argv[2] + fileout = sys.argv[3] + mode = sys.argv[4] compress_file(filein, fileout, mode) if debug: print 'compressed ' + filein + ' to ' + fileout + '!' - elif args.cmd == 'lzf': + elif sys.argv[1] == 'lzf': # python gfx.py lzf [id] [fileout] - compress_monster_frontpic(int(args.arg1), args.arg2) + compress_monster_frontpic(int(sys.argv[2]), sys.argv[3]) - elif args.cmd == 'un': + elif sys.argv[1] == 'un': # python gfx.py un [address] [num_tiles] [filename] rom = load_rom() - get_uncompressed_gfx(int(args.arg1,16), int(args.arg2), args.arg3) + get_uncompressed_gfx(int(sys.argv[2],16), int(sys.argv[3]), sys.argv[4]) - elif args.cmd == 'pal': + elif sys.argv[1] == 'pal': # python gfx.py pal [address] [length] rom = load_rom() - print grab_palettes(int(args.arg1,16), int(args.arg2)) + print grab_palettes(int(sys.argv[2],16), int(sys.argv[3])) - elif args.cmd == 'png': + elif sys.argv[1] == 'png': - if '.2bpp' in args.arg1: - if args.arg3 == 'greyscale': - to_png(args.arg1, args.arg2) + if '.2bpp' in sys.argv[2]: + if sys.argv[4] == 'greyscale': + to_png(sys.argv[2], sys.argv[3]) else: - to_png(args.arg1, args.arg2, args.arg3) + to_png(sys.argv[2], sys.argv[3], sys.argv[4]) - elif '.png' in args.arg1: - to_2bpp(args.arg1, args.arg2) + elif '.png' in sys.argv[2]: + to_2bpp(sys.argv[2], sys.argv[3]) - elif args.cmd == 'mass-decompress': + elif sys.argv[1] == 'mass-decompress': mass_decompress() if debug: print 'decompressed known gfx to pokecrystal/gfx/!' From a1b9fdc9c92279bef7383e8c8278934ca2889de8 Mon Sep 17 00:00:00 2001 From: yenatch Date: Fri, 17 May 2013 02:03:21 -0400 Subject: [PATCH 3/9] gfx.py: remove trailing tabs --- extras/gfx.py | 516 +++++++++++++++++++++++++------------------------- 1 file changed, 258 insertions(+), 258 deletions(-) diff --git a/extras/gfx.py b/extras/gfx.py index 15402ddb0..0a2d60dfa 100644 --- a/extras/gfx.py +++ b/extras/gfx.py @@ -26,28 +26,28 @@ def mkdir_p(path): def hex_dump(input, debug = True): """display hex dump in rows of 16 bytes""" - + dump = '' output = '' stream = '' address = 0x00 margin = 2 + len(hex(len(input))[2:]) - + # dump for byte in input: cool = hex(byte)[2:].zfill(2) dump += cool + ' ' if debug: stream += cool - + # convenient for testing quick edits in bgb if debug: output += stream + '\n' - + # get dump info bytes_per_line = 16 chars_per_byte = 3 # '__ ' chars_per_line = bytes_per_line * chars_per_byte num_lines = int(ceil(float(len(dump)) / float(chars_per_line))) - + # top # margin for char in range(margin): @@ -56,7 +56,7 @@ def hex_dump(input, debug = True): for byte in range(bytes_per_line): output += hex(byte)[2:].zfill(2) + ' ' output = output[:-1] # last space - + # print hex for line in range(num_lines): # address @@ -66,16 +66,16 @@ def hex_dump(input, debug = True): end = chars_per_line + start - 1 # ignore last space output += dump[start:end] address += 0x10 - + return output - + def get_tiles(image): """split a 2bpp image into 8x8 tiles""" tiles = [] tile = [] bytes_per_tile = 16 - + cur_byte = 0 for byte in image: # build tile @@ -97,11 +97,11 @@ def connect(tiles): for byte in tile: out.append(byte) return out - + def transpose(tiles): """transpose a tile arrangement along line y=x""" - + # horizontal <-> vertical # 00 01 02 03 04 05 00 06 0c 12 18 1e # 06 07 08 09 0a 0b 01 07 0d 13 19 1f @@ -110,7 +110,7 @@ def transpose(tiles): # 18 19 1a 1b 1c 1d 04 0a 10 16 1c 22 # 1e 1f 20 21 22 23 05 0b 11 17 1d 23 # etc - + flipped = [] t = 0 # which tile we're on w = int(sqrt(len(tiles))) # assume square image @@ -185,15 +185,15 @@ lowmax = 1 << 5 # standard 5-bit param class Compressed: """compress 2bpp data""" - + def __init__(self, image = None, mode = 'horiz', size = None): - + assert image, 'need something to compress!' image = list(image) self.image = image self.pic = [] self.animtiles = [] - + # only transpose pic (animtiles were never transposed in decompression) if size != None: for byte in range((size*size)*16): @@ -202,21 +202,21 @@ class Compressed: self.animtiles += image[byte] else: self.pic = image - + if mode == 'vert': self.tiles = get_tiles(self.pic) self.tiles = transpose(self.tiles) self.pic = connect(self.tiles) - + self.image = self.pic + self.animtiles - + self.end = len(self.image) - + self.byte = None self.address = 0 - + self.stream = [] - + self.zeros = [] self.alts = [] self.iters = [] @@ -224,65 +224,65 @@ class Compressed: self.flips = [] self.reverses = [] self.literals = [] - + self.output = [] - + self.compress() - - + + def compress(self): """incomplete, but outputs working compressed data""" - + self.address = 0 - + # todo #self.scanRepeats() - + while ( self.address < self.end ): - + #if (self.repeats): # self.doRepeats() - + #if (self.flips): # self.doFlips() - + #if (self.reverses): # self.doReverses - + if (self.checkWhitespace()): self.doLiterals() self.doWhitespace() - + elif (self.checkIter()): self.doLiterals() self.doIter() - + elif (self.checkAlts()): self.doLiterals() self.doAlts() - + else: # doesn't fit any pattern -> literal self.addLiteral() self.next() - + self.doStream() - + # add any literals we've been sitting on self.doLiterals() - + # done self.output.append(lz_end) - - + + def getCurByte(self): if self.address < self.end: self.byte = ord(self.image[self.address]) else: self.byte = None - + def next(self): self.address += 1 self.getCurByte() - + def addLiteral(self): self.getCurByte() self.literals.append(self.byte) @@ -290,7 +290,7 @@ class Compressed: raise Exception, "literals exceeded max length and the compressor didn't catch it" elif len(self.literals) == max_length: self.doLiterals() - + def doLiterals(self): if len(self.literals) > lowmax: self.output.append( (lz_hi << 5) | (lz_lit << 2) | ((len(self.literals) - 1) >> 8) ) @@ -299,24 +299,24 @@ class Compressed: self.output.append( (lz_lit << 5) | (len(self.literals) - 1) ) for byte in self.literals: self.output.append(byte) - self.literals = [] - + self.literals = [] + def doStream(self): for byte in self.stream: self.output.append(byte) self.stream = [] - - + + def scanRepeats(self): """works, but doesn't do flipped/reversed streams yet - + this takes up most of the compress time and only saves a few bytes it might be more feasible to exclude it entirely""" - + self.repeats = [] self.flips = [] self.reverses = [] - + # make a 5-letter word list of the sequence letters = 5 # how many bytes it costs to use a repeat over a literal # any shorter and it's not worth the trouble @@ -327,11 +327,11 @@ class Compressed: for j in range(letters): word.append( ord(self.image[i+j]) ) words.append((word, i)) - + zeros = [] for zero in range(letters): zeros.append( 0 ) - + # check for matches def get_matches(): # TODO: @@ -349,9 +349,9 @@ class Compressed: # remove zeros if this[0] != zeros: yield [this[0], this[1], words[that][1]] - + matches = list(get_matches()) - + # remove more zeros buffer = [] for match in matches: @@ -369,7 +369,7 @@ class Compressed: # (and likely to already be accounted for) buffer.append(match) matches = buffer - + # combine overlapping matches buffer = [] for this, match in enumerate(matches): @@ -385,11 +385,11 @@ class Compressed: buffer.append(match) # else we've gone past it and we can ignore it else: # no more overlaps - buffer.append(match) + buffer.append(match) else: # last match, so there's nothing to check buffer.append(match) matches = buffer - + # remove alternating sequences buffer = [] for match in matches: @@ -398,49 +398,49 @@ class Compressed: buffer.append(match) break matches = buffer - + self.repeats = matches - - + + def doRepeats(self): """doesn't output the right values yet""" - + unusedrepeats = [] for repeat in self.repeats: if self.address >= repeat[2]: - + # how far in we are length = (len(repeat[0]) - (self.address - repeat[2])) - + # decide which side we're copying from if (self.address - repeat[1]) <= 0x80: self.doLiterals() self.stream.append( (lz_repeat << 5) | length - 1 ) - + # wrong? self.stream.append( (((self.address - repeat[1])^0xff)+1)&0xff ) else: self.doLiterals() self.stream.append( (lz_repeat << 5) | length - 1 ) - + # wrong? self.stream.append(repeat[1]>>8) self.stream.append(repeat[1]&0xff) - + #print hex(self.address) + ': ' + hex(len(self.output)) + ' ' + hex(length) self.address += length - + else: unusedrepeats.append(repeat) - + self.repeats = unusedrepeats - - + + def checkWhitespace(self): self.zeros = [] self.getCurByte() original_address = self.address - + if ( self.byte == 0 ): while ( self.byte == 0 ) & ( len(self.zeros) <= max_length ): self.zeros.append(self.byte) @@ -449,7 +449,7 @@ class Compressed: return True self.address = original_address return False - + def doWhitespace(self): if (len(self.zeros) + 1) >= lowmax: self.stream.append( (lz_hi << 5) | (lz_zeros << 2) | ((len(self.zeros) - 1) >> 8) ) @@ -458,20 +458,20 @@ class Compressed: self.stream.append( lz_zeros << 5 | (len(self.zeros) - 1) ) else: raise Exception, "checkWhitespace() should prevent this from happening" - - + + def checkAlts(self): self.alts = [] self.getCurByte() original_address = self.address num_alts = 0 - + # make sure we don't check for alts at the end of the file if self.address+2 >= self.end: return False - + self.alts.append(self.byte) self.alts.append(ord(self.image[self.address+1])) - + # are we onto smething? if ( ord(self.image[self.address+2]) == self.alts[0] ): cur_alt = 0 @@ -486,17 +486,17 @@ class Compressed: elif num_alts > 2: return True return False - + def doAlts(self): original_address = self.address self.getCurByte() - + #self.alts = [] #num_alts = 0 - + #self.alts.append(self.byte) #self.alts.append(ord(self.image[self.address+1])) - + #i = 0 #while (ord(self.image[self.address+1]) == self.alts[i^1]) & (num_alts <= max_length): # num_alts += 1 @@ -504,9 +504,9 @@ class Compressed: # self.next() ## include the last alternated byte #num_alts += 1 - + num_alts = len(self.iters) + 1 - + if num_alts > lowmax: self.stream.append( (lz_hi << 5) | (lz_alt << 2) | ((num_alts - 1) >> 8) ) self.stream.append( num_alts & 0xff ) @@ -518,10 +518,10 @@ class Compressed: self.stream.append( self.alts[1] ) else: raise Exception, "checkAlts() should prevent this from happening" - + self.address = original_address self.address += num_alts - + def checkIter(self): self.iters = [] @@ -536,19 +536,19 @@ class Compressed: # 3 or fewer isn't worth the trouble and actually longer # if part of a larger literal set return True - + return False - + def doIter(self): self.getCurByte() iter = self.byte original_address = self.address - + self.iters = [] while (self.byte == iter) & (len(self.iters) < max_length): self.iters.append(self.byte) self.next() - + if (len(self.iters) - 1) >= lowmax: self.stream.append( (lz_hi << 5) | (lz_iter << 2) | ((len(self.iters)-1) >> 8) ) self.stream.append( (len(self.iters) - 1) & 0xff ) @@ -568,65 +568,65 @@ class Compressed: class Decompressed: """parse compressed 2bpp data - + parameters: [compressed 2bpp data] [tile arrangement] default: 'vert' [size of pic] default: None [start] (optional) - + splits output into pic [size] and animation tiles if applicable data can be fed in from rom if [start] is specified""" - + def __init__(self, lz = None, mode = None, size = None, start = 0): # todo: play nice with Compressed - + assert lz, 'need something to compress!' self.lz = lz - + self.byte = None self.address = 0 self.start = start - + self.output = [] - + self.decompress() - + debug = False # print tuple containing start and end address if debug: print '(' + hex(self.start) + ', ' + hex(self.start + self.address+1) + '),' - + # only transpose pic self.pic = [] self.animtiles = [] - + if size != None: self.tiles = get_tiles(self.output) self.pic = connect(self.tiles[:(size*size)]) self.animtiles = connect(self.tiles[(size*size):]) else: self.pic = self.output - + if mode == 'vert': self.tiles = get_tiles(self.pic) self.tiles = transpose(self.tiles) self.pic = connect(self.tiles) - + self.output = self.pic + self.animtiles - - + + def decompress(self): """replica of crystal's decompression""" - + self.output = [] - + while True: self.getCurByte() - + if (self.byte == lz_end): break - + self.cmd = (self.byte & 0b11100000) >> 5 - + if self.cmd == lz_hi: # 10-bit param self.cmd = (self.byte & 0b00011100) >> 2 self.length = (self.byte & 0b00000011) << 8 @@ -634,7 +634,7 @@ class Decompressed: self.length += self.byte + 1 else: # 5-bit param self.length = (self.byte & 0b00011111) + 1 - + # literals if self.cmd == lz_lit: self.doLiteral() @@ -644,7 +644,7 @@ class Decompressed: self.doAlt() elif self.cmd == lz_zeros: self.doZeros() - + else: # repeaters self.next() if self.byte > 0x7f: # negative @@ -654,37 +654,37 @@ class Decompressed: self.displacement = self.byte * 0x100 self.next() self.displacement += self.byte - + if self.cmd == lz_flip: self.doFlip() elif self.cmd == lz_reverse: self.doReverse() else: # lz_repeat self.doRepeat() - + self.address += 1 #self.next() # somewhat of a hack - - + + def getCurByte(self): self.byte = ord(self.lz[self.start+self.address]) - + def next(self): self.address += 1 self.getCurByte() - + def doLiteral(self): # copy 2bpp data directly for byte in range(self.length): self.next() self.output.append(self.byte) - + def doIter(self): # write one byte repeatedly self.next() for byte in range(self.length): self.output.append(self.byte) - + def doAlt(self): # write alternating bytes self.alts = [] @@ -692,15 +692,15 @@ class Decompressed: self.alts.append(self.byte) self.next() self.alts.append(self.byte) - + for byte in range(self.length): self.output.append(self.alts[byte&1]) - + def doZeros(self): # write zeros for byte in range(self.length): self.output.append(0x00) - + def doFlip(self): # repeat flipped bytes from 2bpp output # eg 11100100 -> 00100111 @@ -708,12 +708,12 @@ class Decompressed: for byte in range(self.length): flipped = sum(1<<(7-i) for i in range(8) if self.output[self.displacement+byte]>>i&1) self.output.append(flipped) - + def doReverse(self): # repeat reversed bytes from 2bpp output for byte in range(self.length): self.output.append(self.output[self.displacement-byte]) - + def doRepeat(self): # repeat bytes from 2bpp output for byte in range(self.length): @@ -746,15 +746,15 @@ def make_sizes(): base_stats = 0x51424 # print monster sizes address = base_stats + 0x11 - + output = '' - + for id in range(top): size = (ord(rom[address])) & 0x0f if id % 16 == 0: output += '\n\t' output += str(size) + ', ' address += 0x20 - + print output @@ -773,7 +773,7 @@ def decompress_fx_by_id(id): # decompress fx = Decompressed(rom, 'horiz', num_tiles, address) return fx - + def decompress_fx(): for id in range(num_fx): fx = decompress_fx_by_id(id) @@ -807,7 +807,7 @@ def decompress_monster_by_id(id = 0, type = front): # decompress monster = Decompressed(rom, 'vert', size, address) return monster - + def decompress_monsters(type = front): for id in range(num_monsters): # decompress @@ -844,7 +844,7 @@ def decompress_unowns(type = front): for letter in range(num_unowns): # decompress unown = decompress_unown_by_id(letter, type) - + if not type: # front filename = 'front.2bpp' folder = '../gfx/pics/' + str(unown_dex).zfill(3) + chr(ord('a') + letter) + '/' @@ -956,7 +956,7 @@ def decompress_misc(): def decompress_all(debug = False): """decompress all known compressed data in baserom""" - + if debug: print 'fronts' decompress_monsters(front) if debug: print 'backs' @@ -965,25 +965,25 @@ def decompress_all(debug = False): decompress_unowns(front) if debug: print 'unown backs' decompress_unowns(back) - + if debug: print 'trainers' decompress_trainers() - + if debug: print 'fx' decompress_fx() - + if debug: print 'intro' decompress_intro() - + if debug: print 'title' decompress_title() - + if debug: print 'tilesets' decompress_tilesets() - + if debug: print 'misc' decompress_misc() - + return @@ -997,9 +997,9 @@ def decompress_file(filein, fileout, mode = 'horiz', size = None): f = open(filein, 'rb') image = f.read() f.close() - + de = Decompressed(image, mode, size) - + to_file(fileout, de.pic) @@ -1007,9 +1007,9 @@ def compress_file(filein, fileout, mode = 'horiz'): f = open(filein, 'rb') image = f.read() f.close() - + lz = Compressed(image, mode) - + to_file(fileout, lz.output) @@ -1017,18 +1017,18 @@ def compress_file(filein, fileout, mode = 'horiz'): def compress_monster_frontpic(id, fileout): mode = 'vert' - + fpic = '../gfx/pics/' + str(id).zfill(3) + '/front.2bpp' fanim = '../gfx/pics/' + str(id).zfill(3) + '/tiles.2bpp' - + pic = open(fpic, 'rb').read() anim = open(fanim, 'rb').read() image = pic + anim - + lz = Compressed(image, mode, sizes[id-1]) - + out = '../gfx/pics/' + str(id).zfill(3) + '/front.lz' - + to_file(out, lz.output) @@ -1075,63 +1075,63 @@ def grab_palettes(address, length = 0x80): def dump_monster_pals(): rom = load_rom() - + pals = 0xa8d6 pal_length = 0x4 for mon in range(251): - + name = pokemon_constants[mon+1].title().replace('_','') num = str(mon+1).zfill(3) dir = 'gfx/pics/'+num+'/' - + address = pals + mon*pal_length*2 - - + + pal_data = [] for byte in range(pal_length): pal_data.append(ord(rom[address])) address += 1 - + filename = 'normal.pal' to_file('../'+dir+filename, pal_data) - + spacing = ' ' * (15 - len(name)) #print name+'Palette:'+spacing+' INCBIN "'+dir+filename+'"' - - + + pal_data = [] for byte in range(pal_length): pal_data.append(ord(rom[address])) address += 1 - + filename = 'shiny.pal' to_file('../'+dir+filename, pal_data) - + spacing = ' ' * (10 - len(name)) #print name+'ShinyPalette:'+spacing+' INCBIN "'+dir+filename+'"' def dump_trainer_pals(): rom = load_rom() - + pals = 0xb0d2 pal_length = 0x4 for trainer in range(67): - + name = trainer_group_names[trainer+1]['constant'].title().replace('_','') num = str(trainer).zfill(3) dir = 'gfx/trainers/' - + address = pals + trainer*pal_length - + pal_data = [] for byte in range(pal_length): pal_data.append(ord(rom[address])) address += 1 - + filename = num+'.pal' to_file('../'+dir+filename, pal_data) - + spacing = ' ' * (12 - len(name)) print name+'Palette:'+spacing+' INCBIN"'+dir+filename+'"' @@ -1157,14 +1157,14 @@ def to_lines(image, width): """ Converts a tiled quaternary pixel map to lines of quaternary pixels. """ - + tile = 8 * 8 - + # so we know how many strips of 8px we're putting into a line num_columns = width / 8 # number of lines height = len(image) / width - + lines = [] for cur_line in range(height): tile_row = int(cur_line / 8) @@ -1203,80 +1203,80 @@ def to_png(filein, fileout=None, pal_file=None, height=None, width=None): """ Takes a planar 2bpp graphics file and converts it to png. """ - + if fileout == None: fileout = '.'.join(filein.split('.')[:-1]) + '.png' - + image = open(filein, 'rb').read() - + num_pixels = len(image) * 4 - + if num_pixels == 0: return 'empty image!' - - + + # unless the pic is square, at least one dimension should be given - + if width == None and height == None: width = int(sqrt(num_pixels)) height = width - + elif height == None: height = num_pixels / width elif width == None: width = num_pixels / height - - + + # but try to see if it can be made rectangular - + if width * height != num_pixels: - + # look for possible combos of width/height that would form a rectangle matches = [] - + # this is pretty inefficient, and there is probably a simpler way for width in range(8,256+1,8): # we only want dimensions that fit in tiles height = num_pixels / width if height % 8 == 0: matches.append((width, height)) - + # go for the most square image width, height = sorted(matches, key=lambda (x,y): x+y)[0] # favors height - - + + # if it can't, the only option is a width of 1 tile - + if width * height != num_pixels: width = 8 height = num_pixels / width - - + + # if this still isn't rectangular, then the image isn't made of tiles - + # for now we'll just spit out a warning if width * height != num_pixels: print 'Warning! ' + fileout + ' is ' + width + 'x' + height + '(' + width*height + ' pixels),\n' +\ 'but ' + filein + ' is ' + num_pixels + ' pixels!' - - + + # map it out - + lines = to_lines(flatten(image), width) - - + + if pal_file == None: palette = None greyscale = True bitdepth = 2 inverse = { 0:3, 1:2, 2:1, 3:0 } map = [[inverse[pixel] for pixel in line] for line in lines] - + else: # gbc color palette = png_pal(pal_file) greyscale = False bitdepth = 8 map = [[pixel for pixel in line] for line in lines] - - + + w = png.Writer(width, height, palette=palette, compression = 9, greyscale = greyscale, bitdepth = bitdepth) with open(fileout, 'wb') as file: w.write(file, map) @@ -1288,44 +1288,44 @@ def to_2bpp(filein, fileout=None, palout=None): """ Takes a png and converts it to planar 2bpp. """ - + if fileout == None: fileout = '.'.join(filein.split('.')[:-1]) + '.2bpp' - + with open(filein, 'rb') as file: - r = png.Reader(file) + r = png.Reader(file) info = r.asRGBA8() - + width = info[0] height = info[1] - + rgba = list(info[2]) greyscale = info[3]['greyscale'] - - + + # commented out for the moment - + padding = { 'left': 0, 'right': 0, 'top': 0, 'bottom': 0, } - + #if width % 8 != 0: # padding['left'] = int(ceil((width / 8 + 8 - width) / 2)) # padding['right'] = int(floor((width / 8 + 8 - width) / 2)) - + #if height % 8 != 0: # padding['top'] = int(ceil((height / 8 + 8 - height) / 2)) # padding['bottom'] = int(floor((height / 8 + 8 - height) / 2)) - - + + # turn the flat values into something more workable - + pixel_length = 4 # rgba image = [] - + # while we're at it, let's size up the palette - + palette = [] for line in rgba: @@ -1339,34 +1339,34 @@ def to_2bpp(filein, fileout=None, palout=None): newline.append(color) if color not in palette: palette.append(color) image.append(newline) - - + + # sort by luminance, because we can - + def luminance(color): # this is actually in reverse, thanks to dmg/cgb palette ordering rough = { 'r': 4.7, 'g': 1.4, 'b': 13.8, } return sum(color[key] * -rough[key] for key in rough.keys()) - + palette = sorted(palette, key = lambda x:luminance(x)) - + # no palette fixing for now - + assert len(palette) <= 4, 'Palette should be 4 colors, is really ' + str(len(palette)) - + # spit out new palette (disabled for now) - + def rgb_to_dmg(color): word = (color['r'] / 8) << 10 word += (color['g'] / 8) << 5 word += (color['b'] / 8) return word - + palout = None - + if palout != None: output = [] for color in palette[1:3]: @@ -1374,10 +1374,10 @@ def to_2bpp(filein, fileout=None, palout=None): output.append(word>>8) output.append(word&0xff) to_file(palout, output) - - + + # create a new map consisting of quaternary color ids - + map = [] if padding['top']: map += [0] * (width + padding['left'] + padding['right']) * padding['top'] for line in image: @@ -1386,14 +1386,14 @@ def to_2bpp(filein, fileout=None, palout=None): map.append(palette.index(color)) if padding['right']: map += [0] * padding['right'] if padding['bottom']: map += [0] * (width + padding['left'] + padding['right']) * padding['bottom'] - + # split it into strips of 8, and make them planar - + num_columns = width / 8 num_rows = height / 8 - + tile = 8 * 8 - + image = [] for row in range(num_rows): for column in range(num_columns): @@ -1407,14 +1407,14 @@ def to_2bpp(filein, fileout=None, palout=None): top += ((quad & 2) >> 1) << (7-bit) image.append(bottom) image.append(top) - + to_file(fileout, image) def png_to_lz(filein): - + name = os.path.splitext(filein)[0] - + to_2bpp(filein) image = open(name+'.2bpp', 'rb').read() to_file(name+'.lz', Compressed(image).output) @@ -1442,7 +1442,7 @@ def mass_to_colored_png(debug=False): else: to_png(os.path.join(root, name)) os.utime(os.path.join(root, name), None) - + # only monster and trainer pics for now for root, dirs, files in os.walk('../gfx/pics/'): for name in files: @@ -1453,7 +1453,7 @@ def mass_to_colored_png(debug=False): else: to_png(os.path.join(root, name)) os.utime(os.path.join(root, name), None) - + for root, dirs, files in os.walk('../gfx/trainers/'): for name in files: if debug: print os.path.splitext(name), os.path.join(root, name) @@ -1519,63 +1519,63 @@ def dump_tileset_pngs(): lz_to_png_by_file(tileset_filename) if __name__ == "__main__": - + debug = False - + if sys.argv[1] == 'dump-pngs': mass_to_colored_png() - + elif sys.argv[1] == 'png-to-lz': # python gfx.py png-to-lz [--front anim(2bpp) | --vert] [png] - + # python gfx.py png-to-lz --front [anim(2bpp)] [png] if sys.argv[2] == '--front': # front.png and tiles.png are combined before compression, # so we have to pass in things like anim file and pic size name = os.path.splitext(sys.argv[4])[0] - + to_2bpp(name+'.png', name+'.2bpp') pic = open(name+'.2bpp', 'rb').read() anim = open(sys.argv[3], 'rb').read() size = int(sqrt(len(pic)/16)) # assume square pic to_file(name+'.lz', Compressed(pic + anim, 'vert', size).output) - - + + # python gfx.py png-to-lz --vert [png] elif sys.argv[2] == '--vert': - + # others are vertically oriented (frontpics are always vertical) - + name = os.path.splitext(sys.argv[3])[0] - + to_2bpp(name+'.png', name+'.2bpp') pic = open(name+'.2bpp', 'rb').read() to_file(name+'.lz', Compressed(pic + anim, 'vert').output) - - + + # python gfx.py png-to-lz [png] else: - + # standard usage - + png_to_lz(sys.argv[2]) - + elif sys.argv[1] == 'png-to-2bpp': to_2bpp(sys.argv[2]) - - + + elif sys.argv[1] == 'de': # python gfx.py de [addr] [fileout] [mode] - + rom = load_rom() - + addr = int(sys.argv[2],16) fileout = sys.argv[3] mode = sys.argv[4] decompress_from_address(addr, fileout, mode) if debug: print 'decompressed to ' + sys.argv[3] + ' from ' + hex(int(sys.argv[2],16)) + '!' - + elif sys.argv[1] == 'lz': # python gfx.py lz [filein] [fileout] [mode] filein = sys.argv[2] @@ -1583,32 +1583,32 @@ if __name__ == "__main__": mode = sys.argv[4] compress_file(filein, fileout, mode) if debug: print 'compressed ' + filein + ' to ' + fileout + '!' - + elif sys.argv[1] == 'lzf': # python gfx.py lzf [id] [fileout] compress_monster_frontpic(int(sys.argv[2]), sys.argv[3]) - + elif sys.argv[1] == 'un': # python gfx.py un [address] [num_tiles] [filename] rom = load_rom() get_uncompressed_gfx(int(sys.argv[2],16), int(sys.argv[3]), sys.argv[4]) - + elif sys.argv[1] == 'pal': # python gfx.py pal [address] [length] rom = load_rom() print grab_palettes(int(sys.argv[2],16), int(sys.argv[3])) - + elif sys.argv[1] == 'png': - + if '.2bpp' in sys.argv[2]: if sys.argv[4] == 'greyscale': to_png(sys.argv[2], sys.argv[3]) else: to_png(sys.argv[2], sys.argv[3], sys.argv[4]) - + elif '.png' in sys.argv[2]: to_2bpp(sys.argv[2], sys.argv[3]) - + elif sys.argv[1] == 'mass-decompress': mass_decompress() if debug: print 'decompressed known gfx to pokecrystal/gfx/!' From 604cafc27859700e45f4c54237b54f3c584cf458 Mon Sep 17 00:00:00 2001 From: yenatch Date: Fri, 17 May 2013 02:05:26 -0400 Subject: [PATCH 4/9] gfx: lz to png from command line --- extras/gfx.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extras/gfx.py b/extras/gfx.py index 0a2d60dfa..53f954b21 100644 --- a/extras/gfx.py +++ b/extras/gfx.py @@ -1525,6 +1525,9 @@ if __name__ == "__main__": if sys.argv[1] == 'dump-pngs': mass_to_colored_png() + elif sys.argv[1] == 'lz-to-png': + lz_to_png_by_file(sys.argv[2]) + elif sys.argv[1] == 'png-to-lz': # python gfx.py png-to-lz [--front anim(2bpp) | --vert] [png] From 77e365d49ffc6caa5364b405a263e4a43938def1 Mon Sep 17 00:00:00 2001 From: yenatch Date: Fri, 17 May 2013 17:28:37 -0400 Subject: [PATCH 5/9] gfx: fix a typo --- extras/gfx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extras/gfx.py b/extras/gfx.py index 53f954b21..8898f77cf 100644 --- a/extras/gfx.py +++ b/extras/gfx.py @@ -1554,7 +1554,7 @@ if __name__ == "__main__": to_2bpp(name+'.png', name+'.2bpp') pic = open(name+'.2bpp', 'rb').read() - to_file(name+'.lz', Compressed(pic + anim, 'vert').output) + to_file(name+'.lz', Compressed(pic, 'vert').output) # python gfx.py png-to-lz [png] From b5e0efc1182db878085f5d5bff99a0c7d94f88aa Mon Sep 17 00:00:00 2001 From: yenatch Date: Sat, 18 May 2013 01:53:16 -0400 Subject: [PATCH 6/9] Working lz make target --- Makefile | 43 ++++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/Makefile b/Makefile index ce6258cc8..2d74da1f9 100644 --- a/Makefile +++ b/Makefile @@ -11,20 +11,17 @@ TEXTFILES = \ text/common_3.tx \ main.tx -VERTGFX = \ - gfx/pics/%.png \ - gfx/trainers/%.png +PNG_PICS = $(shell find gfx/pics/ -type f -name 'front.png') +PNG_ANIMS = $(shell find gfx/pics/ -type f -name 'tiles.png') +PNG_TRAINERS = gfx/trainers/*.png +PNG_GFX = $(PNG_PICS) $(PNG_ANIMS) $(PNG_TRAINERS), $(filter-out $(shell find gfx/ -type f -name '*.png')) -HORIZGFX = $(filter-out gfx/%.png, $(VERTGFX)) +LZ_PICS = $(shell find gfx/pics/ -type f -name 'front.lz') +LZ_ANIMS = $(shell find gfx/pics/ -type f -name 'tiles.lz') +LZ_TRAINERS = gfx/trainers/*.lz +LZ_GFX = $(filter-out $(LZ_PICS) $(LZ_ANIMS) $(LZ_TRAINERS), $(shell find gfx/ -type f -name '*.lz')) -# uncomment this build target to enable png import: - -#all: lzs - -# the recompressed graphics may be larger than the originals, -# so take care to reorganize accordingly - all: pokecrystal.gbc cmp baserom.gbc $< @@ -39,7 +36,7 @@ winclean: pokecrystal.o: pokecrystal.asm constants.asm wram.asm ${TEXTFILES} rgbasm -o pokecrystal.o pokecrystal.asm - + .asm.tx: python preprocessor.py < $< > $@ @@ -47,19 +44,19 @@ pokecrystal.gbc: pokecrystal.o rgblink -o $@ $< rgbfix -Cjv -i BYTE -k 01 -l 0x33 -m 0x10 -p 0 -r 3 -t PM_CRYSTAL $@ - -lzs: ${VERTGFX} ${HORIZGFX} - pngs: cd extras && python gfx.py mass-decompress && python gfx.py dump-pngs +lzs: $(LZ_PICS) $(LZ_ANIMS) $(LZ_TRAINERS) $(LZ_GFX) -front.png: tiles.png - cd extras && python gfx.py png-to-lz --front $@ $(OBJECT_DIRECTORY)/tiles.2bpp -tiles.png: - cd extras && python gfx.py png-to-2bpp $@ -.png:: ${VERTGFX} - cd extras && python gfx.py png-to-lz --vert $@ -.png:: ${HORIZGFX} - cd extras && python gfx.py png-to-lz $@ +gfx/pics/%/front.lz: gfx/pics/%/front.png gfx/pics/%/tiles.2bpp + python extras/gfx.py png-to-lz --front $< $(@D)/tiles.2bpp +gfx/pics/%/tiles.2bpp: + python extras/gfx.py png-to-2bpp $< +gfx/pics/%/back.lz: gfx/pics/%/back.png + python extras/gfx.py png-to-lz --vert $< +gfx/trainers/%.lz: gfx/trainers/%.png + python extras/gfx.py png-to-lz --vert $< +.png.lz: + python extras/gfx.py png-to-lz $< From e3565bd7007b194b04ceecaf1909fff65062211f Mon Sep 17 00:00:00 2001 From: yenatch Date: Sat, 18 May 2013 03:26:06 -0400 Subject: [PATCH 7/9] gfx: safer alternating-byte detection in compression --- extras/gfx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extras/gfx.py b/extras/gfx.py index 8898f77cf..54cd04c5b 100644 --- a/extras/gfx.py +++ b/extras/gfx.py @@ -467,7 +467,7 @@ class Compressed: num_alts = 0 # make sure we don't check for alts at the end of the file - if self.address+2 >= self.end: return False + if self.address+3 >= self.end: return False self.alts.append(self.byte) self.alts.append(ord(self.image[self.address+1])) From 40946fd5254ca4447dcb7c394c9d4ff2273aad2a Mon Sep 17 00:00:00 2001 From: yenatch Date: Sat, 18 May 2013 04:13:45 -0400 Subject: [PATCH 8/9] confident enough in lz make target to include it in general compile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2d74da1f9..6a17a0b5a 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ clean: winclean: del main.tx pokecrystal.o pokecrystal.gbc .\text\sweethoney.tx .\text\phone\bill.tx .\text\phone\elm.tx .\text\phone\mom.tx .\text\phone\trainers1.tx .\text\common.tx .\text\common_2.tx .\text\common_3.tx -pokecrystal.o: pokecrystal.asm constants.asm wram.asm ${TEXTFILES} +pokecrystal.o: pokecrystal.asm constants.asm wram.asm ${TEXTFILES} lzs rgbasm -o pokecrystal.o pokecrystal.asm .asm.tx: From ca22f95db0a89cfa73ba445951e83f131c425d6c Mon Sep 17 00:00:00 2001 From: yenatch Date: Sat, 18 May 2013 04:14:55 -0400 Subject: [PATCH 9/9] get rid of windows build components who needs those anyway? --- Makefile | 8 -------- pokecrystal.bat | 3 --- 2 files changed, 11 deletions(-) delete mode 100644 pokecrystal.bat diff --git a/Makefile b/Makefile index 6a17a0b5a..a83f185a2 100644 --- a/Makefile +++ b/Makefile @@ -24,16 +24,8 @@ LZ_GFX = $(filter-out $(LZ_PICS) $(LZ_ANIMS) $(LZ_TRAINERS), $(shell find gfx/ - all: pokecrystal.gbc cmp baserom.gbc $< - -win: pokecrystal.gbc - fc baserom.gbc $< - clean: rm -f main.tx pokecrystal.o pokecrystal.gbc ${TEXTFILES} - -winclean: - del main.tx pokecrystal.o pokecrystal.gbc .\text\sweethoney.tx .\text\phone\bill.tx .\text\phone\elm.tx .\text\phone\mom.tx .\text\phone\trainers1.tx .\text\common.tx .\text\common_2.tx .\text\common_3.tx - pokecrystal.o: pokecrystal.asm constants.asm wram.asm ${TEXTFILES} lzs rgbasm -o pokecrystal.o pokecrystal.asm diff --git a/pokecrystal.bat b/pokecrystal.bat deleted file mode 100644 index 211c33b1f..000000000 --- a/pokecrystal.bat +++ /dev/null @@ -1,3 +0,0 @@ -@set PATH=%PATH%;C:\Program Files (x86)\GnuWin32\bin\;C:\Python27\ -@make winclean && make win -@pause