gamerom 0.5.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 65b18f9a39d07cc281d8409b6a4ac6992980f450af58aef33e91148ae17c480b
4
- data.tar.gz: 258c224ac84dafe08ae695c1dae35a7ecb7448e331c8a34cd78685c688e8e59a
3
+ metadata.gz: 33c71545b14feb811985d98d9e1e4677b7101f7e86676865c50770eb53b99169
4
+ data.tar.gz: 3388785345996bdb7711c90150ad03cc8bc3cf08afaad281144690ce8da85144
5
5
  SHA512:
6
- metadata.gz: b999f0ee86796dee15f20593cbad36a27051798e01213e64676b004c77452ffe1d5a986d3c50cdcd8bafc09871ccee9278e371ed059d608951fa52a9c864f2b8
7
- data.tar.gz: c1a4433b96a14883f8114eb23687826365c2f2df850d2dc0a686d1e1b01753f1875fda3d790cae458d0def27dceaf9ad28fa901a75c28b0b8fba8a97251fb917
6
+ metadata.gz: '0694ea12b96bac2fb769f0176ca0b459804eb1b955c51c44fb57dc60046d63136836a0b206c696fb6cbee9096c211d475cf038a9dc14c4a2ff43b232af3cd87d'
7
+ data.tar.gz: ff44e316119690d3732f629ff162af18feef6ccb512881063625b899782adf4d95100c9a49829e619f09c82ce4fb944bd793490d0358c0cc3bbbb36158d06603
@@ -1,18 +1,16 @@
1
1
  name: Ruby
2
2
 
3
- on: [push,pull_request]
3
+ on: [push, pull_request]
4
4
 
5
5
  jobs:
6
6
  build:
7
7
  runs-on: ubuntu-latest
8
8
  steps:
9
- - uses: actions/checkout@v4
9
+ - uses: actions/checkout@v7
10
10
  - name: Set up Ruby
11
11
  uses: ruby/setup-ruby@v1
12
12
  with:
13
- ruby-version: 3.4.2
13
+ ruby-version: .ruby-version
14
+ bundler-cache: true
14
15
  - name: Run the default task
15
- run: |
16
- gem install bundler -v 2.6.2
17
- bundle install
18
- bundle exec rake
16
+ run: bundle exec rake
data/.rubocop.yml CHANGED
@@ -42,6 +42,11 @@ Style/SoleNestedConditional:
42
42
  Exclude:
43
43
  - 'lib/mechanizeprogress.rb'
44
44
 
45
+ # mechanizeprogress.rb monkeypatches two existing classes on purpose
46
+ Style/OneClassPerFile:
47
+ Exclude:
48
+ - 'lib/mechanizeprogress.rb'
49
+
45
50
  plugins:
46
51
  - rubocop-rake
47
52
  - rubocop-rspec
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-3.4.2
1
+ ruby-3.4.12
data/.tool-versions CHANGED
@@ -1 +1 @@
1
- ruby 3.4.2
1
+ ruby 3.4.12
data/CHANGELOG.md CHANGED
@@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.6.0] - 2026-11-01
10
+ - feat: add edgeemu repo (https://edgeemu.net), 48 platforms
11
+ - feat: recover - do not claim files that belong to a game with a longer name (e.g. "Kung-Fu" vs "Kung-Fu Master")
12
+ - feat: use ruby 3.4.12 and update all dependencies
13
+ - fix: install_all - Ctrl-C stops the loop again
14
+ - feat: vimm repo - the site now blocks automated downloads, so install shows the game page URL and the target directory for a manual download; database, search and recover keep working
15
+
9
16
  ## [0.5.0] - 2025-03-25
10
17
  - feat: add ps3 platform for coolrom repo
11
18
  - feat: add new platforms for vimm repo (Sega 32X, Atari 2600, Atari 5200, Atari 7800, Game Gear, Lynx, Sega CD, Master System, TurboGrafx-16, TurboGrafx-CD, Virtual Boy, XBox, XBox 360)
@@ -46,7 +53,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
46
53
  ## [0.1.0] - 2021-05-07
47
54
  - Initial release
48
55
 
49
- [Unreleased]: https://github.com/lucasmundim/gamerom/compare/v0.5.0...HEAD
56
+ [Unreleased]: https://github.com/lucasmundim/gamerom/compare/v0.6.0...HEAD
57
+ [0.6.0]: https://github.com/lucasmundim/gamerom/compare/v0.5.0...v0.6.0
50
58
  [0.5.0]: https://github.com/lucasmundim/gamerom/compare/v0.4.1...v0.5.0
51
59
  [0.4.1]: https://github.com/lucasmundim/gamerom/compare/v0.4.0...v0.4.1
52
60
  [0.4.0]: https://github.com/lucasmundim/gamerom/compare/v0.3.0...v0.4.0
data/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM ruby:3.4.2-alpine
1
+ FROM ruby:3.4.12-alpine
2
2
 
3
3
  RUN apk add --no-cache --virtual .build-deps \
4
4
  build-base \
data/Gemfile CHANGED
@@ -5,10 +5,10 @@ source 'https://rubygems.org'
5
5
  # Specify your gem's dependencies in gamerom.gemspec
6
6
  gemspec
7
7
 
8
- gem 'rake', '~> 13.2.1'
8
+ gem 'rake', '~> 13.4.2'
9
9
 
10
- gem 'rspec', '~> 3.13.0'
10
+ gem 'rspec', '~> 3.13.2'
11
11
 
12
- gem 'rubocop', '~> 1.74.0'
12
+ gem 'rubocop', '~> 1.92.2'
13
13
  gem 'rubocop-rake', '~> 0.7.1'
14
- gem 'rubocop-rspec', '~> 3.5.0'
14
+ gem 'rubocop-rspec', '~> 3.10.2'
data/Gemfile.lock CHANGED
@@ -1,32 +1,32 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gamerom (0.5.0)
5
- mechanize (~> 2.14.0)
4
+ gamerom (0.6.0)
5
+ mechanize (~> 2.14.2)
6
6
  mechanize-progressbar (~> 0.2.0)
7
- nokogiri (~> 1.18.5)
7
+ nokogiri (~> 1.19.4)
8
8
  progressbar (~> 1.13.0)
9
9
  rest-client (~> 2.1.0)
10
- thor (~> 1.3.2)
10
+ thor (~> 1.5.0)
11
11
 
12
12
  GEM
13
13
  remote: https://rubygems.org/
14
14
  specs:
15
- addressable (2.8.7)
16
- public_suffix (>= 2.0.2, < 7.0)
15
+ addressable (2.9.2)
16
+ public_suffix (>= 2.0.2, < 8.0)
17
17
  ast (2.4.3)
18
- base64 (0.2.0)
19
- connection_pool (2.5.0)
20
- diff-lcs (1.6.0)
21
- domain_name (0.6.20240107)
18
+ base64 (0.3.0)
19
+ connection_pool (3.0.2)
20
+ diff-lcs (1.6.2)
21
+ domain_name (0.7.20261007)
22
22
  http-accept (1.7.0)
23
- http-cookie (1.0.8)
23
+ http-cookie (1.1.6)
24
24
  domain_name (~> 0.5)
25
- json (2.10.2)
26
- language_server-protocol (3.17.0.4)
25
+ json (3.1.0)
26
+ language_server-protocol (3.17.0.6)
27
27
  lint_roller (1.1.0)
28
- logger (1.6.6)
29
- mechanize (2.14.0)
28
+ logger (1.7.0)
29
+ mechanize (2.14.2)
30
30
  addressable (~> 2.8)
31
31
  base64
32
32
  domain_name (~> 0.5, >= 0.5.20190701)
@@ -42,93 +42,99 @@ GEM
42
42
  mechanize-progressbar (0.2.0)
43
43
  mechanize (>= 1.0.0)
44
44
  progressbar (>= 0.9.0)
45
- mime-types (3.6.1)
45
+ mime-types (3.7.0)
46
46
  logger
47
- mime-types-data (~> 3.2015)
48
- mime-types-data (3.2025.0318)
47
+ mime-types-data (~> 3.2025, >= 3.2025.0507)
48
+ mime-types-data (3.2026.1027)
49
49
  net-http-digest_auth (1.4.1)
50
- net-http-persistent (4.0.5)
51
- connection_pool (~> 2.2)
50
+ net-http-persistent (4.0.8)
51
+ connection_pool (>= 2.2.4, < 4)
52
52
  netrc (0.11.0)
53
- nkf (0.2.0)
54
- nokogiri (1.18.5-aarch64-linux-musl)
53
+ nkf (0.3.0)
54
+ nokogiri (1.19.4-aarch64-linux-musl)
55
55
  racc (~> 1.4)
56
- nokogiri (1.18.5-arm64-darwin)
56
+ nokogiri (1.19.4-arm64-darwin)
57
57
  racc (~> 1.4)
58
- nokogiri (1.18.5-x86_64-darwin)
58
+ nokogiri (1.19.4-x86_64-darwin)
59
59
  racc (~> 1.4)
60
- nokogiri (1.18.5-x86_64-linux-musl)
60
+ nokogiri (1.19.4-x86_64-linux-gnu)
61
61
  racc (~> 1.4)
62
- parallel (1.26.3)
63
- parser (3.3.7.2)
62
+ nokogiri (1.19.4-x86_64-linux-musl)
63
+ racc (~> 1.4)
64
+ parallel (2.3.0)
65
+ parser (3.3.13.0)
64
66
  ast (~> 2.4.1)
65
67
  racc
68
+ prism (1.9.0)
66
69
  progressbar (1.13.0)
67
- public_suffix (6.0.1)
70
+ public_suffix (7.0.5)
68
71
  racc (1.8.1)
69
72
  rainbow (3.1.1)
70
- rake (13.2.1)
71
- regexp_parser (2.10.0)
73
+ rake (13.4.2)
74
+ regexp_parser (2.13.1)
72
75
  rest-client (2.1.0)
73
76
  http-accept (>= 1.7.0, < 2.0)
74
77
  http-cookie (>= 1.0.2, < 2.0)
75
78
  mime-types (>= 1.16, < 4.0)
76
79
  netrc (~> 0.8)
77
- rspec (3.13.0)
80
+ rspec (3.13.2)
78
81
  rspec-core (~> 3.13.0)
79
82
  rspec-expectations (~> 3.13.0)
80
83
  rspec-mocks (~> 3.13.0)
81
- rspec-core (3.13.3)
84
+ rspec-core (3.13.6)
82
85
  rspec-support (~> 3.13.0)
83
- rspec-expectations (3.13.3)
86
+ rspec-expectations (3.13.5)
84
87
  diff-lcs (>= 1.2.0, < 2.0)
85
88
  rspec-support (~> 3.13.0)
86
- rspec-mocks (3.13.2)
89
+ rspec-mocks (3.13.8)
87
90
  diff-lcs (>= 1.2.0, < 2.0)
88
91
  rspec-support (~> 3.13.0)
89
- rspec-support (3.13.2)
90
- rubocop (1.74.0)
91
- json (~> 2.3)
92
+ rspec-support (3.13.7)
93
+ rubocop (1.92.2)
94
+ json (>= 2.3)
92
95
  language_server-protocol (~> 3.17.0.2)
93
96
  lint_roller (~> 1.1.0)
94
- parallel (~> 1.10)
97
+ parallel (>= 1.10)
95
98
  parser (>= 3.3.0.2)
96
99
  rainbow (>= 2.2.2, < 4.0)
97
- regexp_parser (>= 2.9.3, < 3.0)
98
- rubocop-ast (>= 1.38.0, < 2.0)
100
+ regexp_parser (>= 2.11.3, < 3.0)
101
+ rubocop-ast (>= 1.49.0, < 2.0)
99
102
  ruby-progressbar (~> 1.7)
100
103
  unicode-display_width (>= 2.4.0, < 4.0)
101
- rubocop-ast (1.41.0)
104
+ rubocop-ast (1.50.0)
102
105
  parser (>= 3.3.7.2)
106
+ prism (~> 1.7)
103
107
  rubocop-rake (0.7.1)
104
108
  lint_roller (~> 1.1)
105
109
  rubocop (>= 1.72.1)
106
- rubocop-rspec (3.5.0)
110
+ rubocop-rspec (3.10.2)
107
111
  lint_roller (~> 1.1)
108
- rubocop (~> 1.72, >= 1.72.1)
112
+ regexp_parser (>= 2.0)
113
+ rubocop (~> 1.86, >= 1.86.2)
109
114
  ruby-progressbar (1.13.0)
110
- rubyntlm (0.6.5)
115
+ rubyntlm (0.6.8)
111
116
  base64
112
- thor (1.3.2)
113
- unicode-display_width (3.1.4)
114
- unicode-emoji (~> 4.0, >= 4.0.4)
115
- unicode-emoji (4.0.4)
116
- webrick (1.9.1)
117
+ thor (1.5.0)
118
+ unicode-display_width (3.3.0)
119
+ unicode-emoji (~> 4.3)
120
+ unicode-emoji (4.3.0)
121
+ webrick (1.9.2)
117
122
  webrobots (0.1.2)
118
123
 
119
124
  PLATFORMS
120
125
  aarch64-linux-musl
121
126
  arm64-darwin-24
122
127
  x86_64-darwin-20
128
+ x86_64-linux
123
129
  x86_64-linux-musl
124
130
 
125
131
  DEPENDENCIES
126
132
  gamerom!
127
- rake (~> 13.2.1)
128
- rspec (~> 3.13.0)
129
- rubocop (~> 1.74.0)
133
+ rake (~> 13.4.2)
134
+ rspec (~> 3.13.2)
135
+ rubocop (~> 1.92.2)
130
136
  rubocop-rake (~> 0.7.1)
131
- rubocop-rspec (~> 3.5.0)
137
+ rubocop-rspec (~> 3.10.2)
132
138
 
133
139
  BUNDLED WITH
134
- 2.6.2
140
+ 2.6.9
data/README.md CHANGED
@@ -4,8 +4,9 @@ A command-line installer for game ROMs from many repositories.
4
4
 
5
5
  It currently supports the following repositories:
6
6
 
7
- - https://vimm.net
8
7
  - https://coolrom.com.au
8
+ - https://edgeemu.net
9
+ - https://vimm.net (search and database only: downloads must be done manually, see `install` below)
9
10
 
10
11
  ## Installation
11
12
 
@@ -14,7 +15,7 @@ It currently supports the following repositories:
14
15
  Add this to your .bashrc, .bash_aliases, etc:
15
16
 
16
17
  ```
17
- alias gamerom="docker run -e UID=$(id -u) -e GROUP_ID=$(id -g) --rm -it -v ~/.gamerom:/home/runuser/.gamerom --platform=linux/amd64 docker.io/lucasmundim/gamerom:0.5.0"
18
+ alias gamerom="docker run -e UID=$(id -u) -e GROUP_ID=$(id -g) --rm -it -v ~/.gamerom:/home/runuser/.gamerom --platform=linux/amd64 docker.io/lucasmundim/gamerom:0.6.0"
18
19
  ```
19
20
 
20
21
  Use it as:
@@ -85,6 +86,7 @@ List available repo
85
86
  $ gamerom repo
86
87
  listing available repo...
87
88
  coolrom
89
+ edgeemu
88
90
  vimm
89
91
  ```
90
92
 
@@ -122,6 +124,61 @@ listing available platforms for coolrom repo...
122
124
  psp: Sony Playstation Portable
123
125
  ```
124
126
 
127
+ ```
128
+ $ gamerom platform -r edgeemu
129
+ listing available platforms for edgeemu repo...
130
+ ---
131
+ :platforms:
132
+ atari-2600: Atari 2600
133
+ atari-5200: Atari 5200
134
+ atari-7800: Atari 7800
135
+ atari-jaguar: Atari Jaguar
136
+ atari-jaguar-cd: Atari Jaguar CD
137
+ atari-lynx: Atari Lynx
138
+ atari-st: Atari ST
139
+ bandai-wonderswan: Bandai WonderSwan
140
+ colecovision: Coleco ColecoVision
141
+ commodore-64: Commodore 64
142
+ commodore-amiga: Commodore Amiga
143
+ commodore-amiga-cd: Commodore Amiga CD
144
+ commodore-amiga-cd32: Commodore Amiga CD32
145
+ commodore-plus-4: Commodore Plus-4
146
+ commodore-vic-20: Commodore VIC-20
147
+ fairchild-channel-f: Fairchild Channel F
148
+ gce-vectrex: GCE Vectrex
149
+ microsoft-msx: Microsoft MSX / MSX2
150
+ nec-pc-engine-cd-turbografx-cd: NEC PC Engine CD / TurboGrafx CD
151
+ nec-pc-engine-supergrafx: NEC PC Engine SuperGrafx
152
+ nec-pc-engine-turbografx-16: NEC PC Engine / TurboGrafx 16
153
+ nintendo-64: Nintendo 64
154
+ nintendo-ds: Nintendo DS
155
+ nintendo-fds: Nintendo Famicom Disk System (FDS)
156
+ nintendo-gameboy: Nintendo Game Boy
157
+ nintendo-gameboy-advance: Nintendo Game Boy Advance
158
+ nintendo-gameboy-color: Nintendo Game Boy Color
159
+ nintendo-gamecube: Nintendo Gamecube
160
+ nintendo-nes: Nintendo Entertainment System (NES)
161
+ nintendo-satellaview: Nintendo Satellaview
162
+ nintendo-snes: Super NES (SNES)
163
+ nintendo-virtualboy: Nintendo Virtual Boy
164
+ panasonic-3do: Panasonic 3DO
165
+ philips-cdi: Philips CDi
166
+ rca-studioii: RCA Studio II
167
+ sega-32x: Sega 32X
168
+ sega-cd: Sega Mega-CD / Sega CD
169
+ sega-dreamcast: Sega Dreamcast
170
+ sega-gamegear: Sega Game Gear
171
+ sega-genesis: Sega Mega Drive / Genesis
172
+ sega-pico: Sega PICO
173
+ sega-saturn: Sega Saturn
174
+ sega-sg1000: Sega SG-1000
175
+ sega-sms: Sega Master System / Mark III
176
+ sinclair-zx-spectrum-3: Sinclair ZX Spectrum +3
177
+ snk-neo-geo-cd: SNK Neo Geo CD
178
+ snk-ngpc: SNK Neo Geo Pocket Color
179
+ watara-supervision: Watara Supervision
180
+ ```
181
+
125
182
  ```
126
183
  $ gamerom platform -r vimm
127
184
  listing available platforms for vimm repo...
@@ -211,13 +268,24 @@ USA
211
268
 
212
269
  Install game GAME_IDENTIFIER (id/name)
213
270
 
271
+ ```
272
+ $ gamerom install -r edgeemu -p rca-studioii 'Pinball (Europe)'
273
+ installing game 2675927342 - Pinball (Europe) - Europe on rca-studioii platform on edgeemu repo...
274
+ 100% |ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo| 1.1KB 12.3KB/s Time: 00:00:00
275
+ Downloaded: Pinball (Europe).zip
276
+ Game installed
277
+ ```
278
+
279
+ The vimm repo does not allow automated downloads, so `install` tells you where to
280
+ download the game and where to save it. Run `recover` afterwards to register it:
281
+
214
282
  ```
215
283
  $ gamerom install -r vimm -p NES 'Mega Man'
216
284
  installing game 545 - Mega Man - USA on NES platform on vimm repo...
217
- downloading single file rom
218
- http://download4.vimm.net/download/?mediaId=530
219
- 100% |ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo| 74.8KB 46.3KB/s Time: 00:00:01
220
- Game installed
285
+ vimm.net does not allow automated downloads.
286
+ Download the game from https://vimm.net/vault/545 in your browser and save it to:
287
+ /home/user/.gamerom/games/vimm/NES/USA/
288
+ Then run `gamerom recover -r vimm -p NES` to register it.
221
289
  ```
222
290
 
223
291
  ### install_all
@@ -550,7 +618,7 @@ Print program version
550
618
 
551
619
  ```
552
620
  $ gamerom version
553
- 0.5.0
621
+ 0.6.0
554
622
  ```
555
623
 
556
624
  ## Development
data/gamerom.gemspec CHANGED
@@ -30,12 +30,12 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ['lib']
32
32
 
33
- spec.add_dependency 'mechanize', '~> 2.14.0'
33
+ spec.add_dependency 'mechanize', '~> 2.14.2'
34
34
  spec.add_dependency 'mechanize-progressbar', '~> 0.2.0'
35
- spec.add_dependency 'nokogiri', '~> 1.18.5'
35
+ spec.add_dependency 'nokogiri', '~> 1.19.4'
36
36
  spec.add_dependency 'progressbar', '~> 1.13.0'
37
37
  spec.add_dependency 'rest-client', '~> 2.1.0'
38
- spec.add_dependency 'thor', '~> 1.3.2'
38
+ spec.add_dependency 'thor', '~> 1.5.0'
39
39
 
40
40
  # For more information and examples about making a new gem, checkout our
41
41
  # guide at: https://bundler.io/guides/creating_gem.html
data/lib/gamerom/cli.rb CHANGED
@@ -61,6 +61,8 @@ module Gamerom
61
61
  end
62
62
  game.install
63
63
  shell.say 'Game installed', :green
64
+ rescue Gamerom::ManualDownloadRequired => e
65
+ shell.say e.message, :yellow
64
66
  rescue StandardError => e
65
67
  render_error e, options
66
68
  exit 1
@@ -75,7 +77,13 @@ module Gamerom
75
77
  validate_platform repo, options[:platform]
76
78
  games = repo.games options[:platform], region: options[:region]
77
79
  games.each do |game|
78
- install(game.id) unless game.installed?
80
+ if game.installed?
81
+ shell.say "Game #{game.name} already installed", :yellow
82
+ next
83
+ end
84
+ install(game.id)
85
+ rescue StandardError, SystemExit => e
86
+ shell.say "Error installing game #{game.name}: #{e.message}", :red
79
87
  end
80
88
  rescue StandardError => e
81
89
  render_error e, options
@@ -116,28 +124,12 @@ module Gamerom
116
124
  validate_platform repo, options[:platform]
117
125
  puts "recovering state of roms for #{options[:platform]} platform on #{options[:repo]} repo..."
118
126
  games = repo.games options[:platform]
119
- games_not_found = []
120
- games.each do |game|
121
- filename = nil
122
- basename = "#{Gamerom::GAME_DIR}/#{repo.name}/#{options[:platform]}/#{game[:region]}/#{game[:name]}"
123
- %w[zip 7z rar].each do |ext|
124
- fullname = "#{basename}.#{ext}"
125
- filename = fullname if File.exist? fullname
126
- end
127
+ game_names = games.to_h { |game| [game.id, game.name] }
128
+ results = games.group_by { |game| recover_game(game, game_names) }
127
129
 
128
- if filename
129
- game.update_state File.basename(filename)
130
- puts "Found game #{game[:name]}"
131
- else
132
- games_not_found << game[:name]
133
- end
134
- end
135
- if games_not_found.count.positive?
136
- puts 'Games not found:'
137
- puts games_not_found
138
- end
130
+ print_recover_summary(results.fetch(:found, []), results.fetch(:already_installed, []), results.fetch(:not_found, []))
139
131
  rescue StandardError => e
140
- puts e.message
132
+ render_error e, options
141
133
  exit 1
142
134
  end
143
135
 
@@ -332,5 +324,113 @@ module Gamerom
332
324
 
333
325
  raise "Expected '--platform' to be one of #{repo.platforms.keys.join(", ")}; got #{platform}"
334
326
  end
327
+
328
+ # Recover helpers
329
+
330
+ def recover_game(game, game_names)
331
+ puts
332
+ puts "Checking game #{game}"
333
+ if game.filenames.any?
334
+ puts "#{shell.set_color("*", :green)} Found game at: #{game.filenames.first}"
335
+ return :already_installed
336
+ end
337
+
338
+ game_files = glob_game_files(game).select { |file| file_belongs_to_game?(file, game, game_names) }.sort
339
+ if game_files.empty?
340
+ puts "#{shell.set_color("*", :red)} not found"
341
+ return :not_found
342
+ end
343
+
344
+ game.update_state(game_files.map { |file| File.basename(file) })
345
+ print_recovered_files(game_files)
346
+ :found
347
+ end
348
+
349
+ def glob_game_files(game)
350
+ patterns = [
351
+ glob_escape(game.name.tr(' ', '_'), '_'),
352
+ glob_escape(game.name, ' ')
353
+ ]
354
+ patterns.flat_map { |pattern| Dir.glob("#{game.filepath}/#{pattern}*") }.uniq
355
+ end
356
+
357
+ def glob_escape(str, spacer)
358
+ str
359
+ .gsub(/([\\\[\]*?{}) ,])/, '\\\\\1')
360
+ .gsub(':', "#{spacer}-")
361
+ .gsub('!', '\!*')
362
+ .gsub('(', '*\(')
363
+ end
364
+
365
+ # A globbed file is kept for a game unless it is a better match for another
366
+ # game whose name starts with this game's name (e.g. "Kung-Fu" vs "Kung-Fu Master").
367
+ def file_belongs_to_game?(file, game, game_names)
368
+ filename = File.basename(file).downcase
369
+ return true if exact_filename_match?(filename, game.name.downcase)
370
+
371
+ number = sequel_number(game.name)
372
+ return true if number && filename.include?(number)
373
+ return true if game.name.include?('Limited Run Games') && filename.include?('limited_run')
374
+
375
+ !claimed_by_other_game?(filename, game, game_names)
376
+ end
377
+
378
+ # "Game.zip", "Game_Name.zip" or "Game (USA).zip"
379
+ def exact_filename_match?(filename, name)
380
+ [name, name.tr(' ', '_')].any? do |variant|
381
+ filename.match?(/\A#{Regexp.escape(variant)}(\s*\(\w+\))?\.\w+\z/i)
382
+ end
383
+ end
384
+
385
+ # The number that identifies a sequel, e.g. "2" in "Tony Hawk's Pro Skater 2"
386
+ def sequel_number(name)
387
+ match = name.match(/(\d+|\s+\d+)[\s':]/) || name.match(/\s(\d+)$/)
388
+ match && match[1].strip
389
+ end
390
+
391
+ def claimed_by_other_game?(filename, game, game_names)
392
+ name = game.name.downcase
393
+ game_names.any? do |other_id, other_name|
394
+ next false if other_id == game.id
395
+
396
+ other = other_name.downcase
397
+ next false unless other.length > name.length && other.start_with?(name)
398
+
399
+ filename_matches_other_game?(filename, other_name)
400
+ end
401
+ end
402
+
403
+ def filename_matches_other_game?(filename, other_name)
404
+ other = other_name.downcase
405
+ other_underscored = other.tr(' ', '_')
406
+ return false unless filename.include?(other_underscored) || filename.include?(other)
407
+
408
+ number = sequel_number(other_name)
409
+ return filename.include?(number) if number
410
+
411
+ filename.start_with?(other_underscored) ||
412
+ filename.start_with?(other) ||
413
+ filename.include?(other_underscored.tr(':', '_')) ||
414
+ filename.include?(other.tr(':', '-'))
415
+ end
416
+
417
+ def print_recovered_files(game_files)
418
+ if game_files.size > 1
419
+ puts "#{shell.set_color("*", :green)} Found #{game_files.size} disks for game:"
420
+ game_files.each { |file| puts " - #{file}" }
421
+ else
422
+ puts "#{shell.set_color("*", :green)} Found game at: #{game_files.first}"
423
+ end
424
+ end
425
+
426
+ def print_recover_summary(games_found, games_already_installed, games_not_found)
427
+ if games_not_found.empty?
428
+ puts "All #{games_found.count + games_already_installed.count} games found"
429
+ else
430
+ puts "#{shell.set_color(games_found.count, :green)} games found"
431
+ puts "#{shell.set_color(games_already_installed.count, :yellow)} games already installed"
432
+ puts "#{shell.set_color(games_not_found.count, :red)} games not found"
433
+ end
434
+ end
335
435
  end
336
436
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamerom
4
+ class Error < StandardError; end
5
+
6
+ # Raised by repo adapters whose site does not allow automated downloads.
7
+ # The message tells the user where to download the game and where to save it.
8
+ class ManualDownloadRequired < Error; end
9
+ end
data/lib/gamerom/game.rb CHANGED
@@ -6,7 +6,7 @@ require 'yaml'
6
6
  module Gamerom
7
7
  # Game - Represents a game ROM
8
8
  class Game
9
- attr_accessor :id, :name, :platform, :region, :repo, :tags
9
+ attr_accessor :id, :name, :platform, :region, :repo, :tags, :url
10
10
 
11
11
  def initialize(attributes = {})
12
12
  @tags = []
@@ -16,7 +16,9 @@ module Gamerom
16
16
  end
17
17
 
18
18
  def filenames
19
- YAML.load_file(state_filename).map do |filename|
19
+ return [] unless File.exist? state_filename
20
+
21
+ Array(YAML.load_file(state_filename)).map do |filename|
20
22
  "#{filepath}/#{filename}"
21
23
  end
22
24
  end
@@ -12,11 +12,15 @@ module Gamerom
12
12
 
13
13
  def games(platform)
14
14
  games = []
15
+
16
+ columns = `tput cols`.to_i
17
+ length = [120, columns].min
18
+
15
19
  progress_bar = ProgressBar.create(
16
20
  title: platform,
17
21
  total: sections.count,
18
22
  autofinish: true,
19
- length: 80,
23
+ length: length,
20
24
  format: "%t: %b\e[0;93m\u{15E7}\e[0m%i %j%% %e",
21
25
  progress_mark: ' ',
22
26
  remainder_mark: "\e[0;34m\u{FF65}\e[0m"
@@ -81,6 +81,7 @@ module Gamerom
81
81
  filename = response.filename
82
82
  FileUtils.mkdir_p(game.filepath)
83
83
  response.save!("#{game.filepath}/#{filename}")
84
+ puts "Downloaded: #{filename}"
84
85
  yield [filename]
85
86
  end
86
87
  end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mechanize'
4
+ require 'uri'
5
+ require 'mechanize/progressbar'
6
+ require 'mechanizeprogress'
7
+
8
+ module Gamerom
9
+ module RepoAdapters
10
+ # Edgeemu - An adapter for the Edge Emulation repository website
11
+ class Edgeemu
12
+ extend Gamerom::RepoAdapter
13
+
14
+ BASE_URL = 'https://edgeemu.net'
15
+
16
+ PLATFORM = {
17
+ 'atari-2600' => 'Atari 2600',
18
+ 'atari-5200' => 'Atari 5200',
19
+ 'atari-7800' => 'Atari 7800',
20
+ 'atari-jaguar' => 'Atari Jaguar',
21
+ 'atari-jaguar-cd' => 'Atari Jaguar CD',
22
+ 'atari-lynx' => 'Atari Lynx',
23
+ 'atari-st' => 'Atari ST',
24
+ 'bandai-wonderswan' => 'Bandai WonderSwan',
25
+ 'colecovision' => 'Coleco ColecoVision',
26
+ 'commodore-64' => 'Commodore 64',
27
+ 'commodore-amiga' => 'Commodore Amiga',
28
+ 'commodore-amiga-cd' => 'Commodore Amiga CD',
29
+ 'commodore-amiga-cd32' => 'Commodore Amiga CD32',
30
+ 'commodore-plus-4' => 'Commodore Plus-4',
31
+ 'commodore-vic-20' => 'Commodore VIC-20',
32
+ 'fairchild-channel-f' => 'Fairchild Channel F',
33
+ 'gce-vectrex' => 'GCE Vectrex',
34
+ 'microsoft-msx' => 'Microsoft MSX / MSX2',
35
+ 'nec-pc-engine-cd-turbografx-cd' => 'NEC PC Engine CD / TurboGrafx CD',
36
+ 'nec-pc-engine-supergrafx' => 'NEC PC Engine SuperGrafx',
37
+ 'nec-pc-engine-turbografx-16' => 'NEC PC Engine / TurboGrafx 16',
38
+ 'nintendo-64' => 'Nintendo 64',
39
+ 'nintendo-ds' => 'Nintendo DS',
40
+ 'nintendo-fds' => 'Nintendo Famicom Disk System (FDS)',
41
+ 'nintendo-gameboy' => 'Nintendo Game Boy',
42
+ 'nintendo-gameboy-advance' => 'Nintendo Game Boy Advance',
43
+ 'nintendo-gameboy-color' => 'Nintendo Game Boy Color',
44
+ 'nintendo-gamecube' => 'Nintendo Gamecube',
45
+ 'nintendo-nes' => 'Nintendo Entertainment System (NES)',
46
+ 'nintendo-satellaview' => 'Nintendo Satellaview',
47
+ 'nintendo-snes' => 'Super NES (SNES)',
48
+ 'nintendo-virtualboy' => 'Nintendo Virtual Boy',
49
+ 'panasonic-3do' => 'Panasonic 3DO',
50
+ 'philips-cdi' => 'Philips CDi',
51
+ 'rca-studioii' => 'RCA Studio II',
52
+ 'sega-32x' => 'Sega 32X',
53
+ 'sega-cd' => 'Sega Mega-CD / Sega CD',
54
+ 'sega-dreamcast' => 'Sega Dreamcast',
55
+ 'sega-gamegear' => 'Sega Game Gear',
56
+ 'sega-genesis' => 'Sega Mega Drive / Genesis',
57
+ 'sega-pico' => 'Sega PICO',
58
+ 'sega-saturn' => 'Sega Saturn',
59
+ 'sega-sg1000' => 'Sega SG-1000',
60
+ 'sega-sms' => 'Sega Master System / Mark III',
61
+ 'sinclair-zx-spectrum-3' => 'Sinclair ZX Spectrum +3',
62
+ 'snk-neo-geo-cd' => 'SNK Neo Geo CD',
63
+ 'snk-ngpc' => 'SNK Neo Geo Pocket Color',
64
+ 'watara-supervision' => 'Watara Supervision',
65
+ }.freeze
66
+
67
+ # No-Intro region tags, as they appear in parentheses in the file names
68
+ REGIONS = [
69
+ 'World', 'USA', 'Europe', 'Japan', 'Asia', 'Australia', 'Brazil', 'Canada', 'China', 'France',
70
+ 'Germany', 'Greece', 'Hong Kong', 'Italy', 'Korea', 'Netherlands', 'Norway', 'Poland', 'Portugal',
71
+ 'Russia', 'Scandinavia', 'Spain', 'Sweden', 'Taiwan', 'UK', 'Unknown', 'Latin America'
72
+ ].freeze
73
+
74
+ def self.platforms
75
+ PLATFORM
76
+ end
77
+
78
+ def self.sections
79
+ ('a'..'z').to_a.unshift('1')
80
+ end
81
+
82
+ def self.extract_games(platform)
83
+ sections.each_with_index do |section, index|
84
+ page = nokogiri_get("#{BASE_URL}/browse/#{platform}/#{section}")
85
+ yield page.css('div.item details').map { |item| game(item) }, index
86
+ rescue RestClient::NotFound
87
+ yield [], index
88
+ end
89
+ end
90
+
91
+ # Each item looks like:
92
+ # <details data-name="Espial (USA).zip">
93
+ # <summary>Espial (USA)</summary>
94
+ # <p><a href="/download/atari-2600/Espial%20%28USA%29.zip">download</a> ...</p>
95
+ # <p>hash:&nbsp;<span>931A0BDC</span></p>
96
+ # </details>
97
+ # The hash is the CRC32 of the ROM, which is used as the game id.
98
+ def self.game(item)
99
+ name = item.at_css('summary').text.strip
100
+ {
101
+ id: item.css('p').find { |p| p.text.start_with?('hash') }.at_css('span').text.strip.to_i(16),
102
+ name: name,
103
+ region: region(name),
104
+ url: "#{BASE_URL}#{item.at_css('a[href^="/download/"]')["href"]}",
105
+ }
106
+ end
107
+
108
+ # "Espial (USA)" => "USA", "Game (USA, Europe) (Proto)" => "USA, Europe"
109
+ def self.region(name)
110
+ groups = name.scan(/\(([^)]+)\)/).flatten
111
+ groups.find { |group| REGIONS.include?(group.split(',').first.strip) } || 'Unknown'
112
+ end
113
+
114
+ def self.install(game)
115
+ agent = Mechanize.new
116
+ agent.pluggable_parser.default = Mechanize::Download
117
+ agent.user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36'
118
+
119
+ response = nil
120
+ agent.progressbar do
121
+ response = agent.get(game.url)
122
+ end
123
+
124
+ return unless response.code.to_i == 200
125
+
126
+ # The Content-Disposition header ends with a stray ";" that Mechanize cannot parse,
127
+ # so it falls back to the percent-encoded URL basename. Decode it instead.
128
+ filename = URI.decode_uri_component(File.basename(URI.parse(game.url).path))
129
+ FileUtils.mkdir_p(game.filepath)
130
+ response.save!("#{game.filepath}/#{filename}")
131
+ puts "Downloaded: #{filename}"
132
+ yield [filename]
133
+ end
134
+ end
135
+ end
136
+ end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'mechanize'
4
- require 'mechanize/progressbar'
5
- require 'mechanizeprogress'
3
+ require 'fileutils'
6
4
 
7
5
  module Gamerom
8
6
  module RepoAdapters
@@ -69,42 +67,20 @@ module Gamerom
69
67
  }
70
68
  end
71
69
 
70
+ # Vimm's Lair protects game pages with a browser challenge, so games have to be
71
+ # downloaded by hand. Prepare the target directory and tell the user what to do.
72
72
  def self.install(game)
73
73
  FileUtils.mkdir_p(game.filepath)
74
- agent = Mechanize.new
75
- agent.pluggable_parser.default = Mechanize::Download
76
- agent.user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36'
77
- page = agent.get("https://vimm.net/vault/#{game.id}")
78
- form = page.form_with(id: 'dl_form')
79
-
80
- filenames = []
81
- game_files = []
82
- multiple_disks = page.css('#download_disc_number')
83
-
84
- if multiple_disks.empty?
85
- game_files << { id: form['mediaId'], name: 'single file rom' }
86
- else
87
- puts 'multiple discs detected'
88
- game_files.concat(multiple_disks.children[1..-2].map { |disk| { name: disk.text, id: disk['value'] } })
89
- end
90
-
91
- game_files.each do |game_file|
92
- puts "downloading #{game_file[:name]}"
93
- form.method = 'GET'
94
- button = form.button_with(type: 'submit')
95
- response = nil
96
- form['mediaId'] = game_file[:id]
97
- agent.progressbar do
98
- response = form.click_button(button)
99
- end
100
-
101
- break unless response.code.to_i == 200
74
+ raise Gamerom::ManualDownloadRequired, <<~MSG
75
+ vimm.net does not allow automated downloads.
76
+ Download the game from #{game_url(game)} in your browser and save it to:
77
+ #{game.filepath}/
78
+ Then run `gamerom recover -r vimm -p #{game.platform}` to register it.
79
+ MSG
80
+ end
102
81
 
103
- filename = response.filename
104
- response.save!("#{game.filepath}/#{filename}")
105
- filenames << filename
106
- end
107
- yield filenames
82
+ def self.game_url(game)
83
+ "https://vimm.net/vault/#{game.id}"
108
84
  end
109
85
  end
110
86
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gamerom
4
- VERSION = '0.5.0'
4
+ VERSION = '0.6.0'
5
5
  end
data/lib/gamerom.rb CHANGED
@@ -1,12 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'gamerom/errors'
3
4
  require_relative 'gamerom/config'
4
5
  require_relative 'gamerom/game'
5
6
  require_relative 'gamerom/game_info'
6
7
  require_relative 'gamerom/repo'
7
8
  require_relative 'gamerom/cli'
8
9
  require_relative 'gamerom/version'
9
-
10
- module Gamerom
11
- class Error < StandardError; end
12
- end
@@ -3,6 +3,7 @@
3
3
  # Mechanize - A Monkeypatch for the mechanize progressbar
4
4
  class Mechanize
5
5
  include MechanizeProgressBarAPI # (1of7)a
6
+
6
7
  class HTTP
7
8
  # Agent - A Monkeypatch for the mechanize progressbar
8
9
  class Agent
@@ -65,11 +66,14 @@ class MechanizeProgressBar
65
66
  total = pbar_opts[:total] || filesize(response)
66
67
  pbar_class = pbar_opts[:reversed] ? ReversedProgressBar : ProgressBar
67
68
 
69
+ columns = `tput cols`.to_i
70
+ length = [120, columns].min
71
+
68
72
  pbar_class.create(
69
73
  title: title,
70
74
  total: total,
71
75
  output: out,
72
- length: 120,
76
+ length: length,
73
77
  format: format,
74
78
  rate_scale: ->(rate) { rate / 1024 },
75
79
  progress_mark: ' ',
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gamerom
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucas Mundim
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-03-25 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: mechanize
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 2.14.0
18
+ version: 2.14.2
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 2.14.0
25
+ version: 2.14.2
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: mechanize-progressbar
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -43,14 +43,14 @@ dependencies:
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: 1.18.5
46
+ version: 1.19.4
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: 1.18.5
53
+ version: 1.19.4
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: progressbar
56
56
  requirement: !ruby/object:Gem::Requirement
@@ -85,14 +85,14 @@ dependencies:
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: 1.3.2
88
+ version: 1.5.0
89
89
  type: :runtime
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: 1.3.2
95
+ version: 1.5.0
96
96
  description: A command-line installer for game ROMs from many repositories.
97
97
  email:
98
98
  - lucas.mundim@gmail.com
@@ -124,11 +124,13 @@ files:
124
124
  - lib/gamerom.rb
125
125
  - lib/gamerom/cli.rb
126
126
  - lib/gamerom/config.rb
127
+ - lib/gamerom/errors.rb
127
128
  - lib/gamerom/game.rb
128
129
  - lib/gamerom/game_info.rb
129
130
  - lib/gamerom/repo.rb
130
131
  - lib/gamerom/repo_adapter.rb
131
132
  - lib/gamerom/repo_adapters/coolrom.rb
133
+ - lib/gamerom/repo_adapters/edgeemu.rb
132
134
  - lib/gamerom/repo_adapters/vimm.rb
133
135
  - lib/gamerom/version.rb
134
136
  - lib/mechanizeprogress.rb
@@ -155,7 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
155
157
  - !ruby/object:Gem::Version
156
158
  version: '0'
157
159
  requirements: []
158
- rubygems_version: 3.6.2
160
+ rubygems_version: 3.6.9
159
161
  specification_version: 4
160
162
  summary: The Video Game ROM downloader
161
163
  test_files: []