xar unarchiver (.xar, .pkg, .xip)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Initial commit

+129
+129
czar.cr
··· 1 + #!/usr/bin/env crystal 2 + 3 + require "binary_parser" 4 + require "zlib" 5 + require "xml" 6 + 7 + def perror(msg : String) 8 + STDERR.write Slice.new (msg + "\n").to_unsafe, (msg + "\n").size 9 + exit 1 10 + end 11 + 12 + CHECKSUM_ALGO = { 13 + 0 => :NONE, 14 + 1 => :SHA1, 15 + 2 => :MD5, 16 + } 17 + 18 + class XARHeader < BinaryParser 19 + endian :big 20 + string :magic, {count: 4} 21 + uint16 :header_size 22 + uint16 :version 23 + uint64 :length_compressed 24 + uint64 :length_uncompressed 25 + uint32 :checksum_algo 26 + end 27 + 28 + class SliceIO < IO 29 + def initialize(@slice : Bytes) 30 + end 31 + 32 + def read(slice : Bytes) 33 + slice.size.times { |i| slice[i] = @slice[i] } 34 + @slice += slice.size 35 + slice.size 36 + end 37 + 38 + def write(slice : Bytes) 39 + slice.size.times { |i| @slice[i] = slice[i] } 40 + @slice += slice.size 41 + nil 42 + end 43 + 44 + def to_slice 45 + @slice 46 + end 47 + end 48 + 49 + def xml_select(xml : XML::Node, node : String) 50 + perror "error in xml_select" if xml.nil? 51 + xml.children.select { |e| e.name == node } 52 + end 53 + 54 + def xml_value(xml : XML::Node, name : String) 55 + perror "error in xml_value" if xml.nil? 56 + xml.children.select { |e| e.name == name }.map { |e| e.content } 57 + end 58 + 59 + def xar_decode(entity : XML::Node, path : String = "./") 60 + entity_name = xml_value(entity, "name") 61 + if entity_name.size < 1 62 + puts "entity without name: #{entity.to_s}" 63 + return 64 + end 65 + entity_name = entity_name.first 66 + 67 + entity_type = xml_value(entity, "type") 68 + if entity_type.size < 1 69 + puts "entity without type: #{entity.to_s}" 70 + return 71 + end 72 + entity_type = entity_type.first 73 + 74 + puts "#{path}#{entity_name} (#{entity_type})" 75 + children = xml_select(entity, "file") 76 + if children.size > 0 77 + if entity_type != "directory" 78 + puts "warn: found a #{entity_type} with #{children.size} children" 79 + end 80 + 81 + children.each do |child| 82 + xar_decode child, "#{path}#{entity_name}/" 83 + end 84 + end 85 + end 86 + 87 + File.open "test.xar", "r" do |file| 88 + header = XARHeader.new 89 + header.load file 90 + 91 + perror "not a xar file" if header.magic != "xar!" 92 + 93 + puts "#{header.magic}" 94 + puts "header size #{header.header_size}" 95 + puts "format version #{header.version}" 96 + puts "TOC length (compressed) #{header.length_compressed}" 97 + puts "TOC length (uncompressed) #{header.length_uncompressed}" 98 + puts "checksum algo #{CHECKSUM_ALGO[header.checksum_algo]}" 99 + 100 + toc_data = Bytes.new header.length_uncompressed 101 + file.seek header.header_size 102 + Zlib::Reader.open file do |zfile| 103 + zfile.read toc_data 104 + end 105 + 106 + xar = XML.parse SliceIO.new toc_data 107 + 108 + toc = xml_select(xar, "xar").first 109 + toc = xml_select(toc, "toc").first 110 + 111 + data_offset = header.header_size + header.length_compressed 112 + data_checksum = xml_select(toc, "checksum").first 113 + data_checksum_offset = xml_value(data_checksum, "offset").first.to_i 114 + data_checksum_size = xml_value(data_checksum, "size").first.to_i 115 + data_checksum_type = data_checksum["style"] 116 + 117 + puts "TOC is checksummed as #{data_checksum_type}, #{data_checksum_size} bytes stored at offset #{data_checksum_offset}" 118 + 119 + file.seek data_offset 120 + 121 + file.seek data_checksum_offset, IO::Seek::Current 122 + 123 + data_files = xml_select(toc, "file") 124 + puts "found #{data_files.size} files at the root" 125 + 126 + data_files.each do |entity| 127 + xar_decode entity 128 + end 129 + end