diff options
Diffstat (limited to 'extractor')
-rw-r--r-- | extractor/action_format.gd | 58 | ||||
-rw-r--r-- | extractor/actions.gd | 13 | ||||
-rw-r--r-- | extractor/grf.gd | 208 | ||||
-rw-r--r-- | extractor/sprite_format.gd | 10 | ||||
-rw-r--r-- | extractor/sprite_layer_resource.gd | 10 | ||||
-rw-r--r-- | extractor/sprite_resource.gd | 5 |
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] |