xaba 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +64 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +79 -0
- data/LICENSE.txt +21 -0
- data/README.md +36 -0
- data/README.md.tpl +20 -0
- data/Rakefile +150 -0
- data/exe/xaba +6 -0
- data/lib/xaba/cli.rb +203 -0
- data/lib/xaba/container.rb +109 -0
- data/lib/xaba/manifest.rb +59 -0
- data/lib/xaba/version.rb +5 -0
- data/lib/xaba.rb +10 -0
- data/sig/xaba.rbs +4 -0
- data/xaba.gemspec +36 -0
- metadata +105 -0
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
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
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
|
data/lib/xaba/version.rb
ADDED
data/lib/xaba.rb
ADDED
data/sig/xaba.rbs
ADDED
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: []
|