wolftrans 0.0.2 → 0.1.0
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 +4 -4
- data/Rakefile +22 -2
- data/bin/wolftrans +2 -1
- data/lib/wolfrpg.rb +6 -60
- data/lib/wolfrpg/command.rb +30 -30
- data/lib/wolfrpg/common_events.rb +69 -71
- data/lib/wolfrpg/database.rb +89 -91
- data/lib/wolfrpg/debug.rb +60 -0
- data/lib/wolfrpg/filecoder.rb +114 -0
- data/lib/wolfrpg/game_dat.rb +93 -31
- data/lib/wolfrpg/map.rb +81 -85
- data/lib/wolfrpg/route.rb +10 -10
- data/lib/wolftrans.rb +4 -64
- data/lib/wolftrans/context.rb +3 -3
- data/lib/wolftrans/debug.rb +35 -0
- data/lib/wolftrans/patch_data.rb +10 -8
- data/lib/wolftrans/patch_text.rb +2 -2
- data/lib/wolftrans/util.rb +29 -0
- data/lib/wolftrans/version.rb +3 -0
- data/test/test_wolftrans.rb +2 -1
- data/wolftrans.gemspec +6 -4
- metadata +8 -4
- data/lib/wolfrpg/io.rb +0 -63
data/lib/wolfrpg/game_dat.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
-
require '
|
1
|
+
require 'stringio'
|
2
2
|
|
3
3
|
module WolfRpg
|
4
4
|
class GameDat
|
5
|
+
attr_reader :legacy
|
6
|
+
alias_method :legacy?, :legacy
|
7
|
+
|
5
8
|
attr_accessor :unknown1
|
9
|
+
attr_accessor :unknown4
|
6
10
|
attr_accessor :title
|
7
11
|
attr_accessor :unknown2
|
8
12
|
attr_accessor :font
|
@@ -12,65 +16,123 @@ module WolfRpg
|
|
12
16
|
attr_accessor :unknown3
|
13
17
|
|
14
18
|
def initialize(filename)
|
15
|
-
|
16
|
-
|
19
|
+
FileCoder.open(filename, :read) do |coder|
|
20
|
+
if (first_byte = coder.read_byte) == 0
|
21
|
+
@legacy = false
|
22
|
+
coder.verify(MAGIC_NUMBER)
|
23
|
+
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)))
|
38
|
+
end
|
39
|
+
|
17
40
|
#TODO what is most of the junk in this file?
|
18
|
-
|
41
|
+
unknown1_size = coder.read_int
|
42
|
+
@unknown1 = coder.read(unknown1_size)
|
43
|
+
@unknown4 = coder.read_int
|
19
44
|
|
20
|
-
@title =
|
21
|
-
if (magic_string =
|
45
|
+
@title = coder.read_string
|
46
|
+
if (magic_string = coder.read_string) != MAGIC_STRING
|
22
47
|
raise "magic string invalid (got #{magic_string})"
|
23
48
|
end
|
24
49
|
|
25
|
-
unknown2_size =
|
26
|
-
@unknown2 =
|
50
|
+
unknown2_size = coder.read_int
|
51
|
+
@unknown2 = coder.read(unknown2_size)
|
27
52
|
|
28
|
-
|
29
|
-
#abort
|
30
|
-
@font = IO.read_string(file)
|
53
|
+
@font = coder.read_string
|
31
54
|
@subfonts = Array.new(3)
|
32
55
|
@subfonts.each_index do |i|
|
33
|
-
@subfonts[i] =
|
56
|
+
@subfonts[i] = coder.read_string
|
34
57
|
end
|
35
58
|
|
36
|
-
@default_pc_graphic =
|
37
|
-
@version =
|
59
|
+
@default_pc_graphic = coder.read_string
|
60
|
+
@version = coder.read_string
|
38
61
|
|
39
62
|
# This is the size of the file minus one.
|
40
63
|
# We don't need it, so discard it.
|
41
|
-
|
64
|
+
coder.skip(4)
|
42
65
|
|
43
66
|
# We don't care about the rest of this file for translation
|
44
67
|
# purposes.
|
45
68
|
# Someday we will know what the hell is stored in here... But not today.
|
46
|
-
@unknown3 =
|
69
|
+
@unknown3 = coder.read
|
47
70
|
end
|
48
71
|
end
|
49
72
|
|
50
73
|
def dump(filename)
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
82
|
+
|
83
|
+
coder.write_int(@unknown1.size)
|
84
|
+
coder.write(@unknown1)
|
85
|
+
coder.write_int(@unknown4)
|
86
|
+
coder.write_string(@title)
|
87
|
+
coder.write_string(MAGIC_STRING)
|
88
|
+
coder.write_int(@unknown2.bytesize)
|
89
|
+
coder.write(@unknown2)
|
90
|
+
coder.write_string(@font)
|
59
91
|
@subfonts.each do |subfont|
|
60
|
-
|
92
|
+
coder.write_string(subfont)
|
93
|
+
end
|
94
|
+
coder.write_string(@default_pc_graphic)
|
95
|
+
coder.write_string(@version)
|
96
|
+
coder.write_int(coder.tell + 4 + @unknown3.bytesize - 1 + (@legacy ? 10 : 0))
|
97
|
+
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
|
61
126
|
end
|
62
|
-
IO.write_string(file, @default_pc_graphic)
|
63
|
-
IO.write_string(file, @version)
|
64
|
-
IO.write_int(file, file.tell + 4 + @unknown3.bytesize - 1)
|
65
|
-
IO.write(file, @unknown3)
|
66
127
|
end
|
128
|
+
return data.pack('C*')
|
67
129
|
end
|
68
130
|
|
69
131
|
private
|
70
132
|
MAGIC_NUMBER = [
|
71
|
-
|
72
|
-
0x15, 0x00, 0x00, 0x00, # likely an integer
|
133
|
+
0x57, 0x00, 0x00, 0x4f, 0x4c, 0x00, 0x46, 0x4d, 0x00
|
73
134
|
].pack('C*')
|
74
135
|
MAGIC_STRING = "0000-0000" # who knows what this is supposed to be
|
136
|
+
DECRYPT_INTERVALS = [1, 2, 5]
|
75
137
|
end
|
76
138
|
end
|
data/lib/wolfrpg/map.rb
CHANGED
@@ -1,7 +1,3 @@
|
|
1
|
-
require 'wolfrpg/io'
|
2
|
-
require 'wolfrpg/route'
|
3
|
-
require 'wolfrpg/command'
|
4
|
-
|
5
1
|
module WolfRpg
|
6
2
|
class Map
|
7
3
|
attr_reader :tileset_id
|
@@ -14,48 +10,48 @@ module WolfRpg
|
|
14
10
|
|
15
11
|
def initialize(filename)
|
16
12
|
@filename = File.basename(filename, '.*')
|
17
|
-
|
18
|
-
|
13
|
+
FileCoder.open(filename, :read) do |coder|
|
14
|
+
coder.verify(MAGIC_NUMBER)
|
19
15
|
|
20
|
-
@tileset_id =
|
16
|
+
@tileset_id = coder.read_int
|
21
17
|
|
22
18
|
# Read basic data
|
23
|
-
@width =
|
24
|
-
@height =
|
25
|
-
@events = Array.new(
|
19
|
+
@width = coder.read_int
|
20
|
+
@height = coder.read_int
|
21
|
+
@events = Array.new(coder.read_int)
|
26
22
|
|
27
23
|
# Read tiles
|
28
24
|
#TODO: interpret this data later
|
29
|
-
@tiles =
|
25
|
+
@tiles = coder.read(@width * @height * 3 * 4)
|
30
26
|
|
31
27
|
# Read events
|
32
|
-
while (indicator =
|
33
|
-
event = Event.new(
|
28
|
+
while (indicator = coder.read_byte) == 0x6F
|
29
|
+
event = Event.new(coder)
|
34
30
|
@events[event.id] = event
|
35
31
|
end
|
36
32
|
if indicator != 0x66
|
37
33
|
raise "unexpected event indicator: #{indicator.to_s(16)}"
|
38
34
|
end
|
39
|
-
unless
|
35
|
+
unless coder.eof?
|
40
36
|
raise "file not fully parsed"
|
41
37
|
end
|
42
38
|
end
|
43
39
|
end
|
44
40
|
|
45
41
|
def dump(filename)
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
42
|
+
FileCoder.open(filename, :write) do |coder|
|
43
|
+
coder.write(MAGIC_NUMBER)
|
44
|
+
coder.write_int(@tileset_id)
|
45
|
+
coder.write_int(@width)
|
46
|
+
coder.write_int(@height)
|
47
|
+
coder.write_int(@events.size)
|
48
|
+
coder.write(@tiles)
|
53
49
|
@events.each do |event|
|
54
50
|
next unless event
|
55
|
-
|
56
|
-
event.dump(
|
51
|
+
coder.write_byte(0x6F)
|
52
|
+
event.dump(coder)
|
57
53
|
end
|
58
|
-
|
54
|
+
coder.write_byte(0x66)
|
59
55
|
end
|
60
56
|
end
|
61
57
|
|
@@ -94,19 +90,19 @@ module WolfRpg
|
|
94
90
|
attr_accessor :y
|
95
91
|
attr_accessor :pages
|
96
92
|
|
97
|
-
def initialize(
|
98
|
-
|
99
|
-
@id =
|
100
|
-
@name =
|
101
|
-
@x =
|
102
|
-
@y =
|
103
|
-
@pages = Array.new(
|
104
|
-
|
93
|
+
def initialize(coder)
|
94
|
+
coder.verify(MAGIC_NUMBER1)
|
95
|
+
@id = coder.read_int
|
96
|
+
@name = coder.read_string
|
97
|
+
@x = coder.read_int
|
98
|
+
@y = coder.read_int
|
99
|
+
@pages = Array.new(coder.read_int)
|
100
|
+
coder.verify(MAGIC_NUMBER2)
|
105
101
|
|
106
102
|
# Read pages
|
107
103
|
page_id = 0
|
108
|
-
while (indicator =
|
109
|
-
page = Page.new(
|
104
|
+
while (indicator = coder.read_byte) == 0x79
|
105
|
+
page = Page.new(coder, page_id)
|
110
106
|
@pages[page_id] = page
|
111
107
|
page_id += 1
|
112
108
|
end
|
@@ -115,21 +111,21 @@ module WolfRpg
|
|
115
111
|
end
|
116
112
|
end
|
117
113
|
|
118
|
-
def dump(
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
114
|
+
def dump(coder)
|
115
|
+
coder.write(MAGIC_NUMBER1)
|
116
|
+
coder.write_int(@id)
|
117
|
+
coder.write_string(@name)
|
118
|
+
coder.write_int(@x)
|
119
|
+
coder.write_int(@y)
|
120
|
+
coder.write_int(@pages.size)
|
121
|
+
coder.write(MAGIC_NUMBER2)
|
126
122
|
|
127
123
|
# Write pages
|
128
124
|
@pages.each do |page|
|
129
|
-
|
130
|
-
page.dump(
|
125
|
+
coder.write_byte(0x79)
|
126
|
+
page.dump(coder)
|
131
127
|
end
|
132
|
-
|
128
|
+
coder.write_byte(0x70)
|
133
129
|
end
|
134
130
|
|
135
131
|
class Page
|
@@ -150,77 +146,77 @@ module WolfRpg
|
|
150
146
|
attr_accessor :collision_width
|
151
147
|
attr_accessor :collision_height
|
152
148
|
|
153
|
-
def initialize(
|
149
|
+
def initialize(coder, id)
|
154
150
|
@id = id
|
155
151
|
|
156
152
|
#TODO ???
|
157
|
-
@unknown1 =
|
153
|
+
@unknown1 = coder.read_int
|
158
154
|
|
159
155
|
#TODO further abstract graphics options
|
160
|
-
@graphic_name =
|
161
|
-
@graphic_direction =
|
162
|
-
@graphic_frame =
|
163
|
-
@graphic_opacity =
|
164
|
-
@graphic_render_mode =
|
156
|
+
@graphic_name = coder.read_string
|
157
|
+
@graphic_direction = coder.read_byte
|
158
|
+
@graphic_frame = coder.read_byte
|
159
|
+
@graphic_opacity = coder.read_byte
|
160
|
+
@graphic_render_mode = coder.read_byte
|
165
161
|
|
166
162
|
#TODO parse conditions later
|
167
|
-
@conditions =
|
163
|
+
@conditions = coder.read(1 + 4 + 4*4 + 4*4)
|
168
164
|
#TODO parse movement options later
|
169
|
-
@movement =
|
165
|
+
@movement = coder.read(4)
|
170
166
|
|
171
167
|
#TODO further abstract flags
|
172
|
-
@flags =
|
168
|
+
@flags = coder.read_byte
|
173
169
|
|
174
170
|
#TODO further abstract flags
|
175
|
-
@route_flags =
|
171
|
+
@route_flags = coder.read_byte
|
176
172
|
|
177
173
|
# Parse move route
|
178
|
-
@route = Array.new(
|
174
|
+
@route = Array.new(coder.read_int)
|
179
175
|
@route.each_index do |i|
|
180
|
-
@route[i] = RouteCommand.create(
|
176
|
+
@route[i] = RouteCommand.create(coder)
|
181
177
|
end
|
182
178
|
|
183
179
|
# Parse commands
|
184
|
-
@commands = Array.new(
|
180
|
+
@commands = Array.new(coder.read_int)
|
185
181
|
@commands.each_index do |i|
|
186
|
-
@commands[i] = Command.create(
|
182
|
+
@commands[i] = Command.create(coder)
|
187
183
|
end
|
188
|
-
|
184
|
+
coder.verify(COMMANDS_TERMINATOR)
|
189
185
|
|
190
186
|
#TODO abstract these options later
|
191
|
-
@shadow_graphic_num =
|
192
|
-
@collision_width =
|
193
|
-
@collision_height =
|
187
|
+
@shadow_graphic_num = coder.read_byte
|
188
|
+
@collision_width = coder.read_byte
|
189
|
+
@collision_height = coder.read_byte
|
194
190
|
|
195
|
-
if (terminator =
|
191
|
+
if (terminator = coder.read_byte) != 0x7A
|
196
192
|
raise "page terminator not 7A (found #{terminator.to_s(16)})"
|
197
193
|
end
|
198
194
|
end
|
199
195
|
|
200
|
-
def dump(
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
196
|
+
def dump(coder)
|
197
|
+
coder.write_int(@unknown1)
|
198
|
+
coder.write_string(@graphic_name)
|
199
|
+
coder.write_byte(@graphic_direction)
|
200
|
+
coder.write_byte(@graphic_frame)
|
201
|
+
coder.write_byte(@graphic_opacity)
|
202
|
+
coder.write_byte(@graphic_render_mode)
|
203
|
+
coder.write(@conditions)
|
204
|
+
coder.write(@movement)
|
205
|
+
coder.write_byte(@flags)
|
206
|
+
coder.write_byte(@route_flags)
|
207
|
+
coder.write_int(@route.size)
|
212
208
|
@route.each do |cmd|
|
213
|
-
cmd.dump(
|
209
|
+
cmd.dump(coder)
|
214
210
|
end
|
215
|
-
|
211
|
+
coder.write_int(@commands.size)
|
216
212
|
@commands.each do |cmd|
|
217
|
-
cmd.dump(
|
213
|
+
cmd.dump(coder)
|
218
214
|
end
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
215
|
+
coder.write(COMMANDS_TERMINATOR)
|
216
|
+
coder.write_byte(@shadow_graphic_num)
|
217
|
+
coder.write_byte(@collision_width)
|
218
|
+
coder.write_byte(@collision_height)
|
219
|
+
coder.write_byte(0x7A)
|
224
220
|
end
|
225
221
|
|
226
222
|
COMMANDS_TERMINATOR = [
|
data/lib/wolfrpg/route.rb
CHANGED
@@ -1,25 +1,25 @@
|
|
1
1
|
module WolfRpg
|
2
2
|
class RouteCommand
|
3
|
-
def self.create(
|
3
|
+
def self.create(coder)
|
4
4
|
# Read all data for this movement command from file
|
5
|
-
id =
|
6
|
-
args = Array.new(
|
5
|
+
id = coder.read_byte
|
6
|
+
args = Array.new(coder.read_byte)
|
7
7
|
args.each_index do |i|
|
8
|
-
args[i] =
|
8
|
+
args[i] = coder.read_int
|
9
9
|
end
|
10
|
-
|
10
|
+
coder.verify(TERMINATOR)
|
11
11
|
|
12
12
|
#TODO Create proper route command
|
13
13
|
return RouteCommand.new(id, args)
|
14
14
|
end
|
15
15
|
|
16
|
-
def dump(
|
17
|
-
|
18
|
-
|
16
|
+
def dump(coder)
|
17
|
+
coder.write_byte(@id)
|
18
|
+
coder.write_byte(@args.size)
|
19
19
|
@args.each do |arg|
|
20
|
-
|
20
|
+
coder.write_int(arg)
|
21
21
|
end
|
22
|
-
|
22
|
+
coder.write(TERMINATOR)
|
23
23
|
end
|
24
24
|
|
25
25
|
attr_accessor :id
|