This python script is awesome.
Here's my Ruby version of it (with minor improvement) and search capabilities. (for iOS 5)
# encoding: utf-8
require 'fileutils'
require 'digest/sha1'
class ManifestParser
def initialize(mbdb_filename, verbose = false)
@verbose = verbose
process_mbdb_file(mbdb_filename)
end
# Returns the numbers of records in the Manifest files.
def record_number
@mbdb.size
end
# Returns a huge string containing the parsing of the Manifest files.
def to_s
s = ''
@mbdb.each do |v|
s += "#{fileinfo_str(v)}\n"
end
s
end
def to_file(filename)
File.open(filename, 'w') do |f|
@mbdb.each do |v|
f.puts fileinfo_str(v)
end
end
end
# Copy the backup files to their real path/name.
# * domain_match Can be a regexp to restrict the files to copy.
# * filename_match Can be a regexp to restrict the files to copy.
def rename_files(domain_match = nil, filename_match = nil)
@mbdb.each do |v|
if v[:type] == '-' # Only rename files.
if (domain_match.nil? or v[:domain] =~ domain_match) and (filename_match.nil? or v[:filename] =~ filename_match)
dst = "#{v[:domain]}/#{v[:filename]}"
puts "Creating: #{dst}"
FileUtils.mkdir_p(File.dirname(dst))
FileUtils.cp(v[:fileID], dst)
end
end
end
end
# Return the filename that math the given regexp.
def search(regexp)
result = Array.new
@mbdb.each do |v|
if "#{v[:domain]}::#{v[:filename]}" =~ regexp
result << v
end
end
result
end
private
# Retrieve an integer (big-endian) and new offset from the current offset
def getint(data, offset, intsize)
value = 0
while intsize > 0
value = (value<<8) + data[offset].ord
offset += 1
intsize -= 1
end
return value, offset
end
# Retrieve a string and new offset from the current offset into the data
def getstring(data, offset)
return '', offset + 2 if data[offset] == 0xFF.chr and data[offset + 1] == 0xFF.chr # Blank string
length, offset = getint(data, offset, 2) # 2-byte length
value = data[offset...(offset + length)]
return value, (offset + length)
end
def process_mbdb_file(filename)
@mbdb = Array.new
data = File.open(filename, 'rb') { |f| f.read }
puts "MBDB file read. Size: #{data.size}"
raise 'This does not look like an MBDB file' if data[0...4] != 'mbdb'
offset = 4
offset += 2 # value x05 x00, not sure what this is
while offset < data.size
fileinfo = Hash.new
fileinfo[:start_offset] = offset
fileinfo[:domain], offset = getstring(data, offset)
fileinfo[:filename], offset = getstring(data, offset)
fileinfo[:linktarget], offset = getstring(data, offset)
fileinfo[:datahash], offset = getstring(data, offset)
fileinfo[:unknown1], offset = getstring(data, offset)
fileinfo[:mode], offset = getint(data, offset, 2)
if (fileinfo[:mode] & 0xE000) == 0xA000 # Symlink
fileinfo[:type] = 'l'
elsif (fileinfo[:mode] & 0xE000) == 0x8000 # File
fileinfo[:type] = '-'
elsif (fileinfo[:mode] & 0xE000) == 0x4000 # Dir
fileinfo[:type] = 'd'
else
# $stderr.puts "Unknown file type %04x for #{fileinfo_str(f, false)}" % f['mode']
fileinfo[:type] = '?'
end
fileinfo[:unknown2], offset = getint(data, offset, 4)
fileinfo[:unknown3], offset = getint(data, offset, 4)
fileinfo[:userid], offset = getint(data, offset, 4)
fileinfo[:groupid], offset = getint(data, offset, 4)
fileinfo[:mtime], offset = getint(data, offset, 4)
fileinfo[:atime], offset = getint(data, offset, 4)
fileinfo[:ctime], offset = getint(data, offset, 4)
fileinfo[:filelen], offset = getint(data, offset, 8)
fileinfo[:flag], offset = getint(data, offset, 1)
fileinfo[:numprops], offset = getint(data, offset, 1)
fileinfo[:properties] = Hash.new
(0...(fileinfo[:numprops])).each do |ii|
propname, offset = getstring(data, offset)
propval, offset = getstring(data, offset)
fileinfo[:properties][propname] = propval
end
# Compute the ID of the file.
fullpath = fileinfo[:domain] + '-' + fileinfo[:filename]
fileinfo[:fileID] = Digest::SHA1.hexdigest(fullpath)
# We add the file to the list of files.
@mbdb << fileinfo
end
@mbdb
end
def modestr(val)
def mode(val)
r = (val & 0x4) ? 'r' : '-'
w = (val & 0x2) ? 'w' : '-'
x = (val & 0x1) ? 'x' : '-'
r + w + x
end
mode(val >> 6) + mode(val >> 3) + mode(val)
end
def fileinfo_str(f)
return "(#{f[:fileID]})#{f[:domain]}::#{f[:filename]}" unless @verbose
data = [f[:type], modestr(f[:mode]), f[:userid], f[:groupid], f[:filelen], f[:mtime], f[:atime], f[:ctime], f[:fileID], f[:domain], f[:filename]]
info = "%s%s %08x %08x %7d %10d %10d %10d (%s)%s::%s" % data
info += ' -> ' + f[:linktarget] if f[:type] == 'l' # Symlink destination
f[:properties].each do |k, v|
info += " #{k}=#{v.inspect}"
end
info
end
end
if __FILE__ == $0
mp = ManifestParser.new 'Manifest.mbdb', true
mp.to_file 'filenames.txt'
end