gamerom 0.1.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- # 'frozen_string_literal' => true
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'fileutils'
4
4
  require 'logger'
@@ -6,7 +6,7 @@ require 'rest-client'
6
6
  require 'mechanize'
7
7
 
8
8
  module Gamerom
9
- ROM_ROOT = ENV['ROM_ROOT'] || File.expand_path("~/.gamerom")
9
+ ROM_ROOT = ENV['ROM_ROOT'] || File.expand_path('~/.gamerom')
10
10
  CACHE_DIR = ENV['CACHE_DIR'] || "#{ROM_ROOT}/cache"
11
11
  GAME_DIR = ENV['GAME_DIR'] || "#{ROM_ROOT}/games"
12
12
  LOG_DIR = ENV['LOG_DIR'] || "#{ROM_ROOT}/logs"
data/lib/gamerom/game.rb CHANGED
@@ -1,47 +1,56 @@
1
- # 'frozen_string_literal' => true
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'fileutils'
4
4
  require 'ostruct'
5
+ require 'yaml'
5
6
 
6
7
  module Gamerom
8
+ # Game - Represents a game ROM
7
9
  class Game < OpenStruct
8
- def filename
9
- "#{self.filepath}/#{File.read(self.state_filename)}"
10
+ def filenames
11
+ YAML.load_file(state_filename).map do |filename|
12
+ "#{filepath}/#{filename}"
13
+ end
10
14
  end
11
15
 
12
16
  def filepath
13
- "#{Gamerom::GAME_DIR}/#{self.repo.name}/#{self.platform}/#{self.region}"
17
+ "#{Gamerom::GAME_DIR}/#{repo.name}/#{platform}/#{region}"
14
18
  end
15
19
 
16
20
  def install
17
- self.repo.install self do |filename|
18
- self.update_state filename
21
+ repo.install self do |filenames|
22
+ update_state filenames
19
23
  end
20
24
  end
21
25
 
22
26
  def installed?
23
- File.exists? self.state_filename
27
+ File.exist? state_filename
24
28
  end
25
29
 
26
30
  def state_filename
27
- "#{Gamerom::STATE_DIR}/#{self.repo.name}/#{self.platform}/#{self.region}/#{self.id}"
31
+ "#{Gamerom::STATE_DIR}/#{repo.name}/#{platform}/#{region}/#{id}"
28
32
  end
29
33
 
30
34
  def to_s
31
- "#{self.id} - #{self.name} - #{self.region}#{self.installed? ? " (#{shell.set_color 'installed', :green})" : ''}"
35
+ install_status = ''
36
+ install_status = " (#{shell.set_color "installed", :green})" if installed?
37
+ tags = ''
38
+ tags = " - tags: #{tags.join(", ")}" if respond_to?(:tags) && !tags.empty?
39
+ "#{id} - #{name} - #{region}#{install_status}#{tags}"
32
40
  end
33
41
 
34
42
  def uninstall
35
- FileUtils.rm_rf self.filename
36
- FileUtils.rm_rf self.state_filename
43
+ FileUtils.rm_rf filenames
44
+ FileUtils.rm_rf state_filename
37
45
  end
38
46
 
39
- def update_state filename
40
- FileUtils.mkdir_p("#{Gamerom::STATE_DIR}/#{self.repo.name}/#{self.platform}/#{self.region}")
41
- File.write(self.state_filename, filename)
47
+ def update_state(filenames)
48
+ FileUtils.mkdir_p("#{Gamerom::STATE_DIR}/#{repo.name}/#{platform}/#{region}")
49
+ File.write(state_filename, filenames.to_yaml)
42
50
  end
43
51
 
44
52
  private
53
+
45
54
  def shell
46
55
  @shell ||= Thor::Shell::Color.new
47
56
  end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamerom
4
+ # GameInfo - Extracts region and tags from game name
5
+ class GameInfo
6
+ REGIONS = {
7
+ '1' => 'Japan & Korea',
8
+ '4' => 'USA & Brazil - NTSC',
9
+ '5' => 'NTSC',
10
+ '8' => 'PAL',
11
+ 'A' => 'Australia',
12
+ 'As' => 'Asia',
13
+ 'B' => 'Brazil',
14
+ 'C' => 'Canada',
15
+ 'Ch' => 'China',
16
+ 'D' => 'Netherlands (Dutch)',
17
+ 'E' => 'Europe',
18
+ 'F' => 'France',
19
+ 'FC' => 'French Canadian',
20
+ 'FN' => 'Finland',
21
+ 'G' => 'Germany',
22
+ 'GR' => 'Greece',
23
+ 'H' => 'Holland',
24
+ 'HK' => 'Hong Kong',
25
+ 'I' => 'Italy',
26
+ 'J' => 'Japan',
27
+ 'JUE' => 'Japan & USA & Europe',
28
+ 'K' => 'Korea',
29
+ 'Nl' => 'Netherlands',
30
+ 'NL' => 'Netherlands',
31
+ 'No' => 'Norway',
32
+ 'PD' => 'Public Domain',
33
+ 'R' => 'Russia',
34
+ 'S' => 'Spain',
35
+ 'Sw' => 'Sweden',
36
+ 'SW' => 'Sweden',
37
+ 'U' => 'USA',
38
+ 'UK' => 'England',
39
+ 'Unk' => 'Unknown Country',
40
+ 'Unl' => 'Unlicensed',
41
+ 'PAL' => 'PAL regions (Australia, Europe)',
42
+ 'NTSC' => 'NTSC regions (Japan, USA, Latin America)',
43
+ }.freeze
44
+
45
+ TAGS = {
46
+ '!' => :good,
47
+ '!p' => :pending,
48
+ 'a' => :alternate,
49
+ 'b' => :bad,
50
+ 'BF' => :bung,
51
+ 'c' => :checksum,
52
+ 'C' => :color,
53
+ 'f' => :fixed,
54
+ 'h' => :hack,
55
+ 'J' => :japanese_translation,
56
+ 'o' => :overdump,
57
+ 'p' => :pirate,
58
+ 'PC10' => :pc10,
59
+ 'S' => :super,
60
+ 'T-' => :old_translation,
61
+ 't' => :trained,
62
+ 'T+' => :newer_translation,
63
+ 'VS' => :vs,
64
+ 'x' => :bad_checksum,
65
+ }.freeze
66
+
67
+ attr_reader :name
68
+
69
+ def initialize(name)
70
+ @name = name
71
+ end
72
+
73
+ def region
74
+ identifiers = @name.scan(/\((?<region>[A-Za-z0-9]+)\)/).flatten
75
+ region_id = identifiers.find { |i| REGIONS.include? i }
76
+ if region_id
77
+ REGIONS[region_id]
78
+ else
79
+ 'USA'
80
+ end
81
+ end
82
+
83
+ def tags
84
+ tags = []
85
+ codes = @name.scan(/\[(?<code>[^\]]+)\]/).flatten
86
+ codes.each do |code|
87
+ code = Regexp.last_match(1) if code.match /^(?<code>[abcfhop])[0-9]*/
88
+ tags << TAGS[code] if TAGS.include?(code)
89
+ end
90
+ tags
91
+ end
92
+ end
93
+ end
data/lib/gamerom/repo.rb CHANGED
@@ -1,4 +1,4 @@
1
- # 'frozen_string_literal' => true
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'yaml'
4
4
 
@@ -6,30 +6,34 @@ REPOSITORIES = Dir["#{File.dirname(__FILE__)}/repo_adapters/*"].map do |file|
6
6
  File.basename(file, '.rb')
7
7
  end
8
8
 
9
+ require_relative 'repo_adapter'
9
10
  REPOSITORIES.each do |repo|
10
11
  require_relative "repo_adapters/#{repo}"
11
12
  end
12
13
 
13
14
  module Gamerom
15
+ # Repo - Represents a game ROM repository
14
16
  class Repo
17
+ attr_reader :name
18
+
15
19
  def self.list
16
20
  REPOSITORIES.map do |repo|
17
- self.new repo
21
+ new repo
18
22
  end
19
23
  end
20
24
 
21
- def initialize name
25
+ def initialize(name)
22
26
  @name = name
23
27
  @repo = Gamerom::RepoAdapters.const_get(name.capitalize)
24
28
  end
25
29
 
26
- def install game, &block
30
+ def install(game, &block)
27
31
  @repo.install game, &block
28
32
  end
29
33
 
30
- def find platform, game_identifier
34
+ def find(platform, game_identifier)
31
35
  games(platform).find do |game|
32
- if Float(game_identifier, exception: false)
36
+ if Integer(game_identifier, exception: false)
33
37
  game.id == game_identifier.to_i
34
38
  else
35
39
  game.name.downcase == game_identifier.downcase
@@ -37,47 +41,42 @@ module Gamerom
37
41
  end
38
42
  end
39
43
 
40
- def games platform, options={}
44
+ def games(platform, options = {})
41
45
  platform_database = "#{Gamerom::CACHE_DIR}/#{@name}/#{platform}.yml"
42
- update_database platform unless File.exists? platform_database
43
- games = YAML.load_file(platform_database).map { |game|
46
+ update_database platform unless File.exist? platform_database
47
+ games = YAML.load_file(platform_database).map do |game|
44
48
  Game.new(game.merge(platform: platform, repo: self))
45
- }
49
+ end
46
50
 
47
- if !options[:region].nil?
48
- games = games.select { |game|
49
- options[:region].nil? || game.region == options[:region]
50
- }
51
+ unless options[:region].nil?
52
+ games.select! do |game|
53
+ game.region == options[:region]
54
+ end
51
55
  end
52
56
 
53
- if !options[:keyword].nil?
54
- games = games.select { |game|
57
+ unless options[:keyword].nil?
58
+ games.select! do |game|
55
59
  game.name =~ /#{options[:keyword]}/i
56
- }
60
+ end
57
61
  end
58
62
 
59
63
  games
60
64
  end
61
65
 
62
- def name
63
- @name
64
- end
65
-
66
66
  def platforms
67
67
  @repo.platforms
68
68
  end
69
69
 
70
- def regions platform
71
- games(platform).map { |game| game.region }.sort.uniq
70
+ def regions(platform)
71
+ games(platform).map(&:region).sort.uniq
72
72
  end
73
73
 
74
74
  def to_s
75
75
  @name
76
76
  end
77
77
 
78
- def update_database platform
78
+ def update_database(platform)
79
79
  games = @repo.games platform
80
- puts
81
80
  FileUtils.mkdir_p("#{Gamerom::CACHE_DIR}/#{@name}")
82
81
  File.write("#{Gamerom::CACHE_DIR}/#{@name}/#{platform}.yml", games.to_yaml)
83
82
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+ require 'rest-client'
5
+
6
+ module Gamerom
7
+ # RepoAdapter - Common adapter methods
8
+ module RepoAdapter
9
+ def nokogiri_get(url)
10
+ Nokogiri::HTML(RestClient.get(url))
11
+ end
12
+
13
+ def games(platform)
14
+ games = []
15
+ progress_bar = ProgressBar.new(platform, sections.count)
16
+
17
+ extract_games(platform) do |section_games, section_index|
18
+ games.append(*section_games)
19
+ progress_bar.set(section_index + 1)
20
+ end
21
+
22
+ progress_bar.finish
23
+ games
24
+ end
25
+ end
26
+ end
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'nokogiri'
4
- require 'rest-client'
3
+ require 'mechanize'
4
+ require 'mechanize/progressbar'
5
+ require 'mechanizeprogress'
5
6
 
6
7
  module Gamerom
7
8
  module RepoAdapters
9
+ # Coolrom - An adapter for the CoolROM repository website
8
10
  class Coolrom
11
+ extend Gamerom::RepoAdapter
12
+
9
13
  PLATFORM = {
10
14
  'atari2600' => 'Atari 2600',
11
15
  'atari5200' => 'Atari 5200',
@@ -30,48 +34,51 @@ module Gamerom
30
34
  'psx' => 'Sony Playstation',
31
35
  'ps2' => 'Sony Playstation 2',
32
36
  'psp' => 'Sony Playstation Portable',
33
- }
37
+ }.freeze
34
38
 
35
39
  def self.platforms
36
40
  PLATFORM
37
41
  end
38
42
 
39
- def self.games(platform)
40
- games = []
41
- sections = ('a'..'z').to_a.unshift("0")
43
+ def self.sections
44
+ ('a'..'z').to_a.unshift('0')
45
+ end
42
46
 
43
- sections.each do |section|
44
- print "#{section} "
45
- page = Nokogiri::HTML(RestClient.get("https://coolrom.com.au/roms/#{platform}/#{section}/"))
46
- regions = page.css('input.region').map { |i| i["name"] }
47
+ def self.extract_games(platform)
48
+ sections.each_with_index do |section, index|
49
+ page = nokogiri_get("https://coolrom.com.au/roms/#{platform}/#{section}/")
50
+ regions = page.css('input.region').map { |i| i['name'] }
47
51
  regions.each do |region|
48
- games.append *page.css("div.#{region} a").map { |game|
49
- {
50
- id: game['href'].split('/')[3].to_i,
51
- name: game.text,
52
- region: region,
53
- }
54
- }
52
+ game_links = page.css("div.#{region} a")
53
+ yield game_links.map { |game_link| game(game_link, region) }, index
55
54
  end
56
55
  end
57
- games
56
+ end
57
+
58
+ def self.game(game_link, region)
59
+ {
60
+ id: game_link['href'].split('/')[3].to_i,
61
+ name: game_link.text,
62
+ region: region,
63
+ }
58
64
  end
59
65
 
60
66
  def self.install(game)
61
- response = RestClient::Request.execute(
62
- method: :get,
63
- url: "https://coolrom.com.au/downloader.php?id=#{game.id}",
64
- headers: {
65
- '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",
66
- },
67
- raw_response: true,
68
- )
69
- if response.code == 200
70
- filename = response.headers[:content_disposition].split('; ')[1].split('"')[1]
71
- FileUtils.mkdir_p(game.filepath)
72
- FileUtils.cp(response.file.path, "#{game.filepath}/#{filename}")
73
- yield filename
67
+ agent = Mechanize.new
68
+ agent.pluggable_parser.default = Mechanize::Download
69
+ 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'
70
+
71
+ response = nil
72
+ agent.progressbar do
73
+ response = agent.get("https://coolrom.com.au/downloader.php?id=#{game.id}")
74
74
  end
75
+
76
+ return unless response.code.to_i == 200
77
+
78
+ filename = response.filename
79
+ FileUtils.mkdir_p(game.filepath)
80
+ response.save!("#{game.filepath}/#{filename}")
81
+ yield [filename]
75
82
  end
76
83
  end
77
84
  end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi'
4
+ require 'mechanize'
5
+ require 'mechanize/progressbar'
6
+ require 'mechanizeprogress'
7
+
8
+ module Gamerom
9
+ module RepoAdapters
10
+ # Romnation - An adapter for the ROMNation repository website
11
+ class Romnation
12
+ extend Gamerom::RepoAdapter
13
+
14
+ PLATFORM = {
15
+ 'amstrad' => 'Amstrad',
16
+ 'atari2600' => 'Atari 2600',
17
+ 'atari5200' => 'Atari 5200',
18
+ 'atari7800' => 'Atari 7800',
19
+ 'atarijaguar' => 'Atari Jaguar',
20
+ 'atarilynx' => 'Atari Lynx',
21
+ 'colecovision' => 'ColecoVision',
22
+ 'commodore64' => 'Commodore 64',
23
+ 'gamegear' => 'Game Gear',
24
+ 'gb' => 'Game Boy',
25
+ 'gbc' => 'Game Boy Color',
26
+ 'gcdvectrex' => 'Vectrex',
27
+ 'genesis' => 'Genesis',
28
+ 'intellivision' => 'Intellivision',
29
+ 'mame' => 'MAME',
30
+ 'msx1' => 'MSX',
31
+ 'msx2' => 'MSX2',
32
+ 'mtx' => 'MTX',
33
+ 'n64' => 'N64',
34
+ 'neogeocd' => 'Neo Geo CD',
35
+ 'neogeopocket' => 'Neo Geo Pocket',
36
+ 'nes' => 'NES',
37
+ 'oric' => 'Oric',
38
+ 'pce' => 'PC Engine',
39
+ 'radioshackcolorcomputer' => 'TRS-80',
40
+ 'samcoupe' => 'SAM Coupé',
41
+ 'segacd' => 'Sega CD',
42
+ 'segamastersystem' => 'Master System',
43
+ 'snes' => 'SNES',
44
+ 'thompsonmo5' => 'Thomson MO5',
45
+ 'virtualboy' => 'Virtual Boy',
46
+ 'watara' => 'Watara Supervision',
47
+ 'wonderswan' => 'WonderSwan',
48
+ }.freeze
49
+
50
+ def self.platforms
51
+ PLATFORM
52
+ end
53
+
54
+ def self.sections
55
+ ('a'..'z').to_a.unshift('0')
56
+ end
57
+
58
+ def self.extract_games(platform)
59
+ sections.each_with_index do |section, index|
60
+ pages = extract_pages(platform, section)
61
+ yield extract_games_from_section_pages(platform, section, pages), index
62
+ end
63
+ end
64
+
65
+ def self.extract_pages(platform, section)
66
+ page = nokogiri_get("https://www.romnation.net/srv/roms/#{platform}/#{section}/sort-title.html")
67
+ pages = ['1']
68
+ pages = page.css('.pagination').first.css('a').map(&:text).map(&:strip).reject(&:empty?) unless page.css('.pagination').empty?
69
+ pages
70
+ end
71
+
72
+ def self.extract_games_from_section_pages(platform, section, pages)
73
+ pages.reduce([]) do |section_games, p|
74
+ page = nokogiri_get("https://www.romnation.net/srv/roms/#{platform}/#{section}/page-#{p}_sort-title.html")
75
+ games_links = page.css('table.listings td.title a')
76
+ section_games.append(*games_links.map { |game_link| game(game_link) })
77
+ end
78
+ end
79
+
80
+ def self.game(game_link)
81
+ game_info = GameInfo.new(game_link.text)
82
+ {
83
+ id: game_link['href'].split('/')[3].to_i,
84
+ name: game_link.text,
85
+ region: game_info.region,
86
+ tags: game_info.tags,
87
+ }
88
+ end
89
+
90
+ def self.install(game)
91
+ agent = Mechanize.new
92
+ agent.pluggable_parser.default = Mechanize::Download
93
+ 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'
94
+ page = agent.get("https://www.romnation.net/download/rom/#{game.id}")
95
+
96
+ response = nil
97
+ agent.progressbar do
98
+ response = page.link_with(text: 'Download This Rom').click
99
+ end
100
+
101
+ return unless response.code.to_i == 200
102
+
103
+ filename = CGI.unescape(response.filename.split('_host=').first)
104
+ FileUtils.mkdir_p(game.filepath)
105
+ response.save!("#{game.filepath}/#{filename}")
106
+ yield [filename]
107
+ end
108
+ end
109
+ end
110
+ end