Last active
September 6, 2020 20:24
-
-
Save jstanley0/621168ea78890c77bdc10128c4cc66c3 to your computer and use it in GitHub Desktop.
Thing that extracts files from Day of the Tentacle: Remastered (`tenta.cle`) and probably similar things
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
require 'bindata' | |
require 'pathname' | |
require 'fileutils' | |
require 'optparse' | |
class Header < BinData::Record | |
endian :little | |
uint32 :magic | |
float :version | |
uint32 :index_start | |
uint32 :file_entries_start | |
uint32 :file_names_start | |
uint32 :data_start | |
uint32 :index_size | |
uint32 :file_entries_size | |
uint32 :file_names_size | |
uint32 :data_size | |
end | |
class FileEntry < BinData::Record | |
endian :little | |
uint32 :data_start | |
uint32 :name_start # unreliable! | |
uint32 :data_size | |
uint32 :compressed_size | |
uint32 :compression_type | |
attr_accessor :filename | |
def extract(header, file, dest) | |
file.seek header.data_start + data_start | |
IO.copy_stream file, dest, self.data_size | |
end | |
end | |
options = {} | |
opt_parser = OptionParser.new do |opt| | |
opt.banner = "Usage: parse_tentacle filename [OPTIONS]" | |
opt.separator "OPTIONS" | |
opt.on("-x" "--extract BASE", "extract files whose name start with BASE") do |file| | |
options[:extract] = file | |
end | |
end | |
opt_parser.parse! | |
file = ARGF | |
file.binmode | |
header = Header.read(file) | |
abort "unrecognized file type" unless header.magic == 0x4C50414B | |
num_files = header.file_entries_size / FileEntry.new.num_bytes | |
file.seek header.file_entries_start | |
file_entries = num_files.times.map { FileEntry.read(file) } | |
file.seek header.file_names_start | |
names = file.read(header.file_names_size).split("\0") | |
file_entries.each_with_index do |entry, index| | |
entry.filename = names[index] | |
end | |
file_entries.sort_by! { |entry| entry.filename } | |
if options[:extract] | |
file_entries.select { |entry| entry.filename.start_with?(options[:extract]) }.each do |entry| | |
pn = Pathname.new(entry.filename) | |
STDERR.puts entry.filename | |
FileUtils.mkdir_p pn.dirname | |
entry.extract header, file, File.open(entry.filename, 'wb') | |
end | |
else | |
# list | |
file_entries.each do |entry| | |
printf "%11d %s\n", entry.data_size, entry.filename | |
end | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment