Skip to content

Instantly share code, notes, and snippets.

@jstanley0
Last active September 6, 2020 20:24
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jstanley0/621168ea78890c77bdc10128c4cc66c3 to your computer and use it in GitHub Desktop.
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
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