From ae167617cc902417a2fe65524d58f508e3abe4e7 Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Mon, 5 Jan 2026 19:44:02 -0600 Subject: [PATCH] background: add support for 7shades output --- 7shades_binary_spec.txt | 31 +++++ background.py | 244 +++++++++++++++++++++++++++------------- 2 files changed, 199 insertions(+), 76 deletions(-) create mode 100644 7shades_binary_spec.txt diff --git a/7shades_binary_spec.txt b/7shades_binary_spec.txt new file mode 100644 index 0000000..4e4cd9e --- /dev/null +++ b/7shades_binary_spec.txt @@ -0,0 +1,31 @@ + +- 32 byte header that is read as 8 longword values as follows: + +struct { + uint32_t file_type_id; // must be the constant value "0x00000005" + uint32_t size_of_cel_data; // bytes + uint32_t size_of_map_data; // bytes + uint32_t tile_character_size; // (slChar) one of CHAR_SIZE_* + + uint32_t tile_color_mode; // (slChar) one of COL_TYPE_* + uint32_t plane_size; // (slPlane) one of PL_SIZE_* + uint32_t map_data; // (slPage) one of PNB_*, one of CN_* + + uint16_t height; // must be the constant value "1" + uint16_t width; // must be the constant value "1" +} + + -A file type ID - arbitrarily chosen as [5] for this binary type + -# of bytes of Cel Data in binary + -# of bytes of Map Data in binary + -Tile Character Size (Macros defined in sl_def.h) + -Tile Character Color Mode (Macros defined in sl_def.h) + -Plane Unit Size (Macros defined in sl_def.h) + -Map data config (Macros defined in sl_def.h) + -Plane dimensions (Height<<16|Width) + +- Pallet data block of 0, 32, or 512 bytes based on the color mode indicated in header + +- Map data block of size indicated in header + +- Cel data block of size indicated in header diff --git a/background.py b/background.py index 4d13795..179209f 100644 --- a/background.py +++ b/background.py @@ -1,3 +1,4 @@ +import io import sys from pprint import pprint, pformat import textwrap @@ -27,21 +28,18 @@ def pack_old_palette_chunk(old_palette_chunk): for color in old_palette_chunk.packets[0].colors: f.write(pack_bgr555(*color)) -def pack_palette_chunk(palette_chunk): - with open("palette.bin", "wb") as f: - assert palette_chunk.first_color_index_to_change == 0 +def pack_palette_chunk(f, palette_chunk): + assert palette_chunk.first_color_index_to_change == 0 - for entry in palette_chunk.entries: - color = (entry.red, entry.green, entry.blue) - f.write(pack_bgr555(*color)) + for entry in palette_chunk.entries: + color = (entry.red, entry.green, entry.blue) + f.write(pack_bgr555(*color)) - print("palette.bin", f.tell(), file=sys.stderr) - -def pack_palette(palette): +def pack_palette(f, palette): if type(palette) is PaletteChunk: - pack_palette_chunk(palette) + pack_palette_chunk(f, palette) elif type(palette) is OldPaletteChunk: - pack_old_palette_chunk(palette) + pack_old_palette_chunk(f, palette) else: assert False, type(palette) @@ -82,87 +80,181 @@ def pack_character_1x1(tileset_chunk, offset): return bytes(buf) -def pack_character_patterns(filename, tileset_chunk): - with open(filename, "wb") as f: - for i in range(tileset_chunk.number_of_tiles): - offset = tileset_chunk.tile_width * tileset_chunk.tile_height * i +def pack_character_patterns(f, tileset_chunk): + for i in range(tileset_chunk.number_of_tiles): + offset = tileset_chunk.tile_width * tileset_chunk.tile_height * i - if tileset_chunk.tile_width == 8 and tileset_chunk.tile_height == 8: - buf = pack_character_1x1(tileset_chunk, offset) - elif tileset_chunk.tile_width == 16 and tileset_chunk.tile_height == 16: - buf = pack_character_2x2(tileset_chunk, offset) - else: - assert False, (tileset_chunk.tile_width, tileset_chunk.tile_height) + if tileset_chunk.tile_width == 8 and tileset_chunk.tile_height == 8: + buf = pack_character_1x1(tileset_chunk, offset) + elif tileset_chunk.tile_width == 16 and tileset_chunk.tile_height == 16: + buf = pack_character_2x2(tileset_chunk, offset) + else: + assert False, (tileset_chunk.tile_width, tileset_chunk.tile_height) - f.write(buf) + f.write(buf) - print(filename, f.tell(), file=sys.stderr) +def pack_pattern_name_table(f, cel_chunk, x_cells, y_cells): + assert type(cel_chunk.data) == CelChunk_CompressedTilemap + #assert cel_chunk.data.width_in_number_of_tiles <= 64 + #assert cel_chunk.data.height_in_number_of_tiles <= 64 -def pack_pattern_name_table(filename, cel_chunk, x_cells, y_cells): - with open(filename, "wb") as f: - assert type(cel_chunk.data) == CelChunk_CompressedTilemap - #assert cel_chunk.data.width_in_number_of_tiles <= 64 - #assert cel_chunk.data.height_in_number_of_tiles <= 64 + tile_width = cel_chunk.data.width_in_number_of_tiles + tile_height = cel_chunk.data.height_in_number_of_tiles - tile_width = cel_chunk.data.width_in_number_of_tiles - tile_height = cel_chunk.data.height_in_number_of_tiles + print(tile_width, tile_height) - print(tile_width, tile_height) + h_pages = ((tile_width + (x_cells - 1)) & (~(x_cells - 1))) // x_cells + v_pages = ((tile_height + (y_cells - 1)) & (~(y_cells - 1))) // y_cells - h_pages = ((tile_width + (x_cells - 1)) & (~(x_cells - 1))) // x_cells - v_pages = ((tile_height + (y_cells - 1)) & (~(y_cells - 1))) // y_cells + if h_pages > 2: + h_pages = 2 + if v_pages > 2: + v_pages = 2 - if h_pages > 2: - h_pages = 2 - if v_pages > 2: - v_pages = 2 + print("h_pages, v_pages", h_pages, v_pages) - print("h_pages, v_pages", h_pages, v_pages) + for v_page in range(v_pages): + for h_page in range(h_pages): + for y in range(y_cells): + for x in range(x_cells): + tx = (h_page * x_cells) + x + ty = (v_page * y_cells) + y + if tx >= tile_width or ty >= tile_height: + f.write(pack_index(0)) + else: + cel_chunk_ix = ty * tile_width + tx + tile_data = cel_chunk.data.tile[cel_chunk_ix] - for v_page in range(v_pages): - for h_page in range(h_pages): - for y in range(y_cells): - for x in range(x_cells): - tx = (h_page * x_cells) + x - ty = (v_page * y_cells) + y - if tx >= tile_width or ty >= tile_height: - f.write(pack_index(0)) - else: - cel_chunk_ix = ty * tile_width + tx - tile_data = cel_chunk.data.tile[cel_chunk_ix] + tile_id = tile_data & cel_chunk.data.bitmask_for_tile_id.value + x_flip = (tile_data & cel_chunk.data.bitmask_for_x_flip.value) != 0 + y_flip = (tile_data & cel_chunk.data.bitmask_for_y_flip.value) != 0 - tile_id = tile_data & cel_chunk.data.bitmask_for_tile_id.value - x_flip = (tile_data & cel_chunk.data.bitmask_for_x_flip.value) != 0 - y_flip = (tile_data & cel_chunk.data.bitmask_for_y_flip.value) != 0 + pattern = (int(y_flip) << 31) | (int(x_flip) << 30) | tile_id - pattern = (int(y_flip) << 31) | (int(x_flip) << 30) | tile_id + f.write(pack_index(pattern)) - f.write(pack_index(pattern)) +def generate_separate_files(mem): + tilesets, layers, palette, cel_chunks = parse_file(mem) - print(filename, f.tell(), file=sys.stderr) + with open("palette.bin", "wb") as f: + pack_palette(f, palette) + + for tileset_index, tileset_chunk in sorted(tilesets.items(), key=itemgetter(0)): + filename = f"character_pattern__tileset_{tileset_index}.bin" + print(filename) + with open(filename, "wb") as f: + pack_character_patterns(f, tileset_chunk) + + for layer_index, cel_chunk in sorted(cel_chunks.items(), key=itemgetter(0)): + #layers[layer_index] + print(f"layer={layer_index} layer_name={layers[layer_index].layer_name} tileset={layers[layer_index].tileset_index}"); + tileset_chunk = tilesets[layers[layer_index].tileset_index] + + x_cells = 64 // (tileset_chunk.tile_width // 8) + y_cells = 64 // (tileset_chunk.tile_height // 8) + + filename = f"pattern_name_table__layer_{layer_index}.bin" + print(filename) + with open(filename, "wb") as f: + pack_pattern_name_table(f, cel_chunk, x_cells, y_cells) + +PNCN0__N0PNB__2WORD = (0 << 15) # PNB_2WORD +PNCN0__N0PNB__1WORD = (1 << 15) # PNB_1WORD +PNCN0__N0CNSM = (1 << 14) # CN_12BIT + +PLSZ__N0PLSZ__1x1 = (0b00 << 0) # PL_SIZE_1x1 +PLSZ__N0PLSZ__2x1 = (0b01 << 0) # PL_SIZE_2x1 +PLSZ__N0PLSZ__2x2 = (0b11 << 0) # PL_SIZE_2x2 + +CHCTLA__N0CHCN__16_COLOR = (0b000 << 4) # COL_TYPE_16 +CHCTLA__N0CHCN__256_COLOR = (0b001 << 4) # COL_TYPE_256 +CHCTLA__N0CHCN__2048_COLOR = (0b010 << 4) # COL_TYPE_2048 +CHCTLA__N0CHCN__32K_COLOR = (0b011 << 4) # COL_TYPE_32768 +CHCTLA__N0CHCN__16M_COLOR = (0b100 << 4) # COL_TYPE_1M + +CHCTLA__N0CHSZ__1x1_CELL = (0 << 0) +CHCTLA__N0CHSZ__2x2_CELL = (1 << 0) + +def header_7shades(size_of_cel_data, # bytes + size_of_map_data, # bytes + tile_character_size, # CHCTLA__N0CHSZ + tile_color_mode, # CHCTLA__N0CHCN + plane_size, # PLSZ + map_data, # PNCN0 + ): # + file_type_id = 0x5 + width = 1 + height = 1 + + return struct.pack(">IIIIIIIHH", + file_type_id, + size_of_cel_data, + size_of_map_data, + tile_character_size, + tile_color_mode, + plane_size, + map_data, + width, + height) + +def palette_pad_7shades(f_tmp, palette_size): + # - Pallet data block of 0, 32, or 512 bytes based on the color mode indicated in header + for valid_size in [32, 512]: + if palette_size > valid_size: + continue + while palette_size < valid_size: + f_tmp.write(bytes([0])) + palette_size += 1 + break + return palette_size + +def generate_7shades(mem): + tilesets, layers, palette, cel_chunks = parse_file(mem) + + f_tmp = io.BytesIO() + + pack_palette(f_tmp, palette) + palette_size = f_tmp.getbuffer().nbytes + palette_size = palette_pad_7shades(f_tmp, palette_size) + print("palette_size", palette_size) + + character_patterns_start = f_tmp.getbuffer().nbytes + for tileset_index, tileset_chunk in sorted(tilesets.items(), key=itemgetter(0)): + pack_character_patterns(f_tmp, tileset_chunk) + break + character_patterns_end = f_tmp.getbuffer().nbytes + size_of_cel_data = character_patterns_end - character_patterns_start + print("character_patterns_size", size_of_cel_data) + + pattern_name_tables_start = f_tmp.getbuffer().nbytes + for layer_index, cel_chunk in sorted(cel_chunks.items(), key=itemgetter(0)): + tileset_chunk = tilesets[layers[layer_index].tileset_index] + x_cells = 64 // (tileset_chunk.tile_width // 8) + y_cells = 64 // (tileset_chunk.tile_height // 8) + pack_pattern_name_table(f_tmp, cel_chunk, x_cells, y_cells) + pattern_name_tables_end = f_tmp.getbuffer().nbytes + size_of_map_data = pattern_name_tables_end - pattern_name_tables_start + print("pattern_name_table_size", size_of_map_data) + + tile_character_size = CHCTLA__N0CHSZ__2x2_CELL + tile_color_mode = CHCTLA__N0CHCN__256_COLOR + plane_size = PLSZ__N0PLSZ__2x2 + map_data = PNCN0__N0PNB__2WORD + + header = header_7shades(size_of_cel_data, + size_of_map_data, + tile_character_size, + tile_color_mode, + plane_size, + map_data) + + with open("7shades.bin", "wb") as f: + f.write(header) + f.write(f_tmp.getvalue()) with open(sys.argv[1], 'rb') as f: buf = f.read() mem = memoryview(buf) -tilesets, layers, palette, cel_chunks = parse_file(buf) - -pack_palette(palette) - -for tileset_index, tileset_chunk in sorted(tilesets.items(), key=itemgetter(0)): - filename = f"character_pattern__tileset_{tileset_index}.bin" - pack_character_patterns(filename, tileset_chunk) - -for layer_index, cel_chunk in sorted(cel_chunks.items(), key=itemgetter(0)): - filename = f"pattern_name_table__layer_{layer_index}.bin" - #layers[layer_index] - print(f"layer={layer_index} layer_name={layers[layer_index].layer_name} tileset={layers[layer_index].tileset_index}"); - tileset_chunk = tilesets[layers[layer_index].tileset_index] - - x_cells = 64 // (tileset_chunk.tile_width // 8) - y_cells = 64 // (tileset_chunk.tile_height // 8) - - pack_pattern_name_table(filename, cel_chunk, x_cells, y_cells) - -#for layer_index, layer_chunk in enumerate(layers): -# print(f"layer={layer_index} layer_name={layer_chunk.layer_name} tileset={layer_chunk.tileset_index}"); +#generate_separate_files(mem) +generate_7shades(mem)