Last active
June 13, 2025 23:41
-
-
Save ryandagg/5d993bd4ecaa8c0afa0b1e533bc15a2f to your computer and use it in GitHub Desktop.
decode Diablo II's CubeMain.bin, write to JSON
This file contains hidden or 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
# modified from https://gist.github.com/jankowskib/f886f087d9cb1f578284 | |
# works for at least... | |
# % ruby --version | |
# ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5) [arm64-darwin24] | |
# usage: | |
# ruby parser-json.rb ./cubemain.bin somewhere/else/cubemain.json | |
# ^ assumes you named this file "parser-json.rb", otherwise change the command to match | |
=begin | |
getting the JSON back to a .txt file is involved, as it appears you need to parse various other .bin files to translate from various ids to the strings. | |
example cell from a .txt file => .bin => .json | |
the "output" column's value in cubemain.txt is "Cow Portal", but the output object parsed from the .bin is below. I have no idea how to interpret this yet. | |
{ | |
"item_flags": "0", | |
"item_type": "0", | |
"item": "0", | |
"item_id": "0", | |
"param": "0", | |
"output_type": "1", | |
"lvl": "0", | |
"p_lvl": "0", | |
"i_lvl": "0", | |
"prefix_id": [ | |
"0", | |
"0", | |
"0" | |
], | |
"suffix_id": [ | |
"0", | |
"0", | |
"0" | |
], | |
"mods": [ | |
{ | |
"mod": "4294967295", | |
"mod_param": "0", | |
"mod_min": "0", | |
"mod_max": "0", | |
"mod_chance": "0" | |
}, | |
...repeats | |
] | |
} | |
=end | |
require 'bindata' | |
require 'json' | |
require 'fileutils' | |
class CubeMainInput < BinData::Record # size 0x08 | |
uint8 :input_flags #0x00 | |
uint8 :item_type #0x01 | |
uint16le :item #0x02 | |
uint16le :item_id #0x04 | |
uint8 :quality #0x06 | |
uint8 :quantity #0x07 | |
end | |
class CubeMainOutputMod < BinData::Record # size 0xC | |
uint32le :mod #0x00 | |
uint16le :mod_param #0x04 | |
uint16le :mod_min #0x06 | |
uint16le :mod_max #0x08 | |
uint16le :mod_chance #0x0a | |
end | |
class CubeMainOutput < BinData::Record # size 0x54 | |
uint8 :item_flags #0x00 | |
uint8 :item_type #0x01 | |
uint16le :item #0x02 | |
uint16le :item_id #0x04 | |
uint16le :param #0x06 | |
uint8 :output_type #0x08 edited from "type" as that is reserved in this version of Ruby | |
uint8 :lvl #0x09 | |
uint8 :p_lvl #0x0a | |
uint8 :i_lvl #0x0b | |
array :prefix_id, :type => :uint16le, :initial_length => 3 #0x0c | |
array :suffix_id, :type => :uint16le, :initial_length => 3 #0x12 | |
array :mods, :type => :cube_main_output_mod, :initial_length => 5 #0x18 | |
end | |
class CubeMainBinRecord < BinData::Record # size 0x148 | |
uint8 :enabled #0x00 | |
uint8 :ladder #0x01 | |
uint8 :mindiff #0x02 | |
uint8 :class_id #0x03 | |
uint32le :op #0x04 | |
uint32le :param #0x08 | |
uint32le :value_id #0x0c | |
uint16le :numinputs #0x10 | |
uint16le :version #0x12 | |
array :inputs, :type => :cube_main_input, :initial_length => 7 | |
array :outputs, :type => :cube_main_output, :initial_length => 3 | |
end | |
class CubeMainBin < BinData::Record # size 4+ | |
uint32le :record_count | |
array :records, :type => :cube_main_bin_record, :initial_length => :record_count | |
end | |
begin | |
f = ARGV[0] unless ARGV[0].nil? | |
output_path = ARGV[1] unless ARGV[1].nil? | |
bin = File.read(f) | |
records = CubeMainBin.read(bin).records | |
# Helper to recursively convert BinData objects to hashes | |
def bin_to_hash(obj) | |
if obj.is_a?(BinData::Array) || obj.is_a?(Array) | |
obj.map { |el| bin_to_hash(el) } | |
elsif obj.is_a?(BinData::Record) | |
obj.field_names.each_with_object({}) do |field, h| | |
h[field.to_s] = bin_to_hash(obj.send(field)) | |
end | |
else | |
obj | |
end | |
end | |
json_records = records.map { |rec| bin_to_hash(rec) } | |
input_basename = File.basename(f, File.extname(f)) | |
json_file = output_path ? output_path : File.join('assets', 'outputs', "#{input_basename}.json") | |
FileUtils.mkdir_p(File.dirname(json_file)) | |
File.write(json_file, JSON.pretty_generate(json_records)) | |
puts "Wrote #{json_file} with #{json_records.size} records." | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment