xaba 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0a4a201394557976db4185f74720f9d5fbe0a3f6d5ba46d5d915293fc9e2a79a
4
+ data.tar.gz: 7d47f78b0a26305abd7aa7537dfbe81e28a145847656bb0ae2a5eb7d8d6830ba
5
+ SHA512:
6
+ metadata.gz: 5da843576b823b0e7eef843a1e230c78c42be8910f5955e470137b98132eccd9bbc6b807d36020c3b0617890279b22b17555209d8a91244f8fadf66fb87af49a
7
+ data.tar.gz: c0ba45f8f366439ba38db54038ad4e0a180e3b222462ed21a7d2b5a06c67349c2a938640ce90e576d88d4e1f01ec17295f4c0d23dd9f550372ad6b354ace9c08
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,64 @@
1
+ require:
2
+ - rubocop-rspec
3
+ - rubocop-rake
4
+
5
+ AllCops:
6
+ TargetRubyVersion: 2.7
7
+ NewCops: enable
8
+
9
+ Style/StringLiterals:
10
+ Enabled: true
11
+ EnforcedStyle: double_quotes
12
+
13
+ Style/StringLiteralsInInterpolation:
14
+ Enabled: true
15
+ EnforcedStyle: double_quotes
16
+
17
+ Style/Documentation:
18
+ Enabled: false
19
+
20
+ Style/FormatString:
21
+ Enabled: false
22
+
23
+ Style/FormatStringToken:
24
+ Enabled: false
25
+
26
+ Style/MultilineBlockChain:
27
+ Enabled: false
28
+
29
+ Metrics/AbcSize:
30
+ Enabled: false
31
+
32
+ Metrics/BlockLength:
33
+ Enabled: false
34
+
35
+ Metrics/ClassLength:
36
+ Enabled: false
37
+
38
+ Metrics/CyclomaticComplexity:
39
+ Enabled: false
40
+
41
+ Metrics/MethodLength:
42
+ Enabled: false
43
+
44
+ Metrics/PerceivedComplexity:
45
+ Enabled: false
46
+
47
+ Naming/MethodParameterName:
48
+ Enabled: false
49
+
50
+ Layout/LineLength:
51
+ Max: 120
52
+
53
+ RSpec/ContextWording:
54
+ Enabled: false
55
+
56
+ RSpec/DescribeClass:
57
+ Enabled: false
58
+
59
+ RSpec/ExampleLength:
60
+ Enabled: false
61
+
62
+ RSpec/MultipleExpectations:
63
+ Enabled: false
64
+
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in xaba.gemspec
6
+ gemspec
7
+
8
+ group :development, :test do
9
+ gem "rake", "~> 13.0"
10
+ gem "rspec", "~> 3.0"
11
+ gem "rubocop", "~> 1.21"
12
+ gem "rubocop-rake", "~> 0.6.0"
13
+ gem "rubocop-rspec", "~> 2.26"
14
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,79 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ xaba (0.1.0)
5
+ extlz4 (~> 0.3.4)
6
+ iostruct (~> 0.0.5)
7
+ xxhash (~> 0.5.0)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ ast (2.4.2)
13
+ diff-lcs (1.5.0)
14
+ extlz4 (0.3.4)
15
+ iostruct (0.0.5)
16
+ json (2.7.1)
17
+ language_server-protocol (3.17.0.3)
18
+ parallel (1.24.0)
19
+ parser (3.3.0.4)
20
+ ast (~> 2.4.1)
21
+ racc
22
+ racc (1.7.3)
23
+ rainbow (3.1.1)
24
+ rake (13.1.0)
25
+ regexp_parser (2.9.0)
26
+ rexml (3.2.6)
27
+ rspec (3.12.0)
28
+ rspec-core (~> 3.12.0)
29
+ rspec-expectations (~> 3.12.0)
30
+ rspec-mocks (~> 3.12.0)
31
+ rspec-core (3.12.2)
32
+ rspec-support (~> 3.12.0)
33
+ rspec-expectations (3.12.3)
34
+ diff-lcs (>= 1.2.0, < 2.0)
35
+ rspec-support (~> 3.12.0)
36
+ rspec-mocks (3.12.6)
37
+ diff-lcs (>= 1.2.0, < 2.0)
38
+ rspec-support (~> 3.12.0)
39
+ rspec-support (3.12.1)
40
+ rubocop (1.60.1)
41
+ json (~> 2.3)
42
+ language_server-protocol (>= 3.17.0)
43
+ parallel (~> 1.10)
44
+ parser (>= 3.3.0.2)
45
+ rainbow (>= 2.2.2, < 4.0)
46
+ regexp_parser (>= 1.8, < 3.0)
47
+ rexml (>= 3.2.5, < 4.0)
48
+ rubocop-ast (>= 1.30.0, < 2.0)
49
+ ruby-progressbar (~> 1.7)
50
+ unicode-display_width (>= 2.4.0, < 3.0)
51
+ rubocop-ast (1.30.0)
52
+ parser (>= 3.2.1.0)
53
+ rubocop-capybara (2.20.0)
54
+ rubocop (~> 1.41)
55
+ rubocop-factory_bot (2.25.1)
56
+ rubocop (~> 1.41)
57
+ rubocop-rake (0.6.0)
58
+ rubocop (~> 1.0)
59
+ rubocop-rspec (2.26.1)
60
+ rubocop (~> 1.40)
61
+ rubocop-capybara (~> 2.17)
62
+ rubocop-factory_bot (~> 2.22)
63
+ ruby-progressbar (1.13.0)
64
+ unicode-display_width (2.5.0)
65
+ xxhash (0.5.0)
66
+
67
+ PLATFORMS
68
+ arm64-darwin-21
69
+
70
+ DEPENDENCIES
71
+ rake (~> 13.0)
72
+ rspec (~> 3.0)
73
+ rubocop (~> 1.21)
74
+ rubocop-rake (~> 0.6.0)
75
+ rubocop-rspec (~> 2.26)
76
+ xaba!
77
+
78
+ BUNDLED WITH
79
+ 2.3.7
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Andrey "Zed" Zaikin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ xaba
2
+ ======
3
+
4
+ Description
5
+ -----------
6
+ (un)packer for [Xamarin assemblies.blob](https://github.com/xamarin/xamarin-android/blob/main/Documentation/project-docs/AssemblyStores.md)
7
+
8
+ Installation
9
+ ------------
10
+
11
+ gem install xaba
12
+
13
+ Usage
14
+ -----
15
+
16
+ # xaba -h
17
+
18
+ Usage: xaba [options] [files]
19
+ Commands:
20
+ -l, --list List assemblies in the blob
21
+ -u, --unpack Unpack all or specified file(s)
22
+ -r, --replace Replace file(s) in the blob
23
+
24
+ Options:
25
+ -m, --manifest PATH Pathname of the input assemblies.manifest file
26
+ -b, --blob PATH [and/or] pathname of the input assemblies.blob file
27
+
28
+ -o, --output PATH Pathname for the output assemblies.blob file when replacing
29
+ Pathname for the output dir when unpacking
30
+ -v, --verbose Increase verbosity
31
+ --version Prints the version
32
+ -h, --help Prints this help
33
+
34
+ License
35
+ -------
36
+ MIT
data/README.md.tpl ADDED
@@ -0,0 +1,20 @@
1
+ xaba
2
+ ======
3
+
4
+ Description
5
+ -----------
6
+ (un)packer for [Xamarin assemblies.blob](https://github.com/xamarin/xamarin-android/blob/main/Documentation/project-docs/AssemblyStores.md)
7
+
8
+ Installation
9
+ ------------
10
+
11
+ gem install xaba
12
+
13
+ Usage
14
+ -----
15
+
16
+ % xaba -h
17
+
18
+ License
19
+ -------
20
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop readme]
13
+
14
+ desc "generate sample files"
15
+ task :generate_sample_files do
16
+ require "xaba"
17
+
18
+ name = "test"
19
+ data = "A" * 1024
20
+ compressed_data = LZ4.block_compress(data)
21
+
22
+ c = XABA::Container.new
23
+ c.file_header = XABA::FileHeader.new magic: "XABA", version: 1, local_entry_count: 1, global_entry_count: 1,
24
+ store_id: 0
25
+ c.descriptors = [
26
+ XABA::AssemblyDescriptor.new(
27
+ data_offset: XABA::FileHeader::SIZE + XABA::AssemblyDescriptor::SIZE + (XABA::HashEntry::SIZE * 2),
28
+ data_size: compressed_data.size + XABA::DataEntry::SIZE,
29
+ debug_data_offset: 0,
30
+ debug_data_size: 0,
31
+ config_data_offset: 0,
32
+ config_data_size: 0
33
+ )
34
+ ]
35
+ c.hash32_entries = [
36
+ XABA::HashEntry.new(
37
+ hash: XXhash.xxh32(name),
38
+ mapping_index: 0,
39
+ local_store_index: 0,
40
+ store_id: 0
41
+ )
42
+ ]
43
+ c.hash64_entries = [
44
+ XABA::HashEntry.new(
45
+ hash: XXhash.xxh64(name),
46
+ mapping_index: 0,
47
+ local_store_index: 0,
48
+ store_id: 0
49
+ )
50
+ ]
51
+ de = XABA::DataEntry.new(
52
+ original_size: data.size,
53
+ index: 0,
54
+ magic: "XALZ"
55
+ )
56
+ de.data = compressed_data
57
+ c.data_entries = [de]
58
+ File.open("spec/samples/1.blob", "wb") do |f|
59
+ c.write(f)
60
+ end
61
+
62
+ m = XABA::Manifest.new
63
+ m.assemblies = [
64
+ XABA::Manifest::AssemblyInfo.new(
65
+ "0x%x" % XXhash.xxh32(name),
66
+ "0x%x" % XXhash.xxh64(name),
67
+ 0,
68
+ 0,
69
+ name
70
+ )
71
+ ]
72
+ File.open(fname1 = "spec/samples/1.manifest", "wb") do |f|
73
+ m.write(f)
74
+ end
75
+
76
+ ### 2nd file
77
+
78
+ name = "test2"
79
+ data = name
80
+ compressed_data = LZ4.block_compress(data)
81
+
82
+ m.assemblies << XABA::Manifest::AssemblyInfo.new(
83
+ "0x%x" % XXhash.xxh32(name),
84
+ "0x%x" % XXhash.xxh64(name),
85
+ 0,
86
+ 1,
87
+ name
88
+ )
89
+
90
+ File.open("spec/samples/2.manifest", "wb") do |f|
91
+ m.write(f)
92
+ end
93
+
94
+ c.file_header.local_entry_count += 1
95
+ c.file_header.global_entry_count += 1
96
+
97
+ c.descriptors << XABA::AssemblyDescriptor.new(
98
+ data_offset: File.size(fname1) + XABA::AssemblyDescriptor::SIZE + (XABA::HashEntry::SIZE * 2),
99
+ data_size: compressed_data.size + XABA::DataEntry::SIZE,
100
+ debug_data_offset: 0,
101
+ debug_data_size: 0,
102
+ config_data_offset: 0,
103
+ config_data_size: 0
104
+ )
105
+ c.hash32_entries << XABA::HashEntry.new(
106
+ hash: XXhash.xxh32(name),
107
+ mapping_index: 0,
108
+ local_store_index: 0,
109
+ store_id: 0
110
+ )
111
+
112
+ c.hash64_entries << XABA::HashEntry.new(
113
+ hash: XXhash.xxh64(name),
114
+ mapping_index: 0,
115
+ local_store_index: 0,
116
+ store_id: 0
117
+ )
118
+ de = XABA::DataEntry.new(
119
+ original_size: data.size,
120
+ index: 0,
121
+ magic: "XALZ"
122
+ )
123
+ de.data = compressed_data
124
+ c.data_entries << de
125
+ File.open("spec/samples/2.blob", "wb") do |f|
126
+ c.write(f)
127
+ end
128
+ end
129
+
130
+ desc "build readme"
131
+ task :readme do
132
+ require "erb"
133
+ tpl = File.read("README.md.tpl").gsub(/^%\s+(.+)/) do |x|
134
+ x.sub!(/^%/, "")
135
+ "<%= run(\"#{x}\") %>"
136
+ end
137
+ result = ERB.new(tpl, trim_mode: "%>").result
138
+ File.write("README.md", result)
139
+ end
140
+
141
+ def run(cmd)
142
+ cmd.strip!
143
+ puts "[.] #{cmd}"
144
+ r = " # #{cmd}\n\n"
145
+ cmd.sub!(/^xaba/, "./exe/xaba")
146
+ lines = `#{cmd}`.sub(/\A\n+/m, "").sub(/\s+\Z/, "").split("\n")
147
+ lines = lines[0, 25] + ["..."] if lines.size > 50
148
+ r << lines.map { |x| " #{x}" }.join("\n")
149
+ r << "\n"
150
+ end
data/exe/xaba ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "xaba"
5
+
6
+ XABA::CLI.new.run
data/lib/xaba/cli.rb ADDED
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+ require "fileutils"
5
+
6
+ module XABA
7
+ class CLI
8
+ def initialize
9
+ @options = { verbosity: 0 }
10
+ end
11
+
12
+ def parse_args(args)
13
+ OptionParser.new do |opts|
14
+ opts.banner = "Usage: xaba [options] [files]"
15
+
16
+ opts.separator "Commands:"
17
+
18
+ opts.on("-l", "--list", "List assemblies in the blob") do
19
+ @options[:command] = :list
20
+ end
21
+
22
+ opts.on("-u", "--unpack", "Unpack all or specified file(s)") do |_file|
23
+ @options[:command] = :unpack
24
+ end
25
+
26
+ opts.on("-r", "--replace", "Replace file(s) in the blob") do |_file|
27
+ @options[:command] = :replace
28
+ end
29
+
30
+ opts.separator ""
31
+ opts.separator "Options:"
32
+
33
+ opts.on("-m", "--manifest PATH", "Pathname of the input assemblies.manifest file") do |path|
34
+ @options[:manifest] = path
35
+ end
36
+
37
+ opts.on("-b", "--blob PATH", "[and/or] pathname of the input assemblies.blob file") do |path|
38
+ @options[:blob] = path
39
+ end
40
+
41
+ opts.separator ""
42
+
43
+ opts.on("-o", "--output PATH",
44
+ "Pathname for the output assemblies.blob file when replacing",
45
+ "Pathname for the output dir when unpacking") do |path|
46
+ @options[:output] = path
47
+ end
48
+
49
+ opts.on("-v", "--verbose", "Increase verbosity") do
50
+ @options[:verbosity] += 1
51
+ end
52
+
53
+ opts.on("--version", "Prints the version") do
54
+ @options[:command] = :version
55
+ end
56
+
57
+ opts.on("-h", "--help", "Prints this help") do
58
+ # intentonally left blank
59
+ end
60
+ end.tap { |o| o.parse!(args) }
61
+ end
62
+
63
+ def guess_missing_file_paths
64
+ if @options[:blob] && @options[:manifest]
65
+ # OK
66
+ elsif @options[:blob] && !@options[:manifest]
67
+ @options[:manifest] = "#{@options[:blob].sub(/\.blob$/, "")}.manifest"
68
+
69
+ elsif @options[:manifest] && !@options[:blob]
70
+ @options[:blob] = "#{@options[:manifest].sub(/\.manifest$/, "")}.blob"
71
+ else
72
+ abort "[!] The --blob or --manifest option is required"
73
+ end
74
+ end
75
+
76
+ def read_manifest
77
+ Manifest.read(@options[:manifest])
78
+ end
79
+
80
+ def read_blob
81
+ Container.read(@options[:blob])
82
+ end
83
+
84
+ def read_all
85
+ guess_missing_file_paths
86
+ @manifest = read_manifest
87
+ @blob = read_blob
88
+ end
89
+
90
+ ### run
91
+
92
+ def run(args = ARGV)
93
+ option_parser = parse_args(args)
94
+ @args = args
95
+ unless @options[:command]
96
+ puts option_parser
97
+ return
98
+ end
99
+
100
+ send @options[:command]
101
+ end
102
+
103
+ ### commands
104
+
105
+ def version
106
+ puts XABA::VERSION
107
+ end
108
+
109
+ def list
110
+ read_all
111
+
112
+ case @options[:verbosity]
113
+ when 2..Float::INFINITY
114
+ puts "[.] manifest:"
115
+ @manifest.assemblies.each_with_index do |e, i|
116
+ printf "%5d: %s\n", i, e.inspect
117
+ end
118
+
119
+ puts
120
+ puts "[.] blob file header:"
121
+ printf " %s\n", @blob.file_header.inspect
122
+
123
+ puts
124
+ puts "[.] assembly descriptors:"
125
+ @blob.descriptors.each_with_index do |e, i|
126
+ printf "%5d: %s\n", i, e.inspect
127
+ end
128
+
129
+ puts
130
+ puts "[.] hash32 entries:"
131
+ @blob.hash32_entries.each_with_index do |e, i|
132
+ printf "%5d: %s\n", i, e.inspect
133
+ end
134
+
135
+ puts
136
+ puts "[.] hash64 entries:"
137
+ @blob.hash64_entries.each_with_index do |e, i|
138
+ printf "%5d: %s\n", i, e.inspect
139
+ end
140
+
141
+ puts
142
+ puts "[.] data entries:"
143
+ @blob.data_entries.each_with_index do |e, i|
144
+ printf "%5d: %s\n", i, e.inspect
145
+ end
146
+ when 1
147
+ printf "%4s %8s %8s %s\n", "idx", "compsz", "origsz", "name"
148
+ @manifest.assemblies.each_with_index do |a, i|
149
+ compsz = @blob.descriptors[i].data_size
150
+ origsz = @blob.data_entries[i].original_size
151
+ printf "%4d %8d %8d %s\n", a.blob_idx, compsz, origsz, a.name
152
+ end
153
+ else
154
+ # minimal verbosity
155
+ printf "%4s %s\n", "idx", "name"
156
+ @manifest.assemblies.each do |a|
157
+ printf "%4d %s\n", a.blob_idx, a.name
158
+ end
159
+ end
160
+ end
161
+
162
+ def unpack
163
+ abort "[!] The --output option is required for the replace command" unless @options[:output]
164
+ read_all
165
+
166
+ FileUtils.mkdir_p(@options[:output])
167
+
168
+ @blob.data_entries.each_with_index do |de, idx|
169
+ name = @manifest.assemblies[idx].name
170
+ next if @args.any? && !@args.include?(name) && !@args.include?("#{name}.dll")
171
+
172
+ decompressed = LZ4.block_decode(de.data)
173
+ dll_fname = File.join(@options[:output], "#{name}.dll")
174
+ File.binwrite dll_fname, decompressed
175
+ end
176
+ end
177
+
178
+ def replace
179
+ abort "[!] The --output option is required for the replace command" unless @options[:output]
180
+ abort "[!] No files specified for the replace command" if @args.empty?
181
+ read_all
182
+
183
+ # convert args to hash
184
+ h = {}
185
+ @args.each do |arg|
186
+ if File.exist?(arg)
187
+ h[File.basename(arg, ".dll")] = File.binread(arg)
188
+ else
189
+ abort "[!] cannot find #{arg}"
190
+ end
191
+ end
192
+
193
+ @blob.data_entries.each_with_index do |_de, idx|
194
+ name = @manifest.assemblies[idx].name
195
+ @blob.replace!(idx, h[name]) if h[name]
196
+ end
197
+
198
+ File.open(@options[:output], "wb") do |f|
199
+ @blob.write(f)
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XABA
4
+ class Container
5
+ attr_accessor :file_header, :descriptors, :hash32_entries, :hash64_entries, :data_entries
6
+
7
+ def read(f)
8
+ return if f.nil?
9
+
10
+ @file_header = FileHeader.read(f)
11
+ @descriptors = @file_header.local_entry_count.times.map { AssemblyDescriptor.read(f) }
12
+ @hash32_entries = @file_header.local_entry_count.times.map { HashEntry.read(f) }
13
+ @hash64_entries = @file_header.local_entry_count.times.map { HashEntry.read(f) }
14
+
15
+ @data_entries = @descriptors.map do |d|
16
+ DataEntry.read(f).tap do |de|
17
+ de.data = f.read(d.data_size - DataEntry::SIZE)
18
+ end
19
+ end
20
+ self
21
+ end
22
+
23
+ def replace!(idx, newdata)
24
+ compressed_data = LZ4.block_compress(newdata)
25
+ delta = compressed_data.bytesize - data_entries[idx].data.bytesize
26
+ descriptors[idx].data_size += delta
27
+ data_entries[idx].original_size = newdata.bytesize
28
+ data_entries[idx].data = compressed_data
29
+
30
+ descriptors[idx + 1..].each do |d|
31
+ d.data_offset += delta
32
+ end
33
+ end
34
+
35
+ def write(f)
36
+ f.write @file_header.pack
37
+ @descriptors.each do |d|
38
+ f.write d.pack
39
+ end
40
+ @hash32_entries.each do |e|
41
+ f.write e.pack
42
+ end
43
+ @hash64_entries.each do |e|
44
+ f.write e.pack
45
+ end
46
+ @data_entries.each do |e|
47
+ f.write e.pack
48
+ end
49
+ end
50
+
51
+ def self.read(fname)
52
+ File.open(fname, "rb") do |f|
53
+ new.read(f)
54
+ end
55
+ end
56
+ end
57
+
58
+ class FileHeader < IOStruct.new("a4LLLL", :magic, :version, :local_entry_count, :global_entry_count, :store_id)
59
+ def inspect
60
+ super.sub(/^#<struct /, "<")
61
+ end
62
+
63
+ def self.read(f)
64
+ r = super
65
+ raise "invalid magic: #{r.magic}" if r.magic != "XABA"
66
+
67
+ r
68
+ end
69
+ end
70
+
71
+ class AssemblyDescriptor < IOStruct.new(
72
+ "llllll",
73
+ :data_offset, :data_size,
74
+ :debug_data_offset, :debug_data_size,
75
+ :config_data_offset, :config_data_size
76
+ )
77
+ def inspect
78
+ super.sub(/^#<struct /, "<")
79
+ end
80
+ end
81
+
82
+ # hashes of assembly _name_
83
+ # same entry for hash32 and hash64
84
+ class HashEntry < IOStruct.new("QLLL", :hash, :mapping_index, :local_store_index, :store_id)
85
+ def inspect
86
+ format("<HashEntry hash=%016x mapping_index=%3d local_store_index=%3d store_id=%d>", hash, mapping_index,
87
+ local_store_index, store_id)
88
+ end
89
+ end
90
+
91
+ class DataEntry < IOStruct.new("a4LL", :magic, :index, :original_size)
92
+ attr_accessor :data
93
+
94
+ def inspect
95
+ super.sub(/^#<struct /, "<")
96
+ end
97
+
98
+ def pack
99
+ super + data
100
+ end
101
+
102
+ def self.read(f)
103
+ r = super
104
+ raise "invalid magic: #{r.magic}" if r.magic != "XALZ"
105
+
106
+ r
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XABA
4
+ class Manifest
5
+ attr_accessor :assemblies
6
+
7
+ class AssemblyInfo
8
+ attr_accessor :hash32, :hash64, :blob_id, :blob_idx, :name
9
+
10
+ def initialize(hash32, hash64, blob_id, blob_idx, name)
11
+ @hash32 = hash32.to_i(16)
12
+ @hash64 = hash64.to_i(16)
13
+ @blob_id = blob_id.to_i
14
+ @blob_idx = blob_idx.to_i
15
+ @name = name
16
+ raise unless valid?
17
+ end
18
+
19
+ def inspect
20
+ format("<AssemblyInfo hash32=%08x hash64=%016x blob_id=%d blob_idx=%3d name=%s>", hash32, hash64, blob_id,
21
+ blob_idx, name.inspect)
22
+ end
23
+
24
+ def valid?
25
+ XXhash.xxh32(name) == hash32 && XXhash.xxh64(name) == hash64
26
+ end
27
+ end
28
+
29
+ HEADER = "Hash 32 Hash 64 Blob ID Blob idx Name"
30
+
31
+ def self.read(fname = "assemblies.manifest")
32
+ lines = File.readlines(fname).map(&:chomp).map(&:split)
33
+ header = lines.shift
34
+ raise "invalid header: #{header.inspect}" if header != HEADER.split
35
+
36
+ assemblies = lines.map do |line|
37
+ AssemblyInfo.new(*line)
38
+ end
39
+
40
+ new assemblies
41
+ end
42
+
43
+ def write(f)
44
+ data = [HEADER] + @assemblies.map do |a|
45
+ format("0x%08x 0x%016x %03d %04d %s",
46
+ a.hash32, a.hash64, a.blob_id, a.blob_idx, a.name)
47
+ end
48
+ f.write("#{data.join("\n")}\n")
49
+ end
50
+
51
+ def initialize(assemblies = nil)
52
+ @assemblies = assemblies
53
+ end
54
+
55
+ def each(&block)
56
+ @assemblies.each(&block)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XABA
4
+ VERSION = "0.1.0"
5
+ end
data/lib/xaba.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "iostruct"
4
+ require "extlz4"
5
+ require "xxhash"
6
+
7
+ require_relative "xaba/cli"
8
+ require_relative "xaba/container"
9
+ require_relative "xaba/manifest"
10
+ require_relative "xaba/version"
data/sig/xaba.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module XABA
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
data/xaba.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/xaba/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "xaba"
7
+ spec.version = XABA::VERSION
8
+ spec.authors = ["Andrey \"Zed\" Zaikin"]
9
+ spec.email = ["zed.0xff@gmail.com"]
10
+
11
+ spec.summary = "(un)packer for Xamarin assemblies.blob"
12
+ spec.description = spec.summary
13
+ spec.homepage = "https://github.com/zed-0xff/xaba"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.7.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
25
+ end
26
+ end
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_dependency "extlz4", "~> 0.3.4"
32
+ spec.add_dependency "iostruct", "~> 0.0.5"
33
+ spec.add_dependency "xxhash", "~> 0.5.0"
34
+
35
+ spec.metadata["rubygems_mfa_required"] = "true"
36
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xaba
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrey "Zed" Zaikin
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-01-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: extlz4
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.3.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.3.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: iostruct
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.5
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.0.5
41
+ - !ruby/object:Gem::Dependency
42
+ name: xxhash
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.5.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.5.0
55
+ description: "(un)packer for Xamarin assemblies.blob"
56
+ email:
57
+ - zed.0xff@gmail.com
58
+ executables:
59
+ - xaba
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".rspec"
64
+ - ".rubocop.yml"
65
+ - Gemfile
66
+ - Gemfile.lock
67
+ - LICENSE.txt
68
+ - README.md
69
+ - README.md.tpl
70
+ - Rakefile
71
+ - exe/xaba
72
+ - lib/xaba.rb
73
+ - lib/xaba/cli.rb
74
+ - lib/xaba/container.rb
75
+ - lib/xaba/manifest.rb
76
+ - lib/xaba/version.rb
77
+ - sig/xaba.rbs
78
+ - xaba.gemspec
79
+ homepage: https://github.com/zed-0xff/xaba
80
+ licenses:
81
+ - MIT
82
+ metadata:
83
+ homepage_uri: https://github.com/zed-0xff/xaba
84
+ source_code_uri: https://github.com/zed-0xff/xaba
85
+ rubygems_mfa_required: 'true'
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: 2.7.0
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubygems_version: 3.3.7
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: "(un)packer for Xamarin assemblies.blob"
105
+ test_files: []