wolftrans 0.0.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.
@@ -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