mootool 0.1.1 → 0.2.1

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.github/workflows/ci.yml +14 -14
  4. data/.github/workflows/gem-push.yml +26 -28
  5. data/.overcommit.yml +79 -0
  6. data/.rubocop.yml +147 -0
  7. data/.ruby-version +1 -1
  8. data/.run/All Specs.run.xml +46 -0
  9. data/CODE_OF_CONDUCT.md +2 -1
  10. data/Gemfile +14 -11
  11. data/Gemfile.lock +92 -31
  12. data/README.md +12 -6
  13. data/bin/tapioca +27 -0
  14. data/lib/mootool/command.rb +1 -0
  15. data/lib/mootool/controller_base.rb +4 -1
  16. data/lib/mootool/controllers/dwarf.rb +8 -0
  17. data/lib/mootool/controllers/dyld_linker.rb +8 -0
  18. data/lib/mootool/controllers/dyld_shared_cache.rb +8 -0
  19. data/lib/mootool/controllers/fat_binary.rb +8 -0
  20. data/lib/mootool/controllers/kernel_collection.rb +21 -15
  21. data/lib/mootool/controllers/sections.rb +8 -0
  22. data/lib/mootool/core_extensions.rb +26 -0
  23. data/lib/mootool/models/device_tree.rb +85 -0
  24. data/lib/mootool/models/img4.rb +49 -0
  25. data/lib/mootool/models/ipsw.rb +19 -0
  26. data/lib/mootool/version.rb +1 -1
  27. data/lib/mootool/views/sections.full.erb +2 -2
  28. data/lib/mootool.rb +10 -3
  29. data/mootool.gemspec +17 -12
  30. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  31. data/sorbet/rbi/gems/ast@2.4.2.rbi +618 -0
  32. data/sorbet/rbi/gems/childprocess@4.1.0.rbi +447 -0
  33. data/sorbet/rbi/gems/coderay@1.1.3.rbi +8 -0
  34. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1219 -0
  35. data/sorbet/rbi/gems/iniparse@1.5.0.rbi +1007 -0
  36. data/sorbet/rbi/gems/json@2.6.2.rbi +1650 -0
  37. data/sorbet/rbi/gems/method_source@1.0.0.rbi +8 -0
  38. data/sorbet/rbi/gems/netrc@0.11.0.rbi +186 -0
  39. data/sorbet/rbi/gems/overcommit@0.59.1.rbi +2747 -0
  40. data/sorbet/rbi/gems/parallel@1.22.1.rbi +353 -0
  41. data/sorbet/rbi/gems/parser@3.1.2.1.rbi +6198 -0
  42. data/sorbet/rbi/gems/plist@3.6.0.rbi +212 -0
  43. data/sorbet/rbi/gems/pry@0.14.1.rbi +8 -0
  44. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +452 -0
  45. data/sorbet/rbi/gems/rake@13.0.6.rbi +3587 -0
  46. data/sorbet/rbi/gems/rbi@0.0.15.rbi +3619 -0
  47. data/sorbet/rbi/gems/regexp_parser@2.5.0.rbi +3927 -0
  48. data/sorbet/rbi/gems/rexml@3.2.5.rbi +5238 -0
  49. data/sorbet/rbi/gems/rspec-core@3.11.0.rbi +12834 -0
  50. data/sorbet/rbi/gems/rspec-expectations@3.11.0.rbi +9151 -0
  51. data/sorbet/rbi/gems/rspec-mocks@3.11.1.rbi +6506 -0
  52. data/sorbet/rbi/gems/rspec-support@3.11.0.rbi +2040 -0
  53. data/sorbet/rbi/gems/rspec@3.11.0.rbi +120 -0
  54. data/sorbet/rbi/gems/rubocop-ast@1.21.0.rbi +7990 -0
  55. data/sorbet/rbi/gems/rubocop-rake@0.6.0.rbi +413 -0
  56. data/sorbet/rbi/gems/rubocop-rspec@2.12.1.rbi +7604 -0
  57. data/sorbet/rbi/gems/rubocop@1.35.0.rbi +62260 -0
  58. data/sorbet/rbi/gems/ruby-macho@3.0.0.rbi +5039 -0
  59. data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +1445 -0
  60. data/sorbet/rbi/gems/rubyzip@2.3.2.rbi +2868 -0
  61. data/sorbet/rbi/gems/spoom@1.1.12.rbi +2829 -0
  62. data/sorbet/rbi/gems/tapioca@0.9.3.rbi +2151 -0
  63. data/sorbet/rbi/gems/thor@1.2.1.rbi +4532 -0
  64. data/sorbet/rbi/gems/unicode-display_width@2.2.0.rbi +60 -0
  65. data/sorbet/rbi/gems/unparser@0.6.5.rbi +8 -0
  66. data/sorbet/rbi/gems/webrick@1.7.0.rbi +3075 -0
  67. data/sorbet/rbi/gems/yard-sorbet@0.6.1.rbi +458 -0
  68. data/sorbet/rbi/gems/yard@0.9.28.rbi +20844 -0
  69. data/sorbet/rbi/sorbet-typed/lib/rainbow/all/rainbow.rbi +72 -24
  70. data/sorbet/rbi/sorbet-typed/lib/rake/all/rake.rbi +1697 -385
  71. data/sorbet/rbi/sorbet-typed/lib/rspec-core/all/rspec-core.rbi +30 -7
  72. data/sorbet/rbi/sorbet-typed/lib/rubocop/>=1.8/rubocop.rbi +3 -1
  73. data/sorbet/rbi/todo.rbi +7 -0
  74. data/sorbet/tapioca/config.yml +13 -0
  75. data/sorbet/tapioca/require.rb +4 -0
  76. metadata +89 -9
data/bin/tapioca ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'tapioca' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
12
+
13
+ bundle_binstub = File.expand_path('bundle', __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require 'rubygems'
25
+ require 'bundler/setup'
26
+
27
+ load Gem.bin_path('tapioca', 'tapioca')
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MooTool
4
+ # Abstract base command for all command types.
4
5
  class Command
5
6
  attr_reader :file, :controller, :action, :output_format, :remain
6
7
 
@@ -3,7 +3,10 @@
3
3
  CONTROLLERS_PATH = File.join(File.dirname(__FILE__), 'controllers')
4
4
 
5
5
  module MooTool
6
+ # Controller base is the base class for all controllers that respond to command line requests.
6
7
  class ControllerBase
8
+ @controllers = []
9
+
7
10
  def self.load_all
8
11
  Dir.glob(File.join(CONTROLLERS_PATH, '*')).each do |file|
9
12
  require file
@@ -15,8 +18,8 @@ module MooTool
15
18
  end
16
19
 
17
20
  def self.inherited(child)
18
- @@controllers ||= []
19
21
  @@controllers << child
22
+ super
20
23
  end
21
24
 
22
25
  class << self
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MooTools
4
+ module Controllers
5
+ class DWARF < ControllerBase
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MooTool
4
+ module Controllers
5
+ class DyldLinker < ControllerBase
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MooTool
4
+ module Controllers
5
+ class DyldSharedCache < ControllerBase
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MooTool
4
+ module Controllers
5
+ class FatBinary < ControllerBase
6
+ end
7
+ end
8
+ end
@@ -1,24 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MooTool
4
- class KernelCollection < ControllerBase
5
- command 'kc'
6
- description 'Kernel Collections'
4
+ module Controllers
5
+ # Controller for extracting or viewing a KernelCollection (.kc file)
6
+ class KernelCollection < ControllerBase
7
+ command 'kc'
8
+ description 'Kernel Collections'
7
9
 
8
- def extract(command, output_folder)
9
- file = MachO.open command.file
10
- input = File.open(command.file, 'rb')
10
+ def extract(command, output_folder)
11
+ file = MachO.open command.file
12
+ input = File.open(command.file, 'rb')
11
13
 
12
- file.command(:LC_FILESET_ENTRY).each do |entry|
13
- output_path = File.join(output_folder, entry.entry_id.to_s)
14
- puts "Writing to #{output_path}"
15
- output_file = File.open(output_path, 'wb')
16
- matching = file.command(:LC_SEGMENT_64).find { |command| command.fileoff == entry.fileoff }
14
+ file.command(:LC_FILESET_ENTRY).each do |entry|
15
+ output_path = File.join(output_folder, entry.entry_id.to_s)
16
+ puts "Writing to #{output_path}"
17
+ output_file = File.open(output_path, 'wb')
18
+ # rubocop:disable Naming/VariableNumber
19
+ # We do not have control of this name as it is part of ruby-macho
20
+ matching = file.command(:LC_SEGMENT_64).find { |c| c.fileoff == entry.fileoff }
21
+ # rubocop:enable Naming/VariableNumber
17
22
 
18
- input.seek(matching.fileoff)
19
- output_file.write input.read(matching.filesize)
20
- output_file.close
23
+ input.seek(matching.fileoff)
24
+ output_file.write input.read(matching.filesize)
25
+ output_file.close
26
+ end
21
27
  end
22
28
  end
23
29
  end
24
- end
30
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MooTool
4
+ module Controllers
5
+ class Sections < ControllerBase
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ # rubocop:disable Style/Documentation
5
+
6
+ class Integer
7
+ # rubocop:enable Style/Documentation
8
+ extend T::Sig
9
+ # Aligns the integer to the nearest multiple of the alignment.
10
+ # @param alignment [Integer] the alignment
11
+ # @return [Integer] the aligned integer
12
+ sig { params(alignment: T.nilable(Integer)).returns(Integer) }
13
+ def align(alignment = nil)
14
+ alignment ||= Integer.size
15
+
16
+ return self if alignment < 2
17
+
18
+ alignment -= 1
19
+
20
+ if (self & alignment).zero?
21
+ self
22
+ else
23
+ (self | alignment) + 1
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ require 'sorbet-runtime'
5
+
6
+ module MooTool
7
+ module Models
8
+ # DeviceTree is a Apple Device Tree node that has been parsed
9
+ class DeviceTree
10
+ extend T::Sig
11
+
12
+ NODE_FORMAT = 'VV'
13
+ PROP_FORMAT = 'A32V'
14
+
15
+ # Represents a single node in the device tree
16
+ class Node
17
+ extend T::Sig
18
+
19
+ attr_reader :children, :properties
20
+
21
+ sig { params(data: T.any(IO, StringIO)).void }
22
+ def initialize(data)
23
+ vals = T.must(data.read(8)).unpack(NODE_FORMAT)
24
+ property_count = T.cast(vals[0], Integer)
25
+ child_count = T.cast(vals[1], Integer)
26
+
27
+ @properties = T.let({}, T::Hash[String, Property])
28
+ @children = T.let([], T::Array[Node])
29
+
30
+ property_count.times do
31
+ prop = Property.new(data)
32
+ @properties[prop.name] = prop
33
+ end
34
+ child_count.times { @children << Node.new(data) }
35
+ end
36
+ end
37
+
38
+ # Represents a single property and it's value
39
+ class Property
40
+ extend T::Sig
41
+
42
+ attr_accessor :name, :value
43
+
44
+ sig { params(data: T.any(StringIO, IO)).void }
45
+ def initialize(data)
46
+ args = T.must(data.read(36)).unpack(PROP_FORMAT)
47
+
48
+ @name = T.let(T.cast(args[0], String), String)
49
+ @size = T.let(T.cast(args[1], Integer), Integer)
50
+
51
+ if @size & 0x80000000 != 0
52
+ @template = true
53
+ @size &= 0x7fffffff
54
+ end
55
+
56
+ @value = data.read(@size.align(4))
57
+ @value = T.must(@value)[0..@size]
58
+ end
59
+ end
60
+
61
+ attr_reader :root
62
+
63
+ # @param [String] data
64
+ sig { params(data: T.any(IO, String, StringIO, Pathname)).void }
65
+ def initialize(data)
66
+ case data
67
+ when Pathname
68
+ data = T.assert_type!(data, Pathname)
69
+ @data = File.open(data.realpath, 'rb')
70
+ when String
71
+ data = T.assert_type!(data, String)
72
+ @data = StringIO.new(data)
73
+ when IO
74
+ @data = T.cast(data, IO)
75
+ end
76
+ @root = Node.new(@data)
77
+ end
78
+
79
+ sig { params(path: String).returns(DeviceTree) }
80
+ def self.open(path)
81
+ DeviceTree.new(Pathname.new(path))
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ module MooTool
6
+ # Module for Apple's IMG4 encryption and signing format
7
+ module Img4
8
+ # An instance of a IMG4 file
9
+ class File
10
+ attr_reader :payload, :manifest
11
+
12
+ def initialize(path)
13
+ @path = path
14
+ der = ::File.binread(path)
15
+ @data = OpenSSL::ASN1.decode(der)
16
+
17
+ case @data.first.value
18
+ when 'IM4P'
19
+ @type = @data.value[1].value
20
+ @build = @data.value[2].value
21
+ @payload = @data.value[3].value
22
+ when 'IMG4', 'IM4M'
23
+ raise 'Not implemented'
24
+ else
25
+ raise "Unknown IMG4 type #{@data.first.value}"
26
+ end
27
+ end
28
+
29
+ def payload?
30
+ !@payload.nil?
31
+ end
32
+
33
+ def manifest?
34
+ !@manifest.nil?
35
+ end
36
+
37
+ def basename
38
+ basename = ::File.basename(@path)
39
+ extension = ::File.extname(basename)
40
+ "#{basename.chomp(extension)}.#{@type}"
41
+ end
42
+
43
+ def extract_payload
44
+ output_path = ::File.join(::File.dirname(@path), basename)
45
+ ::File.write(output_path, @payload)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'zip'
4
+ require 'plist'
5
+
6
+ module MooTool
7
+ # Represents a single IPSW package
8
+ class IPSW
9
+ def initialize(file)
10
+ @file = file
11
+ @zip = Zip::File.open(file)
12
+ manifest = @zip.find_entry('BuildManifest.plist')
13
+
14
+ raise 'Invalid IPSW, does not contain BuildManifest.plist' unless manifest
15
+
16
+ @manifest = Plist.parse_xml(manifest.get_input_stream.read)
17
+ end
18
+ end
19
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MooTool
4
- VERSION = '0.1.1'
4
+ VERSION = '0.2.1'
5
5
  end
@@ -1,5 +1,5 @@
1
1
  <% @load_commands.each_index do |index| %>
2
- Load command <%= index %>
3
- cmd <%= @load_commands[index].cmd %>
2
+ Load command <%= index %>
3
+ cmd <%= @load_commands[index].cmd %>
4
4
  cmdsize <%= @load_commands[index].cmdsize %>
5
5
  <% end %>
data/lib/mootool.rb CHANGED
@@ -1,14 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'mootool/version'
4
-
5
- require 'bundler/setup'
3
+ require_relative 'mootool/version'
6
4
 
7
5
  require 'macho'
6
+ require 'zip'
7
+
8
+ require_relative 'mootool/core_extensions'
8
9
 
10
+ # MooTool
9
11
  module MooTool
10
12
  class Error < StandardError; end
13
+
11
14
  # Your code goes here...
12
15
  autoload :Command, 'mootool/command'
13
16
  autoload :ControllerBase, 'mootool/controller_base'
17
+
18
+ autoload :Img4, 'mootool/models/img4'
19
+ autoload :DeviceTree, 'mootool/models/device_tree'
20
+ autoload :IPSW, 'mootool/models/ipsw'
14
21
  end
data/mootool.gemspec CHANGED
@@ -5,15 +5,17 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require 'mootool/version'
6
6
 
7
7
  Gem::Specification.new do |spec|
8
- spec.name = 'mootool'
9
- spec.version = MooTool::VERSION
10
- spec.authors = ['Rick Mark']
11
- spec.email = ['rickmark@outlook.com']
8
+ spec.name = 'mootool'
9
+ spec.version = MooTool::VERSION
10
+ spec.authors = ['Rick Mark']
11
+ spec.email = ['rickmark@outlook.com']
12
12
 
13
- spec.summary = "Mach-O's Other Tool"
14
- spec.description = 'mootool is a swiss army knife for dealing wiith Apple Mach-O files'
15
- spec.homepage = 'https://github.com/hack-different/mootool'
16
- spec.license = 'MIT'
13
+ spec.summary = "Mach-O's Other Tool"
14
+ spec.description = 'mootool is a swiss army knife for dealing with Apple Mach-O files'
15
+ spec.homepage = 'https://github.com/hack-different/mootool'
16
+ spec.license = 'MIT'
17
+
18
+ spec.required_ruby_version = '>= 3.0'
17
19
 
18
20
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
19
21
  # to allow pushing to a single host or delete this section to allow pushing to any host.
@@ -23,7 +25,7 @@ Gem::Specification.new do |spec|
23
25
  spec.metadata['changelog_uri'] = 'https://github.com/hack-different/mootool'
24
26
  else
25
27
  raise 'RubyGems 2.0 or newer is required to protect against ' \
26
- 'public gem pushes.'
28
+ 'public gem pushes.'
27
29
  end
28
30
 
29
31
  # Specify which files should be added to the gem when it is released.
@@ -31,10 +33,13 @@ Gem::Specification.new do |spec|
31
33
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
32
34
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
33
35
  end
34
- spec.bindir = 'exe'
35
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
36
+ spec.bindir = 'exe'
37
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
36
38
  spec.require_paths = ['lib']
37
39
 
38
- spec.add_runtime_dependency 'sorbet-runtime'
40
+ spec.add_runtime_dependency 'plist', '~> 3'
39
41
  spec.add_runtime_dependency 'ruby-macho', '~> 3'
42
+ spec.add_runtime_dependency 'rubyzip', '~> 2'
43
+ spec.add_runtime_dependency 'sorbet-runtime', '~> 0'
44
+ spec.metadata['rubygems_mfa_required'] = 'true'
40
45
  end