modsvaskr 0.1.8 → 0.1.12

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: 3a5763808fa45e04cd82bcaf607b12729ca43b5c2a40b8e3476abb3f012154e2
4
- data.tar.gz: 428994ca324ac277fcfcb3e76f2c0a88ee6f8a53d0e862fbdbf7b6abca5323ed
3
+ metadata.gz: 2c41c160023d7591e52e7b93fde57bc33462627d61714384aa3051c8f8052f99
4
+ data.tar.gz: 4e30b5822a983d6e57772ba65e19d7b8b657163c5b3c4d275bb55a61376c9e2c
5
5
  SHA512:
6
- metadata.gz: 629deb5ebde244b3d4eee1d0195aa6c8e84a96282a6968fc99e95a878e911d98bb3d50dd9538ede549163225e54bec1a92e3ac6c5befdca6dc15d3b4bc7c925b
7
- data.tar.gz: 62f17178509041879839df331bcb6db369484159d6b13fe4a2c542ea70878a8571294dd006f68ae92ed0150f65b53daad8c10fbcd1563e2ea07796fedf5c7dff
6
+ metadata.gz: 61ced31f7a9f75bb9679e47045cdfd38d6071f1eb91587cab8a3aa83c8c6d2de559402e3995b65f020a1acf6ce31d2ca96e31341960906c87f0717991a669f1e
7
+ data.tar.gz: c7e3610bc8f62f290fa586e66489f5f4e69b40942814a0ba56237716c925cb5ee7878152f58f9dfc94746242d84dd48bd67f96a37e903db3169cafd19ee2f533
data/bin/modsvaskr CHANGED
@@ -1,11 +1,11 @@
1
- #!/usr/bin/env ruby
2
- # require 'mod_organizer'
3
- require 'modsvaskr/config'
4
- require 'modsvaskr/ui'
5
-
6
- begin
7
- Modsvaskr::Ui.new(config: Modsvaskr::Config.new('./modsvaskr.yaml')).run
8
- rescue
9
- puts 'Press Enter to exit.'
10
- $stdin.gets
11
- end
1
+ #!/usr/bin/env ruby
2
+ # require 'mod_organizer'
3
+ require 'modsvaskr/config'
4
+ require 'modsvaskr/ui'
5
+
6
+ begin
7
+ Modsvaskr::Ui.new(config: Modsvaskr::Config.new('./modsvaskr.yaml')).run
8
+ rescue
9
+ puts 'Press Enter to exit.'
10
+ $stdin.gets
11
+ end
@@ -1,79 +1,78 @@
1
- require 'yaml'
2
- require 'modsvaskr/game'
3
- require 'modsvaskr/xedit'
4
-
5
- module Modsvaskr
6
-
7
- # Configuration
8
- class Config
9
-
10
- # Constructor
11
- #
12
- # Parameters::
13
- # * *file* (String): File containing configuration
14
- def initialize(file)
15
- @config = YAML.load(File.read(file)) || {}
16
- # Parse all game types plugins
17
- # Hash<Symbol, Class>
18
- @game_types = Hash[
19
- Dir.glob("#{__dir__}/games/*.rb").map do |game_type_file|
20
- require game_type_file
21
- base_name = File.basename(game_type_file, '.rb')
22
- [
23
- base_name.to_sym,
24
- Games.const_get(base_name.split('_').collect(&:capitalize).join.to_sym)
25
- ]
26
- end
27
- ]
28
- end
29
-
30
- # Get the games list
31
- #
32
- # Result::
33
- # * Array<Game>: List of games
34
- def games
35
- unless defined?(@games)
36
- @games = (@config['games'] || []).map do |game_info|
37
- game_type = game_info['type'].to_sym
38
- raise "Unknown game type: #{game_type}. Available ones are #{@game_types.keys.join(', ')}" unless @game_types.key?(game_type)
39
- @game_types[game_type].new(self, game_info)
40
- end
41
- end
42
- @games
43
- end
44
-
45
- # Return the xEdit path
46
- #
47
- # Result::
48
- # * String: The xEdit path
49
- def xedit_path
50
- @config['xedit']
51
- end
52
-
53
- # Return the 7-Zip path
54
- #
55
- # Result::
56
- # * String: The 7-Zip path
57
- def seven_zip_path
58
- @config['7zip']
59
- end
60
-
61
- # Return the automated keys to apply
62
- #
63
- # Result::
64
- # * Array<String>: The list of automated keys
65
- def auto_keys
66
- @config['auto_keys'] || []
67
- end
68
-
69
- # Return the no_prompt flag
70
- #
71
- # Result::
72
- # * Boolean: no_prompt flag
73
- def no_prompt
74
- @config['no_prompt'] || false
75
- end
76
-
77
- end
78
-
79
- end
1
+ require 'yaml'
2
+ require 'modsvaskr/game'
3
+ require 'modsvaskr/xedit'
4
+
5
+ module Modsvaskr
6
+
7
+ # Configuration
8
+ class Config
9
+
10
+ # Constructor
11
+ #
12
+ # Parameters::
13
+ # * *file* (String): File containing configuration
14
+ def initialize(file)
15
+ @config = YAML.safe_load(File.read(file)) || {}
16
+ # Parse all game types plugins
17
+ # Hash<Symbol, Class>
18
+ @game_types = Dir.glob("#{__dir__}/games/*.rb").map do |game_type_file|
19
+ require game_type_file
20
+ base_name = File.basename(game_type_file, '.rb')
21
+ [
22
+ base_name.to_sym,
23
+ Games.const_get(base_name.split('_').collect(&:capitalize).join.to_sym)
24
+ ]
25
+ end.to_h
26
+ end
27
+
28
+ # Get the games list
29
+ #
30
+ # Result::
31
+ # * Array<Game>: List of games
32
+ def games
33
+ unless defined?(@games)
34
+ @games = (@config['games'] || []).map do |game_info|
35
+ game_type = game_info['type'].to_sym
36
+ raise "Unknown game type: #{game_type}. Available ones are #{@game_types.keys.join(', ')}" unless @game_types.key?(game_type)
37
+
38
+ @game_types[game_type].new(self, game_info)
39
+ end
40
+ end
41
+ @games
42
+ end
43
+
44
+ # Return the xEdit path
45
+ #
46
+ # Result::
47
+ # * String: The xEdit path
48
+ def xedit_path
49
+ @config['xedit']
50
+ end
51
+
52
+ # Return the 7-Zip path
53
+ #
54
+ # Result::
55
+ # * String: The 7-Zip path
56
+ def seven_zip_path
57
+ @config['7zip']
58
+ end
59
+
60
+ # Return the automated keys to apply
61
+ #
62
+ # Result::
63
+ # * Array<String>: The list of automated keys
64
+ def auto_keys
65
+ @config['auto_keys'] || []
66
+ end
67
+
68
+ # Return the no_prompt flag
69
+ #
70
+ # Result::
71
+ # * Boolean: no_prompt flag
72
+ def no_prompt
73
+ @config['no_prompt'] || false
74
+ end
75
+
76
+ end
77
+
78
+ end
@@ -0,0 +1,31 @@
1
+ module Modsvaskr
2
+
3
+ # Provide helpers to encode Windows and Linux strings to UTF-8
4
+ module Encoding
5
+
6
+ # Convert a string to UTF-8
7
+ #
8
+ # Parameters::
9
+ # * *str* (String): The string to convert
10
+ # Result::
11
+ # * String: The converted string
12
+ def self.to_utf_8(str)
13
+ orig_encoding = str.encoding
14
+ encoding = nil
15
+ begin
16
+ encoding = %w[
17
+ UTF-8
18
+ Windows-1252
19
+ ISO-8859-1
20
+ ].find { |search_encoding| str.force_encoding(search_encoding).valid_encoding? }
21
+ ensure
22
+ str.force_encoding(orig_encoding)
23
+ end
24
+ raise "Unknown encoding for string #{str[0..127].inspect}" if encoding.nil?
25
+
26
+ str.encode('UTF-8', encoding)
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -1,179 +1,208 @@
1
- require 'yaml'
2
- require 'modsvaskr/logger'
3
- require 'modsvaskr/run_cmd'
4
-
5
- module Modsvaskr
6
-
7
- # Common functionality for any Game
8
- class Game
9
-
10
- include Logger, RunCmd
11
-
12
- # Constructor
13
- #
14
- # Parameters::
15
- # * *config* (Config): The config
16
- # * *game_info* (Hash<String,Object>): Game info:
17
- # * *name* (String): Game name
18
- # * *path* (String): Game installation dir
19
- # * *launch_exe* (String): Executable to be launched
20
- # * *min_launch_time_secs* (Integer): Minimum expected lauch time for the game, in seconds [default: 10]
21
- # * *tests_poll_secs* (Integer): Interval in seconds to be respected between 2 test statuses polling [default: 5]
22
- # * *timeout_frozen_tests_secs* (Integer): Timeout in seconds of a frozen game [default: 300]
23
- # * *timeout_interrupt_tests_secs* (Integer): Timeout in seconds for the player to interrupt a tests session before restarting the game [default: 10]
24
- def initialize(config, game_info)
25
- @config = config
26
- # Set default values here
27
- @game_info = {
28
- 'min_launch_time_secs' => 10,
29
- 'tests_poll_secs' => 5,
30
- 'timeout_frozen_tests_secs' => 300,
31
- 'timeout_interrupt_tests_secs' => 10
32
- }.merge(game_info)
33
- @name = name
34
- @pid = nil
35
- init if self.respond_to?(:init)
36
- end
37
-
38
- # Return the game name
39
- #
40
- # Result::
41
- # * String: Game name
42
- def name
43
- @game_info['name']
44
- end
45
-
46
- # Return the game path
47
- #
48
- # Result::
49
- # * String: Game path
50
- def path
51
- @game_info['path']
52
- end
53
-
54
- # Return the launch executable
55
- #
56
- # Result::
57
- # * String: Launch executable
58
- def launch_exe
59
- @game_info['launch_exe']
60
- end
61
-
62
- # Return the tests polling interval
63
- #
64
- # Result::
65
- # * Integer: Tests polling interval
66
- def tests_poll_secs
67
- @game_info['tests_poll_secs']
68
- end
69
-
70
- # Return the timeout to detect a frozen game
71
- #
72
- # Result::
73
- # * Integer: Timeout to detect a frozen game
74
- def timeout_frozen_tests_secs
75
- @game_info['timeout_frozen_tests_secs']
76
- end
77
-
78
- # Return the timeout before restarting a game tests session
79
- #
80
- # Result::
81
- # * Integer: Timeout before restarting a game tests session
82
- def timeout_interrupt_tests_secs
83
- @game_info['timeout_interrupt_tests_secs']
84
- end
85
-
86
- # Return an xEdit instance for this game
87
- #
88
- # Result::
89
- # * Xedit: The xEdit instance
90
- def xedit
91
- @xedit = Xedit.new(@config.xedit_path, path) unless defined?(@xedit)
92
- @xedit
93
- end
94
-
95
- # Launch the game, and wait for launch to be successful
96
- #
97
- # Parameters::
98
- # * *autoload* (Boolean or String): If false, then launch the game using the normal launcher. If String, then use AutoLoad to load a given saved file (or empty to continue latest save) [default: false].
99
- def launch(autoload: false)
100
- # Launch the game
101
- @idx_launch = 0 unless defined?(@idx_launch)
102
- if autoload
103
- log "[ Game #{name} ] - Launch game (##{@idx_launch}) using AutoLoad #{autoload}..."
104
- autoload_file = "#{path}/Data/AutoLoad.cmd"
105
- if File.exist?(autoload_file)
106
- run_cmd({
107
- dir: path,
108
- exe: 'Data\AutoLoad.cmd',
109
- args: [autoload]
110
- })
111
- else
112
- log "[ Game #{name} ] - Missing file #{autoload_file}. Can't use AutoLoad to load game automatically. Please install the AutoLoad mod."
113
- end
114
- else
115
- log "[ Game #{name} ] - Launch game (##{@idx_launch}) using configured launcher (#{launch_exe})..."
116
- run_cmd({
117
- dir: path,
118
- exe: launch_exe
119
- })
120
- end
121
- @idx_launch += 1
122
- # The game launches asynchronously, so just wait a little bit and check for the process existence
123
- sleep @game_info['min_launch_time_secs']
124
- tasklist_stdout = nil
125
- loop do
126
- tasklist_stdout = `tasklist | find "#{running_exe}"`.strip
127
- break unless tasklist_stdout.empty?
128
- log "[ Game #{name} ] - #{running_exe} is not running. Wait for its startup..."
129
- sleep 1
130
- end
131
- @pid = Integer(tasklist_stdout.split(' ')[1])
132
- log "[ Game #{name} ] - #{running_exe} has started with PID #{@pid}"
133
- end
134
-
135
- # Is the game currently running?
136
- #
137
- # Result::
138
- # * Boolean: Is the game currently running?
139
- def running?
140
- if @pid
141
- running = true
142
- begin
143
- # Process.kill does not work when the game has crashed (the process is still detected as zombie)
144
- # running = Process.kill(0, @pid) == 1
145
- tasklist_stdout = `tasklist | find "#{running_exe}"`.strip
146
- running = !tasklist_stdout.empty?
147
- # log "[ Game #{name} ] - Tasklist returned no #{running_exe}:\n#{tasklist_stdout}" unless running
148
- rescue Errno::ESRCH
149
- log "[ Game #{name} ] - Got error while waiting for #{running_exe} PID #{@pid}: #{$!}"
150
- running = false
151
- end
152
- @pid = nil unless running
153
- running
154
- else
155
- false
156
- end
157
- end
158
-
159
- # Kill the game, and wait till it is killed
160
- def kill
161
- if @pid
162
- first_time = true
163
- while @pid do
164
- system "taskkill #{first_time ? '' : '/F '}/pid #{@pid}"
165
- first_time = false
166
- sleep 1
167
- if running?
168
- log "[ Game #{name} ] - #{running_exe} is still running (PID #{@pid}). Wait for its kill..."
169
- sleep 5
170
- end
171
- end
172
- else
173
- log "[ Game #{name} ] - Game not started, so nothing to kill."
174
- end
175
- end
176
-
177
- end
178
-
179
- end
1
+ require 'yaml'
2
+ require 'modsvaskr/logger'
3
+ require 'modsvaskr/run_cmd'
4
+
5
+ module Modsvaskr
6
+
7
+ # Common functionality for any Game
8
+ class Game
9
+
10
+ include RunCmd
11
+ include Logger
12
+
13
+ # Constructor
14
+ #
15
+ # Parameters::
16
+ # * *config* (Config): The config
17
+ # * *game_info* (Hash<String,Object>): Game info:
18
+ # * *name* (String): Game name
19
+ # * *path* (String): Game installation dir
20
+ # * *launch_exe* (String): Executable to be launched
21
+ # * *min_launch_time_secs* (Integer): Minimum expected lauch time for the game, in seconds [default: 10]
22
+ # * *tests_poll_secs* (Integer): Interval in seconds to be respected between 2 test statuses polling [default: 5]
23
+ # * *timeout_frozen_tests_secs* (Integer): Timeout in seconds of a frozen game [default: 300]
24
+ # * *timeout_interrupt_tests_secs* (Integer): Timeout in seconds for the player to interrupt a tests session before restarting the game [default: 10]
25
+ def initialize(config, game_info)
26
+ @config = config
27
+ # Set default values here
28
+ @game_info = {
29
+ 'min_launch_time_secs' => 10,
30
+ 'tests_poll_secs' => 5,
31
+ 'timeout_frozen_tests_secs' => 300,
32
+ 'timeout_interrupt_tests_secs' => 10
33
+ }.merge(game_info)
34
+ @name = name
35
+ @pid = nil
36
+ init if respond_to?(:init)
37
+ end
38
+
39
+ # Return the game name
40
+ #
41
+ # Result::
42
+ # * String: Game name
43
+ def name
44
+ @game_info['name']
45
+ end
46
+
47
+ # Return the game path
48
+ #
49
+ # Result::
50
+ # * String: Game path
51
+ def path
52
+ @game_info['path']
53
+ end
54
+
55
+ # Return the launch executable
56
+ #
57
+ # Result::
58
+ # * String: Launch executable
59
+ def launch_exe
60
+ @game_info['launch_exe']
61
+ end
62
+
63
+ # Return the tests polling interval
64
+ #
65
+ # Result::
66
+ # * Integer: Tests polling interval
67
+ def tests_poll_secs
68
+ @game_info['tests_poll_secs']
69
+ end
70
+
71
+ # Return the timeout to detect a frozen game
72
+ #
73
+ # Result::
74
+ # * Integer: Timeout to detect a frozen game
75
+ def timeout_frozen_tests_secs
76
+ @game_info['timeout_frozen_tests_secs']
77
+ end
78
+
79
+ # Return the timeout before restarting a game tests session
80
+ #
81
+ # Result::
82
+ # * Integer: Timeout before restarting a game tests session
83
+ def timeout_interrupt_tests_secs
84
+ @game_info['timeout_interrupt_tests_secs']
85
+ end
86
+
87
+ # Return an xEdit instance for this game
88
+ #
89
+ # Result::
90
+ # * Xedit: The xEdit instance
91
+ def xedit
92
+ @xedit = Xedit.new(@config.xedit_path, path) unless defined?(@xedit)
93
+ @xedit
94
+ end
95
+
96
+ # Launch the game, and wait for launch to be successful
97
+ #
98
+ # Parameters::
99
+ # * *autoload* (Boolean or String): If false, then launch the game using the normal launcher. If String, then use AutoLoad to load a given saved file (or empty to continue latest save) [default: false].
100
+ def launch(autoload: false)
101
+ # Launch the game
102
+ @idx_launch = 0 unless defined?(@idx_launch)
103
+ if autoload
104
+ log "[ Game #{name} ] - Launch game (##{@idx_launch}) using AutoLoad #{autoload}..."
105
+ autoload_file = "#{path}/Data/AutoLoad.cmd"
106
+ if File.exist?(autoload_file)
107
+ run_cmd(
108
+ {
109
+ dir: path,
110
+ exe: 'Data\AutoLoad.cmd',
111
+ args: [autoload]
112
+ }
113
+ )
114
+ else
115
+ log "[ Game #{name} ] - Missing file #{autoload_file}. Can't use AutoLoad to load game automatically. Please install the AutoLoad mod."
116
+ end
117
+ else
118
+ log "[ Game #{name} ] - Launch game (##{@idx_launch}) using configured launcher (#{launch_exe})..."
119
+ run_cmd(
120
+ {
121
+ dir: path,
122
+ exe: launch_exe
123
+ }
124
+ )
125
+ end
126
+ @idx_launch += 1
127
+ # The game launches asynchronously, so just wait a little bit and check for the process existence
128
+ sleep @game_info['min_launch_time_secs']
129
+ tasklist_stdout = nil
130
+ loop do
131
+ tasklist_stdout = `tasklist | find "#{running_exe}"`.strip
132
+ break unless tasklist_stdout.empty?
133
+
134
+ log "[ Game #{name} ] - #{running_exe} is not running. Wait for its startup..."
135
+ sleep 1
136
+ end
137
+ @pid = Integer(tasklist_stdout.split[1])
138
+ log "[ Game #{name} ] - #{running_exe} has started with PID #{@pid}"
139
+ end
140
+
141
+ # Is the game currently running?
142
+ #
143
+ # Result::
144
+ # * Boolean: Is the game currently running?
145
+ def running?
146
+ if @pid
147
+ running = true
148
+ begin
149
+ # Process.kill does not work when the game has crashed (the process is still detected as zombie)
150
+ # running = Process.kill(0, @pid) == 1
151
+ tasklist_stdout = `tasklist | find "#{running_exe}"`.strip
152
+ running = !tasklist_stdout.empty?
153
+ # log "[ Game #{name} ] - Tasklist returned no #{running_exe}:\n#{tasklist_stdout}" unless running
154
+ rescue Errno::ESRCH
155
+ log "[ Game #{name} ] - Got error while waiting for #{running_exe} PID #{@pid}: #{$ERROR_INFO}"
156
+ running = false
157
+ end
158
+ @pid = nil unless running
159
+ running
160
+ else
161
+ false
162
+ end
163
+ end
164
+
165
+ # Kill the game, and wait till it is killed
166
+ def kill
167
+ if @pid
168
+ first_time = true
169
+ while @pid
170
+ system "taskkill #{first_time ? '' : '/F '}/pid #{@pid}"
171
+ first_time = false
172
+ sleep 1
173
+ if running?
174
+ log "[ Game #{name} ] - #{running_exe} is still running (PID #{@pid}). Wait for its kill..."
175
+ sleep 5
176
+ end
177
+ end
178
+ else
179
+ log "[ Game #{name} ] - Game not started, so nothing to kill."
180
+ end
181
+ end
182
+
183
+ # Get the load order.
184
+ # Keep a cache of it.
185
+ #
186
+ # Result::
187
+ # * Array<String>: List of all active plugins, including masters
188
+ def load_order
189
+ @cache_load_order = read_load_order unless defined?(@cache_load_order)
190
+ @cache_load_order
191
+ end
192
+
193
+ # Get the mod and base id corresponding to a given form id.
194
+ # Uses load order to determine it.
195
+ #
196
+ # Parameters::
197
+ # * *form_id* (String or Integer): Form ID, either as hexadecimal string, or numercial value
198
+ # Result::
199
+ # * String: Plugin name
200
+ # * Integer: Base form id, independent from the load order
201
+ def decode_form_id(form_id)
202
+ form_id = form_id.to_i(16) if form_id.is_a?(String)
203
+ [load_order[form_id / 16_777_216], form_id % 16_777_216]
204
+ end
205
+
206
+ end
207
+
208
+ end