wolftrans 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e5810f165d8477fd00571f8cf4c0b599f4ee2bd7
4
- data.tar.gz: d3fb00f064a84ef6f740329fe26ea98158522ecc
3
+ metadata.gz: c841512ed31767357a132f115dd9c9d5d4d85ae3
4
+ data.tar.gz: 13b81d6e79ce6e6e7a3ca202073dd4b8c2a22dd8
5
5
  SHA512:
6
- metadata.gz: 11fa3045617bc45a79ca011ee8f42f44e5d05e1bc0b11917395936b2f22e12471e4010aabf623a1a775db050013d4a29b583e468c82b9ee6e9e737d4a1e5f7a4
7
- data.tar.gz: 8741262f4c7622bb688554f2e65bcb6273c5cbb7d6b917e6d3770ce24b94f6010f87d59813da42d02bf02b3a53fb0fda92f860f3fcccafd1c7faff45c5b2ef0f
6
+ metadata.gz: 4b63ce1e61df8e456faf61ae7064c8732dfc8626b75262641a9afe76b5f271c5e813daa97042435ea237fedf923e7129b74e363c6fbac6b5322f41a6316a5901
7
+ data.tar.gz: faea22c7bbf27d5e4171fa3056306014e869e4c57e4ea9c5697c6359a0a84255ae1b0ccf5ea14ee9d474d2fb00b8f4328790a96e93bbff84443b408d687ea7bc
@@ -0,0 +1,6 @@
1
+ # Ignore generated files
2
+ *.gem
3
+
4
+ # Ignore any zip archives or extracted data in test/
5
+ test/*/*
6
+ test/*.zip
data/Rakefile CHANGED
@@ -17,6 +17,10 @@ task :install => :build do
17
17
  system "gem install #{GEM_FILENAME}"
18
18
  end
19
19
 
20
+ task :uninstall do
21
+ system "gem uninstall wolftrans --version #{WolfTrans::VERSION}"
22
+ end
23
+
20
24
  task :clean do
21
25
  File.delete(GEM_FILENAME) if File.exist? GEM_FILENAME
22
26
  end
@@ -1,5 +1,7 @@
1
1
  require 'wolfrpg/route'
2
2
 
3
+ # Special thanks to vgperson for mapping most of these command IDs out.
4
+
3
5
  module WolfRpg
4
6
  class Command
5
7
  attr_reader :cid
@@ -13,6 +15,9 @@ module WolfRpg
13
15
  class Blank < Command
14
16
  end
15
17
 
18
+ class Checkpoint < Command
19
+ end
20
+
16
21
  class Message < Command
17
22
  def text
18
23
  @string_args[0]
@@ -34,15 +39,27 @@ module WolfRpg
34
39
  end
35
40
  end
36
41
 
42
+ class ForceStopMessage < Command
43
+ end
44
+
37
45
  class DebugMessage < Command
38
46
  def text
39
47
  @string_args[0]
40
48
  end
41
49
  end
42
50
 
51
+ class ClearDebugText < Command
52
+ end
53
+
54
+ class VariableCondition < Command
55
+ end
56
+
43
57
  class StringCondition < Command
44
58
  end
45
59
 
60
+ class SetVariable < Command
61
+ end
62
+
46
63
  class SetString < Command
47
64
  def text
48
65
  if @string_args.length > 0
@@ -57,6 +74,24 @@ module WolfRpg
57
74
  end
58
75
  end
59
76
 
77
+ class InputKey < Command
78
+ end
79
+
80
+ class SetVariableEx < Command
81
+ end
82
+
83
+ class AutoInput < Command
84
+ end
85
+
86
+ class BanInput < Command
87
+ end
88
+
89
+ class Teleport < Command
90
+ end
91
+
92
+ class Sound < Command
93
+ end
94
+
60
95
  class Picture < Command
61
96
  def type
62
97
  case (args[0] >> 4) & 0x07
@@ -111,25 +146,51 @@ module WolfRpg
111
146
  end
112
147
  end
113
148
 
114
- #class
149
+ class ChangeColor < Command
150
+ end
115
151
 
116
- private
117
- ##########################
118
- # Map of CIDs to classes #
152
+ class SetTransition < Command
153
+ end
119
154
 
120
- CID_TO_CLASS = {
121
- 0 => Command::Blank,
122
- 101 => Command::Message,
123
- 102 => Command::Choices,
124
- 103 => Command::Comment,
125
- 106 => Command::DebugMessage,
126
- 112 => Command::StringCondition,
127
- 122 => Command::SetString,
128
- 150 => Command::Picture,
129
- }
130
- CID_TO_CLASS.default = Command
155
+ class PrepareTransition < Command
156
+ end
157
+
158
+ class ExecuteTransition < Command
159
+ end
160
+
161
+ class StartLoop < Command
162
+ end
163
+
164
+ class BreakLoop < Command
165
+ end
166
+
167
+ class BreakEvent < Command
168
+ end
169
+
170
+ class EraseEvent < Command
171
+ end
172
+
173
+ class ReturnToTitle < Command
174
+ end
175
+
176
+ class EndGame < Command
177
+ end
178
+
179
+ class LoopToStart < Command
180
+ end
181
+
182
+ class StopNonPic < Command
183
+ end
184
+
185
+ class ResumeNonPic < Command
186
+ end
187
+
188
+ class LoopTimes < Command
189
+ end
190
+
191
+ class Wait < Command
192
+ end
131
193
 
132
- public
133
194
  class Move < Command
134
195
  def initialize(cid, args, string_args, indent, coder)
135
196
  super(cid, args, string_args, indent)
@@ -162,6 +223,155 @@ module WolfRpg
162
223
  end
163
224
  end
164
225
 
226
+ class WaitForMove < Command
227
+ end
228
+
229
+ class CommonEvent < Command
230
+ end
231
+
232
+ class CommonEventReserve < Command
233
+ end
234
+
235
+ class SetLabel < Command
236
+ end
237
+
238
+ class JumpLabel < Command
239
+ end
240
+
241
+ class SaveLoad < Command
242
+ end
243
+
244
+ class LoadGame < Command
245
+ end
246
+
247
+ class SaveGame < Command
248
+ end
249
+
250
+ class MoveDuringEventOn < Command
251
+ end
252
+
253
+ class MoveDuringEventOff < Command
254
+ end
255
+
256
+ class Chip < Command
257
+ end
258
+
259
+ class ChipSet < Command
260
+ end
261
+
262
+ class ChipOverwrite < Command
263
+ end
264
+
265
+ class Database < Command
266
+ end
267
+
268
+ class ImportDatabase < Command
269
+ end
270
+
271
+ class Party < Command
272
+ end
273
+
274
+ class MapEffect < Command
275
+ end
276
+
277
+ class ScrollScreen < Command
278
+ end
279
+
280
+ class Effect < Command
281
+ end
282
+
283
+ class CommonEventByName < Command
284
+ end
285
+
286
+ class ChoiceCase < Command
287
+ end
288
+
289
+ class SpecialChoiceCase < Command
290
+ end
291
+
292
+ class ElseCase < Command
293
+ end
294
+
295
+ class CancelCase < Command
296
+ end
297
+
298
+ class LoopEnd < Command
299
+ end
300
+
301
+ class BranchEnd < Command
302
+ end
303
+
304
+ #class
305
+
306
+ private
307
+ ##########################
308
+ # Map of CIDs to classes #
309
+
310
+ CID_TO_CLASS = {
311
+ 0 => Command::Blank,
312
+ 99 => Command::Checkpoint,
313
+ 101 => Command::Message,
314
+ 102 => Command::Choices,
315
+ 103 => Command::Comment,
316
+ 105 => Command::ForceStopMessage,
317
+ 106 => Command::DebugMessage,
318
+ 107 => Command::ClearDebugText,
319
+ 111 => Command::VariableCondition,
320
+ 112 => Command::StringCondition,
321
+ 121 => Command::SetVariable,
322
+ 122 => Command::SetString,
323
+ 123 => Command::InputKey,
324
+ 124 => Command::SetVariableEx,
325
+ 125 => Command::AutoInput,
326
+ 126 => Command::BanInput,
327
+ 130 => Command::Teleport,
328
+ 140 => Command::Sound,
329
+ 150 => Command::Picture,
330
+ 151 => Command::ChangeColor,
331
+ 160 => Command::SetTransition,
332
+ 161 => Command::PrepareTransition,
333
+ 162 => Command::ExecuteTransition,
334
+ 170 => Command::StartLoop,
335
+ 171 => Command::BreakLoop,
336
+ 172 => Command::BreakEvent,
337
+ 173 => Command::EraseEvent,
338
+ 174 => Command::ReturnToTitle,
339
+ 175 => Command::EndGame,
340
+ 176 => Command::StartLoop,
341
+ 177 => Command::StopNonPic,
342
+ 178 => Command::ResumeNonPic,
343
+ 179 => Command::LoopTimes,
344
+ 180 => Command::Wait,
345
+ 201 => Command::Move, # special case
346
+ 202 => Command::WaitForMove,
347
+ 210 => Command::CommonEvent,
348
+ 211 => Command::CommonEventReserve,
349
+ 212 => Command::SetLabel,
350
+ 213 => Command::JumpLabel,
351
+ 220 => Command::SaveLoad,
352
+ 221 => Command::LoadGame,
353
+ 222 => Command::SaveGame,
354
+ 230 => Command::MoveDuringEventOn,
355
+ 231 => Command::MoveDuringEventOff,
356
+ 240 => Command::Chip,
357
+ 241 => Command::ChipSet,
358
+ 250 => Command::Database,
359
+ 251 => Command::ImportDatabase,
360
+ 270 => Command::Party,
361
+ 280 => Command::MapEffect,
362
+ 281 => Command::ScrollScreen,
363
+ 290 => Command::Effect,
364
+ 300 => Command::CommonEventByName,
365
+ 401 => Command::ChoiceCase,
366
+ 402 => Command::SpecialChoiceCase,
367
+ 420 => Command::ElseCase,
368
+ 421 => Command::CancelCase,
369
+ 498 => Command::LoopEnd,
370
+ 499 => Command::BranchEnd
371
+ }
372
+ CID_TO_CLASS.default = Command
373
+
374
+ public
165
375
  # Load from the file and create the appropriate class object
166
376
  def self.create(coder)
167
377
  # Read all data for this command from file
@@ -87,7 +87,8 @@ module WolfRpg
87
87
  raise "expected 0x91, got 0x#{indicator.to_s(16)}"
88
88
  end
89
89
  @unknown9 = coder.read_string
90
- if (indicator = coder.read_byte) != 0x92
90
+ return if (indicator = coder.read_byte) == 0x91
91
+ unless indicator == 0x92
91
92
  raise "expected 0x92, got 0x#{indicator.to_s(16)}"
92
93
  end
93
94
  @unknown10 = coder.read_string
@@ -138,10 +139,14 @@ module WolfRpg
138
139
  end
139
140
  coder.write_byte(0x91)
140
141
  coder.write_string(@unknown9)
141
- coder.write_byte(0x92)
142
- coder.write_string(@unknown10)
143
- coder.write_int(@unknown12)
144
- coder.write_byte(0x92)
142
+ if @unknown10
143
+ coder.write_byte(0x92)
144
+ coder.write_string(@unknown10)
145
+ coder.write_int(@unknown12)
146
+ coder.write_byte(0x92)
147
+ else
148
+ coder.write_byte(0x91)
149
+ end
145
150
  end
146
151
 
147
152
  private
@@ -2,6 +2,12 @@ module WolfRpg
2
2
  class Database
3
3
  attr_accessor :types
4
4
 
5
+ def encrypted?
6
+ @crypt_header != nil
7
+ end
8
+
9
+ DAT_SEED_INDICES = [0, 3, 9]
10
+
5
11
  def initialize(project_filename, dat_filename)
6
12
  FileCoder.open(project_filename, :read) do |coder|
7
13
  @types = Array.new(coder.read_int)
@@ -9,8 +15,13 @@ module WolfRpg
9
15
  @types[i] = Type.new(coder)
10
16
  end
11
17
  end
12
- FileCoder.open(dat_filename, :read) do |coder|
13
- coder.verify(DAT_MAGIC_NUMBER)
18
+ FileCoder.open(dat_filename, :read, DAT_SEED_INDICES) do |coder|
19
+ if coder.encrypted?
20
+ @crypt_header = coder.crypt_header
21
+ @unknown_encrypted_1 = coder.read_byte
22
+ else
23
+ coder.verify(DAT_MAGIC_NUMBER)
24
+ end
14
25
  num_types = coder.read_int
15
26
  unless num_types == @types.size
16
27
  raise "database project and dat Type count mismatch (#{@types.size} vs. #{num_types})"
@@ -30,8 +41,12 @@ module WolfRpg
30
41
  type.dump_project(coder)
31
42
  end
32
43
  end
33
- FileCoder.open(dat_filename, :write) do |coder|
34
- coder.write(DAT_MAGIC_NUMBER)
44
+ FileCoder.open(dat_filename, :write, DAT_SEED_INDICES, @crypt_header) do |coder|
45
+ if encrypted?
46
+ coder.write_byte(@unknown_encrypted_1)
47
+ else
48
+ coder.write(DAT_MAGIC_NUMBER)
49
+ end
35
50
  coder.write_int(@types.size)
36
51
  @types.each do |type|
37
52
  type.dump_dat(coder)
@@ -300,7 +315,7 @@ module WolfRpg
300
315
  end
301
316
 
302
317
  DAT_MAGIC_NUMBER = [
303
- 0x00, 0x57, 0x00, 0x00, 0x4F, 0x4C, 0x00, 0x46, 0x4D, 0x00, 0xC1
318
+ 0x57, 0x00, 0x00, 0x4F, 0x4C, 0x00, 0x46, 0x4D, 0x00, 0xC1
304
319
  ].pack('C*')
305
320
  DAT_TYPE_SEPARATOR = [
306
321
  0xFE, 0xFF, 0xFF, 0xFF
@@ -1,19 +1,49 @@
1
1
  module WolfRpg
2
2
  class FileCoder
3
+ ##############
4
+ # Attributes #
3
5
  attr_reader :io
4
-
5
- def initialize(io)
6
- @io = io
6
+ attr_reader :crypt_header
7
+ def encrypted?
8
+ @crypt_header != nil
7
9
  end
8
10
 
9
- def self.open(filename, mode)
11
+ #############
12
+ # Constants #
13
+ CRYPT_HEADER_SIZE = 10
14
+ DECRYPT_INTERVALS = [1, 2, 5]
15
+
16
+ #################
17
+ # Class methods #
18
+ def self.open(filename, mode, seed_indices = nil, crypt_header = nil)
10
19
  case mode
11
20
  when :read
12
- file = File.open(filename, 'rb')
21
+ coder = FileCoder.new(File.open(filename, 'rb'))
22
+
23
+ # If encryptable,
24
+ # we need to make an extra check to see if it needs decrypting
25
+ if seed_indices
26
+ unless (indicator = coder.read_byte) == 0
27
+ header = [indicator]
28
+ (CRYPT_HEADER_SIZE - 1).times { |i| header << coder.read_byte }
29
+ seeds = seed_indices.map { |i| header[i] }
30
+ data = crypt(coder.read, seeds)
31
+ coder = FileCoder.new(StringIO.new(data, 'rb'), header)
32
+ end
33
+ end
13
34
  when :write
14
- file = File.open(filename, 'wb')
35
+ # If encryptable, open a StringIO and pass the encryption options
36
+ # to the FileCoder
37
+ if seed_indices && crypt_header
38
+ coder = FileCoder.new(StringIO.new(''.force_encoding('BINARY'), 'wb'),
39
+ crypt_header, filename, seed_indices)
40
+ coder.write(crypt_header.pack('C*'))
41
+ else
42
+ coder = FileCoder.new(File.open(filename, 'wb'))
43
+ coder.write_byte(0) if seed_indices
44
+ end
15
45
  end
16
- coder = FileCoder.new(file)
46
+
17
47
  if block_given?
18
48
  begin
19
49
  yield coder
@@ -24,6 +54,14 @@ module WolfRpg
24
54
  return coder
25
55
  end
26
56
 
57
+ ##############
58
+ # Initialize #
59
+ def initialize(io, crypt_header = nil, filename = nil, seed_indices = nil)
60
+ @io = io
61
+ @crypt_header = crypt_header
62
+ @filename = filename
63
+ end
64
+
27
65
  ########
28
66
  # Read #
29
67
  def read(size = nil)
@@ -50,6 +88,30 @@ module WolfRpg
50
88
  return str
51
89
  end
52
90
 
91
+ def read_byte_array
92
+ bytes = Array.new(read_int)
93
+ bytes.each_index do |i|
94
+ bytes[i] = read_byte
95
+ end
96
+ return bytes
97
+ end
98
+
99
+ def read_int_array
100
+ ints = Array.new(read_int)
101
+ ints.each_index do |i|
102
+ ints[i] = read_int
103
+ end
104
+ return ints
105
+ end
106
+
107
+ def read_string_array
108
+ strings = Array.new(read_int)
109
+ strings.each_index do |i|
110
+ strings[i] = read_string
111
+ end
112
+ return strings
113
+ end
114
+
53
115
  def verify(data)
54
116
  got = read(data.length)
55
117
  if got != data
@@ -84,12 +146,15 @@ module WolfRpg
84
146
  def write(data)
85
147
  @io.write(data)
86
148
  end
149
+
87
150
  def write_byte(data)
88
151
  @io.write(data.chr)
89
152
  end
153
+
90
154
  def write_int(data)
91
155
  @io.write([data].pack('l<'))
92
156
  end
157
+
93
158
  def write_string(str)
94
159
  str = str.encode(Encoding::WINDOWS_31J, Encoding::UTF_8)
95
160
  write_int(str.bytesize + 1)
@@ -97,9 +162,37 @@ module WolfRpg
97
162
  write_byte(0)
98
163
  end
99
164
 
165
+ def write_byte_array(bytes)
166
+ write_int(bytes.size)
167
+ bytes.each do |b|
168
+ write_byte(b)
169
+ end
170
+ end
171
+
172
+ def write_int_array(ints)
173
+ write_int(ints.size)
174
+ ints.each do |i|
175
+ write_int(i)
176
+ end
177
+ end
178
+
179
+ def write_string_array(strings)
180
+ write_int(strings.size)
181
+ strings.each do |s|
182
+ write_string(s)
183
+ end
184
+ end
185
+
100
186
  #########
101
187
  # Other #
102
188
  def close
189
+ if @crypt_header && @filename && @seed_indices
190
+ File.open(@filename, 'wb') do |file|
191
+ file.write(@crypt_header.pack('C*'))
192
+ seeds = @seed_indices.map { |i| crypt_header[i] }
193
+ file.write(FileCoder.crypt(@io.string, seeds))
194
+ end
195
+ end
103
196
  @io.close
104
197
  end
105
198
 
@@ -108,7 +201,25 @@ module WolfRpg
108
201
  end
109
202
 
110
203
  def tell
111
- @io.tell
204
+ if encrypted?
205
+ @io.tell + CRYPT_HEADER_SIZE
206
+ else
207
+ @io.tell
208
+ end
209
+ end
210
+
211
+ #################
212
+ # Private class #
213
+ private
214
+ def self.crypt(data_str, seeds)
215
+ data = data_str.unpack('C*')
216
+ seeds.each_with_index do |seed, s|
217
+ (0...data.size).step(DECRYPT_INTERVALS[s]) do |i|
218
+ seed = (seed * 0x343FD + 0x269EC3) & 0xFFFFFFFF
219
+ data[i] ^= (seed >> 28) & 7
220
+ end
221
+ end
222
+ return data.pack('C*')
112
223
  end
113
224
  end
114
225
  end
@@ -2,11 +2,8 @@ require 'stringio'
2
2
 
3
3
  module WolfRpg
4
4
  class GameDat
5
- attr_reader :legacy
6
- alias_method :legacy?, :legacy
7
-
8
5
  attr_accessor :unknown1
9
- attr_accessor :unknown4
6
+ attr_accessor :file_version # only a guess
10
7
  attr_accessor :title
11
8
  attr_accessor :unknown2
12
9
  attr_accessor :font
@@ -15,40 +12,29 @@ module WolfRpg
15
12
  attr_accessor :version
16
13
  attr_accessor :unknown3
17
14
 
15
+ def encrypted?
16
+ @crypt_header != nil
17
+ end
18
+
19
+ SEED_INDICES = [0, 8, 6]
20
+ #XSEED_INDICES = [3, 4, 5]
21
+
18
22
  def initialize(filename)
19
- FileCoder.open(filename, :read) do |coder|
20
- if (first_byte = coder.read_byte) == 0
21
- @legacy = false
22
- coder.verify(MAGIC_NUMBER)
23
+ FileCoder.open(filename, :read, SEED_INDICES) do |coder|
24
+ if coder.encrypted?
25
+ @crypt_header = coder.crypt_header
23
26
  else
24
- @legacy = true
25
- @seeds = Array.new(3)
26
- @xseeds = Array.new(3)
27
- @seeds[0] = first_byte
28
- coder.skip(1) # garbage
29
- @xseeds[0] = coder.read_byte
30
- @xseeds[1] = coder.read_byte
31
- @xseeds[2] = coder.read_byte
32
- coder.skip(1) # garbage
33
- @seeds[2] = coder.read_byte
34
- coder.skip(1) # garbage
35
- @seeds[1] = coder.read_byte
36
- coder.skip(1) # garbage
37
- coder = FileCoder.new(StringIO.new(crypt(coder.read)))
27
+ coder.verify(MAGIC_NUMBER)
38
28
  end
39
29
 
40
30
  #TODO what is most of the junk in this file?
41
- unknown1_size = coder.read_int
42
- @unknown1 = coder.read(unknown1_size)
43
- @unknown4 = coder.read_int
44
-
31
+ @unknown1 = coder.read_byte_array
32
+ @file_version = coder.read_int
45
33
  @title = coder.read_string
46
34
  if (magic_string = coder.read_string) != MAGIC_STRING
47
35
  raise "magic string invalid (got #{magic_string})"
48
36
  end
49
-
50
- unknown2_size = coder.read_int
51
- @unknown2 = coder.read(unknown2_size)
37
+ @unknown2 = coder.read_byte_array
52
38
 
53
39
  @font = coder.read_string
54
40
  @subfonts = Array.new(3)
@@ -57,7 +43,11 @@ module WolfRpg
57
43
  end
58
44
 
59
45
  @default_pc_graphic = coder.read_string
60
- @version = coder.read_string
46
+ if @file_version >= 9
47
+ @version = coder.read_string
48
+ else
49
+ @version = ''
50
+ end
61
51
 
62
52
  # This is the size of the file minus one.
63
53
  # We don't need it, so discard it.
@@ -71,61 +61,23 @@ module WolfRpg
71
61
  end
72
62
 
73
63
  def dump(filename)
74
- begin
75
- if @legacy
76
- coder = FileCoder.new(StringIO.new('', 'wb'))
77
- else
78
- coder = FileCoder.open(filename, :write)
79
- coder.write_byte(0)
80
- coder.write(MAGIC_NUMBER)
81
- end
64
+ FileCoder.open(filename, :write, SEED_INDICES, @crypt_header) do |coder|
65
+ coder.write(MAGIC_NUMBER) unless encrypted?
82
66
 
83
- coder.write_int(@unknown1.size)
84
- coder.write(@unknown1)
85
- coder.write_int(@unknown4)
67
+ coder.write_byte_array(@unknown1)
68
+ coder.write_int(@file_version)
86
69
  coder.write_string(@title)
87
70
  coder.write_string(MAGIC_STRING)
88
- coder.write_int(@unknown2.bytesize)
89
- coder.write(@unknown2)
71
+ coder.write_byte_array(@unknown2)
90
72
  coder.write_string(@font)
91
73
  @subfonts.each do |subfont|
92
74
  coder.write_string(subfont)
93
75
  end
94
76
  coder.write_string(@default_pc_graphic)
95
- coder.write_string(@version)
96
- coder.write_int(coder.tell + 4 + @unknown3.bytesize - 1 + (@legacy ? 10 : 0))
77
+ coder.write_string(@version) if @file_version >= 9
78
+ coder.write_int(coder.tell + 4 + @unknown3.bytesize - 1)
97
79
  coder.write(@unknown3)
98
-
99
- if @legacy
100
- data = crypt(coder.io.string)
101
- FileCoder.open(filename, :write) do |coder|
102
- coder.write_byte(@seeds[0])
103
- coder.write_byte(0) # garbage
104
- coder.write_byte(@xseeds[0])
105
- coder.write_byte(@xseeds[1])
106
- coder.write_byte(@xseeds[2])
107
- coder.write_byte(0) # garbage
108
- coder.write_byte(@seeds[2])
109
- coder.write_byte(0) # garbage
110
- coder.write_byte(@seeds[1])
111
- coder.write_byte(0) # garbage
112
- coder.write(data)
113
- end
114
- end
115
- ensure
116
- coder.close
117
- end
118
- end
119
-
120
- def crypt(data_str)
121
- data = data_str.unpack('C*')
122
- @seeds.each_with_index do |seed, s|
123
- (0...data.size).step(DECRYPT_INTERVALS[s]) do |i|
124
- seed = (seed * 0x343FD + 0x269EC3) & 0xFFFFFFFF
125
- data[i] ^= (seed >> 28) & 7
126
- end
127
80
  end
128
- return data.pack('C*')
129
81
  end
130
82
 
131
83
  private
@@ -133,6 +85,5 @@ module WolfRpg
133
85
  0x57, 0x00, 0x00, 0x4f, 0x4c, 0x00, 0x46, 0x4d, 0x00
134
86
  ].pack('C*')
135
87
  MAGIC_STRING = "0000-0000" # who knows what this is supposed to be
136
- DECRYPT_INTERVALS = [1, 2, 5]
137
88
  end
138
89
  end
@@ -18,7 +18,8 @@ module WolfRpg
18
18
  # Read basic data
19
19
  @width = coder.read_int
20
20
  @height = coder.read_int
21
- @events = Array.new(coder.read_int)
21
+ coder.skip(4) # skip event count
22
+ @events = []
22
23
 
23
24
  # Read tiles
24
25
  #TODO: interpret this data later
@@ -26,8 +27,7 @@ module WolfRpg
26
27
 
27
28
  # Read events
28
29
  while (indicator = coder.read_byte) == 0x6F
29
- event = Event.new(coder)
30
- @events[event.id] = event
30
+ @events << Event.new(coder)
31
31
  end
32
32
  if indicator != 0x66
33
33
  raise "unexpected event indicator: #{indicator.to_s(16)}"
@@ -83,27 +83,6 @@ module WolfTrans
83
83
  end
84
84
  end
85
85
 
86
- # IO functions
87
- module IO
88
- def self.read_txt(filename)
89
- # Read file into memory, forcing UTF-8 without BOM.
90
- text = nil
91
- File.open(filename, 'rb:UTF-8') do |file|
92
- if text = file.read(3)
93
- if text == "\xEF\xBB\xBF"
94
- raise "UTF-8 BOM detected; refusing to read file"
95
- end
96
- text << file.read
97
- else
98
- STDERR.puts "warning: empty patch file '#{filename}'"
99
- return ''
100
- end
101
- end
102
- # Convert Windows newlines and return
103
- text.gsub(/\r\n?/, "\n")
104
- end
105
- end
106
-
107
86
  # Latest patch version format that can be read
108
87
  TXT_VERSION = Version.new(major: 1, minor: 0)
109
88
  end
@@ -36,6 +36,7 @@ module WolfTrans
36
36
  load_map(filename) if extension == '.mps'
37
37
  when 'basicdata'
38
38
  if basename_downcase == 'game.dat'
39
+ @game_dat_filename = 'Data/BasicData/Game.dat'
39
40
  load_game_dat(filename)
40
41
  elsif extension == '.project'
41
42
  next if basename_downcase == 'sysdatabasebasic.project'
@@ -48,19 +49,34 @@ module WolfTrans
48
49
  end
49
50
  end
50
51
  end
52
+
53
+ # Game.dat is in a different place on older versions
54
+ unless @game_dat
55
+ Dir.entries(@game_dir).each do |entry|
56
+ if entry.downcase == 'game.dat'
57
+ @game_dat_filename = 'Game.dat'
58
+ load_game_dat("#{@game_dir}/#{entry}")
59
+ break
60
+ end
61
+ end
62
+ end
51
63
  end
52
64
 
53
65
  # Apply the patch to the files in the game path and write them to the
54
66
  # output directory
67
+ DATA_FILE_EXTENSIONS = ['png','jpg','jpeg','bmp','ogg',
68
+ 'mp3','wav','mid','midi',
69
+ 'dat','project','xxxxx']
55
70
  def apply(out_dir)
56
71
  out_dir = Util.sanitize_path(out_dir)
57
72
  out_data_dir = "#{out_dir}/Data"
58
73
 
59
74
  # Clear out directory
60
75
  FileUtils.rm_rf(out_dir)
76
+ FileUtils.mkdir_p("#{out_data_dir}/BasicData")
77
+ FileUtils.mkdir_p("#{out_data_dir}/MapData")
61
78
 
62
79
  # Patch all the maps and dump them
63
- FileUtils.mkdir_p("#{out_data_dir}/MapData")
64
80
  @maps.each do |map_name, map|
65
81
  map.events.each do |event|
66
82
  next unless event
@@ -75,7 +91,6 @@ module WolfTrans
75
91
  end
76
92
 
77
93
  # Patch the databases
78
- FileUtils.mkdir_p("#{out_data_dir}/BasicData")
79
94
  @databases.each do |db_name, db|
80
95
  db.types.each_with_index do |type, type_index|
81
96
  next if type.name.empty?
@@ -102,43 +117,40 @@ module WolfTrans
102
117
  @common_events.dump("#{out_data_dir}/BasicData/CommonEvent.dat")
103
118
 
104
119
  # Patch Game.dat
105
- FileUtils.mkdir_p("#{out_data_dir}/BasicData")
106
120
  patch_game_dat
107
- @game_dat.dump("#{out_data_dir}/BasicData/Game.dat")
108
-
109
- # Copy image files
110
- [
111
- 'BattleEffect',
112
- 'CharaChip',
113
- 'EnemyGraphic',
114
- 'Fog',
115
- 'Fog_BackGround',
116
- 'MapChip',
117
- 'Picture',
118
- 'SystemFile',
119
- 'SystemGraphic',
120
- ].each do |dirname|
121
- copy_data_files(out_data_dir, dirname, ['png','jpg','jpeg','bmp'])
122
- end
121
+ @game_dat.dump("#{out_dir}/#{@game_dat_filename}")
123
122
 
124
- # Copy sound/music files
125
- [
126
- 'BGM',
127
- 'SE',
128
- 'SystemFile',
129
- ].each do |dirname|
130
- copy_data_files(out_data_dir, dirname, ['ogg','mp3','wav','mid','midi'])
131
- end
123
+ # Copy image/sound/music files
124
+ Dir.entries(@game_data_dir).each do |entry|
125
+ # Skip dot and dot-dot and non-directories
126
+ next if entry == '.' || entry == '..'
127
+ path = "#{@game_data_dir}/#{entry}"
128
+ next unless FileTest.directory? path
132
129
 
133
- # Copy BasicData
134
- copy_data_files(out_data_dir, 'BasicData', ['dat','project','xxxxx','png'])
130
+ # Find the corresponding folder in the out dir
131
+ unless (out_path = Util.join_path_nocase(out_data_dir, entry))
132
+ out_path = "#{out_data_dir}/#{entry}"
133
+ FileUtils.mkdir_p(out_path)
134
+ end
135
+
136
+ # Find the corresponding folder in the patch
137
+ if @patch_data_dir && (asset_entry = Util.join_path_nocase(@patch_data_dir, entry))
138
+ copy_data_files("#{@patch_data_dir}/#{asset_entry}", DATA_FILE_EXTENSIONS, out_path)
139
+ end
140
+
141
+ # Copy the original game files
142
+ copy_data_files(path, DATA_FILE_EXTENSIONS, out_path)
143
+ end
135
144
 
136
145
  # Copy fonts
137
- copy_data_files(out_data_dir, '', ['ttf','ttc'])
146
+ if @patch_data_dir
147
+ copy_data_files(@patch_data_dir, ['ttf','ttc','otf'], out_data_dir)
148
+ end
149
+ copy_data_files(@game_data_dir, ['ttf','ttc','otf'], out_data_dir)
138
150
 
139
151
  # Copy remainder of files in the base patch/game dirs
140
- copy_files(@patch_assets_dir, @patch_data_dir, out_dir)
141
- copy_files(@game_dir, @game_data_dir, out_dir)
152
+ copy_files(@patch_assets_dir, out_dir)
153
+ copy_files(@game_dir, out_dir)
142
154
  end
143
155
 
144
156
  private
@@ -214,20 +226,20 @@ module WolfTrans
214
226
  def strings_of_command(command)
215
227
  case command
216
228
  when WolfRpg::Command::Message
217
- yield command.text unless command.text.empty?
229
+ yield command.text if Util.translatable? command.text
218
230
  when WolfRpg::Command::Choices
219
231
  command.text.each do |s|
220
- yield s
232
+ yield s if Util.translatable? s
221
233
  end
222
234
  when WolfRpg::Command::StringCondition
223
235
  command.string_args.each do |s|
224
- yield s unless s.empty?
236
+ yield s if Util.translatable? s
225
237
  end
226
238
  when WolfRpg::Command::SetString
227
- yield command.text unless command.text.empty?
239
+ yield command.text if Util.translatable? command.text
228
240
  when WolfRpg::Command::Picture
229
241
  if command.type == :text
230
- yield command.text unless command.text.empty?
242
+ yield command.text if Util.translatable? command.text
231
243
  end
232
244
  end
233
245
  end
@@ -284,63 +296,53 @@ module WolfTrans
284
296
 
285
297
  # Yield a translation for the given string and context if it exists
286
298
  def yield_translation(string, context)
287
- return if string.empty?
299
+ return unless Util.translatable? string
288
300
  if @strings.include? string
289
- unless @strings[string][context].string.empty?
290
- yield @strings[string][context].string
291
- end
301
+ str = @strings[string][context].string
302
+ yield str if Util.translatable? str
292
303
  end
293
304
  end
294
305
 
295
306
  # Copy normal, non-data files
296
- def copy_files(src_dir, src_data_dir, out_dir)
307
+ def copy_files(src_dir, out_dir)
297
308
  Find.find(src_dir) do |path|
298
- next if path == src_dir
299
- Find.prune if path == src_data_dir
309
+ basename = File.basename(path)
310
+ basename_downcase = basename.downcase
311
+
312
+ # Don't do anything in Data/
313
+ Find.prune if basename_downcase == 'data' && File.dirname(path) == src_dir
314
+
315
+ # Skip directories
316
+ next if FileTest.directory? path
317
+
318
+ # "Short name", relative to the game base dir
300
319
  short_path = path[src_dir.length+1..-1]
301
320
  Find.prune if @file_blacklist.include? short_path.downcase
321
+
302
322
  out_path = "#{out_dir}/#{short_path}"
303
- if FileTest.directory? path
304
- FileUtils.mkdir_p(out_path)
305
- else
306
- next if ['thumbs.db', 'desktop.ini', '.ds_store'].include? File.basename(path).downcase
307
- FileUtils.cp(path, out_path) unless File.exist? out_path
308
- end
323
+ next if ['thumbs.db', 'desktop.ini', '.ds_store'].include? basename_downcase
324
+ next if File.exist? out_path
325
+ # Make directory only only when copying a file to avoid making empty directories
326
+ FileUtils.mkdir_p(File.dirname(out_path))
327
+ FileUtils.cp(path, out_path)
309
328
  end
310
329
  end
311
330
 
312
331
  # Copy data files
313
- def copy_data_files(out_data_dir, dirname, extensions)
314
- copy_data_files_from(@game_data_dir, out_data_dir, dirname, extensions)
315
- if @patch_data_dir
316
- copy_data_files_from(@patch_data_dir, out_data_dir, dirname, extensions)
317
- end
318
- end
332
+ def copy_data_files(src_dir, extensions, out_dir)
333
+ Dir.entries(src_dir).each do |entry|
334
+ # Don't care about directories
335
+ next if entry == '.' || entry == '..'
336
+ path = "#{src_dir}/#{entry}"
337
+ next if FileTest.directory? path
319
338
 
320
- def copy_data_files_from(src_data_dir, out_data_dir, dirname, extensions)
321
- out_dir = File.join(out_data_dir, dirname)
339
+ # Skip invalid file extensions
340
+ next unless extensions.include? File.extname(entry)[1..-1]
322
341
 
323
- Find.find(src_data_dir) do |path|
324
- if dirname.empty?
325
- if FileTest.directory? path
326
- Find.prune if path != src_data_dir
327
- next
328
- end
329
- else
330
- next if path == src_data_dir
331
- if FileTest.directory?(path)
332
- Find.prune unless File.basename(path).casecmp(dirname) == 0
333
- next
334
- end
335
- next if File.dirname(path) == src_data_dir
336
- end
337
- basename = File.basename(path)
338
- next unless extensions.include? File.extname(basename)[1..-1]
339
- next if @file_blacklist.include? "data/#{dirname.downcase}/#{basename.downcase}"
340
- out_name = "#{out_dir}/#{basename}"
341
- next if File.exist? out_name
342
- FileUtils.mkdir_p(out_dir)
343
- FileUtils.cp(path, out_name)
342
+ # Copy the file if it doesn't already exist
343
+ next if Util.join_path_nocase(out_dir, entry)
344
+
345
+ FileUtils.cp(path, "#{out_dir}/#{entry}")
344
346
  end
345
347
  end
346
348
  end
@@ -24,7 +24,7 @@ module WolfTrans
24
24
  # Load blacklist
25
25
  @file_blacklist = []
26
26
  if File.exists? "#{patch_dir}/blacklist.txt"
27
- IO.read_txt("#{patch_dir}/blacklist.txt").each_line do |line|
27
+ Util.read_txt("#{patch_dir}/blacklist.txt").each_line do |line|
28
28
  line.strip!
29
29
  next if line.empty?
30
30
  if line.include? '\\'
@@ -87,7 +87,7 @@ module WolfTrans
87
87
 
88
88
  if File.exists? filename
89
89
  output_write = true if mode == :update
90
- IO.read_txt(filename).each_line.with_index do |pristine_line, index|
90
+ Util.read_txt(filename).each_line.with_index do |pristine_line, index|
91
91
  # Remove comments and strip
92
92
  pristine_line.gsub!(/\n$/, '')
93
93
  line = pristine_line.gsub(/(?!\\)#.*$/, '').rstrip
@@ -10,7 +10,8 @@ module WolfTrans
10
10
 
11
11
  # Get the name of a path case-insensitively
12
12
  def self.join_path_nocase(parent, child)
13
- child_case = Dir.entries(parent).select { |e| e.downcase == child }.first
13
+ child_downcase = child.downcase
14
+ child_case = Dir.entries(parent).select { |e| e.downcase == child_downcase }.first
14
15
  return nil unless child_case
15
16
  return "#{parent}/#{child_case}"
16
17
  end
@@ -25,5 +26,30 @@ module WolfTrans
25
26
  def self.escape_path(path)
26
27
  full_strip(path).gsub(/[\x00\/\\:\*\?\"<>\|]/, '_')
27
28
  end
29
+
30
+ # Determine if a string should be added to the translation map.
31
+ # All non-nill, non-empty, and "black square" character strings are good.
32
+ def self.translatable?(string)
33
+ string && !string.empty? && string != "\u25A0"
34
+ end
35
+
36
+ # Read all lines of a txt file as UTF-8, rejecting
37
+ # anything with a BOM
38
+ def self.read_txt(filename)
39
+ text = nil
40
+ File.open(filename, 'rb:UTF-8') do |file|
41
+ if text = file.read(3)
42
+ if text == "\xEF\xBB\xBF"
43
+ raise "UTF-8 BOM detected; refusing to read file"
44
+ end
45
+ text << file.read
46
+ else
47
+ STDERR.puts "warning: empty patch file '#{filename}'"
48
+ return ''
49
+ end
50
+ end
51
+ # Convert Windows newlines and return
52
+ text.gsub(/\r\n?/, "\n")
53
+ end
28
54
  end
29
55
  end
@@ -1,3 +1,3 @@
1
1
  module WolfTrans
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wolftrans
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mathew Velasquez
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-16 00:00:00.000000000 Z
11
+ date: 2015-12-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A utility to translate Wolf RPG Editor games
14
14
  email: mathewvq@gmail.com
@@ -17,6 +17,7 @@ executables:
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
+ - ".gitignore"
20
21
  - LICENSE
21
22
  - README.md
22
23
  - Rakefile