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 +4 -4
- data/.gitignore +6 -0
- data/Rakefile +4 -0
- data/lib/wolfrpg/command.rb +226 -16
- data/lib/wolfrpg/common_events.rb +10 -5
- data/lib/wolfrpg/database.rb +20 -5
- data/lib/wolfrpg/filecoder.rb +119 -8
- data/lib/wolfrpg/game_dat.rb +27 -76
- data/lib/wolfrpg/map.rb +3 -3
- data/lib/wolftrans.rb +0 -21
- data/lib/wolftrans/patch_data.rb +81 -79
- data/lib/wolftrans/patch_text.rb +2 -2
- data/lib/wolftrans/util.rb +27 -1
- data/lib/wolftrans/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c841512ed31767357a132f115dd9c9d5d4d85ae3
|
4
|
+
data.tar.gz: 13b81d6e79ce6e6e7a3ca202073dd4b8c2a22dd8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b63ce1e61df8e456faf61ae7064c8732dfc8626b75262641a9afe76b5f271c5e813daa97042435ea237fedf923e7129b74e363c6fbac6b5322f41a6316a5901
|
7
|
+
data.tar.gz: faea22c7bbf27d5e4171fa3056306014e869e4c57e4ea9c5697c6359a0a84255ae1b0ccf5ea14ee9d474d2fb00b8f4328790a96e93bbff84443b408d687ea7bc
|
data/.gitignore
ADDED
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
|
data/lib/wolfrpg/command.rb
CHANGED
@@ -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
|
-
|
149
|
+
class ChangeColor < Command
|
150
|
+
end
|
115
151
|
|
116
|
-
|
117
|
-
|
118
|
-
# Map of CIDs to classes #
|
152
|
+
class SetTransition < Command
|
153
|
+
end
|
119
154
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
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)
|
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
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
data/lib/wolfrpg/database.rb
CHANGED
@@ -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.
|
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
|
-
|
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
|
-
|
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
|
data/lib/wolfrpg/filecoder.rb
CHANGED
@@ -1,19 +1,49 @@
|
|
1
1
|
module WolfRpg
|
2
2
|
class FileCoder
|
3
|
+
##############
|
4
|
+
# Attributes #
|
3
5
|
attr_reader :io
|
4
|
-
|
5
|
-
def
|
6
|
-
@
|
6
|
+
attr_reader :crypt_header
|
7
|
+
def encrypted?
|
8
|
+
@crypt_header != nil
|
7
9
|
end
|
8
10
|
|
9
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/wolfrpg/game_dat.rb
CHANGED
@@ -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 :
|
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
|
21
|
-
@
|
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
|
-
|
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
|
-
|
42
|
-
@
|
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
|
-
@
|
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
|
-
|
75
|
-
|
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.
|
84
|
-
coder.
|
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.
|
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
|
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
|
data/lib/wolfrpg/map.rb
CHANGED
@@ -18,7 +18,8 @@ module WolfRpg
|
|
18
18
|
# Read basic data
|
19
19
|
@width = coder.read_int
|
20
20
|
@height = coder.read_int
|
21
|
-
|
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
|
-
|
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)}"
|
data/lib/wolftrans.rb
CHANGED
@@ -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
|
data/lib/wolftrans/patch_data.rb
CHANGED
@@ -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("#{
|
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
|
-
|
127
|
-
'
|
128
|
-
|
129
|
-
|
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
|
-
|
134
|
-
|
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
|
-
|
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,
|
141
|
-
copy_files(@game_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
|
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
|
236
|
+
yield s if Util.translatable? s
|
225
237
|
end
|
226
238
|
when WolfRpg::Command::SetString
|
227
|
-
yield command.text
|
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
|
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
|
299
|
+
return unless Util.translatable? string
|
288
300
|
if @strings.include? string
|
289
|
-
|
290
|
-
|
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,
|
307
|
+
def copy_files(src_dir, out_dir)
|
297
308
|
Find.find(src_dir) do |path|
|
298
|
-
|
299
|
-
|
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
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
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(
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
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
|
-
|
321
|
-
|
339
|
+
# Skip invalid file extensions
|
340
|
+
next unless extensions.include? File.extname(entry)[1..-1]
|
322
341
|
|
323
|
-
|
324
|
-
if
|
325
|
-
|
326
|
-
|
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
|
data/lib/wolftrans/patch_text.rb
CHANGED
@@ -24,7 +24,7 @@ module WolfTrans
|
|
24
24
|
# Load blacklist
|
25
25
|
@file_blacklist = []
|
26
26
|
if File.exists? "#{patch_dir}/blacklist.txt"
|
27
|
-
|
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
|
-
|
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
|
data/lib/wolftrans/util.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/wolftrans/version.rb
CHANGED
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.
|
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-
|
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
|