From 074af007d560b51c0ed03657e3c24a850e2b4b62 Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Thu, 25 Nov 2021 18:27:23 -0600 Subject: [PATCH] Added digest-crc gem, added Mixer class to read and write Renegade MIX1 files --- Gemfile | 1 + lib/mixer.rb | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++ w3dhub.rb | 1 + 3 files changed, 172 insertions(+) create mode 100644 lib/mixer.rb diff --git a/Gemfile b/Gemfile index d60d4a0..484913f 100644 --- a/Gemfile +++ b/Gemfile @@ -4,4 +4,5 @@ gem "cyberarm_engine" gem "launchy" gem "i18n" gem "rexml" +gem "digest-crc" # gem "async-websocket" diff --git a/lib/mixer.rb b/lib/mixer.rb new file mode 100644 index 0000000..e8c14ef --- /dev/null +++ b/lib/mixer.rb @@ -0,0 +1,170 @@ +require "digest" + +class W3DHub + + # https://github.com/TheUnstoppable/MixLibrary used for reference + class Mixer + class MixParserException < RuntimeError; end + class MixFormatException < RuntimeError; end + + class Reader + attr_reader :package + + def initialize(file_path:, ignore_crc_mismatches: false) + @package = Package.new + @file = File.open(file_path) + + @file.pos = 0 + + # Valid header + if read_i32 == 0x3158494D + file_data_offset = read_i32 + file_names_offset = read_i32 + + @file.pos = file_names_offset + file_count = read_i32 + + file_count.times do + @package.files << Package::File.new(name: read_string) + end + + @file.pos = file_data_offset + _file_count = read_i32 + + file_count.times do |i| + file = @package.files[i] + + file.mix_crc = read_u32.to_s(16) + file.content_offset = read_u32 + file.content_length = read_u32 + + if file.mix_crc != file.file_crc && !ignore_crc_mismatches + raise MixParserException, "CRC mismatch for #{file.name}. #{file.mix_crc.inspect} != #{file.file_crc.inspect}" + end + + pos = @file.pos + @file.pos = file.content_offset + file.data = @file.read(file.content_length) + @file.pos = pos + end + else + raise MixParserException, "Invalid MIX file" + end + + ensure + @file&.close + end + + def read_i32 + @file.read(4).unpack1("l") + end + + def read_u32 + @file.read(4).unpack1("L") + end + + def read_string + buffer = "" + + length = @file.readbyte + + length.times do + buffer << @file.readbyte + end + + buffer.strip + end + end + + class Writer + attr_reader :package + + def initialize(file_path:, package:) + @package = package + + @file = File.open(file_path, "wb") + @file.pos = 0 + + @file.write("MIX1") + + files = @package.files.sort { |a, b| a.file_crc <=> b.file_crc } + + @file.pos = 16 + + files.each do |file| + file.content_offset = @file.pos + file.content_length = file.data.length + @file.write(file.data) + + @file.pos += -@file.pos & 7 + end + + file_data_offset = @file.pos + write_i32(files.count) + + files.each do |file| + write_u32(file.file_crc.to_i(16)) + write_u32(file.content_offset) + write_u32(file.content_length) + end + + file_name_offset = @file.pos + write_i32(files.count) + + files.each do |file| + write_byte(file.name.length + 1) + @file.write("#{file.name}\0") + end + + @file.pos = 4 + write_i32(file_data_offset) + write_i32(file_name_offset) + + @file.pos = 0 + @file.flush + ensure + @file&.close + end + + def write_i32(int) + @file.write([int].pack("l")) + end + + def write_u32(uint) + @file.write([uint].pack("L")) + end + + def write_byte(byte) + @file.write([byte].pack("c")) + end + end + + class Package + attr_reader :files + + def initialize(files: []) + @files = files + end + + class File + attr_accessor :name, :mix_crc, :content_offset, :content_length, :data + + def initialize(name:, mix_crc: nil, content_offset: nil, content_length: nil, data: nil) + @name = name + @mix_crc = mix_crc + @content_offset = content_offset + @content_length = content_length + @data = data + end + + def file_crc + Digest::CRC32.hexdigest(@name.upcase) + end + + def data_crc + Digest::CRC32.hexdigest(@data) + end + end + end + end +end \ No newline at end of file diff --git a/w3dhub.rb b/w3dhub.rb index 3b72948..d2260ed 100644 --- a/w3dhub.rb +++ b/w3dhub.rb @@ -32,6 +32,7 @@ require_relative "lib/store" require_relative "lib/window" require_relative "lib/cache" require_relative "lib/settings" +require_relative "lib/mixer" require_relative "lib/application_manager" require_relative "lib/application_manager/manifest" require_relative "lib/application_manager/task"