wolftrans 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,218 @@
1
+ require 'wolfrpg/route'
2
+
3
+ module WolfRpg
4
+ class Command
5
+ attr_reader :cid
6
+ attr_reader :args
7
+ attr_reader :string_args
8
+ attr_reader :indent
9
+
10
+ #############################
11
+ # Command class definitions #
12
+
13
+ class Blank < Command
14
+ end
15
+
16
+ class Message < Command
17
+ def text
18
+ @string_args[0]
19
+ end
20
+ def text=(value)
21
+ @string_args[0] = value
22
+ end
23
+ end
24
+
25
+ class Choices < Command
26
+ def text
27
+ @string_args
28
+ end
29
+ end
30
+
31
+ class Comment < Command
32
+ def text
33
+ @string_args[0]
34
+ end
35
+ end
36
+
37
+ class DebugMessage < Command
38
+ def text
39
+ @string_args[0]
40
+ end
41
+ end
42
+
43
+ class StringCondition < Command
44
+ end
45
+
46
+ class SetString < Command
47
+ def text
48
+ if @string_args.length > 0
49
+ @string_args[0]
50
+ else
51
+ ''
52
+ end
53
+ end
54
+
55
+ def text=(value)
56
+ @string_args[0] = value
57
+ end
58
+ end
59
+
60
+ class Picture < Command
61
+ def type
62
+ case (args[0] >> 4) & 0x07
63
+ when 0
64
+ :file
65
+ when 1
66
+ :file_string
67
+ when 2
68
+ :text
69
+ when 3
70
+ :window_file
71
+ when 4
72
+ :window_string
73
+ else
74
+ nil
75
+ end
76
+ end
77
+
78
+ def num
79
+ args[1]
80
+ end
81
+
82
+ def text
83
+ if type != :text
84
+ raise "picture type #{type} has no text"
85
+ end
86
+ return '' if string_args.empty?
87
+ string_args[0]
88
+ end
89
+ def text=(value)
90
+ if type != :text
91
+ raise "picture type #{type} has no text"
92
+ end
93
+ if string_args.empty?
94
+ string_args << value
95
+ else
96
+ string_args[0] = value
97
+ end
98
+ end
99
+
100
+ def filename
101
+ if type != :file && type != :window_file
102
+ raise "picture type #{type} has no filename"
103
+ end
104
+ string_args[0]
105
+ end
106
+ def filename=(value)
107
+ if type != :file && type != :window_file
108
+ raise "picture type #{type} has no filename"
109
+ end
110
+ string_args[0] = value
111
+ end
112
+ end
113
+
114
+ #class
115
+
116
+ private
117
+ ##########################
118
+ # Map of CIDs to classes #
119
+
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
131
+
132
+ public
133
+ class Move < Command
134
+ def initialize(cid, args, string_args, indent, file)
135
+ super(cid, args, string_args, indent)
136
+ # Read unknown data
137
+ @unknown = Array.new(5)
138
+ @unknown.each_index do |i|
139
+ @unknown[i] = IO.read_byte(file)
140
+ end
141
+ # Read known data
142
+ #TODO further abstract this
143
+ @flags = IO.read_byte(file)
144
+
145
+ # Read route
146
+ @route = Array.new(IO.read_int(file))
147
+ @route.each_index do |i|
148
+ @route[i] = RouteCommand.create(file)
149
+ end
150
+ end
151
+
152
+ def dump_terminator(file)
153
+ IO.write_byte(file, 1)
154
+ @unknown.each do |byte|
155
+ IO.write_byte(file, byte)
156
+ end
157
+ IO.write_byte(file, @flags)
158
+ IO.write_int(file, @route.size)
159
+ @route.each do |cmd|
160
+ cmd.dump(file)
161
+ end
162
+ end
163
+ end
164
+
165
+ # Load from the file and create the appropriate class object
166
+ def self.create(file)
167
+ # Read all data for this command from file
168
+ args = Array.new(IO.read_byte(file) - 1)
169
+ cid = IO.read_int(file)
170
+ args.each_index do |i|
171
+ args[i] = IO.read_int(file)
172
+ end
173
+ indent = IO.read_byte(file)
174
+ string_args = Array.new(IO.read_byte(file))
175
+ string_args.each_index do |i|
176
+ string_args[i] = IO.read_string(file)
177
+ end
178
+
179
+ # Read the move list if necessary
180
+ terminator = IO.read_byte(file)
181
+ if terminator == 0x01
182
+ return Command::Move.new(cid, args, string_args, indent, file)
183
+ elsif terminator != 0x00
184
+ raise "command terminator is an unexpected value (#{terminator})"
185
+ end
186
+
187
+ # Create command
188
+ return CID_TO_CLASS[cid].new(cid, args, string_args, indent)
189
+ end
190
+
191
+ def dump(file)
192
+ IO.write_byte(file, @args.size + 1)
193
+ IO.write_int(file, @cid)
194
+ @args.each do |arg|
195
+ IO.write_int(file, arg)
196
+ end
197
+ IO.write_byte(file, indent)
198
+ IO.write_byte(file, @string_args.size)
199
+ @string_args.each do |arg|
200
+ IO.write_string(file, arg)
201
+ end
202
+
203
+ dump_terminator(file)
204
+ end
205
+
206
+ private
207
+ def initialize(cid, args, string_args, indent)
208
+ @cid = cid
209
+ @args = args
210
+ @string_args = string_args
211
+ @indent = indent
212
+ end
213
+
214
+ def dump_terminator(file)
215
+ IO.write_byte(file, 0)
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,160 @@
1
+ require 'wolfrpg/command'
2
+
3
+ module WolfRpg
4
+ class CommonEvents
5
+ attr_accessor :events
6
+
7
+ def initialize(filename)
8
+ File.open(filename, 'rb') do |file|
9
+ IO.verify(file, MAGIC_NUMBER)
10
+ @events = Array.new(IO.read_int(file))
11
+ @events.each_index do |i|
12
+ event = Event.new(file)
13
+ events[event.id] = event
14
+ end
15
+ if (terminator = IO.read_byte(file)) != 0x8F
16
+ raise "CommonEvents terminator not 0x8F (got 0x#{terminator.to_s(16)})"
17
+ end
18
+ end
19
+ end
20
+
21
+ def dump(filename)
22
+ File.open(filename, 'wb') do |file|
23
+ IO.write(file, MAGIC_NUMBER)
24
+ IO.write_int(file, @events.size)
25
+ @events.each do |event|
26
+ event.dump(file)
27
+ end
28
+ IO.write_byte(file, 0x8F)
29
+ end
30
+ end
31
+
32
+ def grep(needle)
33
+ end
34
+
35
+ class Event
36
+ attr_accessor :id
37
+ attr_accessor :name
38
+ attr_accessor :commands
39
+
40
+ def initialize(file)
41
+ if (indicator = IO.read_byte(file)) != 0x8E
42
+ raise "CommonEvent header indicator not 0x8E (got 0x#{indicator.to_s(16)})"
43
+ end
44
+ @id = IO.read_int(file)
45
+ @unknown1 = IO.read_int(file)
46
+ @unknown2 = IO.read(file, 7)
47
+ @name = IO.read_string(file)
48
+ @commands = Array.new(IO.read_int(file))
49
+ @commands.each_index do |i|
50
+ @commands[i] = Command.create(file)
51
+ end
52
+ @unknown11 = IO.read_string(file)
53
+ @description = IO.read_string(file)
54
+ if (indicator = IO.read_byte(file)) != 0x8F
55
+ raise "CommonEvent data indicator not 0x8F (got 0x#{indicator.to_s(16)})"
56
+ end
57
+ IO.verify(file, MAGIC_NUMBER)
58
+ @unknown3 = Array.new(10)
59
+ @unknown3.each_index do |i|
60
+ @unknown3[i] = IO.read_string(file)
61
+ end
62
+ IO.verify(file, MAGIC_NUMBER)
63
+ @unknown4 = Array.new(10)
64
+ @unknown4.each_index do |i|
65
+ @unknown4[i] = IO.read_byte(file)
66
+ end
67
+ IO.verify(file, MAGIC_NUMBER)
68
+ @unknown5 = Array.new(10)
69
+ @unknown5.each_index do |i|
70
+ @unknown5[i] = Array.new(IO.read_int(file))
71
+ @unknown5[i].each_index do |j|
72
+ @unknown5[i][j] = IO.read_string(file)
73
+ end
74
+ end
75
+ IO.verify(file, MAGIC_NUMBER)
76
+ @unknown6 = Array.new(10)
77
+ @unknown6.each_index do |i|
78
+ @unknown6[i] = Array.new(IO.read_int(file))
79
+ @unknown6[i].each_index do |j|
80
+ @unknown6[i][j] = IO.read_int(file)
81
+ end
82
+ end
83
+ @unknown7 = IO.read(file, 0x1D)
84
+ @unknown8 = Array.new(100)
85
+ @unknown8.each_index do |i|
86
+ @unknown8[i] = IO.read_string(file)
87
+ end
88
+ if (indicator = IO.read_byte(file)) != 0x91
89
+ raise "expected 0x91, got 0x#{indicator.to_s(16)}"
90
+ end
91
+ @unknown9 = IO.read_string(file)
92
+ if (indicator = IO.read_byte(file)) != 0x92
93
+ raise "expected 0x92, got 0x#{indicator.to_s(16)}"
94
+ end
95
+ @unknown10 = IO.read_string(file)
96
+ @unknown12 = IO.read_int(file)
97
+ if (indicator = IO.read_byte(file)) != 0x92
98
+ raise "expected 0x92, got 0x#{indicator.to_s(16)}"
99
+ end
100
+ end
101
+
102
+ def dump(file)
103
+ IO.write_byte(file, 0x8E)
104
+ IO.write_int(file, @id)
105
+ IO.write_int(file, @unknown1)
106
+ IO.write(file, @unknown2)
107
+ IO.write_string(file, @name)
108
+ IO.write_int(file, @commands.size)
109
+ @commands.each do |cmd|
110
+ cmd.dump(file)
111
+ end
112
+ IO.write_string(file, @unknown11)
113
+ IO.write_string(file, @description)
114
+ IO.write_byte(file, 0x8F)
115
+ IO.write(file, MAGIC_NUMBER)
116
+ @unknown3.each do |i|
117
+ IO.write_string(file, i)
118
+ end
119
+ IO.write(file, MAGIC_NUMBER)
120
+ @unknown4.each do |i|
121
+ IO.write_byte(file, i)
122
+ end
123
+ IO.write(file, MAGIC_NUMBER)
124
+ @unknown5.each do |i|
125
+ IO.write_int(file, i.size)
126
+ i.each do |j|
127
+ IO.write_string(file, j)
128
+ end
129
+ end
130
+ IO.write(file, MAGIC_NUMBER)
131
+ @unknown6.each do |i|
132
+ IO.write_int(file, i.size)
133
+ i.each do |j|
134
+ IO.write_int(file, j)
135
+ end
136
+ end
137
+ IO.write(file, @unknown7)
138
+ @unknown8.each do |i|
139
+ IO.write_string(file, i)
140
+ end
141
+ IO.write_byte(file, 0x91)
142
+ IO.write_string(file, @unknown9)
143
+ IO.write_byte(file, 0x92)
144
+ IO.write_string(file, @unknown10)
145
+ IO.write_int(file, @unknown12)
146
+ IO.write_byte(file, 0x92)
147
+ end
148
+
149
+ private
150
+ MAGIC_NUMBER = [
151
+ 0x0A, 0x00, 0x00, 0x00
152
+ ].pack('C*')
153
+ end
154
+
155
+ private
156
+ MAGIC_NUMBER = [
157
+ 0x00, 0x57, 0x00, 0x00, 0x4F, 0x4C, 0x00, 0x46, 0x43, 0x00, 0x8F
158
+ ].pack('C*')
159
+ end
160
+ end
@@ -0,0 +1,315 @@
1
+ module WolfRpg
2
+ class Database
3
+ attr_accessor :name #DEBUG
4
+ attr_accessor :types
5
+
6
+ def initialize(project_filename, dat_filename)
7
+ @name = File.basename(project_filename, '.*')
8
+ File.open(project_filename, 'rb') do |file|
9
+ @types = Array.new(IO.read_int(file))
10
+ @types.each_index do |i|
11
+ @types[i] = Type.new(file)
12
+ end
13
+ end
14
+ File.open(dat_filename, 'rb') do |file|
15
+ IO.verify(file, DAT_MAGIC_NUMBER)
16
+ num_types = IO.read_int(file)
17
+ unless num_types == @types.size
18
+ raise "database project and dat Type count mismatch (#{@types.size} vs. #{num_types})"
19
+ end
20
+ @types.each do |type|
21
+ type.read_dat(file)
22
+ end
23
+ if IO.read_byte(file) != 0xC1
24
+ STDERR.puts "warning: no C1 terminator at the end of '#{dat_filename}'"
25
+ end
26
+ end
27
+
28
+ def dump(project_filename, dat_filename)
29
+ File.open(project_filename, 'wb') do |file|
30
+ IO.write_int(file, @types.size)
31
+ @types.each do |type|
32
+ type.dump_project(file)
33
+ end
34
+ end
35
+ File.open(dat_filename, 'wb') do |file|
36
+ IO.write(file, DAT_MAGIC_NUMBER)
37
+ IO.write_int(file, @types.size)
38
+ @types.each do |type|
39
+ type.dump_dat(file)
40
+ end
41
+ IO.write_byte(file, 0xC1)
42
+ end
43
+ end
44
+
45
+ def grep(needle)
46
+ @types.each_with_index do |type, type_index|
47
+ type.data.each_with_index do |datum, datum_index|
48
+ datum.each_translatable do |value, field|
49
+ next unless value =~ needle
50
+ puts "DB:#{@name}/[#{type_index}]#{type.name}/[#{datum_index}]#{datum.name}/[#{field.index}]#{field.name}"
51
+ puts "\t" + value
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ class Type
59
+ attr_accessor :name
60
+ attr_accessor :fields
61
+ attr_accessor :data
62
+ attr_accessor :description
63
+ attr_accessor :unknown1
64
+
65
+ # Initialize from project file IO
66
+ def initialize(file)
67
+ @name = IO.read_string(file)
68
+ @fields = Array.new(IO.read_int(file))
69
+ @fields.each_index do |i|
70
+ @fields[i] = Field.new(file)
71
+ end
72
+ @data = Array.new(IO.read_int(file))
73
+ @data.each_index do |i|
74
+ @data[i] = Data.new(file)
75
+ end
76
+ @description = IO.read_string(file)
77
+
78
+ # Add misc data to fields. It's separated for some reason.
79
+
80
+ # This appears to always be 0x64, but save it anyway
81
+ @field_type_list_size = IO.read_int(file)
82
+ index = 0
83
+ while index < @fields.size
84
+ @fields[index].type = IO.read_byte(file)
85
+ index += 1
86
+ end
87
+ file.seek(@field_type_list_size - index, :CUR)
88
+
89
+ IO.read_int(file).times do |i|
90
+ @fields[i].unknown1 = IO.read_string(file)
91
+ end
92
+ IO.read_int(file).times do |i|
93
+ @fields[i].string_args = Array.new(IO.read_int(file))
94
+ @fields[i].string_args.each_index do |j|
95
+ @fields[i].string_args[j] = IO.read_string(file)
96
+ end
97
+ end
98
+ IO.read_int(file).times do |i|
99
+ @fields[i].args = Array.new(IO.read_int(file))
100
+ @fields[i].args.each_index do |j|
101
+ @fields[i].args[j] = IO.read_int(file)
102
+ end
103
+ end
104
+ IO.read_int(file).times do |i|
105
+ @fields[i].default_value = IO.read_int(file)
106
+ end
107
+ end
108
+
109
+ def dump_project(file)
110
+ IO.write_string(file, @name)
111
+ IO.write_int(file, @fields.size)
112
+ @fields.each do |field|
113
+ field.dump_project(file)
114
+ end
115
+ IO.write_int(file, @data.size)
116
+ @data.each do |datum|
117
+ datum.dump_project(file)
118
+ end
119
+ IO.write_string(file, @description)
120
+
121
+ # Dump misc field data
122
+ IO.write_int(file, @field_type_list_size)
123
+ index = 0
124
+ while index < @fields.size
125
+ IO.write_byte(file, @fields[index].type)
126
+ index += 1
127
+ end
128
+ while index < @field_type_list_size
129
+ IO.write_byte(file, 0)
130
+ index += 1
131
+ end
132
+ IO.write_int(file, @fields.size)
133
+ @fields.each do |field|
134
+ IO.write_string(file, field.unknown1)
135
+ end
136
+ IO.write_int(file, @fields.size)
137
+ @fields.each do |field|
138
+ IO.write_int(file, field.string_args.size)
139
+ field.string_args.each do |arg|
140
+ IO.write_string(file, arg)
141
+ end
142
+ end
143
+ IO.write_int(file, @fields.size)
144
+ @fields.each do |field|
145
+ IO.write_int(file, field.args.size)
146
+ field.args.each do |arg|
147
+ IO.write_int(file, arg)
148
+ end
149
+ end
150
+ IO.write_int(file, @fields.size)
151
+ @fields.each do |field|
152
+ IO.write_int(file, field.default_value)
153
+ end
154
+ end
155
+
156
+ # Read the rest of the data from the dat file
157
+ def read_dat(file)
158
+ IO.verify(file, DAT_TYPE_SEPARATOR)
159
+ @unknown1 = IO.read_int(file)
160
+ num_fields = IO.read_int(file)
161
+ unless num_fields == @fields.size
162
+ raise "database project and dat Field count mismatch (#{@fields.size} vs. #{num_fields})"
163
+ end
164
+ @fields.each do |field|
165
+ field.read_dat(file)
166
+ end
167
+ num_data = IO.read_int(file)
168
+ unless num_data == @data.size
169
+ raise "database project and dat Field count mismatch (#{@data.size} vs. #{num_data})"
170
+ end
171
+ @data.each do |datum|
172
+ datum.read_dat(file, @fields)
173
+ end
174
+ end
175
+
176
+ def dump_dat(file)
177
+ IO.write(file, DAT_TYPE_SEPARATOR)
178
+ IO.write_int(file, @unknown1)
179
+ IO.write_int(file, @fields.size)
180
+ @fields.each do |field|
181
+ field.dump_dat(file)
182
+ end
183
+ IO.write_int(file, @data.size)
184
+ @data.each do |datum|
185
+ datum.dump_dat(file)
186
+ end
187
+ end
188
+ end
189
+
190
+
191
+ class Field
192
+ attr_accessor :name
193
+ attr_accessor :type
194
+ attr_accessor :unknown1
195
+ attr_accessor :string_args
196
+ attr_accessor :args
197
+ attr_accessor :default_value
198
+ attr_accessor :indexinfo
199
+
200
+ def initialize(file)
201
+ @name = IO.read_string(file)
202
+ end
203
+
204
+ def dump_project(file)
205
+ IO.write_string(file, @name)
206
+ end
207
+
208
+ def read_dat(file)
209
+ @indexinfo = IO.read_int(file)
210
+ end
211
+
212
+ def dump_dat(file)
213
+ IO.write_int(file, @indexinfo)
214
+ end
215
+
216
+ def string?
217
+ @indexinfo >= STRING_START
218
+ end
219
+
220
+ def int?
221
+ !string?
222
+ end
223
+
224
+ def index
225
+ if string?
226
+ @indexinfo - STRING_START
227
+ else
228
+ @indexinfo - INT_START
229
+ end
230
+ end
231
+
232
+ STRING_START = 0x07D0
233
+ INT_START = 0x03E8
234
+ end
235
+
236
+ class Data
237
+ attr_accessor :name
238
+ attr_accessor :int_values
239
+ attr_accessor :string_values
240
+
241
+ def initialize(file)
242
+ @name = IO.read_string(file)
243
+ end
244
+
245
+ def dump_project(file)
246
+ IO.write_string(file, @name)
247
+ end
248
+
249
+ def read_dat(file, fields)
250
+ @fields = fields
251
+ @int_values = Array.new(fields.select(&:int?).size)
252
+ @string_values = Array.new(fields.select(&:string?).size)
253
+
254
+ @int_values.each_index do |i|
255
+ @int_values[i] = IO.read_int(file)
256
+ end
257
+ @string_values.each_index do |i|
258
+ @string_values[i] = IO.read_string(file)
259
+ end
260
+ end
261
+
262
+ def dump_dat(file)
263
+ @int_values.each do |i|
264
+ IO.write_int(file, i)
265
+ end
266
+ @string_values.each do |i|
267
+ IO.write_string(file, i)
268
+ end
269
+ end
270
+
271
+ def [](key)
272
+ if key.is_a? Field
273
+ if key.string?
274
+ @string_values[key.index]
275
+ else
276
+ @int_values[key.index]
277
+ end
278
+ elsif value.is_a? Integer
279
+ self[@fields[key]]
280
+ else
281
+ raise "Data[] takes a Field, got a #{value.class}"
282
+ end
283
+ end
284
+
285
+ def []=(key, value)
286
+ if key.is_a? Field
287
+ if key.string?
288
+ @string_values[key.index] = value
289
+ else
290
+ @int_values[key.index] = value
291
+ end
292
+ elsif value.is_a? Integer
293
+ self[@fields[key]] = value
294
+ else
295
+ raise "Data[] takes a Field, got a #{value.class}"
296
+ end
297
+ end
298
+
299
+ def each_translatable
300
+ @fields.each do |field|
301
+ next unless field.string? && field.type == 0
302
+ value = self[field]
303
+ yield [value, field] unless value.empty? || value.include?("\n")
304
+ end
305
+ end
306
+ end
307
+
308
+ DAT_MAGIC_NUMBER = [
309
+ 0x00, 0x57, 0x00, 0x00, 0x4F, 0x4C, 0x00, 0x46, 0x4D, 0x00, 0xC1
310
+ ].pack('C*')
311
+ DAT_TYPE_SEPARATOR = [
312
+ 0xFE, 0xFF, 0xFF, 0xFF
313
+ ].pack('C*')
314
+ end
315
+ end