Skip to content

Instantly share code, notes, and snippets.

@taylorfinnell
Created August 1, 2024 23:08
Show Gist options
  • Save taylorfinnell/5d7da3dc9d6c39401525fd0c671d8654 to your computer and use it in GitHub Desktop.
Save taylorfinnell/5d7da3dc9d6c39401525fd0c671d8654 to your computer and use it in GitHub Desktop.
Novalogic .bad parser
require "option_parser"
class Vec3
x : Float32
y : Float32
z : Float32
property x
property y
property z
def initialize(@x : Float32, @y : Float32, @z : Float32)
end
def self.from_io(io, format)
Vec3.new(
io.read_bytes(Float32, format),
io.read_bytes(Float32, format),
io.read_bytes(Float32, format)
)
end
end
class Mat
# Notsure on order
getter row1 : Vec3
getter row2 : Vec3
getter row3 : Vec3
def initialize(@row1 : Vec3, @row2 : Vec3, @row3 : Vec3)
end
def self.from_io(io, format)
Mat.new(
Vec3.from_io(io, format),
Vec3.from_io(io, format),
Vec3.from_io(io, format),
)
end
end
class Bone
property parent : Bone? = nil
property child : Bone? = nil
getter name
getter num_children
getter length
getter transform_matrix
getter position
getter top_offset
def initialize(*, @name : String, @num_children : Int32, @length : Float32, @position : Vec3, @transform_matrix : Mat, @top_offset : Vec3)
end
end
class BADFile
property bones = Array(Bone).new
getter num_bones
def initialize(@num_bones : Int32)
end
def bone_children(bone : Bone) : Array(Bone)
return bones.select { |b| !b.parent.nil? && b.parent.not_nil!.name == bone.name }
end
end
def read_prop(file, addr, offset, type)
file.seek(offset + addr, IO::Seek::Set)
return file.read_bytes(type, IO::ByteFormat::LittleEndian)
end
def read_bone(file, addr) : Bone
file.seek(addr, IO::Seek::Set)
name = ""
while true
b = file.read_byte
if !b || b.zero?
break
end
name += b.chr
end
return Bone.new(
name: name,
num_children: read_prop(file, addr, 0x24, Int32),
length: read_prop(file, addr, 0x30, Float32),
position: read_prop(file, addr, 0x34, Vec3),
transform_matrix: read_prop(file, addr, 0x40, Mat)
top_offset: read_prop(file, addr, 0x40, Mat)
)
end
def main
path = ARGV[0]?
if path.nil?
puts <<-USAGE
Dump bone information from Novalogic BAD files
bones.exe <path>
USAGE
exit
end
file = File.open(File.expand_path(path.not_nil!))
num_bones = read_prop(file, 0, 0x14, Int32)
offset = read_prop(file, 0, 0x18, Int32)
bad = BADFile.new(num_bones)
num_bones.times do |i|
addr = offset + (i * 0x64)
# Markers for child and parent
parent_offset = read_prop(file, addr, 0x2C, Int32)
child_offset = read_prop(file, addr, 0x28, Int32)
bone = read_bone(file, addr)
if !child_offset.zero?
bone.child = read_bone(file, child_offset)
end
if !parent_offset.zero?
bone.parent = read_bone(file, parent_offset)
end
bad.bones.push(bone)
end
bad.bones[0].parent = nil
puts
puts "#{bad.num_bones} bone(s)"
puts
bad.bones.each_with_index do |bone, i|
puts "#{(i + 1)}: #{bone.name} (#{bone.num_children} children, length: #{bone.length})"
puts "--------------------------------------------------------------------"
puts "Transformation matrix:"
puts <<-MAT
\t#{bone.transform_matrix.row1.x.round(4)}\t#{bone.transform_matrix.row1.y.round(4)}\t#{bone.transform_matrix.row1.z.round(4)}
\t#{bone.transform_matrix.row2.x.round(4)}\t#{bone.transform_matrix.row2.y.round(4)}\t#{bone.transform_matrix.row2.z.round(4)}
\t#{bone.transform_matrix.row3.x.round(4)}\t#{bone.transform_matrix.row3.y.round(4)}\t#{bone.transform_matrix.row3.z.round(4)}
MAT
puts "Position: "
puts "\t#{bone.position.x.round(4)}\t#{bone.position.y.round(4)}\t#{bone.position.z.round(4)}"
puts "Offset: "
puts "\t#{bone.offset_top.x.round(4)}\t#{bone.offset_top.y.round(4)}\t#{bone.offset_top.z.round(4)}"
children = bad.bone_children(bone)
puts "Children: " if children.any?
children.each do |child|
puts "\t#{child.name}"
end
if (parent = bone.parent)
puts "Parent: "
puts "\t#{parent.name}"
end
puts
puts
end
end
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment