summaryrefslogtreecommitdiff
path: root/extractor
diff options
context:
space:
mode:
Diffstat (limited to 'extractor')
-rw-r--r--extractor/action.gd222
-rw-r--r--extractor/grf.gd12
-rw-r--r--extractor/sprite.gd204
-rw-r--r--extractor/version.gd15
4 files changed, 449 insertions, 4 deletions
diff --git a/extractor/action.gd b/extractor/action.gd
new file mode 100644
index 0000000..1865786
--- /dev/null
+++ b/extractor/action.gd
@@ -0,0 +1,222 @@
+class_name ActionFormat
+
+
+## Byte Length: 2 [br]
+## SP
+var signature: String = "AC"
+
+## Byte Type: u8 [br]
+## Byte Length: 2
+var version: Version
+
+## Byte Type: u16 [br]
+## Byte Length: 2
+var action_count: int
+
+## Byte Type: u8 [br]
+## Byte Length: 10
+var reserved: PackedByteArray
+
+## Length: [member action_count]
+var actions: Array[Action]
+
+## Byte Type: u16 [br]
+## Byte Length: 2
+var event_count: int
+
+## Length: [member event_count]
+var events: Array[Event]
+
+## Byte Type: f32 [br]
+## Byte Length: 4
+var frame_times: float
+
+
+func get_byte_length() -> int:
+ var length := 22
+
+ for action in actions:
+ length += action.get_byte_length()
+
+ length += Event.BYTE_LENGTH * event_count
+
+ return length
+
+
+static func from_bytes(bytes: PackedByteArray):
+ var action = ActionFormat.new()
+
+ @warning_ignore("shadowed_variable")
+ var version = Version.new()
+ version.minor = bytes.decode_u8(2)
+ version.major = bytes.decode_u8(3)
+ action.version = version
+
+ print(version)
+
+ action.action_count = bytes.decode_u16(4)
+ action.reserved = bytes.slice(6, 6 + 10)
+
+ # TODO
+ action.actions = [] as Array[Action]
+
+ action.event_count = 0
+
+ action.frame_times = 0.0
+
+ print(inst_to_dict(action))
+
+ return action
+
+
+class Action:
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4
+ var motion_count: int
+
+ ## Length: [member motion_count]
+ var motions: Array[Motion]
+
+
+ func get_byte_length() -> int:
+ var length := 4
+ for motion in motions:
+ length += motion.get_byte_length()
+
+ return length
+
+
+class Motion:
+ ## Byte Type: u8 [br]
+ ## Byte Length: 32
+ var unused: PackedByteArray
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4
+ var sprite_layer_count: int
+
+ ## Length: [member sprite_layer_count]
+ var sprite_layers: Array[SpriteLayer]
+
+ ## Byte Type: i32 [br]
+ ## Byte Length: 4
+ var event_id: int
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4
+ var sprite_anchor_count: int
+
+ ## Length: [member sprite_anchor_count]
+ var sprite_anchors: Array[SpriteAnchor]
+
+
+ func get_byte_length() -> int:
+ return 44 + SpriteLayer.BYTE_LENGTH * sprite_layer_count + SpriteAnchor.BYTE_LENGTH * sprite_anchor_count
+
+
+class SpriteLayer:
+ const BYTE_LENGTH := 48
+
+ ## Byte Type: i32 [br]
+ ## Byte Length: 4
+ var position_u: int
+
+ ## Byte Type: i32 [br]
+ ## Byte Length: 4
+ var position_v: int
+
+ ## Byte Type: i32 [br]
+ ## Byte Length: 4
+ var sprite_index: int
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4
+ var is_flipped_vertical: int
+
+ ## Byte Type: u8 [br]
+ ## Byte Length: 1
+ var color_r: int
+
+ ## Byte Type: u8 [br]
+ ## Byte Length: 1
+ var color_g: int
+
+ ## Byte Type: u8 [br]
+ ## Byte Length: 1
+ var color_b: int
+
+ ## Byte Type: u8 [br]
+ ## Byte Length: 1
+ var color_a: int
+
+ ## Byte Type: f32 [br]
+ ## Byte Length: 4
+ var scale_u: float
+
+ ## Byte Type: f32 [br]
+ ## Byte Length: 4
+ var scale_v: float
+
+ ## Byte Type: i32 [br]
+ ## Byte Length: 4
+ var rotation_degrees: int
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4
+ var sprite_type: int
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4
+ var sprite_width: int
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4
+ var sprite_height: int
+
+
+ func get_position() -> Vector2:
+ return Vector2(position_u, position_v)
+
+
+ func get_color() -> Color:
+ return Color8(color_r, color_g, color_b, color_a)
+
+
+ func get_scale() -> Vector2:
+ return Vector2(scale_u, scale_v)
+
+
+ func get_size() -> Vector2:
+ return Vector2(sprite_width, sprite_height)
+
+
+class SpriteAnchor:
+ const BYTE_LENGTH := 16
+
+ ## Byte Type: u8 [br]
+ ## Byte Length: 4
+ var unused: PackedByteArray
+
+ ## Byte Type: i32 [br]
+ ## Byte Length: 4
+ var position_u: int
+
+ ## Byte Type: i32 [br]
+ ## Byte Length: 4
+ var position_v: int
+
+ ## Byte Type: u32 [br]
+ ## Byte Length: 4
+ var flag: int
+
+
+ func get_position() -> Vector2:
+ return Vector2(position_u, position_v)
+
+
+class Event:
+ const BYTE_LENGTH := 40
+
+ ## Byte Type: u8 [br]
+ ## Byte Length: 40
+ var name: String
diff --git a/extractor/grf.gd b/extractor/grf.gd
index 1e17264..3739c8c 100644
--- a/extractor/grf.gd
+++ b/extractor/grf.gd
@@ -9,7 +9,7 @@ class Header:
## Byte Length: 15
## Master of Magic
- var signature: String
+ var signature: String = "Master of Magic"
## Byte Length: 15
var encryption: PackedByteArray
@@ -138,6 +138,7 @@ class FileEntry:
return contents
+ @warning_ignore("shadowed_variable")
static func from_bytes_with_filename(bytes: PackedByteArray, file_name: String):
var file_entry = FileEntry.new()
@@ -188,12 +189,15 @@ static func open(path: String):
return grf
-func write():
+func extract(destination: String = "res://data"):
for file_entry in file_entries:
var file_path: String = file_entry.get_file_path()
- var base_directory = DirAccess.open("res://data")
+ var base_directory = DirAccess.open(destination)
base_directory.make_dir_recursive("extracted/" + file_path.get_base_dir())
- var file = FileAccess.open("res://data/extracted/%s" % [file_path], FileAccess.WRITE_READ)
+ var file = FileAccess.open("%s/extracted/%s" % [destination, file_path], FileAccess.WRITE_READ)
file.store_buffer(file_entry.get_contents(file_access))
+
+ # TODO: write pngs for sprites (and .tres files maybe if necessary)
+ # TODO: (also maybe write .tres files for action data files(whatever they are))
diff --git a/extractor/sprite.gd b/extractor/sprite.gd
new file mode 100644
index 0000000..410ce07
--- /dev/null
+++ b/extractor/sprite.gd
@@ -0,0 +1,204 @@
+class_name Sprite
+
+
+## Byte Length: 2 [br]
+## SP
+var signature: String = "SP"
+
+## Byte Type: u8 [br]
+## Byte Length: 2
+var version: Version
+
+## Byte Type: u16 [br]
+## Byte Length: 2
+var palette_image_count: int
+
+## Byte Type: u16 [br]
+## Byte Length: 2
+var rgba_image_count: int
+
+## Length: [member palette_image_count]
+var palette_image_data: Array[PaletteImageData]
+
+## Byte Type: u8 [br]
+## Byte Length: [member rgba_image_count]
+var rgba_image_data: Array[RGBAImageData]
+
+## Byte Type: u16 [br]
+## Byte Length: 256 * 4 (rgba u8) [br]
+## The color with palette index 0 can be considered the "background color". [br]
+## It must be cleared manually on load.
+var palette: Array[PaletteColor]
+
+
+static func from_bytes(bytes: PackedByteArray):
+ var sprite = Sprite.new()
+
+ @warning_ignore("shadowed_variable")
+ var version = Version.new()
+ version.minor = bytes.decode_u8(2)
+ version.major = bytes.decode_u8(3)
+ sprite.version = version
+
+ sprite.palette_image_count = bytes.decode_u16(4)
+ sprite.rgba_image_count = bytes.decode_u16(6)
+
+ sprite.palette_image_data = [] as Array[PaletteImageData]
+ var palette_image_offset = 8
+ var processed_palette_images = 0
+ while processed_palette_images < sprite.palette_image_count:
+ var data = PaletteImageData.new()
+ data.width = bytes.decode_u16(palette_image_offset)
+ data.height = bytes.decode_u16(palette_image_offset + 2)
+
+ data.data_size = bytes.decode_u16(palette_image_offset + 4)
+ data.encoded_data = bytes.slice(
+ palette_image_offset + 6,
+ palette_image_offset + 6 + data.data_size
+ )
+
+ data.decode()
+
+ sprite.palette_image_data.append(data)
+ processed_palette_images += 1
+ palette_image_offset += data.get_byte_length()
+
+ sprite.rgba_image_data = [] as Array[RGBAImageData]
+ var rgba_image_offset = palette_image_offset
+ var processed_rgba_images = 0
+ while processed_rgba_images < sprite.rgba_image_count:
+ var data = RGBAImageData.new()
+ data.width = bytes.decode_u16(rgba_image_offset)
+ data.height = bytes.decode_u16(rgba_image_offset + 2)
+
+ data.data = bytes.slice(
+ rgba_image_offset + 4,
+ rgba_image_offset + 4 + (data.width * data.height * 4)
+ )
+
+ sprite.rgba_image_data.append(data)
+ processed_rgba_images += 1
+ rgba_image_offset += data.get_byte_length()
+
+ sprite.palette = [] as Array[PaletteColor]
+ var palette_offset = rgba_image_offset
+ while palette_offset < bytes.size():
+ var color = PaletteColor.new()
+ color.r = bytes.decode_u8(palette_offset)
+ color.g = bytes.decode_u8(palette_offset + 1)
+ color.b = bytes.decode_u8(palette_offset + 2)
+ color.a = bytes.decode_u8(palette_offset + 3)
+ sprite.palette.append(color)
+ palette_offset += 4
+
+ #print(sprite.palette_image_data[0].get_rgba_data(sprite.palette))
+
+ for idx in sprite.palette_image_data.size():
+ var d: PaletteImageData = sprite.palette_image_data[idx]
+ var i = Image.create_from_data(
+ d.width,
+ d.height,
+ false,
+ Image.FORMAT_RGBA8,
+ d.get_rgba_data(sprite.palette)
+ )
+ i.save_png("res://extractor/test/test-" + str(idx).pad_zeros(3) + ".png")
+
+ return sprite
+
+
+class PaletteImageData:
+ ## Byte Type: u16
+ ## Byte Length: 2
+ var width: int
+
+ ## Byte Type: u16
+ ## Byte Length: 2
+ var height: int
+
+ ## Byte Type: u16
+ ## Byte Length: 2
+ var data_size: int
+
+ ## Byte Type: u8
+ ## Byte Length: [member data_size]
+ var encoded_data: PackedByteArray
+
+ var decoded_data: PackedByteArray
+
+
+ func get_byte_length() -> int:
+ return 6 + data_size
+
+
+ func decode():
+ decoded_data = PackedByteArray([])
+
+ var offset = 0
+ while offset < encoded_data.size():
+ var byte = encoded_data.decode_u8(offset)
+ if byte == 0:
+ var length = encoded_data.decode_u8(offset + 1)
+ var padding = PackedByteArray([])
+ padding.resize(length)
+ padding.fill(0)
+ decoded_data.append_array(padding)
+
+ offset += 2
+ else:
+ decoded_data.append(byte)
+ offset += 1
+
+
+ func get_rgba_data(palette: Array[PaletteColor]) -> PackedByteArray:
+ var rgba := PackedByteArray([])
+ var background_color := palette[0]
+
+ for idx in decoded_data:
+ var color := palette[idx]
+ if color == background_color:
+ rgba.append_array(PackedByteArray([0, 0, 0, 0]))
+ else:
+ rgba.append_array(color.to_bytes())
+
+ return rgba
+
+
+class PaletteColor:
+ const BYTE_LENGTH := 4
+
+ var r: int
+ var g: int
+ var b: int
+ var a: int
+
+
+ func to_bytes():
+ return PackedByteArray([r, g, b, 255])
+
+
+class RGBAImageData:
+ ## Byte Type: u16
+ ## Byte Length: 2
+ var width: int
+
+ ## Byte Type: u16
+ ## Byte Length: 2
+ var height: int
+
+ ## Byte Type: u8
+ ## Byte Length: width * height * RGBAPixel.byte_length
+ var data: Array[Pixel]
+
+
+ class Pixel:
+ const BYTE_LENGTH := 4
+
+ var r: int
+ var g: int
+ var b: int
+ var a: int
+
+
+ func get_byte_length() -> int:
+ return width * height * data.size()
diff --git a/extractor/version.gd b/extractor/version.gd
new file mode 100644
index 0000000..679a8f0
--- /dev/null
+++ b/extractor/version.gd
@@ -0,0 +1,15 @@
+class_name Version
+
+
+## Byte Type: u8
+## Byte Length: 1
+var major: int
+
+
+## Byte Type: u8
+## Byte Length: 1
+var minor: int
+
+
+func _to_string() -> String:
+ return "%s.%s" % [major, minor]