-
-
Save trishume/138ef8f6c66fabd2d76b9fdf8d5c4c67 to your computer and use it in GitHub Desktop.
Unity Asset Hex Fiend Template
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
# Hex Fiend Template for Unity Asset Files | |
# By Tristan Hume | |
# Based on inspection and reading the code for https://github.com/Perfare/AssetStudio | |
# See https://github.com/ridiculousfish/HexFiend/blob/master/templates/Tutorial.md | |
# Only supports the Unity version Beat Saber uses and Beat Saber's MonoBehaviors | |
# Match Unity Version 2018. | |
requires 20 "32 30 31 38 2E" | |
proc align_stream {align} { | |
set misalign [expr {$align - [pos] % $align}] | |
if {$misalign < $align} { | |
move $misalign | |
} | |
} | |
proc c_string {name} { | |
set length 1 | |
while {[uint8] != 0} { | |
incr length | |
} | |
move -$length | |
ascii $length $name | |
} | |
proc aligned_string {name} { | |
set length [uint32] | |
if {$length > 0} { | |
str $length "utf8" $name | |
align_stream 4 | |
} else { | |
move -4 | |
bytes 4 $name | |
} | |
} | |
proc object_ref {name} { | |
section $name { | |
# index in export table | |
uint32 "File ID" | |
# this is 1 if exterior, object index+1 if interior | |
uint64 "Path ID" | |
} | |
} | |
proc beatmap_difficulties {} { | |
set difficulties_count [uint32 "Difficulties Count"] | |
for {set i 0} {$i < $difficulties_count} {incr i} { | |
section "Difficulty $i" { | |
uint32 "Difficulty" | |
uint32 "Rank" | |
float "Note Jump Movement Speed" | |
uint32 "Note Jump Start Beat Offset" | |
object_ref "Beatmap Data" | |
} | |
} | |
} | |
proc beatmap_level {} { | |
aligned_string "Level ID" | |
aligned_string "Song Name" | |
aligned_string "Sub Name" | |
aligned_string "Author Name" | |
aligned_string "Level Author" | |
object_ref "Audio Clip" | |
float "BPM" | |
float "Time Offset" | |
float "Shuffle" | |
float "Shuffle Period" | |
float "Preview Start Time" | |
float "Preview Duration" | |
# Dunno why song duration is missing in the data | |
# float "Song Duration" | |
object_ref "Cover Image" | |
object_ref "Environment" | |
set sets_count [uint32 "Beatmap Set Count"] | |
for {set i 0} {$i < $sets_count} {incr i} { | |
section "Set $i" { | |
object_ref "Characteristic" | |
beatmap_difficulties | |
} | |
} | |
} | |
proc beatmap_data {} { | |
aligned_string "JSON Data" | |
set sig_len [uint32 "Signature Length"] | |
hex $sig_len "Signature" | |
set data_len [uint32 "Data Length"] | |
bytes $data_len "Projected Data" | |
} | |
proc level_collection {} { | |
set sets_count [uint32 "Level Count"] | |
for {set i 0} {$i < $sets_count} {incr i} { | |
object_ref "Level $i" | |
} | |
} | |
proc audio_clip {} { | |
aligned_string "Name" | |
uint32 "Load Type" | |
uint32 "Channels" | |
uint32 "Frequency" | |
uint32 "Bits Per Sample" | |
float "Length" | |
uint8 "Is Tracker" | |
align_stream 4 | |
uint32 "Subsound Index" | |
uint8 "Preload Audio" | |
uint8 "Background Load" | |
uint8 "Legacy 3D" | |
align_stream 4 | |
aligned_string "Source" | |
uint64 "Offset" | |
uint64 "Size" | |
# 0 = PCM, 1 = Vorbis, 3 = MP3 | |
uint32 "Compression Format" | |
} | |
big_endian | |
section "Header" { | |
set meta_s [uint32 "Metadata Size"] | |
set file_size [uint32 "File Size"] | |
uint32 "Generation" | |
set data_offset [uint32 "Data Offset"] | |
uint32 "Is Big Endian" | |
} | |
little_endian | |
section "Metadata" { | |
# ascii 12 "Unity Version" | |
c_string "Unity Version" | |
uint32 "Target Platform" | |
uint8 "Enable Type Tree" | |
set type_count [uint32 "Type Count"] | |
set types_classes [dict create] | |
section "Types" { | |
for {set i 0} {$i < $type_count} {incr i} { | |
section [expr $i + 1] { | |
set class_id [uint32 "Class ID"] | |
dict set types_classes $i $class_id | |
uint8 "Is Stripped" | |
int16 "Script Type Index" | |
if {$class_id == 114} { | |
hex 16 "Script ID" | |
} | |
hex 16 "Type Hash" | |
} | |
} | |
} | |
set object_count [uint32 "Object Count"] | |
set objects [list] | |
section "Objects" { | |
for {set i 0} {$i < $object_count} {incr i} { | |
section [expr $i] { | |
align_stream 4 | |
uint64 "Path ID" | |
set offset [uint32 "Byte Offset"] | |
uint32 "Byte Size" | |
set type_id [uint32 "Type ID"] | |
set class_id [dict get $types_classes $type_id] | |
entry "Class ID" $class_id | |
lappend objects [dict create offset $offset id $i class_id $class_id] | |
} | |
} | |
} | |
set script_count [uint32 "Script Count"] | |
section "Scripts" { | |
for {set i 0} {$i < $script_count} {incr i} { | |
section [expr $i] { | |
uint32 "File Index" | |
align_stream 4 | |
uint64 "In-File ID" | |
} | |
} | |
} | |
set externals_count [uint32 "Externals Count"] | |
section "Externals" { | |
for {set i 0} {$i < $externals_count} {incr i} { | |
section [expr $i] { | |
c_string "Temp Empty" | |
hex 16 "GUID" | |
uint32 "Type" | |
c_string "Path Name" | |
} | |
} | |
} | |
} | |
goto $data_offset | |
set data_len [expr {$file_size - $data_offset}] | |
section "Data" { | |
# bytes $data_len "Data" | |
foreach object $objects { | |
set object_offset [dict get $object offset] | |
set object_id [dict get $object id] | |
set object_class [dict get $object class_id] | |
goto [expr {$data_offset + $object_offset}] | |
if {$object_class == 114} { | |
section "$object_id: MonoBehavior" { | |
object_ref "GameObject" | |
uint32 "Enabled" | |
uint32 "Script Export ID" | |
set kind [uint64 "Kind"] | |
# TODO | |
# section "Script" { | |
# uint32 "Execution Order" | |
# hex 16 "Properties Hash" | |
# aligned_string "Class Name" | |
# } | |
aligned_string "Name" | |
# BeatmapLevelSO | |
if {$kind == 644} { | |
beatmap_level | |
} elseif {$kind == 762} { | |
level_collection | |
} elseif {$kind == 1552} { | |
beatmap_data | |
} | |
} | |
} elseif {$object_class == 83} { | |
section "$object_id: AudioClip" { | |
audio_clip | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment