summaryrefslogtreecommitdiff
path: root/extractor/grf.gd
diff options
context:
space:
mode:
Diffstat (limited to 'extractor/grf.gd')
-rw-r--r--extractor/grf.gd181
1 files changed, 171 insertions, 10 deletions
diff --git a/extractor/grf.gd b/extractor/grf.gd
index 27dcab0..c0c42a6 100644
--- a/extractor/grf.gd
+++ b/extractor/grf.gd
@@ -1,8 +1,11 @@
+## see https://ragnarokresearchlab.github.io/file-formats/grf
class_name GRF
+var header: Header
+
class Header:
- static var byte_length: int = 46
+ const BYTE_LENGTH := 46
## Byte Length: 15
## Master of Magic
@@ -17,24 +20,182 @@ class Header:
## Byte Type: u32
## Byte Length: 4
- var reserved_files: int
+ var scrambling_seed: int
## Byte Type: u32
## Byte Length: 4
- var file_count: int
+ var scrambled_file_count: int
## Byte Type: u32
## Byte Length: 4
var version: int
- static func from_file(file: FileAccess):
+ func get_file_count() -> int:
+ return scrambled_file_count - scrambling_seed - 7
+
+
+ static func from_bytes(bytes: PackedByteArray):
var header = Header.new()
- header.signature = file.get_buffer(15).get_string_from_utf8()
- header.encryption = file.get_buffer(15)
- header.file_table_offset = file.get_buffer(4).decode_u32(0)
- header.reserved_files = file.get_buffer(4).decode_u32(0)
- header.file_count = file.get_buffer(4).decode_u32(0)
- header.version = file.get_buffer(4).decode_u32(0)
+
+ 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
+
+ grf.write()
+
+ 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))