summaryrefslogtreecommitdiff
path: root/extractor
diff options
context:
space:
mode:
Diffstat (limited to 'extractor')
-rw-r--r--extractor/action_format.gd58
-rw-r--r--extractor/actions.gd13
-rw-r--r--extractor/grf.gd208
-rw-r--r--extractor/sprite_format.gd10
-rw-r--r--extractor/sprite_layer_resource.gd10
-rw-r--r--extractor/sprite_resource.gd5
6 files changed, 279 insertions, 25 deletions
diff --git a/extractor/action_format.gd b/extractor/action_format.gd
index ff2ba64..719bc2d 100644
--- a/extractor/action_format.gd
+++ b/extractor/action_format.gd
@@ -57,18 +57,22 @@ static func from_bytes(bytes: ByteStream) -> ActionFormat:
version.major = bytes.decode_u8()
action_format.version = version
+ if version.major < 2 and version.minor < 3:
+ print(version)
+ return action_format
+
action_format.action_count = bytes.decode_u16()
action_format.reserved = bytes.get_buffer(10).bytes
action_format.actions = [] as Array[ActionData]
for idx in action_format.action_count:
- var action = ActionData.from_bytes(bytes)
+ var action = ActionData.from_bytes(bytes, version)
action_format.actions.append(action)
action_format.event_count = bytes.decode_u32()
action_format.events = [] as Array[Event]
for idx in action_format.event_count:
- var event = Event.from_bytes(bytes)
+ var event = Event.from_bytes(bytes, version)
action_format.events.append(event)
action_format.frame_times = [] as Array[float]
@@ -95,13 +99,13 @@ class ActionData:
return length
- static func from_bytes(bytes: ByteStream) -> ActionData:
+ static func from_bytes(bytes: ByteStream, version: Version) -> ActionData:
var action = ActionData.new()
action.motion_count = bytes.decode_u32()
action.motions = [] as Array[Motion]
for idx in action.motion_count:
- var motion = Motion.from_bytes(bytes)
+ var motion = Motion.from_bytes(bytes, version)
action.motions.append(motion)
return action
@@ -135,7 +139,7 @@ class Motion:
return 44 + SpriteLayer.BYTE_LENGTH * sprite_layer_count + SpriteAnchor.BYTE_LENGTH * sprite_anchor_count
- static func from_bytes(bytes: ByteStream):
+ static func from_bytes(bytes: ByteStream, version: Version):
var motion = Motion.new()
motion.unused = bytes.get_buffer(32).bytes
@@ -143,7 +147,7 @@ class Motion:
motion.sprite_layer_count = bytes.decode_u32()
motion.sprite_layers = [] as Array[SpriteLayer]
for idx in motion.sprite_layer_count:
- var sprite_layer = SpriteLayer.from_bytes(bytes)
+ var sprite_layer = SpriteLayer.from_bytes(bytes, version)
motion.sprite_layers.append(sprite_layer)
motion.event_id = bytes.decode_s32()
@@ -151,8 +155,8 @@ class Motion:
motion.sprite_anchor_count = bytes.decode_u32()
motion.sprite_anchors = [] as Array[SpriteAnchor]
for idx in motion.sprite_anchor_count:
- var sprite_anchor = SpriteAnchor.from_bytes(bytes)
- motion.sprite_anchor_count.append(sprite_anchor)
+ var sprite_anchor = SpriteAnchor.from_bytes(bytes, version)
+ motion.sprite_anchors.append(sprite_anchor)
return motion
@@ -194,10 +198,17 @@ class SpriteLayer:
## Byte Type: f32 [br]
## Byte Length: 4
+ ## Versions: [2.3, 2.4]
+ var scale: float
+
+ ## Byte Type: f32 [br]
+ ## Byte Length: 4
+ ## Versions: [2.5]
var scale_u: float
## Byte Type: f32 [br]
## Byte Length: 4
+ ## Versions: [2.5]
var scale_v: float
## Byte Type: i32 [br]
@@ -226,14 +237,20 @@ class SpriteLayer:
func get_scale() -> Vector2:
- return Vector2(scale_u, scale_v)
+ if scale:
+ return Vector2(scale, scale)
+ else:
+ return Vector2(scale_u, scale_v)
func get_size() -> Vector2:
- return Vector2(width, height)
+ if width and height:
+ return Vector2(width, height)
+ else:
+ return Vector2.ZERO
- static func from_bytes(bytes: ByteStream):
+ static func from_bytes(bytes: ByteStream, version: Version):
var sprite_layer = SpriteLayer.new()
sprite_layer.position_u = bytes.decode_s32()
@@ -244,12 +261,19 @@ class SpriteLayer:
sprite_layer.color_g = bytes.decode_u8()
sprite_layer.color_b = bytes.decode_u8()
sprite_layer.color_a = bytes.decode_u8()
- sprite_layer.scale_u = bytes.decode_float()
- sprite_layer.scale_v = bytes.decode_float()
+
+ if version.major == 2 and version.minor >= 4:
+ sprite_layer.scale_u = bytes.decode_float()
+ sprite_layer.scale_v = bytes.decode_float()
+ else:
+ sprite_layer.scale = bytes.decode_float()
+
sprite_layer.rotation_degrees = bytes.decode_s32()
sprite_layer.type = bytes.decode_u32()
- sprite_layer.width = bytes.decode_u32()
- sprite_layer.height = bytes.decode_u32()
+
+ if version.to_string() == "2.5":
+ sprite_layer.width = bytes.decode_u32()
+ sprite_layer.height = bytes.decode_u32()
return sprite_layer
@@ -278,7 +302,7 @@ class SpriteAnchor:
return Vector2(position_u, position_v)
- static func from_bytes(bytes: ByteStream):
+ static func from_bytes(bytes: ByteStream, version: Version):
var sprite_anchor = SpriteAnchor.new()
sprite_anchor.unused = bytes.get_buffer(4).bytes
@@ -297,7 +321,7 @@ class Event:
var name: String
- static func from_bytes(bytes: ByteStream):
+ static func from_bytes(bytes: ByteStream, version: Version):
var event = Event.new()
event.name = bytes.get_string_from_utf8(BYTE_LENGTH)
diff --git a/extractor/actions.gd b/extractor/actions.gd
new file mode 100644
index 0000000..5c94cef
--- /dev/null
+++ b/extractor/actions.gd
@@ -0,0 +1,13 @@
+extends Node2D
+
+
+@onready var animation_player: AnimationPlayer = %AnimationPlayer
+@onready var sprite_layers: CanvasGroup = %SpriteLayers
+
+
+func play(animation_name: StringName = &"", custom_blend: float = -1, custom_speed: float = 1.0, from_end: bool = false):
+ animation_player.play(animation_name, custom_blend, custom_speed, from_end)
+
+
+func pause():
+ animation_player.pause()
diff --git a/extractor/grf.gd b/extractor/grf.gd
index 7e7e93c..fa7021a 100644
--- a/extractor/grf.gd
+++ b/extractor/grf.gd
@@ -140,7 +140,7 @@ class FileEntry:
@warning_ignore("shadowed_variable")
static func from_bytes_with_filename(bytes: PackedByteArray, file_name: String):
- print(file_name)
+ #print(file_name)
var file_entry = FileEntry.new()
file_entry.file_name = file_name
@@ -199,14 +199,214 @@ func extract(destination: String = "res://data"):
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))
+func convert(destination: String = "res://data"):
+ for file_entry in file_entries:
+ var file_path: String = file_entry.get_file_path()
+
+ var base_directory = DirAccess.open(destination)
+ var base_directory_path = "extracted/%s" % file_path.get_base_dir()
+ base_directory.make_dir_recursive(base_directory_path)
+ base_directory.change_dir(base_directory_path)
+
+ var file_name = file_path.get_file().substr(0,
+ file_path.get_file().length() - (file_path.get_extension().length() + 1)
+ )
+ var base_file_directory_path := "%s/%s" % [base_directory.get_current_dir(), file_name]
+
+ #DirAccess.make_dir_recursive_absolute(base_file_directory_path)
+
+ if file_path.ends_with(".spr") and file_path.contains("cursors"):
+ var sprite = SpriteFormat.from_bytes(file_entry.get_contents(file_access))
+ sprite.save_to_file(base_file_directory_path)
+
+ elif file_path.ends_with(".act") and file_path.contains("cursors"):
+ if not FileAccess.file_exists("%s/000.png.import" % base_file_directory_path):
+ continue
+
+ var scene := PackedScene.new()
+ var scene_root := Node2D.new()
+ scene_root.name = "Actions"
+ scene_root.set_script(load("res://extractor/actions.gd"))
+
+ var animation_player := AnimationPlayer.new()
+ animation_player.name = "AnimationPlayer"
+ animation_player.unique_name_in_owner = true
+ scene_root.add_child(animation_player)
+ animation_player.owner = scene_root
+
+ var sprite_layers := CanvasGroup.new()
+ sprite_layers.name = "SpriteLayers"
+ sprite_layers.unique_name_in_owner = true
+
+ scene_root.add_child(sprite_layers)
+ sprite_layers.owner = scene_root
+
+ var track_properties = [
+ "animation",
+ "frame",
+ "speed_scale",
+ "position",
+ "self_modulate",
+ "scale",
+ "rotation_degrees",
+ "flip_h",
+ "visible",
+ ]
+
+ var sprite_frames := SpriteFrames.new()
+ #sprite_frames.add_animation("default")
+ for img_file_path in DirAccess.get_files_at(base_file_directory_path):
+ if img_file_path.ends_with(".png"):
+ sprite_frames.add_frame("default", load("%s/%s" % [base_file_directory_path, img_file_path]))
+
+ var animation_library := AnimationLibrary.new()
+ var action_data := ActionFormat.from_bytes(ByteStream.from_bytes(file_entry.get_contents(file_access)))
+
+ # get max number of sprite layers for all actions
+ var action_sprite_layers_max_count = action_data.actions.reduce(func(accum, action: ActionFormat.ActionData):
+ return max(accum, action.motions.reduce(func(accum2, motion: ActionFormat.Motion):
+ return max(accum2, motion.sprite_layer_count)
+ , 0))
+ , 0)
+
+ # add Nodes for each sprite layer
+ for sprite_layer_idx in action_sprite_layers_max_count:
+ var sprite = AnimatedSprite2D.new()
+ sprite.centered = false # 必要!!
+ sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
+ sprite.sprite_frames = sprite_frames
+ sprite.name = str(sprite_layer_idx).pad_zeros(3)
+ sprite_layers.add_child(sprite)
+ sprite.owner = scene_root
+
+ for action_idx in action_data.actions.size():
+ var action: ActionFormat.ActionData = action_data.actions[action_idx]
+ var frame_timing_base := ((action_data.frame_times[action_idx] * 24) / 1000)
+
+ if file_path.contains("cursors") and action_idx == 0:
+ frame_timing_base = ((action_data.frame_times[action_idx] * 24 * 2) / 1000)
+
+ # add animation for each action
+ var animation := Animation.new()
+ animation.loop_mode = Animation.LOOP_LINEAR
+ animation.length = frame_timing_base * action.motion_count
+ animation_library.add_animation(str(action_idx).pad_zeros(3), animation)
+
+ # TODO: set animation max length
+
+ # get max number of sprite layers for current action motions
+ var motion_sprite_layers_max_count = action.motions.reduce(func(accum, motion: ActionFormat.Motion):
+ return max(accum, motion.sprite_layer_count)
+ , 0)
+
+ # add animation tracks for each sprite layer
+ for sprite_layer_idx in motion_sprite_layers_max_count:
+ var sprite := sprite_layers.get_child(sprite_layer_idx)
+ for property_idx in track_properties.size():
+ var track_idx = (sprite_layer_idx * track_properties.size()) + property_idx
+ animation.add_track(Animation.TYPE_VALUE, track_idx)
+ animation.value_track_set_update_mode(track_idx, Animation.UPDATE_DISCRETE)
+ animation.track_set_path(
+ track_idx,
+ "%s:%s" % ["SpriteLayers/" + sprite.name, track_properties[property_idx]]
+ )
+
+ for i in range(motion_sprite_layers_max_count, action_sprite_layers_max_count):
+ var sprite := sprite_layers.get_child(i)
+ var track_idx = animation.add_track(Animation.TYPE_VALUE)
+ animation.track_set_path(
+ track_idx,
+ "%s:visible" % ["SpriteLayers/" + sprite.name]
+ )
+ animation.track_insert_key(track_idx, 0.0, false)
+
+ # add animation tracks
+ for motion_idx in action.motions.size():
+ var motion: ActionFormat.Motion = action.motions[motion_idx]
+
+ var timing = motion_idx * frame_timing_base
+ var visible_key = 0
+
+ # add visible = false animation tracks to other sprite_layers
+ for i in motion_sprite_layers_max_count:
+ var track_idx = i * track_properties.size() + track_properties.find("visible")
+ visible_key = animation.track_insert_key(track_idx, timing, false)
+
+ for sprite_layer_idx in motion.sprite_layers.size():
+ var layer: ActionFormat.SpriteLayer = motion.sprite_layers[sprite_layer_idx]
+
+ var track_base_idx = sprite_layer_idx * track_properties.size()
+
+ animation.track_insert_key(
+ track_base_idx + track_properties.find("animation"),
+ timing,
+ "default"
+ )
+
+ animation.track_insert_key(
+ track_base_idx + track_properties.find("frame"),
+ timing,
+ layer.sprite_index
+ )
+
+ animation.track_insert_key(
+ track_base_idx + track_properties.find("speed_scale"),
+ timing,
+ 1.0
+ )
+
+ var layer_image := sprite_frames.get_frame_texture("default", layer.sprite_index)
+ var position: Vector2 = layer.get_position() - ceil(layer_image.get_size() / 2) # for fixing half pixel drawing
+ var rotated = layer_image.get_size().rotated(deg_to_rad(layer.rotation_degrees))
+ var distance = layer_image.get_size() - rotated
+ animation.track_insert_key(
+ track_base_idx + track_properties.find("position"),
+ timing,
+ position + (distance / 2)
+ )
+
+ animation.track_insert_key(
+ track_base_idx + track_properties.find("self_modulate"),
+ timing,
+ layer.get_color()
+ )
+
+ animation.track_insert_key(
+ track_base_idx + track_properties.find("scale"),
+ timing,
+ layer.get_scale()
+ )
+
+ animation.track_insert_key(
+ track_base_idx + track_properties.find("rotation_degrees"),
+ timing,
+ layer.rotation_degrees
+ )
+
+ animation.track_insert_key(
+ track_base_idx + track_properties.find("flip_h"),
+ timing,
+ layer.flip_h
+ )
+
+ animation.track_set_key_value(
+ track_base_idx + track_properties.find("visible"),
+ visible_key,
+ true
+ )
+
+ animation_player.add_animation_library("", animation_library)
+ scene.pack(scene_root)
+
+ # TODO: doesn't work if png is not imported via editor focus => run game twice
+ ResourceSaver.save(scene, "%s/actions.tscn" % base_file_directory_path)
+
static func decode_string(bytes: PackedByteArray):
return bytes.get_string_from_ascii()
+ @warning_ignore("unreachable_code")
# TODO: check unicode codepoints and parse accordingly
var string = bytes.get_string_from_utf32()
if string == "":
diff --git a/extractor/sprite_format.gd b/extractor/sprite_format.gd
index fab0873..46b20c1 100644
--- a/extractor/sprite_format.gd
+++ b/extractor/sprite_format.gd
@@ -108,10 +108,11 @@ static func from_bytes(bytes: PackedByteArray) -> SpriteFormat:
return sprite_format
-func save_to_file():
- assert(filepath != "")
+func save_to_file(base_dir: String = ""):
+ if base_dir == "":
+ assert(filepath != "")
+ base_dir = filepath.substr(0, filepath.length() - 4) # cut off .spr
- var base_dir = filepath.substr(0, filepath.length() - 4) # cut off .spr
DirAccess.make_dir_recursive_absolute(base_dir)
for idx in palette_image_data.size():
@@ -124,7 +125,8 @@ func save_to_file():
data.get_rgba_data(palette)
)
- image.save_png("%s/%s.png" % [base_dir, str(idx).pad_zeros(3)])
+ var path = "%s/%s.png" % [base_dir, str(idx).pad_zeros(3)]
+ image.save_png(path)
func files_exist() -> bool:
diff --git a/extractor/sprite_layer_resource.gd b/extractor/sprite_layer_resource.gd
new file mode 100644
index 0000000..d67707e
--- /dev/null
+++ b/extractor/sprite_layer_resource.gd
@@ -0,0 +1,10 @@
+class_name SpriteLayerResource
+extends Resource
+
+
+@export var index: int
+@export var position: Vector2
+@export var color: Color
+@export var scale: Vector2
+@export var rotation_degrees: float
+@export var flip_h: bool
diff --git a/extractor/sprite_resource.gd b/extractor/sprite_resource.gd
new file mode 100644
index 0000000..380fac4
--- /dev/null
+++ b/extractor/sprite_resource.gd
@@ -0,0 +1,5 @@
+class_name SpriteResource
+extends Resource
+
+
+@export var images: Array[Texture2D]