From 980a26f08b4ec4bd96daf21acd3666017b909985 Mon Sep 17 00:00:00 2001 From: ProjectRevoTPP Date: Fri, 11 Oct 2024 21:09:53 -0400 Subject: [PATCH] decompress Yay0s and extract PRESJPEGs --- src/3FB0.h | 4 +- tools/decompress_persszp.py | 38 ++++++++ tools/extract_assets.sh | 178 ++++++++++++++++++++++++++++++++++++ tools/extract_presjpeg.py | 36 ++++++++ tools/unpack_asset.py | 8 +- 5 files changed, 259 insertions(+), 5 deletions(-) create mode 100755 tools/decompress_persszp.py create mode 100755 tools/extract_presjpeg.py diff --git a/src/3FB0.h b/src/3FB0.h index fc3256b..a0534a9 100644 --- a/src/3FB0.h +++ b/src/3FB0.h @@ -23,8 +23,8 @@ typedef struct PERSSZP { typedef struct PRESJPEG { /* 0x00 */ char magic[0x8]; // PRESJPEG - /* 0x08 */ u32 unk_08; - /* 0x0C */ u32 unk_0C; + /* 0x08 */ u32 unk_08; // header size? always 0x10 + /* 0x0C */ u32 unk_0C; } PRESJPEG; // size = 0x10 typedef struct Yay0 { diff --git a/tools/decompress_persszp.py b/tools/decompress_persszp.py new file mode 100755 index 0000000..91b3c89 --- /dev/null +++ b/tools/decompress_persszp.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 + +import sys +import re +import os +from pathlib import Path +import crunch64 + +# PERS-SZP decompressor v0.1 (WIP) +# Usage: decompress_persszp.py [path to PERS-SZP file] + +# Read a big endian 32-bit value from a bytearray file with a given offset. +def read_32_be_value(file_arr, i): + return (file_arr[i + 0] << 24) + (file_arr[i + 1] << 16) \ + + (file_arr[i + 2] << 8) + (file_arr[i + 3]) + +assets_path = "assets/us/" + +filepath = Path(sys.argv[1]) +filename = os.path.splitext(os.path.basename(filepath))[0] + +file = open(filepath, 'rb') +file_header = bytearray(file.read(0xC)) # First, we need to fetch the header size. This is right after the magic part of the header. + +# Check for 50 45 52 53 2D 53 5A 50, which is "PERS-SZP" +if file_header[0] != 0x50 or file_header[1] != 0x45 or file_header[2] != 0x52 or file_header[3] != 0x53 or file_header[4] != 0x2D or file_header[5] != 0x53 or file_header[6] != 0x5A or file_header[7] != 0x50: + print(filepath, "File magic is NOT 'PERS-SZP'. Please pass a valid file.") + sys.exit(1) + +header_size = read_32_be_value(file_header, 8) +# Now that we have the header size, seek past the header. +file.seek(header_size, os.SEEK_SET) +yay0_file = file.read() + +decompressed = crunch64.yay0.decompress(yay0_file) +file.close() +file = open(filepath, 'wb') +file.write(decompressed) diff --git a/tools/extract_assets.sh b/tools/extract_assets.sh index 057e316..720d302 100755 --- a/tools/extract_assets.sh +++ b/tools/extract_assets.sh @@ -4,6 +4,7 @@ # TODO: The packed bins do not depend on their extracted contents yet so modifying these files will do nothing. Add dependencies +# Unpack/move any multi or single asset bins. tools/unpack_asset.py assets/us/n64_logo_texture.bin tools/unpack_asset.py assets/us/fonts.bin tools/unpack_asset.py assets/us/backgrounds.bin @@ -52,3 +53,180 @@ tools/unpack_asset.py assets/us/textdata.bin tools/unpack_asset.py assets/us/798CD0.bin tools/unpack_asset.py assets/us/snap_mode_ui.bin tools/unpack_asset.py assets/us/copyright.bin + +# Next, decompress all the PERS-SZP files with a Yay0. TODO: Roll these up +# into recursive calls. All of the files within an asset bin SHOULD be the +# same type, but it in theory it isnt guaranteed to be. Does the game ensure +# that? If so, we should be able to safely roll these up. +tools/decompress_persszp.py assets/us/n64_logo_texture/0/file.bin +tools/decompress_persszp.py assets/us/fonts/0/file.bin +tools/decompress_persszp.py assets/us/fonts/1/file.bin +tools/decompress_persszp.py assets/us/fonts/2/file.bin +tools/decompress_persszp.py assets/us/fonts/3/file.bin +tools/decompress_persszp.py assets/us/fonts/4/file.bin +tools/decompress_persszp.py assets/us/fonts/5/file.bin +tools/decompress_persszp.py assets/us/battle_headers/0/file.bin +tools/decompress_persszp.py assets/us/battle_headers/1/file.bin +tools/decompress_persszp.py assets/us/battle_headers/2/file.bin +tools/decompress_persszp.py assets/us/battle_headers/3/file.bin +tools/decompress_persszp.py assets/us/battle_headers/4/file.bin +tools/decompress_persszp.py assets/us/battle_headers/5/file.bin +tools/decompress_persszp.py assets/us/battle_headers/6/file.bin +tools/decompress_persszp.py assets/us/battle_headers/7/file.bin +tools/decompress_persszp.py assets/us/battle_headers/8/file.bin +tools/decompress_persszp.py assets/us/battle_headers/9/file.bin +tools/decompress_persszp.py assets/us/battle_headers/10/file.bin +tools/decompress_persszp.py assets/us/battle_headers/11/file.bin +tools/decompress_persszp.py assets/us/battle_headers/12/file.bin +tools/decompress_persszp.py assets/us/common_menu0_ui/0/file.bin +tools/decompress_persszp.py assets/us/common_menu1_ui/0/file.bin +tools/decompress_persszp.py assets/us/common_menu2_ui/0/file.bin +tools/decompress_persszp.py assets/us/area_select_ui/0/file.bin +tools/decompress_persszp.py assets/us/menu_select_ui/0/file.bin +tools/decompress_persszp.py assets/us/stadium_select_ui/0/file.bin +tools/decompress_persszp.py assets/us/title_ui/0/file.bin +tools/decompress_persszp.py assets/us/52F6D0/0/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/0/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/1/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/2/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/3/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/4/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/5/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/6/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/7/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/8/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/9/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/10/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/11/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/12/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/13/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/14/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/15/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/16/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/17/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/18/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/19/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/20/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/21/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/22/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/23/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/24/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/25/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/26/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/27/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/28/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/29/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/30/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/31/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/32/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/33/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/34/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/35/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/36/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/37/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/38/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/39/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/40/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/41/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/42/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/43/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/44/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/45/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/46/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/47/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/48/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/49/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/50/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/51/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/52/file.bin +tools/decompress_persszp.py assets/us/battle_portraits/53/file.bin +tools/decompress_persszp.py assets/us/stadium_models/0/file.bin +tools/decompress_persszp.py assets/us/stadium_models/1/file.bin +tools/decompress_persszp.py assets/us/stadium_models/2/file.bin +tools/decompress_persszp.py assets/us/stadium_models/3/file.bin +tools/decompress_persszp.py assets/us/stadium_models/4/file.bin +tools/decompress_persszp.py assets/us/stadium_models/5/file.bin +tools/decompress_persszp.py assets/us/stadium_models/6/file.bin +tools/decompress_persszp.py assets/us/stadium_models/7/file.bin +tools/decompress_persszp.py assets/us/stadium_models/8/file.bin +tools/decompress_persszp.py assets/us/stadium_models/9/file.bin +tools/decompress_persszp.py assets/us/stadium_models/10/file.bin +tools/decompress_persszp.py assets/us/stadium_models/11/file.bin +tools/decompress_persszp.py assets/us/stadium_models/12/file.bin +tools/decompress_persszp.py assets/us/stadium_models/13/file.bin +tools/decompress_persszp.py assets/us/stadium_models/14/file.bin +tools/decompress_persszp.py assets/us/stadium_models/15/file.bin +tools/decompress_persszp.py assets/us/stadium_models/16/file.bin +tools/decompress_persszp.py assets/us/stadium_models/17/file.bin +tools/decompress_persszp.py assets/us/5C7A70/0/file.bin +tools/decompress_persszp.py assets/us/pokedex_area_model/0/file.bin +tools/decompress_persszp.py assets/us/sushi_go_round/0/file.bin +tools/decompress_persszp.py assets/us/order_select_ui/0/file.bin +tools/decompress_persszp.py assets/us/cup_ball_select_ui/0/file.bin +tools/decompress_persszp.py assets/us/trade_select_ui/0/file.bin +tools/decompress_persszp.py assets/us/kids_club_select_ui/0/file.bin +tools/decompress_persszp.py assets/us/gym_leader_castle_clear_gfx/0/file.bin +tools/decompress_persszp.py assets/us/rental_rules_ui/0/file.bin +tools/decompress_persszp.py assets/us/sushi_go_round_sprites/0/file.bin +tools/decompress_persszp.py assets/us/kids_club_game_ui/0/file.bin +tools/decompress_persszp.py assets/us/gallery_ui/0/file.bin +tools/decompress_persszp.py assets/us/album_ui/0/file.bin +tools/decompress_persszp.py assets/us/6A9750/0/file.bin +tools/decompress_persszp.py assets/us/snap_select_ui/0/file.bin +tools/decompress_persszp.py assets/us/cup_clear_ui/0/file.bin +tools/decompress_persszp.py assets/us/gb_pak_select_ui/0/file.bin +tools/decompress_persszp.py assets/us/victory_palace_plate_text/0/file.bin +tools/decompress_persszp.py assets/us/run_rattata_run_flag_gfx/0/file.bin +tools/decompress_persszp.py assets/us/kanto_gb_map/0/file.bin +tools/decompress_persszp.py assets/us/6E2F90/0/file.bin +tools/decompress_persszp.py assets/us/battle_ui/0/file.bin +tools/decompress_persszp.py assets/us/transfer_pak_error_ui/0/file.bin +tools/decompress_persszp.py assets/us/6EB340/0/file.bin +tools/decompress_persszp.py assets/us/6EB340/1/file.bin +tools/decompress_persszp.py assets/us/6EB340/2/file.bin +tools/decompress_persszp.py assets/us/6EB340/3/file.bin +tools/decompress_persszp.py assets/us/6EC4D0/0/file.bin +tools/decompress_persszp.py assets/us/badge_ui/0/file.bin +tools/decompress_persszp.py assets/us/gym_leader_castle_ui/0/file.bin +tools/decompress_persszp.py assets/us/798CD0/0/file.bin +tools/decompress_persszp.py assets/us/snap_mode_ui/0/file.bin +tools/decompress_persszp.py assets/us/copyright/0/file.bin + +# Extract all PRESJPEG files. +tools/extract_presjpeg.py assets/us/backgrounds/0/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/1/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/2/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/3/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/4/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/5/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/6/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/7/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/8/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/9/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/10/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/11/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/12/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/13/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/14/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/15/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/16/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/17/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/18/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/19/file.bin +tools/extract_presjpeg.py assets/us/backgrounds/20/file.bin +tools/extract_presjpeg.py assets/us/6CA730/0/file.bin +tools/extract_presjpeg.py assets/us/6CA730/1/file.bin +tools/extract_presjpeg.py assets/us/6CA730/2/file.bin +tools/extract_presjpeg.py assets/us/6CA730/3/file.bin +tools/extract_presjpeg.py assets/us/6CA730/4/file.bin +tools/extract_presjpeg.py assets/us/6CA730/5/file.bin +tools/extract_presjpeg.py assets/us/6CA730/6/file.bin +tools/extract_presjpeg.py assets/us/6CA730/7/file.bin +tools/extract_presjpeg.py assets/us/6CA730/8/file.bin +tools/extract_presjpeg.py assets/us/6CA730/9/file.bin +tools/extract_presjpeg.py assets/us/6CA730/10/file.bin +tools/extract_presjpeg.py assets/us/6CA730/11/file.bin +tools/extract_presjpeg.py assets/us/6CA730/12/file.bin +tools/extract_presjpeg.py assets/us/6CA730/13/file.bin +tools/extract_presjpeg.py assets/us/6CA730/14/file.bin +tools/extract_presjpeg.py assets/us/6CA730/15/file.bin \ No newline at end of file diff --git a/tools/extract_presjpeg.py b/tools/extract_presjpeg.py new file mode 100755 index 0000000..d52a258 --- /dev/null +++ b/tools/extract_presjpeg.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +import sys +import re +import os +from pathlib import Path +import crunch64 + +# PRESJPEG extractor +# Usage: decompress_presjpeg.py [path to PRESJPEG file] + +# Read a big endian 32-bit value from a bytearray file with a given offset. +def read_32_be_value(file_arr, i): + return (file_arr[i + 0] << 24) + (file_arr[i + 1] << 16) \ + + (file_arr[i + 2] << 8) + (file_arr[i + 3]) + +assets_path = "assets/us/" + +filepath = Path(sys.argv[1]) +filename = os.path.splitext(os.path.basename(filepath))[0] + +file = open(filepath, 'rb') +file_header = bytearray(file.read(0xC)) # First, we need to fetch the header size. This is right after the magic part of the header. + +# Check for 50 52 45 53 4A 50 45 47, which is "PRESJPEG" +if file_header[0] != 0x50 or file_header[1] != 0x52 or file_header[2] != 0x45 or file_header[3] != 0x53 or file_header[4] != 0x4A or file_header[5] != 0x50 or file_header[6] != 0x45 or file_header[7] != 0x47: + print(filepath, "File magic is NOT 'PRESJPEG'. Please pass a valid file.") + sys.exit(1) + +header_size = read_32_be_value(file_header, 8) +file.seek(header_size, os.SEEK_SET) +jpeg_file = bytearray(file.read()) +file.close() +file = open(filepath, 'wb') +file.write(jpeg_file) +filepath.rename(filepath.with_suffix('.jpeg')) diff --git a/tools/unpack_asset.py b/tools/unpack_asset.py index f1b71b4..19038a5 100755 --- a/tools/unpack_asset.py +++ b/tools/unpack_asset.py @@ -29,8 +29,10 @@ file_header = bytearray(file.read(0x10)) #print("[DEBUG] File name:", filename) # Magic check for multi asset bin. If no match, treat as a single asset bin, and just copy it to the expected folder. -if file_header[0] != 0x00 or file_header[1] != 0x00 or file_header[2] != 0x00 or file_header[3] != 0x00 or file_header[4] != 0x00 or file_header[5] != 0x00 or file_header[6] != 0x00 or file_header[7] != 0x00: - file_path_to_write = assets_path + filename + "/0/0.bin" +# For some reason, stadium_models.bin breaks this pattern and uses this for something. (HACK: Workaround by just not checking the 4th byte.) +# TODO: Properly handle this +if file_header[0] != 0x00 or file_header[1] != 0x00 or file_header[2] != 0x00 or file_header[4] != 0x00 or file_header[5] != 0x00 or file_header[6] != 0x00 or file_header[7] != 0x00: + file_path_to_write = assets_path + filename + "/0/file.bin" os.makedirs(os.path.dirname(file_path_to_write), exist_ok=True) with open(file_path_to_write, 'wb') as f: fin = open(filepath, 'rb') @@ -59,7 +61,7 @@ while file_num < file_count: # Seek to the file offset. file.seek(bin_offset, os.SEEK_SET) sub_file_bytes = bytearray(file.read(bin_size)) - file_path_to_write = assets_path + filename + "/" + str(file_num) + "/" + str(file_num) + ".bin" + file_path_to_write = assets_path + filename + "/" + str(file_num) + "/file.bin" #print("[DEBUG] Path to write:", file_path_to_write) os.makedirs(os.path.dirname(file_path_to_write), exist_ok=True) file_to_write = open(file_path_to_write, 'wb')