rvpacker-txt 1.4.0 → 1.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90b4214a5f31ca357f08bb57ab08a8fdfeeed37aff4674d73dc2c866bdb3f30e
4
- data.tar.gz: d35c5da8a2755b486ef5b98f9c9091f682b8d3143f3dde0f2adafa4348fc71e7
3
+ metadata.gz: e3cdabd3e6867b3a5736a998345b2df35273565179c720c03acfb294bb121644
4
+ data.tar.gz: dd8ebfa554210a573d6aceef337ae4009759828a6fa53f5d19ad412c936165d0
5
5
  SHA512:
6
- metadata.gz: db1067185a2c07d44c96393d9d4b1c63bd27d424d946558e23a9bd4c6f8f6a9f4961cbddba5f8ce32ac5d1074d15aa2cc6cf64a45d3a10400ac753cc8380b26d
7
- data.tar.gz: 8a696a3a4cb22ac06425207218f8aedebbe1bc730fc36a474d589a6dd0d1b9c8320d270beb67235f7d75666caf35edc5fd7a83f5245ffe798aa76f50b29fa099
6
+ metadata.gz: 6146633f8ac594f5db5cdc7b9fd1914c3e011e242531f943c2dbcad48c00ee8bda79f06511db125e588be4cc6c6869e3c9bfccc24a7c1820418d4cd7688f5c6d
7
+ data.tar.gz: db7a233da1abe10a24c0c5180a06f022f46cf07685e33e3a7081ac8dc5082210c14042f7605482c7d5ad99a4efabad870a01c55e0efac3f3ec8249aec998d888
data/README.md CHANGED
@@ -29,11 +29,16 @@ COMMANDS:
29
29
  read - Parses RPG Maker game files to .txt
30
30
  write - Writes parsed files back to their initial form
31
31
  OPTIONS:
32
- -d, --input-dir DIR Input directory of RPG Maker project
32
+ -i, --input-dir DIR Input directory of RPG Maker project
33
+ -o, --output-dir DIR Output directory of parsed/written files
33
34
  --disable-processing FILES Don't process specified files (maps, other, system, plugins)
34
35
  -s, --shuffle NUM Shuffle level (1: lines, 2: lines and words)
35
- --disable-custom-parsing Disables built-in custom parsing for some games
36
+ --disable_custom_processing Disables built-in custom parsing/writing for some games
36
37
  -l, --log Log information while processing
38
+ -f, --force Force rewrite all files. Cannot be used with --append.
39
+ USE WITH CAUTION!
40
+ -a, --append When you update the rvpacker-txt, you probably should re-read your files with append, as some new text might be added to parser.
41
+ Cannot be used with --force
37
42
  -h, --help Show help message
38
43
  ```
39
44
 
data/bin/rvpacker-txt CHANGED
@@ -34,14 +34,23 @@ def self.parse_options
34
34
  options[:shuffle_level] = num
35
35
  end
36
36
 
37
- cmd.on('--disable-custom-parsing', 'Disables built-in custom parsing for some games') do
38
- options[:disable_custom_parsing] = true
37
+ cmd.on('--disable_custom_processing', 'Disables built-in custom parsing/writing for some games') do
38
+ options[:disable_custom_processing] = true
39
39
  end
40
40
 
41
41
  cmd.on('-l', '--log', 'Log information while processing') do
42
42
  options[:logging] = true
43
43
  end
44
44
 
45
+ cmd.on('-f', '--force', 'Force rewrite all files. Cannot be used with --append.', 'USE WITH CAUTION!') do
46
+ options[:force] = true
47
+ end
48
+
49
+ cmd.on('-a', '--append', 'When you update the rvpacker-txt, you probably should re-read your files with append, as some new text might be added to parser.', 'Cannot be used with --force') do
50
+ raise '--append cannot be used beside --force.' if options[:force]
51
+ options[:append] = true
52
+ end
53
+
45
54
  cmd.on('-h', '--help', 'Show help message') do
46
55
  puts cmd
47
56
  exit
@@ -55,9 +64,7 @@ def self.parse_options
55
64
  options
56
65
  end
57
66
 
58
- def self.get_game_type(system_file_path, disable_custom_parsing)
59
- return nil if disable_custom_parsing
60
-
67
+ def self.get_game_type(system_file_path)
61
68
  object = Marshal.load(File.binread(system_file_path))
62
69
  game_title = object.instance_variable_get(:@game_title).to_s.downcase
63
70
  game_title.include?('lisa') ? 'lisa' : nil
@@ -68,10 +75,12 @@ start_time = Time.now
68
75
  options = parse_options
69
76
  input_dir = options[:input_dir]
70
77
  output_dir = options[:output_dir]
71
- disable_custom_parsing = options[:disable_custom_parsing]
78
+ disable_custom_processing = options[:disable_custom_processing]
72
79
  shuffle_level = options[:shuffle_level]
73
80
  logging = options[:logging]
74
81
  disable_processing = options[:disable_processing]
82
+ force = options[:force]
83
+ append = options[:append]
75
84
 
76
85
  extensions = { xp: '.rxdata', vx: 'rvdata', ace: 'rvdata2' }
77
86
 
@@ -94,37 +103,57 @@ end || (raise "Couldn't determine project engine.")
94
103
 
95
104
  files = Dir.glob("#{paths[:original_path]}/*#{extensions[engine]}")
96
105
 
97
- maps_files = []
98
- other_files = []
99
- system_file = nil
100
- scripts_file = nil
106
+ maps_files_paths = []
107
+ other_files_paths = []
108
+ system_file_path = nil
109
+ scripts_file_path = nil
101
110
 
102
111
  files.each do |file|
103
112
  basename = File.basename(file)
104
113
 
105
114
  if basename.start_with?(/Map[0-9]/)
106
- maps_files.push(file)
115
+ maps_files_paths.push(file)
107
116
  elsif !basename.start_with?(/Map|Tilesets|Animations|System|Scripts|Areas/)
108
- other_files.push(file)
117
+ other_files_paths.push(file)
109
118
  elsif basename.start_with?('System')
110
- system_file = file
119
+ system_file_path = file
111
120
  elsif basename.start_with?('Scripts')
112
- scripts_file = file
121
+ scripts_file_path = file
113
122
  end
114
123
  end
115
124
 
116
- game_type = get_game_type(system_file, disable_custom_parsing)
125
+ ini_file_path = File.join(input_dir, "Game.ini")
126
+
127
+ game_type = disable_custom_processing ? nil : get_game_type(system_file_path)
128
+
129
+ wait_time = 0
130
+ processing_type = if force
131
+ wait_time_start = Time.now
132
+
133
+ puts "WARNING! You\'re about to forcefully rewrite all your translation files, including _trans files.\nIf you really want to do it, make sure you've made a backup of your _trans files, if you made some changes in them already.\nInput 'Y' to continue."
134
+ exit unless gets.chomp == 'Y'
135
+
136
+ wait_time = Time.now - wait_time_start
137
+ 'force'
138
+ else
139
+ append ? 'append' : 'default'
140
+ end
141
+
142
+ puts 'Custom processing for this game is enabled. Use --disable-custom-processing to disable it.' unless game_type.nil?
117
143
 
118
144
  if options[:action] == 'read'
119
- read_map(maps_files, paths[:maps_path], logging, game_type) unless disable_processing[0]
120
- read_other(other_files, paths[:other_path], logging, game_type) unless disable_processing[1]
121
- read_system(system_file, paths[:other_path], logging) unless disable_processing[2]
122
- read_scripts(scripts_file, paths[:other_path], logging) unless disable_processing[3]
145
+ read_map(maps_files_paths, paths[:maps_path], logging, game_type, processing_type) unless disable_processing[0]
146
+ read_other(other_files_paths, paths[:other_path], logging, game_type, processing_type) unless disable_processing[1]
147
+ read_system(system_file_path, ini_file_path, paths[:other_path], logging, processing_type) unless disable_processing[2]
148
+ read_scripts(scripts_file_path, paths[:other_path], logging, processing_type) unless disable_processing[3]
123
149
  else
124
- write_map(maps_files, paths[:maps_path], paths[:output_path], shuffle_level, logging, game_type) unless disable_processing[0]
125
- write_other(other_files, paths[:other_path], paths[:output_path], shuffle_level, logging, game_type) unless disable_processing[1]
126
- write_system(system_file, paths[:other_path], paths[:output_path], shuffle_level, logging) unless disable_processing[2]
127
- write_scripts(scripts_file, paths[:other_path], paths[:output_path], logging) unless disable_processing[3]
150
+ write_map(maps_files_paths, paths[:maps_path], paths[:output_path], shuffle_level, logging, game_type, processing_type) unless disable_processing[0]
151
+ write_other(other_files_paths, paths[:other_path], paths[:output_path], shuffle_level, logging, game_type, processing_type) unless disable_processing[1]
152
+ write_system(system_file_path, ini_file_path, paths[:other_path], paths[:output_path], shuffle_level, logging, processing_type) unless disable_processing[2]
153
+ write_scripts(scripts_file_path, paths[:other_path], paths[:output_path], logging, processing_type) unless disable_processing[3]
128
154
  end
129
155
 
130
- puts "Done in #{Time.now - start_time}"
156
+ $wait_time = 0 if $wait_time.nil?
157
+ end_time = Time.now - start_time - wait_time - $wait_time
158
+
159
+ puts "Done in #{end_time}"
data/lib/classes.rb CHANGED
@@ -77,7 +77,7 @@ class Rect
77
77
  end
78
78
 
79
79
  # Fuck using an array with set, that's just straight dumb and not efficient
80
- class IndexedSet
80
+ class IndexSet
81
81
  def initialize
82
82
  @hash = Hash.new
83
83
  end
data/lib/read.rb CHANGED
@@ -2,26 +2,41 @@
2
2
 
3
3
  require 'zlib'
4
4
 
5
+ def self.insert_at_index(hash, index, key, value)
6
+ return hash[key] = value if index >= hash.size
7
+
8
+ temp_hash = hash.to_a
9
+ temp_hash.insert(index, [key, value])
10
+ hash.clear
11
+ hash.merge!(temp_hash.to_h)
12
+ end
13
+
5
14
  def self.extract_quoted_strings(string)
6
- result = []
15
+ # Hash of string-index key-value pairs
16
+ result = {}
7
17
 
8
18
  skip_block = false
9
19
  in_quotes = false
10
20
  quote_type = nil
11
21
  buffer = []
12
22
 
13
- string.each_line(chomp: true) do |line|
14
- line.strip!
15
- next if line[0] == '#' || line.start_with?(/(Win|Lose)|_Fanfare/)
23
+ # I hope this calculates index correctly
24
+ current_string_index = 0
25
+ string.each_line do |line|
26
+ stripped = line.strip
27
+
28
+ if stripped[0] == '#' || stripped.start_with?(/(Win|Lose)|_Fanfare/)
29
+ next
30
+ end
16
31
 
17
- skip_block = true if line.start_with?('=begin')
18
- skip_block = false if line.start_with?('=end')
32
+ skip_block = true if stripped.start_with?('=begin')
33
+ skip_block = false if stripped.start_with?('=end')
19
34
 
20
35
  next if skip_block
21
36
 
22
37
  buffer.push('\#') if in_quotes
23
38
 
24
- line.each_char do |char|
39
+ line.each_char.each_with_index do |char, index|
25
40
  if %w[' "].include?(char)
26
41
  unless quote_type.nil? || char == quote_type
27
42
  buffer.push(char)
@@ -30,7 +45,7 @@ def self.extract_quoted_strings(string)
30
45
 
31
46
  quote_type = char
32
47
  in_quotes = !in_quotes
33
- result.push(buffer.join)
48
+ result[buffer.join] = current_string_index + index
34
49
  buffer.clear
35
50
  next
36
51
  end
@@ -39,6 +54,8 @@ def self.extract_quoted_strings(string)
39
54
  buffer.push(char)
40
55
  end
41
56
  end
57
+
58
+ current_string_index += line.length
42
59
  end
43
60
 
44
61
  result
@@ -54,7 +71,9 @@ def self.parse_parameter(code, parameter, game_type)
54
71
  else
55
72
  nil
56
73
  end
57
- when 102, 356
74
+ when 102
75
+ # Implement some custom parsing
76
+ when 356
58
77
  # Implement some custom parsing
59
78
  else
60
79
  return nil
@@ -80,16 +99,47 @@ def self.parse_variable(variable, game_type)
80
99
  variable
81
100
  end
82
101
 
83
- def self.read_map(original_map_files, output_path, logging, game_type)
84
- maps_object_map = Hash[original_map_files.map do |filename|
102
+ def self.read_map(maps_files, output_path, logging, game_type, processing_type)
103
+ maps_output_path = File.join(output_path, 'maps.txt')
104
+ names_output_path = File.join(output_path, 'names.txt')
105
+ maps_trans_output_path = File.join(output_path, 'maps_trans.txt')
106
+ names_trans_output_path = File.join(output_path, 'names_trans.txt')
107
+
108
+ if processing_type == 'default' && (File.exist?(maps_trans_output_path) || File.exist?(names_trans_output_path))
109
+ puts 'maps_trans.txt or names_trans.txt file already exists. If you want to forcefully re-read all files, use --force flag, or --append if you want append new text to already existing files.'
110
+ return
111
+ end
112
+
113
+ maps_object_map = Hash[maps_files.map do |filename|
85
114
  [File.basename(filename), Marshal.load(File.binread(filename))]
86
115
  end]
87
116
 
88
- maps_lines = [IndexedSet.new, IndexedSet.new]
117
+ maps_lines = IndexSet.new
118
+ names_lines = IndexSet.new
119
+
120
+ maps_translation_map = nil
121
+ names_translation_map = nil
122
+
123
+ if processing_type == 'append'
124
+ if File.exist?(maps_trans_output_path)
125
+ maps_translation_map = Hash[File.readlines(maps_output_path, chomp: true).zip(File.readlines(maps_trans_output_path, chomp: true))]
126
+ names_translation_map = Hash[File.readlines(names_output_path, chomp: true).zip(File.readlines(names_trans_output_path, chomp: true))]
127
+ else
128
+ puts "Files aren't already parsed. Continuing as if --append flag was omitted."
129
+ processing_type = 'default'
130
+ end
131
+ end
89
132
 
90
133
  maps_object_map.each do |filename, object|
91
134
  display_name = object.instance_variable_get(:@display_name)
92
- maps_lines[1].add(display_name) if display_name.is_a?(String) && !display_name.empty?
135
+
136
+ if display_name.is_a?(String) && !display_name.empty?
137
+ if processing_type == 'append' && !names_translation_map.include?(display_name)
138
+ insert_at_index(names_translation_map, names_lines.length, display_name, '')
139
+ end
140
+
141
+ names_lines.add(display_name)
142
+ end
93
143
 
94
144
  events = object.instance_variable_get(:@events)
95
145
  next if events.nil?
@@ -113,12 +163,20 @@ def self.read_map(original_map_files, output_path, logging, game_type)
113
163
  if code == 401
114
164
  if parameter.is_a?(String) && !parameter.empty?
115
165
  in_sequence = true
166
+
116
167
  parsed = parse_parameter(code, parameter, game_type)
117
168
  line.push(parsed) unless parsed.nil?
118
169
  end
119
170
  else
120
171
  if in_sequence
121
- maps_lines[0].add(line.join('\#'))
172
+ joined = line.join('\#')
173
+
174
+ if processing_type == 'append' && !maps_translation_map.include?(joined)
175
+ insert_at_index(maps_translation_map, maps_lines.length, joined, '')
176
+ end
177
+
178
+ maps_lines.add(joined)
179
+
122
180
  line.clear
123
181
  in_sequence = false
124
182
  end
@@ -127,12 +185,28 @@ def self.read_map(original_map_files, output_path, logging, game_type)
127
185
  parameter.each do |subparameter|
128
186
  if subparameter.is_a?(String) && !subparameter.empty?
129
187
  parsed = parse_parameter(code, subparameter, game_type)
130
- maps_lines[0].add(parsed) unless parsed.nil?
188
+
189
+ unless parsed.nil?
190
+ if processing_type == 'append' && !maps_translation_map.include?(parsed)
191
+ insert_at_index(maps_translation_map, maps_lines.length, parsed, '')
192
+ end
193
+
194
+ maps_lines.add(parsed)
195
+ end
131
196
  end
132
197
  end
133
198
  elsif code == 356 && parameter.is_a?(String) && !parameter.empty?
134
199
  parsed = parse_parameter(code, parameter, game_type)
135
- maps_lines[0].add(parsed.gsub(/\r?\n/, '\#')) unless parsed.nil?
200
+
201
+ unless parsed.nil?
202
+ subbed = parsed.gsub(/\r?\n/, '\#')
203
+
204
+ if processing_type == 'append' && !maps_translation_map.include?(parsed)
205
+ insert_at_index(maps_translation_map, maps_lines.length, parsed, '')
206
+ end
207
+
208
+ maps_lines.add(subbed)
209
+ end
136
210
  end
137
211
  end
138
212
  end
@@ -143,14 +217,29 @@ def self.read_map(original_map_files, output_path, logging, game_type)
143
217
  puts "Parsed #{filename}" if logging
144
218
  end
145
219
 
146
- File.binwrite("#{output_path}/maps.txt", maps_lines[0].join("\n"))
147
- File.binwrite("#{output_path}/maps_trans.txt", "\n" * (maps_lines[0].empty? ? 0 : maps_lines[0].length - 1))
148
- File.binwrite("#{output_path}/names.txt", maps_lines[1].join("\n"))
149
- File.binwrite("#{output_path}/names_trans.txt", "\n" * (maps_lines[1].empty? ? 0 : maps_lines[1].length - 1))
220
+ maps_original_content,
221
+ maps_translated_content,
222
+ names_original_content,
223
+ names_translated_content = if processing_type == 'append'
224
+ [maps_translation_map.keys.join("\n"),
225
+ maps_translation_map.values.join("\n"),
226
+ names_translation_map.keys.join("\n"),
227
+ names_translation_map.values.join("\n")]
228
+ else
229
+ [maps_lines.join("\n"),
230
+ "\n" * (maps_lines.empty? ? 0 : maps_lines.length - 1),
231
+ names_lines.join("\n"),
232
+ "\n" * (names_lines.empty? ? 0 : names_lines.length - 1)]
233
+ end
234
+
235
+ File.binwrite(maps_output_path, maps_original_content)
236
+ File.binwrite(maps_trans_output_path, maps_translated_content)
237
+ File.binwrite(names_output_path, names_original_content)
238
+ File.binwrite(names_trans_output_path, names_translated_content)
150
239
  end
151
240
 
152
- def self.read_other(original_other_files, output_path, logging, game_type)
153
- other_object_array_map = Hash[original_other_files.map do |filename|
241
+ def self.read_other(other_files, output_path, logging, game_type, processing_type)
242
+ other_object_array_map = Hash[other_files.map do |filename|
154
243
  basename = File.basename(filename)
155
244
  object = Marshal.load(File.binread(filename))
156
245
  object = merge_other(object).slice(1..) if basename.start_with?(/Common|Troops/)
@@ -158,9 +247,31 @@ def self.read_other(original_other_files, output_path, logging, game_type)
158
247
  [basename, object]
159
248
  end]
160
249
 
250
+ internal_processing_type = processing_type
251
+
161
252
  other_object_array_map.each do |filename, other_object_array|
162
253
  processed_filename = File.basename(filename, '.*').downcase
163
- other_lines = IndexedSet.new
254
+
255
+ other_output_path = File.join(output_path, "#{processed_filename}.txt")
256
+ other_trans_output_path = File.join(output_path, "#{processed_filename}_trans.txt")
257
+
258
+ if processing_type == 'default' && File.exist?(other_trans_output_path)
259
+ puts "#{processed_filename}_trans.txt file already exists. If you want to forcefully re-read all files, use --force flag, or --append if you want append new text to already existing files."
260
+ next
261
+ end
262
+
263
+ other_lines = IndexSet.new
264
+ other_translation_map = nil
265
+
266
+ if processing_type == 'append'
267
+ if File.exist?(other_trans_output_path)
268
+ internal_processing_type == 'append'
269
+ other_translation_map = Hash[File.readlines(other_output_path, chomp: true).zip(File.readlines(other_trans_output_path, chomp: true))]
270
+ else
271
+ puts "Files aren't already parsed. Continuing as if --append flag was omitted."
272
+ internal_processing_type = 'default'
273
+ end
274
+ end
164
275
 
165
276
  if !filename.start_with?(/Common|Troops/)
166
277
  other_object_array.each do |object|
@@ -172,7 +283,14 @@ def self.read_other(original_other_files, output_path, logging, game_type)
172
283
  [name, nickname, description, note].each do |variable|
173
284
  if variable.is_a?(String) && !variable.empty?
174
285
  parsed = parse_variable(variable, game_type)
175
- other_lines.add(parsed) unless parsed.nil?
286
+
287
+ unless parsed.nil?
288
+ if internal_processing_type == 'append' && !other_translation_map.include?(parsed)
289
+ insert_at_index(other_translation_map, other_lines.length, parsed, '')
290
+ end
291
+
292
+ other_lines.add(parsed)
293
+ end
176
294
  end
177
295
  end
178
296
  end
@@ -198,7 +316,14 @@ def self.read_other(original_other_files, output_path, logging, game_type)
198
316
  line.push(parameter.gsub(/\r?\n/, '\#')) if parameter.is_a?(String) && !parameter.empty?
199
317
  else
200
318
  if in_sequence
201
- other_lines.add(line.join('\#'))
319
+ joined = line.join('\#')
320
+
321
+ if internal_processing_type == 'append' && !other_translation_map.include?(joined)
322
+ insert_at_index(other_translation_map, other_lines.length, joined, '')
323
+ end
324
+
325
+ other_lines.add(joined)
326
+
202
327
  line.clear
203
328
  in_sequence = false
204
329
  end
@@ -207,12 +332,25 @@ def self.read_other(original_other_files, output_path, logging, game_type)
207
332
  when 102
208
333
  if parameter.is_a?(Array)
209
334
  parameter.each do |subparameter|
210
- other_lines.add(subparameter) if subparameter.is_a?(String) && !subparameter.empty?
335
+ if subparameter.is_a?(String) && !subparameter.empty?
336
+ if internal_processing_type == 'append' && !other_translation_map.include?(subparameter)
337
+ insert_at_index(other_translation_map, other_lines.length, subparameter, '')
338
+ end
339
+
340
+ other_lines.add(subparameter)
341
+ end
211
342
  end
212
343
  end
213
344
  when 356
214
- other_lines.add(parameter.gsub(/\r?\n/, '\#')) if parameter.is_a?(String) &&
215
- !parameter.empty?
345
+ if parameter.is_a?(String) && !parameter.empty?
346
+ subbed = parameter.gsub(/\r?\n/, '\#')
347
+
348
+ if internal_processing_type == 'append'
349
+ insert_at_index(other_translation_map, other_lines.length, subbed, '')
350
+ end
351
+
352
+ other_lines.add(subbed)
353
+ end
216
354
  else
217
355
  nil
218
356
  end
@@ -225,17 +363,52 @@ def self.read_other(original_other_files, output_path, logging, game_type)
225
363
 
226
364
  puts "Parsed #{filename}" if logging
227
365
 
228
- File.binwrite("#{output_path}/#{processed_filename}.txt", other_lines.join("\n"))
229
- File.binwrite("#{output_path}/#{processed_filename}_trans.txt", "\n" * (other_lines.empty? ? 0 : other_lines.length - 1))
366
+ original_content, translated_content = if processing_type == 'append'
367
+ [other_translation_map.keys.join("\n"), other_translation_map.values.join("\n")]
368
+ else
369
+ [other_lines.join("\n"), "\n" * (other_lines.empty? ? 0 : other_lines.length - 1)]
370
+ end
371
+
372
+ File.binwrite(other_output_path, original_content)
373
+ File.binwrite(other_trans_output_path, translated_content)
230
374
  end
231
375
  end
232
376
 
233
- def self.read_system(system_file_path, output_path, logging)
377
+ def self.read_system(system_file_path, ini_file_path, output_path, logging, processing_type)
378
+ def self.read_ini_title(ini_file_path)
379
+ file_lines = File.readlines(ini_file_path, chomp: true)
380
+ file_lines.each do |line|
381
+ if line.start_with?('title')
382
+ parts = line.partition('=')
383
+ break parts[2].strip
384
+ end
385
+ end
386
+ end
387
+
234
388
  system_filename = File.basename(system_file_path)
235
389
  system_basename = File.basename(system_file_path, '.*').downcase
390
+
391
+ system_output_path = File.join(output_path, "#{system_basename}.txt")
392
+ system_trans_output_path = File.join(output_path, "#{system_basename}_trans.txt")
393
+
394
+ if processing_type == 'default' && File.exist?(system_trans_output_path)
395
+ puts "system_trans.txt file already exists. If you want to forcefully re-read all files, use --force flag, or --append if you want append new text to already existing files."
396
+ return
397
+ end
398
+
236
399
  system_object = Marshal.load(File.binread(system_file_path))
237
400
 
238
- system_lines = IndexedSet.new
401
+ system_lines = IndexSet.new
402
+ system_translation_map = nil
403
+
404
+ if processing_type == 'append'
405
+ if File.exist?(system_trans_output_path)
406
+ system_translation_map = Hash[File.readlines(system_output_path, chomp: true).zip(File.readlines(system_trans_output_path, chomp: true))]
407
+ else
408
+ puts "Files aren't already parsed. Continuing as if --append flag was omitted."
409
+ processing_type = 'default'
410
+ end
411
+ end
239
412
 
240
413
  elements = system_object.instance_variable_get(:@elements)
241
414
  skill_types = system_object.instance_variable_get(:@skill_types)
@@ -247,40 +420,133 @@ def self.read_system(system_file_path, output_path, logging)
247
420
 
248
421
  [elements, skill_types, weapon_types, armor_types].each do |array|
249
422
  next if array.nil?
250
- array.each { |string| system_lines.add(string) if string.is_a?(String) && !string.empty? }
423
+
424
+ array.each do |string|
425
+ if string.is_a?(String) && !string.empty?
426
+ if processing_type == 'append' && !system_translation_map.include?(string)
427
+ insert_at_index(system_translation_map, system_lines.length, string, '')
428
+ end
429
+
430
+ system_lines.add(string)
431
+ end
432
+ end
251
433
  end
252
434
 
253
- system_lines.add(currency_unit) if currency_unit.is_a?(String) && !currency_unit.empty?
435
+ if currency_unit.is_a?(String) && !currency_unit.empty?
436
+ if processing_type == 'append' && !system_translation_map.include?(currency_unit)
437
+ insert_at_index(system_translation_map, system_lines.length, currency_unit, '')
438
+ end
439
+
440
+ system_lines.add(currency_unit)
441
+ end
254
442
 
255
443
  terms.instance_variables.each do |variable|
256
444
  value = terms.instance_variable_get(variable)
257
445
 
258
446
  if value.is_a?(String)
259
- system_lines.add(value) unless value.empty?
447
+ unless value.empty?
448
+ if processing_type == 'append' && !system_translation_map.include?(value)
449
+ insert_at_index(system_translation_map, system_lines.length, value, '')
450
+ end
451
+
452
+ system_lines.add(value)
453
+ end
454
+
260
455
  next
261
456
  end
262
457
 
263
- value.each { |string| system_lines.add(string) if string.is_a?(String) && !string.empty? }
458
+ value.each do |string|
459
+ if string.is_a?(String) && !string.empty?
460
+ if processing_type == 'append' && !system_translation_map.include?(string)
461
+ insert_at_index(system_translation_map, system_lines.length, string, '')
462
+ end
463
+
464
+ system_lines.add(string)
465
+ end
466
+ end
264
467
  end
265
468
 
266
- system_lines.add(game_title) if game_title.is_a?(String) && !game_title.empty?
469
+ ini_game_title = read_ini_title(ini_file_path)
470
+
471
+ $wait_time = 0
472
+
473
+ if ini_game_title != game_title
474
+ if game_title.is_a?(String) && !game_title.empty?
475
+ wait_time_start = Time.now
476
+
477
+ puts "Game title from the Game.ini file and game title from the System file are different.\nWhich game title would you like to parse?\n(That doesn't affect anything major, just when you'll write the game back, translated game title will be applied both to the .ini and System file.)\n0, System title - #{game_title}\n1, Game.ini title - #{ini_game_title}"
478
+ choice = gets.chomp.to_i
479
+
480
+ $wait_time = Time.now - wait_time_start
481
+
482
+ if choice == 0
483
+ if processing_type == 'append' && !system_translation_map.include?(game_title)
484
+ insert_at_index(system_translation_map, system_lines.length, game_title, '')
485
+ end
486
+
487
+ system_lines.add(game_title)
488
+ else
489
+ if processing_type == 'append' && !system_translation_map.include?(ini_game_title)
490
+ insert_at_index(system_translation_map, system_lines.length, ini_game_title, '')
491
+ end
492
+
493
+ system_lines.add(ini_game_title)
494
+ end
495
+ else
496
+ if processing_type == 'append' && !system_translation_map.include?(ini_game_title)
497
+ insert_at_index(system_translation_map, system_lines.length, ini_game_title, '')
498
+ end
499
+
500
+ system_lines.add(ini_game_title)
501
+ end
502
+ end
267
503
 
268
504
  puts "Parsed #{system_filename}" if logging
269
505
 
270
- File.binwrite("#{output_path}/#{system_basename}.txt", system_lines.join("\n"))
271
- File.binwrite("#{output_path}/#{system_basename}_trans.txt", "\n" * (system_lines.empty? ? 0 : system_lines.length - 1))
506
+ original_content, translated_content = if processing_type == 'append'
507
+ [system_translation_map.keys.join("\n"), system_translation_map.values.join("\n")]
508
+ else
509
+ [system_lines.join("\n"), "\n" * (system_lines.empty? ? 0 : system_lines.length - 1)]
510
+ end
511
+
512
+ File.binwrite(system_output_path, original_content)
513
+ File.binwrite(system_trans_output_path, translated_content)
272
514
  end
273
515
 
274
- def self.read_scripts(scripts_file_path, output_path, logging)
516
+ def self.read_scripts(scripts_file_path, output_path, logging, processing_type)
517
+ scripts_filename = File.basename(scripts_file_path)
518
+ scripts_basename = File.basename(scripts_file_path, '.*').downcase
519
+
520
+ scripts_plain_output_path = File.join(output_path, "#{scripts_basename}_plain.txt")
521
+ scripts_output_path = File.join(output_path, "#{scripts_basename}.txt")
522
+ scripts_trans_output_path = File.join(output_path, "#{scripts_basename}_trans.txt")
523
+
524
+ if processing_type == 'default' && File.exist?(scripts_trans_output_path)
525
+ puts "scripts_trans.txt file already exists. If you want to forcefully re-read all files, use --force flag, or --append if you want append new text to already existing files."
526
+ return
527
+ end
528
+
275
529
  script_entries = Marshal.load(File.binread(scripts_file_path))
276
- scripts_lines = IndexedSet.new
530
+
531
+ scripts_lines = IndexSet.new
532
+ scripts_translation_map = nil
533
+
534
+ if processing_type == 'append'
535
+ if File.exist?(scripts_trans_output_path)
536
+ scripts_translation_map = Hash[File.readlines(scripts_output_path, chomp: true).zip(File.readlines(scripts_trans_output_path, chomp: true))]
537
+ else
538
+ puts "Files aren't already parsed. Continuing as if --append flag was omitted."
539
+ processing_type = 'default'
540
+ end
541
+ end
542
+
277
543
  codes_content = []
278
544
 
279
545
  script_entries.each do |script|
280
546
  code = Zlib::Inflate.inflate(script[2]).force_encoding('UTF-8')
281
547
  codes_content.push(code)
282
548
 
283
- extract_quoted_strings(code).each do |string|
549
+ extract_quoted_strings(code).keys.each do |string|
284
550
  string.strip!
285
551
 
286
552
  # Removes the U+3000 Japanese typographical space to check if string, when stripped, is truly empty
@@ -329,13 +595,24 @@ def self.read_scripts(scripts_file_path, output_path, logging)
329
595
  string.match?(/Clear image/) ||
330
596
  string.match?(/Can Collapse/)
331
597
 
598
+ if processing_type == 'append' && !scripts_translation_map.include?(string)
599
+ insert_at_index(scripts_translation_map, scripts_lines.length, string, '')
600
+ end
601
+
332
602
  scripts_lines.add(string)
333
603
  end
334
604
  end
335
605
 
336
- puts "Parsed #{File.basename(scripts_file_path)}" if logging
606
+ puts "Parsed #{scripts_filename}" if logging
337
607
 
338
- File.binwrite("#{output_path}/scripts_plain.txt", codes_content.join("\n"))
339
- File.binwrite("#{output_path}/scripts.txt", scripts_lines.join("\n"))
340
- File.binwrite("#{output_path}/scripts_trans.txt", "\n" * (scripts_lines.empty? ? 0 : scripts_lines.length - 1))
341
- end
608
+ File.binwrite(scripts_plain_output_path, codes_content.join("\n"))
609
+
610
+ original_content, translated_content = if processing_type == 'append'
611
+ [scripts_translation_map.keys.join("\n"), scripts_translation_map.values.join("\n")]
612
+ else
613
+ [scripts_lines.join("\n"), "\n" * (scripts_lines.empty? ? 0 : scripts_lines.length - 1)]
614
+ end
615
+
616
+ File.binwrite(scripts_output_path, original_content)
617
+ File.binwrite(scripts_trans_output_path, translated_content)
618
+ end
data/lib/write.rb CHANGED
@@ -2,17 +2,61 @@
2
2
 
3
3
  require 'zlib'
4
4
 
5
- def self.shuffle_words(array)
6
- array.map do |string|
7
- re = /\S+/
8
- words = string.scan(re)
9
- shuffled = words.shuffle
10
-
11
- (0..(words.length)).each do |i|
12
- string.sub!(words[i], shuffled[i])
5
+ def self.extract_quoted_strings(string)
6
+ # Hash of string-index key-value pairs
7
+ result = {}
8
+
9
+ skip_block = false
10
+ in_quotes = false
11
+ quote_type = nil
12
+ buffer = []
13
+
14
+ # I hope this calculates index correctly
15
+ current_string_index = 0
16
+ string.each_line do |line|
17
+ stripped = line.strip
18
+
19
+ if stripped[0] == '#' || stripped.start_with?(/(Win|Lose)|_Fanfare/)
20
+ next
13
21
  end
14
22
 
15
- string
23
+ skip_block = true if stripped.start_with?('=begin')
24
+ skip_block = false if stripped.start_with?('=end')
25
+
26
+ next if skip_block
27
+
28
+ buffer.push('\#') if in_quotes
29
+
30
+ line.each_char.each_with_index do |char, index|
31
+ if %w[' "].include?(char)
32
+ unless quote_type.nil? || char == quote_type
33
+ buffer.push(char)
34
+ next
35
+ end
36
+
37
+ quote_type = char
38
+ in_quotes = !in_quotes
39
+ result[buffer.join] = current_string_index + index
40
+ buffer.clear
41
+ next
42
+ end
43
+
44
+ if in_quotes
45
+ buffer.push(char)
46
+ end
47
+ end
48
+
49
+ current_string_index += line.length
50
+ end
51
+
52
+ result
53
+ end
54
+
55
+ def shuffle_words(array)
56
+ array.each do |string|
57
+ words = string.scan(/\S+/)
58
+ shuffled_words = words.shuffle
59
+ string.gsub(/\S+/) { shuffled_words.pop || "" }
16
60
  end
17
61
  end
18
62
 
@@ -103,7 +147,7 @@ def self.get_parameter_translated(code, parameter, hashmap, game_type)
103
147
  lisa_start = nil
104
148
 
105
149
  case code
106
- when 401, 356, 405
150
+ when 401, 405
107
151
  case game_type
108
152
  when 'lisa'
109
153
  match = parameter.scan(/^(\\et\[[0-9]+\]|\\nbt)/)
@@ -113,7 +157,9 @@ def self.get_parameter_translated(code, parameter, hashmap, game_type)
113
157
  nil
114
158
  end
115
159
  when 102, 402
116
- nil
160
+ # Implement some custom parsing
161
+ when 356
162
+ # Implement some custom parsing
117
163
  else
118
164
  nil
119
165
  end
@@ -309,7 +355,17 @@ def self.write_other(original_files, other_path, output_path, shuffle_level, log
309
355
  end
310
356
  end
311
357
 
312
- def self.write_system(system_file_path, other_path, output_path, shuffle_level, logging)
358
+ def self.write_system(system_file_path, ini_file_path, other_path, output_path, shuffle_level, logging)
359
+ def self.write_ini_title(ini_file_path, translated)
360
+ file_lines = File.readlines(ini_file_path, chomp: true)
361
+ title_line_index = file_lines.each_with_index do |line, i|
362
+ break i if line.start_with?('title')
363
+ end
364
+
365
+ file_lines[title_line_index] = translated
366
+ File.binwrite(ini_file_path, file_lines.join)
367
+ end
368
+
313
369
  system_basename = File.basename(system_file_path)
314
370
  system_object = Marshal.load(File.binread(system_file_path))
315
371
 
@@ -333,7 +389,6 @@ def self.write_system(system_file_path, other_path, output_path, shuffle_level,
333
389
  armor_types = system_object.instance_variable_get(system_symbols[3])
334
390
  currency_unit = system_object.instance_variable_get(system_symbols[4])
335
391
  terms = system_object.instance_variable_get(system_symbols[5]) || system_object.instance_variable_get(system_symbols[6])
336
- game_title = system_object.instance_variable_get(system_symbols[7])
337
392
 
338
393
  [elements, skill_types, weapon_types, armor_types].each_with_index.each do |array, i|
339
394
  next unless array.is_a?(Array)
@@ -363,20 +418,19 @@ def self.write_system(system_file_path, other_path, output_path, shuffle_level,
363
418
  system_object.instance_variable_set(system_symbols[5], terms) :
364
419
  system_object.instance_variable_set(system_symbols[6], terms)
365
420
 
366
- game_title_translated = system_translation_map[game_title]
421
+ game_title_translated = system_translated_text[-1]
367
422
  system_object.instance_variable_set(system_symbols[7], game_title_translated) if currency_unit.is_a?(String) && !game_title_translated.nil?
368
423
 
424
+ write_ini_title(ini_file_path, game_title_translated)
425
+
369
426
  puts "Written #{system_basename}" if logging
370
427
 
371
428
  File.binwrite("#{output_path}/#{system_basename}", Marshal.dump(system_object))
372
429
  end
373
430
 
374
- def self.write_scripts(scripts_file, other_path, output_path, logging)
375
- scripts_basename = File.basename(scripts_file)
376
- script_entries = Marshal.load(File.binread(scripts_file))
377
-
378
- scripts_original_text = File.readlines("#{other_path}/scripts.txt", encoding: 'UTF-8', chomp: true)
379
- .map { |line| line.gsub('\#', "\r\n") }
431
+ def self.write_scripts(scripts_file_path, other_path, output_path, logging)
432
+ scripts_basename = File.basename(scripts_file_path)
433
+ script_entries = Marshal.load(File.binread(scripts_file_path))
380
434
 
381
435
  scripts_translated_text = File.readlines("#{other_path}/scripts_trans.txt", encoding: 'UTF-8', chomp: true)
382
436
  .map { |line| line.gsub('\#', "\r\n") }
@@ -386,10 +440,58 @@ def self.write_scripts(scripts_file, other_path, output_path, logging)
386
440
  script_entries.each do |script|
387
441
  code = Zlib::Inflate.inflate(script[2]).force_encoding('UTF-8')
388
442
 
389
- scripts_original_text.zip(scripts_translated_text).each do |original, translated|
390
- # That may possibly break something, but until it does, who cares
391
- # Honestly, it needs to be changed to find quoted strings like in `extract_quoted_strings` method
392
- code.gsub!(original, translated) unless translated.nil?
443
+ (extract_quoted_strings(code)).each_with_index do |string_data, i|
444
+ string, string_index = string_data
445
+
446
+ string.strip!
447
+
448
+ # Removes the U+3000 Japanese typographical space to check if string, when stripped, is truly empty
449
+ next if string.empty? || string.gsub(' ', '').empty?
450
+
451
+ # Maybe this mess will remove something that mustn't be removed, but it needs to be tested
452
+ next if string.start_with?(/([#!?$@]|(\.\/)?(Graphics|Data|Audio|CG|Movies|Save)\/)/) ||
453
+ string.match?(/^\d+$/) ||
454
+ string.match?(/^(.)\1{2,}$/) ||
455
+ string.match?(/^(false|true)$/) ||
456
+ string.match?(/^[wr]b$/) ||
457
+ string.match?(/^(?=.*\d)[A-Za-z0-9\-]+$/) ||
458
+ string.match?(/^[A-Z\-()\/ +'&]*$/) ||
459
+ string.match?(/^[a-z\-()\/ +'&]*$/) ||
460
+ string.match?(/^[A-Za-z]+[+-]$/) ||
461
+ string.match?(/^[.()+-:;\[\]^~%&!*\/→×??x%▼|]$/) ||
462
+ string.match?(/^Tile.*[A-Z]$/) ||
463
+ string.match?(/^:?%.*[ds][:%]*?$/) ||
464
+ string.match?(/^[a-zA-Z]+([A-Z][a-z]*)+$/) ||
465
+ string.match?(/^Cancel Action$|^Invert$|^End$|^Individual$|^Missed File$|^Bitmap$|^Audio$/) ||
466
+ string.match?(/\.(mp3|ogg|jpg|png|ini)$/) ||
467
+ string.match?(/\/(\d.*)?$/) ||
468
+ string.match?(/FILE$/) ||
469
+ string.match?(/#\{/) ||
470
+ string.match?(/\\(?!#)/) ||
471
+ string.match?(/\+?=?=/) ||
472
+ string.match?(/[}{_<>]/) ||
473
+ string.match?(/r[vx]data/) ||
474
+ string.match?(/No such file or directory/) ||
475
+ string.match?(/level \*\*/) ||
476
+ string.match?(/Courier New/) ||
477
+ string.match?(/Comic Sans/) ||
478
+ string.match?(/Lucida/) ||
479
+ string.match?(/Verdana/) ||
480
+ string.match?(/Tahoma/) ||
481
+ string.match?(/Arial/) ||
482
+ string.match?(/Player start location/) ||
483
+ string.match?(/Common event call has exceeded/) ||
484
+ string.match?(/se-/) ||
485
+ string.match?(/Start Pos/) ||
486
+ string.match?(/An error has occurred/) ||
487
+ string.match?(/Define it first/) ||
488
+ string.match?(/Process Skill/) ||
489
+ string.match?(/Wpn Only/) ||
490
+ string.match?(/Don't Wait/) ||
491
+ string.match?(/Clear image/) ||
492
+ string.match?(/Can Collapse/)
493
+
494
+ code[string_index, string.length] = scripts_translated_text[i]
393
495
  end
394
496
 
395
497
  script[2] = Zlib::Deflate.deflate(code, Zlib::BEST_COMPRESSION)
data/rvpacker-txt.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = 'rvpacker-txt'
5
- spec.version = '1.4.0'
5
+ spec.version = '1.5.1'
6
6
  spec.authors = ['Howard Jeng', 'Andrew Kesterson', 'Solistra', 'Darkness9724', 'savannstm']
7
7
  spec.email = ['savannstm@gmail.com']
8
8
  spec.summary = 'Reads or writes RPG Maker XP/VX/VXAce game text to .txt files'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rvpacker-txt
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Howard Jeng
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2024-07-09 00:00:00.000000000 Z
15
+ date: 2024-07-13 00:00:00.000000000 Z
16
16
  dependencies: []
17
17
  description:
18
18
  email: