Created
July 1, 2017 15:23
-
-
Save Ishotihadus/c6b4d906ab0e2cf0a5f5db7df3704bc0 to your computer and use it in GitHub Desktop.
UnityFS unpack tool for MLTD (Million live theater days).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# UnityFS unpack tool for MLTD (Million live theater days). | |
# Output directory is created automatically. | |
# Usage: ruby unityfs2asset.rb data.unity3d | |
require 'fileutils' | |
require 'extlz4' | |
require 'bin_utils' | |
class BinaryReader | |
def initialize(data) | |
@data = data | |
@pos = 0 | |
end | |
def read(size) | |
data = @data.byteslice(@pos, size) | |
@pos += size | |
data | |
end | |
def cstr | |
r = @data.unpack("@#{@pos}Z*")[0] | |
@pos += r.bytesize + 1 | |
r | |
end | |
def i16u | |
r = BinUtils.get_int16_be(@data, @pos) | |
@pos += 2 | |
r | |
end | |
def i32u | |
r = BinUtils.get_int32_be(@data, @pos) | |
@pos += 4 | |
r | |
end | |
def i64u | |
r = BinUtils.get_int64_be(@data, @pos) | |
@pos += 8 | |
r | |
end | |
end | |
def pinfo(str, d) | |
puts "#{str.ljust(25)}: #{d}" | |
end | |
def comptype(flags) | |
case flags & 0x3f | |
when 0 | |
'None' | |
when 1 | |
'LZMA' | |
when 2 | |
'LZ4' | |
when 3 | |
'LZ4HC' | |
when 4 | |
'LZHAM' | |
end | |
end | |
def uncompress(block, flags) | |
case flags & 0x3f | |
when 0 | |
block | |
when 2, 3 | |
LZ4::raw_decode(block) | |
end | |
end | |
bin = BinaryReader.new(File.binread(ARGV[0])) | |
sign = bin.cstr | |
pinfo("Signature", sign) | |
raise "Signature does not match: #{sign}" unless sign == 'UnityFS' | |
fmt = bin.i32u | |
unt_ver = bin.cstr | |
gen_ver = bin.cstr | |
pinfo("Format", fmt) | |
pinfo("Unity Version", unt_ver) | |
pinfo("Generator Version", gen_ver) | |
size = bin.i64u | |
ci_size = bin.i32u | |
ui_size = bin.i32u | |
flags = bin.i32u | |
pinfo("File Size", size) | |
pinfo("Compressed Block Size", ci_size) | |
pinfo("Uncompressed Block Size", ui_size) | |
pinfo("Compression Flag", "#{flags} (#{comptype(flags)})") | |
data = BinaryReader.new(uncompress(bin.read(ci_size), flags)) | |
guid = data.read(16) | |
num_blocks = data.i32u | |
pinfo("GUID", "#{guid}") | |
pinfo("Block Count", "#{num_blocks}") | |
BlockInfo = Struct.new(:ui_size, :ci_size, :flags) | |
blocks = [] | |
num_blocks.times{ blocks << BlockInfo.new(data.i32u, data.i32u, data.i16u) } | |
num_nodes = data.i32u | |
pinfo("Node Count", "#{num_nodes}") | |
NodeInfo = Struct.new(:offset, :size, :status, :name) | |
nodes = [] | |
num_nodes.times{ nodes << NodeInfo.new(data.i64u, data.i64u, data.i32u, data.cstr) } | |
storage = '' | |
blocks.each do |block| | |
storage << uncompress(bin.read(block.ci_size), block.flags) | |
end | |
outputdir = ARGV[1] || ARGV[0].match(/\A(.*?)(\.unity3d)?\z/)[1] | |
nodes.each do |node| | |
puts "Node #{node.name.ljust(30)} (#{node.size} bytes)" | |
outfile = outputdir + '/' + node.name | |
dirname = File.dirname(outfile) | |
FileUtils.mkdir_p(dirname) unless Dir.exist?(dirname) | |
File.binwrite(outfile, storage.byteslice(node.offset, node.size)) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment