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.
- checksums.yaml +7 -0
- data/LICENSE +363 -0
- data/README.md +42 -0
- data/bin/wolftrans +11 -0
- data/lib/wolfrpg.rb +65 -0
- data/lib/wolfrpg/command.rb +218 -0
- data/lib/wolfrpg/common_events.rb +160 -0
- data/lib/wolfrpg/database.rb +315 -0
- data/lib/wolfrpg/game_dat.rb +76 -0
- data/lib/wolfrpg/io.rb +63 -0
- data/lib/wolfrpg/map.rb +250 -0
- data/lib/wolfrpg/route.rb +36 -0
- data/lib/wolftrans.rb +169 -0
- data/lib/wolftrans/context.rb +193 -0
- data/lib/wolftrans/patch_data.rb +341 -0
- data/lib/wolftrans/patch_text.rb +319 -0
- data/wolftrans.gemspec +14 -0
- metadata +60 -0
@@ -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
|