app-info 2.8.2 → 3.0.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +12 -7
  3. data/.github/workflows/ci.yml +7 -5
  4. data/.github/workflows/create_release.yml +15 -0
  5. data/.rubocop.yml +33 -11
  6. data/CHANGELOG.md +107 -1
  7. data/Gemfile +10 -5
  8. data/README.md +82 -15
  9. data/Rakefile +11 -0
  10. data/app_info.gemspec +14 -5
  11. data/lib/app_info/aab.rb +76 -110
  12. data/lib/app_info/android/signature.rb +114 -0
  13. data/lib/app_info/android/signatures/base.rb +53 -0
  14. data/lib/app_info/android/signatures/info.rb +158 -0
  15. data/lib/app_info/android/signatures/v1.rb +63 -0
  16. data/lib/app_info/android/signatures/v2.rb +121 -0
  17. data/lib/app_info/android/signatures/v3.rb +131 -0
  18. data/lib/app_info/android/signatures/v4.rb +18 -0
  19. data/lib/app_info/android.rb +181 -0
  20. data/lib/app_info/apk.rb +77 -112
  21. data/lib/app_info/apple.rb +192 -0
  22. data/lib/app_info/certificate.rb +176 -0
  23. data/lib/app_info/const.rb +76 -0
  24. data/lib/app_info/core_ext/object/try.rb +3 -1
  25. data/lib/app_info/core_ext/string/inflector.rb +2 -0
  26. data/lib/app_info/dsym/debug_info.rb +81 -0
  27. data/lib/app_info/dsym/macho.rb +62 -0
  28. data/lib/app_info/dsym.rb +35 -135
  29. data/lib/app_info/error.rb +3 -1
  30. data/lib/app_info/file.rb +49 -0
  31. data/lib/app_info/helper/archive.rb +37 -0
  32. data/lib/app_info/helper/file_size.rb +25 -0
  33. data/lib/app_info/helper/generate_class.rb +29 -0
  34. data/lib/app_info/helper/protobuf.rb +12 -0
  35. data/lib/app_info/helper/signatures.rb +229 -0
  36. data/lib/app_info/helper.rb +5 -128
  37. data/lib/app_info/info_plist.rb +66 -29
  38. data/lib/app_info/ipa/framework.rb +4 -4
  39. data/lib/app_info/ipa.rb +61 -135
  40. data/lib/app_info/macos.rb +54 -102
  41. data/lib/app_info/mobile_provision.rb +66 -48
  42. data/lib/app_info/pe.rb +322 -0
  43. data/lib/app_info/png_uncrush.rb +25 -5
  44. data/lib/app_info/proguard.rb +39 -22
  45. data/lib/app_info/protobuf/manifest.rb +22 -11
  46. data/lib/app_info/protobuf/models/Configuration_pb.rb +1 -0
  47. data/lib/app_info/protobuf/models/README.md +8 -1
  48. data/lib/app_info/protobuf/models/Resources.proto +51 -0
  49. data/lib/app_info/protobuf/models/Resources_pb.rb +42 -0
  50. data/lib/app_info/protobuf/resources.rb +5 -5
  51. data/lib/app_info/version.rb +1 -1
  52. data/lib/app_info.rb +93 -43
  53. metadata +57 -37
@@ -0,0 +1,322 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pedump'
4
+ require 'fileutils'
5
+ require 'forwardable'
6
+
7
+ module AppInfo
8
+ # Windows PE parser
9
+ #
10
+ # @see https://learn.microsoft.com/zh-cn/windows/win32/debug/pe-format Microsoft PE Format
11
+ class PE < File
12
+ include Helper::HumanFileSize
13
+ include Helper::Archive
14
+ extend Forwardable
15
+
16
+ ARCH = {
17
+ 0x014c => 'x86',
18
+ 0x0200 => 'Intel Itanium',
19
+ 0x8664 => 'x64',
20
+ 0x1c0 => 'arm',
21
+ 0xaa64 => 'arm64',
22
+ 0x5032 => 'RISC-v 32',
23
+ 0x5064 => 'RISC-v 64',
24
+ 0x5128 => 'RISC-v 128'
25
+ }.freeze
26
+
27
+ # @return [Symbol] {Manufacturer}
28
+ def manufacturer
29
+ Manufacturer::MICROSOFT
30
+ end
31
+
32
+ # @return [Symbol] {Platform}
33
+ def platform
34
+ Platform::WINDOWS
35
+ end
36
+
37
+ # @return [Symbol] {Device}
38
+ def device
39
+ Device::WINDOWS
40
+ end
41
+
42
+ # return file size
43
+ # @example Read file size in integer
44
+ # aab.size # => 3618865
45
+ #
46
+ # @example Read file size in human readabale
47
+ # aab.size(human_size: true) # => '3.45 MB'
48
+ #
49
+ # @param [Boolean] human_size Convert integer value to human readable.
50
+ # @return [Integer, String]
51
+ def size(human_size: false)
52
+ file_to_human_size(@file, human_size: human_size)
53
+ end
54
+
55
+ def binary_size(human_size: false)
56
+ file_to_human_size(binary_file, human_size: human_size)
57
+ end
58
+
59
+ # @!method product_name
60
+ # @see VersionInfo#product_name
61
+ # @return [String]
62
+ # @!method product_version
63
+ # @see VersionInfo#product_version
64
+ # @return [String]
65
+ # @!method company_name
66
+ # @see VersionInfo#company_name
67
+ # @return [String]
68
+ # @!method assembly_version
69
+ # @see VersionInfo#assembly_version
70
+ # @return [String]
71
+ # @!method file_version
72
+ # @see VersionInfo#file_version
73
+ # @return [String]
74
+ # @!method file_description
75
+ # @see VersionInfo#file_description
76
+ # @return [String]
77
+ # @!method copyright
78
+ # @see VersionInfo#copyright
79
+ # @return [String]
80
+ # @return [String]
81
+ # @!method special_build
82
+ # @see VersionInfo#special_build
83
+ # @return [String]
84
+ # @!method private_build
85
+ # @see VersionInfo#private_build
86
+ # @return [String]
87
+ # @!method original_filename
88
+ # @see VersionInfo#original_filename
89
+ # @return [String]
90
+ # @!method internal_name
91
+ # @see VersionInfo#internal_name
92
+ # @return [String]
93
+ # @!method legal_trademarks
94
+ # @see VersionInfo#legal_trademarks
95
+ # @return [String]
96
+ def_delegators :version_info, :product_name, :product_version, :company_name, :assembly_version,
97
+ :file_version, :file_description, :copyright, :special_build, :private_build,
98
+ :original_filename, :internal_name, :legal_trademarks
99
+
100
+ alias name product_name
101
+
102
+ # Find {#product_version} then fallback to {#file_version}
103
+ # @return [String, nil]
104
+ def release_version
105
+ product_version || file_version
106
+ end
107
+
108
+ # Find {#special_build}, {#private_build} then fallback to {#assembly_version}
109
+ # @return [String, nil]
110
+ def build_version
111
+ special_build || private_build || assembly_version
112
+ end
113
+
114
+ # @return [String]
115
+ def archs
116
+ ARCH[image_file_header.Machine] || 'unknown'
117
+ end
118
+ alias architectures archs
119
+
120
+ # @return [Hash{String => String}] imports imports of libraries
121
+ def imports
122
+ @imports ||= pe.imports.each_with_object({}) do |import, obj|
123
+ obj[import.module_name] = import.first_thunk.map(&:name).compact
124
+ end
125
+ end
126
+
127
+ # @return [Array{String}] icons paths of bmp image icons
128
+ def icons
129
+ @icons ||= lambda {
130
+ # Fetch the largest size icon
131
+ files = []
132
+ pe.resources&.find_all do |res|
133
+ next unless res.type == 'ICON'
134
+
135
+ filename = "#{::File.basename(file, '.*')}-#{res.type}-#{res.id}.bmp"
136
+ icon_file = tempdir(filename, prefix: 'pe', system: true)
137
+ mask_icon_file = icon_file.sub('.bmp', '.mask.bmp')
138
+
139
+ begin
140
+ ::File.open(icon_file, 'wb') do |f|
141
+ f << res.restore_bitmap(io)
142
+ end
143
+
144
+ if res.bitmap_mask(io)
145
+ mask_icon_file = icon_file.sub('.bmp', '.mask.bmp')
146
+ ::File.open(mask_icon_file, 'wb') do |f|
147
+ f << res.bitmap_mask(io)
148
+ end
149
+ end
150
+ rescue StandardError => e
151
+ # ignore pedump throws any exception.
152
+ raise e unless e.backtrace.first.include?('pedump')
153
+
154
+ FileUtils.rm_f(icon_file)
155
+ ensure
156
+ next unless ::File.exist?(icon_file)
157
+
158
+ mask_file = ::File.exist?(mask_icon_file) ? mask_icon_file : nil
159
+ files << icon_metadata(icon_file, mask_file: mask_file)
160
+ end
161
+ end
162
+
163
+ files
164
+ }.call
165
+ end
166
+
167
+ # @return [PEdump]
168
+ def pe
169
+ @pe ||= lambda {
170
+ pe = PEdump.new(io)
171
+ pe.logger.level = Logger::FATAL # ignore :warn logger output
172
+ pe
173
+ }.call
174
+ end
175
+
176
+ # @return [VersionInfo]
177
+ def version_info
178
+ @version_info ||= VersionInfo.new(pe.version_info)
179
+ end
180
+
181
+ def clear!
182
+ @io = nil
183
+ @pe = nil
184
+ @icons = nil
185
+ @imports = nil
186
+ end
187
+
188
+ # @return [String] binary_file path
189
+ def binary_file
190
+ @binary_file ||= lambda {
191
+ file_io = ::File.open(@file, 'rb')
192
+ return @file unless file_io.read(100) =~ AppInfo::ZIP_RETGEX
193
+
194
+ zip_file = Zip::File.open(@file)
195
+ zip_entry = zip_file.glob('*.exe').first
196
+ raise NotFoundError, 'Not found .exe file in archive file' if zip_entry.nil?
197
+
198
+ exe_file = tempdir(zip_entry.name, prefix: 'pe-exe', system: true)
199
+ zip_entry.extract(exe_file)
200
+ zip_file.close
201
+
202
+ return exe_file
203
+ }.call
204
+ end
205
+
206
+ private
207
+
208
+ def image_file_header
209
+ @image_file_header ||= pe.pe.image_file_header
210
+ end
211
+
212
+ # @return [Hash{Symbol => String}]
213
+ def icon_metadata(file, mask_file: nil)
214
+ {
215
+ name: ::File.basename(file),
216
+ file: file,
217
+ mask: mask_file,
218
+ dimensions: ImageSize.path(file).size
219
+ }
220
+ end
221
+
222
+ # @return [File]
223
+ def io
224
+ @io ||= ::File.open(binary_file, 'rb')
225
+ end
226
+
227
+ # VersionInfo class
228
+ #
229
+ # @see https://learn.microsoft.com/zh-cn/windows/win32/menurc/versioninfo-resource
230
+ class VersionInfo
231
+ def initialize(raw)
232
+ @raw = raw
233
+ end
234
+
235
+ # @return [String]
236
+ def company_name
237
+ @company_name ||= value_of('CompanyName')
238
+ end
239
+
240
+ # @return [String]
241
+ def product_name
242
+ @product_name ||= value_of('ProductName')
243
+ end
244
+
245
+ # @return [String]
246
+ def product_version
247
+ @product_version ||= value_of('ProductVersion')
248
+ end
249
+
250
+ # @return [String]
251
+ def assembly_version
252
+ @assembly_version ||= value_of('Assembly Version')
253
+ end
254
+
255
+ # @return [String]
256
+ def file_version
257
+ @file_version ||= value_of('FileVersion')
258
+ end
259
+
260
+ # @return [String, nil]
261
+ def file_description
262
+ @file_description ||= value_of('FileDescription')
263
+ end
264
+
265
+ # @return [String, nil]
266
+ def special_build
267
+ @special_build ||= value_of('SpecialBuild')
268
+ end
269
+
270
+ # @return [String, nil]
271
+ def private_build
272
+ @private_build ||= value_of('PrivateBuild')
273
+ end
274
+
275
+ # @return [String]
276
+ def original_filename
277
+ @original_filename ||= value_of('OriginalFilename')
278
+ end
279
+
280
+ # @return [String]
281
+ def internal_name
282
+ @internal_name ||= value_of('InternalName')
283
+ end
284
+
285
+ # @return [String]
286
+ def legal_trademarks
287
+ @legal_trademarks ||= value_of('LegalTrademarks')
288
+ end
289
+
290
+ # @return [String]
291
+ def copyright
292
+ @copyright ||= value_of('LegalCopyright')
293
+ end
294
+
295
+ private
296
+
297
+ def value_of(key)
298
+ info.each do |v|
299
+ return v[:Value] if v[:szKey] == key.to_s
300
+ end
301
+
302
+ nil
303
+ end
304
+
305
+ def info
306
+ return @info if @info
307
+
308
+ @raw.each do |item|
309
+ next unless item.is_a?(PEdump::VS_VERSIONINFO)
310
+
311
+ versions = item[:Children].select { |v| v.is_a?(PEdump::StringFileInfo) }
312
+ next if versions.empty?
313
+
314
+ @info = versions[0][:Children][0][:Children]
315
+ return @info
316
+ end
317
+
318
+ @info = []
319
+ end
320
+ end
321
+ end
322
+ end
@@ -7,9 +7,10 @@ require 'zlib'
7
7
  require 'stringio'
8
8
 
9
9
  module AppInfo
10
+ # Decompress iOS Png image file.
11
+ # @see https://github.com/swcai/iphone-png-normalizer swcai/iphone-png-normalizer
12
+ # @author Wenwei Cai
10
13
  class PngUncrush
11
- class Error < StandardError; end
12
-
13
14
  class FormatError < Error; end
14
15
 
15
16
  class PngReader # :nodoc:
@@ -31,20 +32,26 @@ module AppInfo
31
32
  @data = String.new
32
33
  end
33
34
 
35
+ # @return [Integer]
34
36
  def size
35
37
  @io.size
36
38
  end
37
39
 
40
+ # @param [String] format
41
+ # @return [String]
42
+ # @see IO package data
38
43
  def unpack(format)
39
44
  @io.unpack(format)
40
45
  end
41
46
 
47
+ # @return [String]
42
48
  def header
43
49
  @header ||= self[0, 8]
44
50
  end
45
51
 
52
+ # @return [Boolean]
46
53
  def png?
47
- PNG_HEADER == header.bytes
54
+ header.bytes == PNG_HEADER
48
55
  end
49
56
 
50
57
  def [](offset, length)
@@ -60,23 +67,36 @@ module AppInfo
60
67
  end
61
68
  end
62
69
 
70
+ # Decompress crushed png file.
71
+ # @param [String] input path of png file
72
+ # @param [String] output path of output file
73
+ # @return [Boolean] result whether decompress successfully
63
74
  def self.decompress(input, output)
64
75
  new(input).decompress(output)
65
76
  end
66
77
 
78
+ # get png dimensions
79
+ # @param [String] input path of png file
80
+ # @return [Array<Integer>] dimensions width, height value of png file
67
81
  def self.dimensions(input)
68
82
  new(input).dimensions
69
83
  end
70
84
 
71
85
  def initialize(filename)
72
- @io = PngReader.new(File.open(filename))
86
+ @io = PngReader.new(::File.open(filename))
73
87
  raise FormatError, 'not a png file' unless @io.png?
74
88
  end
75
89
 
90
+ # get png dimensions
91
+ # @param [String] input path of png file
92
+ # @return [Array<Integer>] dimensions width, height value of png file
76
93
  def dimensions
77
94
  _dump_sections(dimensions: true)
78
95
  end
79
96
 
97
+ # Decompress crushed png file.
98
+ # @param [String] output path of output file
99
+ # @return [Boolean] result whether decompress successfully
80
100
  def decompress(output)
81
101
  content = _remap(_dump_sections)
82
102
  return false unless content
@@ -125,7 +145,7 @@ module AppInfo
125
145
  end
126
146
 
127
147
  def write_file(path, content)
128
- File.write(path, content, encoding: Encoding::BINARY)
148
+ ::File.write(path, content, encoding: Encoding::BINARY)
129
149
  true
130
150
  end
131
151
 
@@ -5,52 +5,52 @@ require 'rexml/document'
5
5
 
6
6
  module AppInfo
7
7
  # Proguard parser
8
- class Proguard
8
+ class Proguard < File
9
9
  include Helper::Archive
10
10
 
11
11
  NAMESPACE = UUIDTools::UUID.sha1_create(UUIDTools::UUID_DNS_NAMESPACE, 'icyleaf.com')
12
12
 
13
- attr_reader :file
14
-
15
- def initialize(file)
16
- @file = file
13
+ # @return [Symbol] {Manufacturer}
14
+ def manufacturer
15
+ Manufacturer::GOOGLE
17
16
  end
18
17
 
19
- def file_type
20
- Platform::PROGUARD
18
+ # @return [Symbol] {Platform}
19
+ def platform
20
+ Platform::ANDROID
21
21
  end
22
22
 
23
+ # @return [String]
23
24
  def uuid
24
25
  # Similar to https://docs.sentry.io/workflow/debug-files/#proguard-uuids
25
- UUIDTools::UUID.sha1_create(NAMESPACE, File.read(mapping_path)).to_s
26
+ UUIDTools::UUID.sha1_create(NAMESPACE, ::File.read(mapping_path)).to_s
26
27
  end
27
28
  alias debug_id uuid
28
29
 
30
+ # @return [Boolean]
29
31
  def mapping?
30
- File.exist?(mapping_path)
32
+ ::File.exist?(mapping_path)
31
33
  end
32
34
 
35
+ # @return [Boolean]
33
36
  def manifest?
34
- File.exist?(manifest_path)
37
+ ::File.exist?(manifest_path)
35
38
  end
36
39
 
40
+ # @return [Boolean]
37
41
  def symbol?
38
- File.exist?(symbol_path)
42
+ ::File.exist?(symbol_path)
39
43
  end
40
44
  alias resource? symbol?
41
45
 
46
+ # @return [String, nil]
42
47
  def package_name
43
48
  return unless manifest?
44
49
 
45
50
  manifest.root.attributes['package']
46
51
  end
47
52
 
48
- def releasd_version
49
- return unless manifest?
50
-
51
- manifest.root.attributes['package']
52
- end
53
-
53
+ # @return [String, nil]
54
54
  def version_name
55
55
  return unless manifest?
56
56
 
@@ -58,6 +58,7 @@ module AppInfo
58
58
  end
59
59
  alias release_version version_name
60
60
 
61
+ # @return [String, nil]
61
62
  def version_code
62
63
  return unless manifest?
63
64
 
@@ -65,27 +66,43 @@ module AppInfo
65
66
  end
66
67
  alias build_version version_code
67
68
 
69
+ def files
70
+ Dir.children(contents).each_with_object([]) do |filename, obj|
71
+ path = ::File.join(contents, filename)
72
+ obj << {
73
+ name: filename,
74
+ path: path,
75
+ size: ::File.size(path)
76
+ }
77
+ end
78
+ end
79
+
80
+ # @return [REXML::Document]
68
81
  def manifest
69
82
  return unless manifest?
70
83
 
71
- @manifest ||= REXML::Document.new(File.new(manifest_path))
84
+ @manifest ||= REXML::Document.new(::File.new(manifest_path))
72
85
  end
73
86
 
87
+ # @return [String]
74
88
  def mapping_path
75
- @mapping_path ||= Dir.glob(File.join(contents, '*mapping*.txt')).first
89
+ @mapping_path ||= Dir.glob(::File.join(contents, '*mapping*.txt')).first
76
90
  end
77
91
 
92
+ # @return [String]
78
93
  def manifest_path
79
- @manifest_path ||= File.join(contents, 'AndroidManifest.xml')
94
+ @manifest_path ||= ::File.join(contents, 'AndroidManifest.xml')
80
95
  end
81
96
 
97
+ # @return [String]
82
98
  def symbol_path
83
- @symbol_path ||= File.join(contents, 'R.txt')
99
+ @symbol_path ||= ::File.join(contents, 'R.txt')
84
100
  end
85
101
  alias resource_path symbol_path
86
102
 
103
+ # @return [String] contents path of contents
87
104
  def contents
88
- @contents ||= unarchive(@file, path: 'proguard')
105
+ @contents ||= unarchive(@file, prefix: 'proguard')
89
106
  end
90
107
 
91
108
  def clear!
@@ -6,8 +6,9 @@ require 'app_info/core_ext'
6
6
 
7
7
  module AppInfo
8
8
  module Protobuf
9
+ # AAB Protobuf Base class
9
10
  class Base
10
- include Helper::Defines
11
+ include Helper::GenerateClass
11
12
 
12
13
  def initialize(doc, resources = nil)
13
14
  @resources = resources
@@ -17,10 +18,11 @@ module AppInfo
17
18
  private
18
19
 
19
20
  def parse(_)
20
- raise 'not implemented'
21
+ raise ProtobufParseError, 'not implemented'
21
22
  end
22
23
  end
23
24
 
25
+ # AAB Protobuf Attribute
24
26
  class Attribute < Base
25
27
  attr_reader :namespace, :name, :value, :resource_id
26
28
 
@@ -47,6 +49,8 @@ module AppInfo
47
49
  end
48
50
  end
49
51
 
52
+ # AAB Protobuf Node class.
53
+ # example: manifest,activity, activity-alias, service, receiver, provider, application
50
54
  class Node < Base
51
55
  attr_reader :name, :attributes, :children
52
56
 
@@ -106,6 +110,7 @@ module AppInfo
106
110
  end
107
111
  end
108
112
 
113
+ # AAB Protobuf Manifest
109
114
  class Manifest < Node
110
115
  def self.parse(io, resources = nil)
111
116
  doc = Aapt::Pb::XmlNode.decode(io)
@@ -152,8 +157,10 @@ module AppInfo
152
157
 
153
158
  def intent_filters(search: nil)
154
159
  activities.each_with_object([]) do |activity, obj|
160
+ next unless activity.respond_to?(:intent_filter)
161
+
155
162
  intent_filters = activity.intent_filter
156
- next if intent_filters&.empty?
163
+ next if intent_filters.nil? || intent_filters&.empty?
157
164
 
158
165
  if search.nil? || search.empty?
159
166
  obj << intent_filters
@@ -168,7 +175,6 @@ module AppInfo
168
175
  end.flatten.uniq
169
176
  end
170
177
 
171
- # :nodoc:
172
178
  # Workaround ruby always return true by called `Object.const_defined?(Data)`
173
179
  class Data < Node; end
174
180
 
@@ -182,6 +188,8 @@ module AppInfo
182
188
  CATEGORY_BROWSABLE = 'android.intent.category.BROWSABLE'
183
189
 
184
190
  def deep_links?
191
+ return unless respond_to?(:data)
192
+
185
193
  browsable? && data.any? { |d| DEEP_LINK_SCHEMES.include?(d.scheme) }
186
194
  end
187
195
 
@@ -193,6 +201,12 @@ module AppInfo
193
201
  .uniq
194
202
  end
195
203
 
204
+ def schemes?
205
+ return unless respond_to?(:data)
206
+
207
+ browsable? && data.any? { |d| !DEEP_LINK_SCHEMES.include?(d.scheme) }
208
+ end
209
+
196
210
  def schemes
197
211
  return unless schemes?
198
212
 
@@ -201,23 +215,20 @@ module AppInfo
201
215
  .uniq
202
216
  end
203
217
 
204
- def schemes?
205
- browsable? && data.any? { |d| !DEEP_LINK_SCHEMES.include?(d.scheme) }
206
- end
207
-
208
218
  def browsable?
209
219
  exist?(CATEGORY_BROWSABLE)
210
220
  end
211
221
 
212
222
  def exist?(name, type: nil)
213
223
  if type.to_s.empty? && !name.start_with?('android.intent.')
214
- raise 'Fill type or use correct name'
224
+ raise ProtobufParseError, 'Not found intent type'
215
225
  end
216
226
 
217
227
  type ||= name.split('.')[2]
218
- raise 'Not found type' unless TYPES.include?(type)
228
+ raise ProtobufParseError, 'Not found intent type' unless TYPES.include?(type)
229
+ return false unless intent = send(type.to_sym)
219
230
 
220
- values = send(type.to_sym).select { |e| e.name == name }
231
+ values = intent.select { |e| e.name == name }
221
232
  values.empty? ? false : values
222
233
  end
223
234
  end
@@ -118,6 +118,7 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
118
118
  end
119
119
  end
120
120
 
121
+ # @!visibility private
121
122
  module Aapt
122
123
  module Pb
123
124
  Configuration = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("aapt.pb.Configuration").msgclass
@@ -3,12 +3,19 @@
3
3
  ## Convert to ruby model
4
4
 
5
5
  ```bash
6
- cd app_info/lib/app_info/abb/proto
6
+ cd lib/app_info/protobuf/models
7
7
 
8
8
  protoc --ruby_out=. Resources.proto
9
9
  protoc --ruby_out=. Configuration.proto
10
10
  ```
11
11
 
12
+ ## Decode
13
+
14
+ ```bash
15
+ protoc --decode=aapt.pb.ResourceTable Resources.proto < aab/base/BundleConfig.pb > BundleConfig.txt
16
+ protoc --decode=aapt.pb.ResourceTable Resources.proto < aab/base/native.pb > native.txt
17
+ ```
18
+
12
19
  ## Resouces
13
20
 
14
21
  `Configuration.proto` and `Resources.proto` can be found in aapt2's github: