Example #1
0
def decompress(f, offset, game, region, level, level_type):
    """Returns a decompressed map.
    f = file
    offset = Self explanatory.
    game = Name of game, used for the filename for logging.
    level = Name of level, used for the filename for logging.
    level_type = Type of level (e.g. Stilt), used for the filename for logging."""
    width = rom_utilities.readbyte(f)
    height = rom_utilities.readbyte(f)
    print "Width: {}; Height: {}".format(width,height)
    offset += 2

    read_map = True
    level_map = []
    for i in range(height):
        level_map.append([])
    x = 0
    y = 0
    buf = []

    #Log to external file for debugging
    if settings.maplog == True:
        if not os.path.exists(os.path.join(os.getcwd(), game)):
            os.makedirs(os.path.join(os.getcwd(), game))
        if not os.path.exists(os.path.join(os.getcwd(), game, 'Logs')):
            os.makedirs(os.path.join(os.getcwd(), game, 'Logs'))

        filename = "{}_{}_Map_{}_log.txt".format(game,region,level_info.level_names[game][level].replace(" ","_"))
        filename = os.path.join(os.getcwd(), game, 'Logs', filename)
        maplog = open(filename, "wb")
        print "Writing log of map decompression to {}.".format(filename)

        rom_utilities.write_log("Width: {}; Height: {}".format(width,height),maplog)
    else:
        maplog = None

    #Decompression starts here, read one nibble (4 bits) at a time
    nib = Nibble(f, maplog, offset, settings.maplog)

    while read_map == True:
        nibble = nib.readnibble()
        rom_utilities.write_log("Nibble read: {:#x}".format(nibble),maplog)
        #Nibbles 0xC, 0xD, 0xE, and 0xF are special cases, as are nibble pairs 0xBE and 0xBF
        #(Note: Figuring this out would have been IMPOSSIBLE without BGB's debugger!!)
        if nibble == 0xB:
            temp = nibble
            nibble = nib.readnibble()
            rom_utilities.write_log("Nibble read: {:#x}".format(nibble),maplog)
            if nibble < 0xE: #Normal byte, this means nibble pair is from 0xB0-0xBD -- this is uncompressed
                buf.append((temp << 4) + nibble)
            elif nibble == 0xE: #0xBE
                #Decompress certain tiles, increasing tile value by 1 each time (useful for long tile structures)
                tile = nib.readnibble(count=2)
                rom_utilities.write_log("Tile: {:#04x}".format(tile),maplog)
                length = nib.readnibble()
                rom_utilities.write_log("Length: {:#x} + 0x3 -> {:#x}".format(length,length+0x3),maplog)
                for i in range(length+3):
                    buf.append(tile)
                    tile += 1
                    
            elif nibble == 0xF: #0xBF
                #Meant for 32x16-tile structures
                tile1 = nib.readnibble(count=2)
                tile2 = nib.readnibble(count=2)

                length = nib.readnibble()

                rom_utilities.write_log("Tile 1: {:#04x}; Tile 2: {:#04x}; Length: {:#x} + 0x2 -> {:#x}".format(tile1, tile2, length, length+0x2),maplog)
                for i in range(length+2):
                    buf.append(tile1)
                    buf.append(tile2)
        elif nibble == 0xC:
            #Repeat previously used tile sequence, allows the same structure over and over
            rew = nib.readnibble(count=2)

            if rew % 2 == 1: #Odd number
                rew = rew / 2 + 1
                rew = rew + (nib.readnibble() << 7)
            else: #Even number
                rew = rew / 2 + 1

            rom_utilities.write_log("Rewind: {:#04x}".format(rew),maplog)
            
            length = nib.readnibble()

            if length == 0xF:
                length = nib.readnibble(count=2)

            rom_utilities.write_log("Length: {:#04x} + 0x04 -> {:#04x}".format(length, length+0x4),maplog)

            for i in range(length+4):
                ptr = y*width+x - rew
                ptr_y = ptr / width
                ptr_x = ptr % width
                tile = level_map[ptr_y][ptr_x]
                rom_utilities.write_log("Pointer: {}; x: {}; y: {}; tile: {:#04x}; RAM: {:#x} -> {:#x}".format(ptr, ptr_x, ptr_y, tile, 0xC600+(height+4)*2+ptr, 0xC600+(height+4)*2+y*width+x),maplog)
                if x >= width:
                    y += 1
                    x = 0
                try:
                    level_map[y].append(tile)
                except IndexError:
                    if settings.warnings == True:
                        #Error handler
                        print "WARNING: Out of range!\n\
There are more bytes than the map can handle.\n\
This is either due to a badly hacked ROM, a bug in the game itself,\n\
or an error in the program's decompression function.\n\
If this results in a buggy map, please report this.\n\
x: {}; y: {}; i: {}; buffer: {}; map width: {}; map height: {}; RAM: {:#x}; ROM: {:#x}\n\
Press Enter to continue or Ctrl+C to quit.".format(x,y,i,buf,len(level_map[0]),len(level_map),0xC600+(height+4)*2+y*width+x,offset)
                    read_map = False
                    byte = 0xEE
                    f.seek(1,1)
                    raw_input()
                    break
                x += 1

        elif nibble == 0xD:
            #Here, tiles only take up four bits -- useful when lots of tiles in a row are next to each other
            high_nibble = nib.readnibble() << 4
            rom_utilities.write_log("High nibble: {:#x}".format(high_nibble),maplog)
            length = nib.readnibble(count=2)
            rom_utilities.write_log("Length: {:#04x} + 0x14 -> {:#x}".format(length,length+0x14),maplog)

            for i in range(length+0x14):
                low_nibble = nib.readnibble()
                tile = high_nibble | low_nibble
                rom_utilities.write_log("Low nibble: {:#x}; Tile: {:#04x}".format(low_nibble, tile),maplog)
                buf.append(tile)
        
        elif nibble == 0xE:
            #Same case as before, but for shorter structures (also takes slightly up less space in ROM)
            high_nibble = nib.readnibble() << 4
            rom_utilities.write_log("High nibble: {:#x}".format(high_nibble),maplog)
            if high_nibble == 0xE0: #We are done!! Stop reading map tiles
                read_map = False
            else:
                length = nib.readnibble()

                rom_utilities.write_log("Length: {:#x} + 0x4 -> {:#x}".format(length,length+0x4),maplog)

                for i in range(length+4):
                    low_nibble = nib.readnibble()
                    tile = high_nibble | low_nibble
                    rom_utilities.write_log("Low nibble: {:#x}; Tile: {:#04x}".format(low_nibble, tile),maplog)
                    buf.append(tile)

        elif nibble == 0xF:
            #Same tile repeated over and over
            tile = nib.readnibble(count=2)
            length = nib.readnibble()

            rom_utilities.write_log("Tile: {:#04x}".format(tile),maplog)

            if length >= 8:
                length = ((length & 0x7) << 4) + nib.readnibble()
                rom_utilities.write_log("Length: {:#04x} + 0x0b -> {:#04x}".format(length,length+0xb),maplog)

                for i in range(length+11):
                    buf.append(tile)
            else:
                rom_utilities.write_log("Length: {:#x} + 0x3 -> {:#x}".format(length,length+0x3),maplog)
                for i in range(length+3):
                    buf.append(tile)
        else: #Normal case!! Uncompressed tile (0x00-0xAF)
            temp = nibble
            nibble = nib.readnibble()
            rom_utilities.write_log("Nibble read: {:#x}".format(nibble),maplog)
            tile = (temp << 4) + nibble
            buf.append(tile)
            rom_utilities.write_log("Uncompressed tile! ({:#04x})".format(tile),maplog)
        if len(buf) > 1:
            rom_utilities.write_log("Buffer: {}; x: {}; y: {}; RAM: {:#x} - {:#x}".format(buf,x,y,0xC600+(height+4)*2+y*width+x,0xC5FF+(height+4)*2+y*width+x+len(buf)),maplog)
        elif len(buf) == 1:
            rom_utilities.write_log("Buffer: {}; x: {}; y: {}; RAM: {:#x}".format(buf,x,y,0xC600+(height+4)*2+y*width+x),maplog)
        else:
            rom_utilities.write_log("x: {}; y: {}; RAM: {:#x}".format(x-1,y,0xC5FF+(height+4)*2+y*width+x),maplog)
        #Write the tiles
        for i in buf:
            if x >= width:
                y += 1
                x = 0
            try:
                level_map[y].append(i)
            except IndexError:
                if settings.warnings == True:
                    #Error handler
                    print "WARNING: Out of range!\n\
There are more bytes than the map can handle.\n\
This is either due to a badly hacked ROM, a bug in the game itself,\n\
or an error in the program's decompression function.\n\
If this results in a buggy map, please report this.\n\
x: {}; y: {}; i: {}; buffer: {}; map width: {}; map height: {}; RAM: {:#x}; ROM: {:#x}\n\
Press Enter to continue or Ctrl+C to quit.".format(x,y,i,buf,len(level_map[0]),len(level_map),0xC600+(height+4)*2+y*width+x,offset)
                read_map = False
                nib.byte = 0xEE
                f.seek(1,1)
                raw_input()
                break
            x += 1
        buf = []

    print "Done decompressing the map tiles."
    if settings.maplog == True:
        print "Done writing to log."
        maplog.close()
    #print level_map

    print "Note: Sprites are not supported yet."

    #Initial code for sprites
    if nib.byte & 0xF0 == 0xE0 and nib.byte != 0xE0 and nib.byte != 0xEE:
        #Not sure if this ever actually occurs, but it imitates the game
        #This function exists because sometimes the EE string ends on a high nibble (e.g. _E E_); sometimes on a low one (e.g. __ EE __)
        f.seek(-1,1)
    #Initially, any tile with a sprite is initialized to 0x80. We must find the sprite table to use the correct graphics (and add sprites when that gets implemented)
    #Sprite pointers take place just after the initial map data, and they are read each time the game finds a tile with value 0x80 or greater
    for i,v in enumerate(level_map):
        for j,w in enumerate(level_map[i]):
            tile_id = w
            if tile_id == 0xFF:
                break
            if rom_utilities.bit_test(tile_id,7) == 1:
                sprite_id = rom_utilities.readbyte(f)
                level_map[i][j] = sprite_id

    sprite_address = rom_utilities.get_abs_ptr(f, 0x10, 0x4000B, level_type)
    sprite_table = []
    #Now the sprite pointers are used to find tuples with the right tile graphics and the sprites (latter is not implemented yet)
    for i,v in enumerate(level_map):
        for j,w in enumerate(level_map[i]):
            sprite_id = w
            if rom_utilities.bit_test(sprite_id,7) == 1:
                sprite_id &= 0x7F
                f.seek(sprite_address + sprite_id*3)
                tile_id = rom_utilities.readbyte(f)
                level_map[i][j] = tile_id
                sprite_id = struct.unpack("<BB",f.read(2))
                sprite_table.append((i,j,sprite_id[0],sprite_id[1]))
                #Used to implement sprites later on!!!
    return width, height, level_map, sprite_table
Example #2
0
level_id = level_ptrs[game][level]
print "Internal level ID: {}".format(level_id)
print "You have selected: {}.".format(level_names[game][level])

#Get level header data, which is used for various things like the right tiles, water level, etc.
level_header = rom_utilities.get_rel_ptr(rom, 0x10, 0x40001, level_id)
print "Level header offset: {:#x}".format(level_header)

#Get level type (Stilt, River, etc.)
rom.seek(level_header+1)

level_type = rom_utilities.readbyte(rom)
print "Level type: {:#x} ({})".format(level_type,level_types[game][level_type])

#Find address for compressed tiles
tile_offset = rom_utilities.get_abs_ptr(rom, 0x10, 0x40005, level_type)
print "Compressed tiles are located at: {:#x}".format(tile_offset)

raw_tiles, no_tiles = tiles.decompress(rom,tile_offset)

#Output tileset as PNG file
if settings.tileset == True:
    imagearea=(no_tiles+0xF)/0x10*0x400
    imagewidth=0x80
    imageheight=imagearea/imagewidth
    pixel_map = rom_utilities.decode_tiles(imageheight, imagewidth, raw_tiles, 1)

    grayscale = True

    #Could use some cleaning up
    if grayscale == True: