Esempio n. 1
0
def decompress(f,offset):
    f.seek(offset)

    #Number of tiles
    no_tiles = rom_utilities.readbyte(f)
    print "Number of tiles:", no_tiles
    offset += 1

    #REALLY HARD PART
    #DECOMPRESS TILES
    print "Decompressing the tiles."

    tile_counter = 0
    row_counter = 0
    tile_pointer = 0x00FE
    carry = 0
    raw_tiles = []

    #This code is confusing, but it works perfectly!
    while no_tiles > tile_counter:
        f.seek(offset)
        x = rom_utilities.readbyte(f) #x = register c in ASM code
        offset += 1
        counter = 8
        while counter > 0:
            tile_pointer = (tile_pointer & 0xFF) + 0x3F00
            f.seek(tile_pointer)
            y = rom_utilities.readbyte(f) #y = register a in ASM code
            tile_pointer -= 0x100
            x = (x << 1) + carry
            if x > 0xFF:
                carry = 1
            else:
                carry = 0
                tile_pointer -= 0x100
                y = rom_utilities.swap_nib(y)
            x &= 0xFF
            y = (y << 1) + carry
            if y > 0xFF:
                carry = 1
                f.seek(tile_pointer)
                temp = rom_utilities.readbyte(f)
                tile_pointer = (tile_pointer & 0xFF00) + temp
                counter -= 1
            else:
                carry = 0
                f.seek(tile_pointer)
                tile = rom_utilities.readbyte(f)
                raw_tiles.append(tile)
                row_counter += 1
                tile_pointer = (tile_pointer & 0xFF00) + 0x00FE
                if row_counter == 0x10:
                    row_counter = 0
                    tile_counter += 1
                    if no_tiles <= tile_counter:
                        counter = 0 #This breaks the loop!
                counter -= 1
            y &= 0xFF
    return raw_tiles, no_tiles
Esempio n. 2
0
 def readnibble(self, count=1):
     self.value = 0
     for i in range(count):
         self.value <<= 4
         if self.nib_pos == 1:
             if self.log == True:
                 self.logfile.write("ROM: {:#x}\r\n".format(self.offset))
             self.byte = rom_utilities.readbyte(self.rom)
             self.nibble = (self.byte & 0xF0) >> 4
             self.nib_pos = 0
         elif self.nib_pos == 0:
             self.nibble = (self.byte & 0xF)
             self.nib_pos = 1
             self.offset += 1
         self.value |= self.nibble
     return self.value
Esempio n. 3
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
Esempio n. 4
0
def gen_image(f,width,height,no_tiles,pixel_map,level_map,level_id,tile_address,color_address,mode,game,water_shade,water_level):
    """Generates uncompressed image of level.
    f: File to read from.
    width: Width of LEVEL in 32x32-pixel tiles.
    height: Height of LEVEl in 32x32-pixel tiles.
    no_tiles: Number of tiles.
    pixel_map: Pixel map, generated from tile.decompress().
    level_map: Uncompress level map from decompress() function.
    level_id: Internal level id.
    tile_address: Location of tile arrangement (used to arrange 8x8 tiles into 32x32 ones).
    color_address: Location of color tile arrangement.
    mode: "GB", "SGB", or "GBC".
    game: "DKL2" or "DKL3", used for proper water shading in certain levels.
    water_shade: Either 1 or 0. 1 causes water to be shaded (in DKL2, only white pixels are shaded; in DKL3, all pixels except black are shaded).
    water_level: Elevation of water. 0 means the level is filled with water; each time this is increased by 1, the water level drops by 32 pixels. It is then further dropped by a certain constant (3 for DKL2, 11 for DKL3)."""
    map_image = []
    
    imageheight = height*0x20
    imagewidth = width*0x20
    imagearea = imageheight*imagewidth

    for i in range(imageheight):
        map_image.append([])

    for i in range(height):
        for j in range(0x4):
            for k in range(width):
                tile_value = level_map[i][k] & 0x7F
                for l in range(0x4):
                    tile_pos = j*4+l
                    tile_pointer = tile_address + tile_value + 0x100 * tile_pos
                    f.seek(tile_pointer)
                    map_tile = rom_utilities.readbyte(f)
                    pixel_pos = map_tile * 0x8
                    if mode == "GBC":
                        #Color the tiles now
                        color_pointer = color_address + tile_value + 0x100 * tile_pos
                        f.seek(color_pointer)
                        color_tile = rom_utilities.readbyte(f)
                        color_tile &= 0xF
                    else:
                        color_tile = 0
                    for m in range(0x8):
                        for n in range(0x8):
                            if pixel_pos < no_tiles * 8:
                                #Get the right pixel based on the map tiles, the way they are arranged, the level map, and the current pixel coordinates
                                pixel = pixel_map[m][n+pixel_pos]
                            else:
                                pixel = 0
                            pixel = pixel + color_tile*4
                            #Shade the water if necessary
                            if game == "DKL3" and water_shade == 1 and water_level * 0x20 < i * 0x20 + j * 0x8 + m - 11:
                                if mode != "GBC":
                                    if pixel > 0: #Non-black pixels
                                        pixel -= 1
                                else: #Darken one color -- uses weird raster effect in-game
                                    if level_id == 34 and pixel == 1: #Ugly Ducting
                                        pixel = 32
                                    elif level_id == 25 and pixel == 3: #Haunted Hollows
                                        pixel = 32
                            elif game == "DKL2" and water_shade == 1 and water_level * 0x20 < i * 0x20 + j * 0x8 + m - 3:
                                if pixel == 3: #White pixels
                                    pixel = 2 #Turn to light gray
                            map_image[m+0x8*j+0x20*i].append(pixel)
        #Refreshes after every 96 rows of pixels                    
        progress = (i + 1)* 100 / height
        if i % 3 == 2 or i + 1 == height or i == 0:
            sys.stdout.write("Progress: {: >7} of {: >7} pixels ({: >3}%) [{}]\r".format((i+1)*0x20*imagewidth, imagearea, progress, "-" * (progress / 5) + " " * ((104 - progress) / 5)))
            sys.stdout.flush()
            
    return map_image
Esempio n. 5
0
game = None
region = None
version = 0

if header == b"DONKEYKONGLAND95":
    game, region = "DKL", "U"
elif header == b"SUPERDONKEYKONG ":
    game, region = "DKL", "J"
elif header == b"DONKEYKONGLAND 2":
    game, region = "DKL2", "U"
elif header == b"DONKEYKONGLAND\0\0":
    game, region = "DKL2", "J"
elif header == b"DONKEYKONGLAND 3":
    game, region = "DKL3", "U"
    rom.seek(0x14C)
    version = rom_utilities.readbyte(rom) #There are two English versions of DKL3, but I don't know of any difference besides the title screen.
elif header == b"DONKEY KONGAD3J\xC0":
    game, region = "DKL3", "J"
elif header == b"DK COUNTRY\0BDDE\xC0":
    game, region = "DKC", "U"
elif header == b"DKC 2001\0\0\0BDDJ\xC0":
    game, region = "DKC", "J"
else: #Reject non-Donkey Kong ROM
    sys.exit("Invalid ROM. You must use a DKL2 or DKL3 ROM.")

if game == "DKC":
    sys.exit("Donkey Kong Country (GBC) is not supported by this tool. A different tool would have to be used, although it is unlikely that I will ever create one due to lack of interest.")
if game == "DKL":
    sys.exit("Donkey Kong Land is not supported by this tool. A different tool would have to be used!")
elif game == "DKL2":
    if region == "U":