## see https://ragnarokresearchlab.github.io/file-formats/grf class_name GRF var header: Header class Header: const BYTE_LENGTH := 46 ## Byte Length: 15 ## Master of Magic var signature: String ## Byte Length: 15 var encryption: PackedByteArray ## Byte Type: u32 ## Byte Length: 4 var file_table_offset: int ## Byte Type: u32 ## Byte Length: 4 var scrambling_seed: int ## Byte Type: u32 ## Byte Length: 4 var scrambled_file_count: int ## Byte Type: u32 ## Byte Length: 4 var version: int func get_file_count() -> int: return scrambled_file_count - scrambling_seed - 7 static func from_bytes(bytes: PackedByteArray): var header = Header.new() header.signature = bytes.slice(0, 15).get_string_from_utf8() header.encryption = bytes.slice(15, 15 + 15) header.file_table_offset = bytes.decode_u32(30) header.scrambling_seed = bytes.decode_u32(34) header.scrambled_file_count = bytes.decode_u32(38) header.version = bytes.decode_u32(42) return header var file_table: FileTable class FileTable: const BYTE_LENGTH := 8 ## Byte Type: u32 ## Byte Length: 4 var compressed_size: int ## Byte Type: u32 ## Byte Length: 4 var decompressed_size: int var compressed_record_headers: PackedByteArray var decompressed_record_headers: PackedByteArray func generate_compressed_record_headers(grf_file: FileAccess): compressed_record_headers = grf_file.get_buffer(compressed_size) func decompress(): decompressed_record_headers = compressed_record_headers.decompress( decompressed_size, FileAccess.CompressionMode.COMPRESSION_DEFLATE ) static func from_bytes(bytes: PackedByteArray): var file_table = FileTable.new() file_table.compressed_size = bytes.decode_u32(0) file_table.decompressed_size = bytes.decode_u32(4) return file_table var file_entries: Array[FileEntry] class FileEntry: const BYTE_LENGTH := 17 var file_name: String ## Byte Type: u32 ## Byte Length: 4 var compressed_size: int ## Byte Type: u32 ## Byte Length: 4 var byte_aligned_size: int ## Byte Type: u32 ## Byte Length: 4 var decompressed_size: int ## Byte Type: u8 ## Byte Length: 1 var file_type: int ## Byte Type: u32 ## Byte Length: 4 var offset: int func get_byte_length() -> int: return BYTE_LENGTH #+ file_name.to_ascii_buffer().size() func get_file_path() -> String: var parts = file_name.split("\\") return "/".join(parts) func get_contents(grf_file: FileAccess): var previous_position = grf_file.get_position() grf_file.seek(Header.BYTE_LENGTH + offset) var buffer = grf_file.get_buffer(byte_aligned_size) var contents = buffer.decompress( decompressed_size, FileAccess.CompressionMode.COMPRESSION_DEFLATE ) grf_file.seek(previous_position) return contents static func from_bytes_with_filename(bytes: PackedByteArray, file_name: String): var file_entry = FileEntry.new() file_entry.file_name = file_name file_entry.compressed_size = bytes.decode_u32(0) file_entry.byte_aligned_size = bytes.decode_u32(4) file_entry.decompressed_size = bytes.decode_u32(8) file_entry.file_type = bytes.decode_u8(12) file_entry.offset = bytes.decode_u32(13) return file_entry var file_access: FileAccess static func open(path: String): var grf = GRF.new() grf.file_access = FileAccess.open(path, FileAccess.ModeFlags.READ) grf.header = GRF.Header.from_bytes(grf.file_access.get_buffer(Header.BYTE_LENGTH)) grf.file_access.seek(Header.BYTE_LENGTH + grf.header.file_table_offset) grf.file_table = FileTable.from_bytes(grf.file_access.get_buffer(8)) grf.file_table.generate_compressed_record_headers(grf.file_access) grf.file_table.decompress() grf.file_entries = [] as Array[FileEntry] var file_entry_offset = 0 while file_entry_offset < grf.file_table.decompressed_record_headers.size(): var file_name_size = grf.file_table.decompressed_record_headers.find(0, file_entry_offset) + 1 - file_entry_offset var file_entry = FileEntry.from_bytes_with_filename( grf.file_table.decompressed_record_headers.slice( file_entry_offset + file_name_size, file_entry_offset + file_name_size + FileEntry.BYTE_LENGTH ), grf.file_table.decompressed_record_headers.slice( file_entry_offset, file_entry_offset + file_name_size ).get_string_from_ascii() ) grf.file_entries.append(file_entry) file_entry_offset += file_entry.get_byte_length() + file_name_size return grf func write(): for file_entry in file_entries: var file_path: String = file_entry.get_file_path() var base_directory = DirAccess.open("res://data") base_directory.make_dir_recursive("extracted/" + file_path.get_base_dir()) var file = FileAccess.open("res://data/extracted/%s" % [file_path], FileAccess.WRITE_READ) file.store_buffer(file_entry.get_contents(file_access))