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,76 @@
|
|
1
|
+
require 'wolfrpg/io'
|
2
|
+
|
3
|
+
module WolfRpg
|
4
|
+
class GameDat
|
5
|
+
attr_accessor :unknown1
|
6
|
+
attr_accessor :title
|
7
|
+
attr_accessor :unknown2
|
8
|
+
attr_accessor :font
|
9
|
+
attr_accessor :subfonts
|
10
|
+
attr_accessor :default_pc_graphic
|
11
|
+
attr_accessor :version
|
12
|
+
attr_accessor :unknown3
|
13
|
+
|
14
|
+
def initialize(filename)
|
15
|
+
File.open(filename, 'rb') do |file|
|
16
|
+
IO.verify(file, MAGIC_NUMBER)
|
17
|
+
#TODO what is most of the junk in this file?
|
18
|
+
@unknown1 = IO.read(file, 25)
|
19
|
+
|
20
|
+
@title = IO.read_string(file)
|
21
|
+
if (magic_string = IO.read_string(file)) != MAGIC_STRING
|
22
|
+
raise "magic string invalid (got #{magic_string})"
|
23
|
+
end
|
24
|
+
|
25
|
+
unknown2_size = IO.read_int(file)
|
26
|
+
@unknown2 = IO.read(file, unknown2_size)
|
27
|
+
|
28
|
+
#IO.dump(file, 64)
|
29
|
+
#abort
|
30
|
+
@font = IO.read_string(file)
|
31
|
+
@subfonts = Array.new(3)
|
32
|
+
@subfonts.each_index do |i|
|
33
|
+
@subfonts[i] = IO.read_string(file)
|
34
|
+
end
|
35
|
+
|
36
|
+
@default_pc_graphic = IO.read_string(file)
|
37
|
+
@version = IO.read_string(file)
|
38
|
+
|
39
|
+
# This is the size of the file minus one.
|
40
|
+
# We don't need it, so discard it.
|
41
|
+
file.seek(4, :CUR)
|
42
|
+
|
43
|
+
# We don't care about the rest of this file for translation
|
44
|
+
# purposes.
|
45
|
+
# Someday we will know what the hell is stored in here... But not today.
|
46
|
+
@unknown3 = file.read
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def dump(filename)
|
51
|
+
File.open(filename, 'wb') do |file|
|
52
|
+
IO.write(file, MAGIC_NUMBER)
|
53
|
+
IO.write(file, @unknown1)
|
54
|
+
IO.write_string(file, @title)
|
55
|
+
IO.write_string(file, MAGIC_STRING)
|
56
|
+
IO.write_int(file, @unknown2.bytesize)
|
57
|
+
IO.write(file, @unknown2)
|
58
|
+
IO.write_string(file, @font)
|
59
|
+
@subfonts.each do |subfont|
|
60
|
+
IO.write_string(file, subfont)
|
61
|
+
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
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
MAGIC_NUMBER = [
|
71
|
+
0x00, 0x57, 0x00, 0x00, 0x4f, 0x4c, 0x00, 0x46, 0x4d, 0x00,
|
72
|
+
0x15, 0x00, 0x00, 0x00, # likely an integer
|
73
|
+
].pack('C*')
|
74
|
+
MAGIC_STRING = "0000-0000" # who knows what this is supposed to be
|
75
|
+
end
|
76
|
+
end
|
data/lib/wolfrpg/io.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
module WolfRpg
|
2
|
+
module IO
|
3
|
+
########
|
4
|
+
# Read #
|
5
|
+
def self.read(io, size)
|
6
|
+
io.readpartial(size)
|
7
|
+
end
|
8
|
+
def self.read_byte(io)
|
9
|
+
io.readpartial(1).unpack('C').first
|
10
|
+
end
|
11
|
+
def self.read_int(io)
|
12
|
+
io.readpartial(4).unpack('l<').first
|
13
|
+
end
|
14
|
+
def self.read_string(io)
|
15
|
+
size = read_int(io)
|
16
|
+
return '' if size == 0
|
17
|
+
str = io.readpartial(size - 1).encode(Encoding::UTF_8, Encoding::WINDOWS_31J)
|
18
|
+
raise "string not null-terminated" unless read_byte(io) == 0
|
19
|
+
return str
|
20
|
+
end
|
21
|
+
def self.verify(io, data)
|
22
|
+
got = io.readpartial(data.length)
|
23
|
+
if got != data
|
24
|
+
raise "could not verify magic data (expecting #{data.unpack('C*')}, got #{got.unpack('C*')})"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
def self.dump(io, length)
|
28
|
+
length.times do |i|
|
29
|
+
print " %02x" % read_byte(io)
|
30
|
+
end
|
31
|
+
print "\n"
|
32
|
+
end
|
33
|
+
def self.dump_until(pattern)
|
34
|
+
escaped_pattern = Regexp.escape(pattern)
|
35
|
+
str = ''.force_encoding('BINARY')
|
36
|
+
until str =~ /#{escaped_pattern}\z/nm
|
37
|
+
str << io.readpartial(1)
|
38
|
+
end
|
39
|
+
str.gsub(/#{escaped_pattern}\z/nm, '').each_byte do |byte|
|
40
|
+
print " %02x" % byte
|
41
|
+
end
|
42
|
+
print "\n"
|
43
|
+
end
|
44
|
+
|
45
|
+
#########
|
46
|
+
# Write #
|
47
|
+
def self.write(io, data)
|
48
|
+
io.write(data)
|
49
|
+
end
|
50
|
+
def self.write_byte(io, data)
|
51
|
+
io.write(data.chr)
|
52
|
+
end
|
53
|
+
def self.write_int(io, data)
|
54
|
+
io.write([data].pack('l<'))
|
55
|
+
end
|
56
|
+
def self.write_string(io, data)
|
57
|
+
new_data = data.encode(Encoding::WINDOWS_31J, Encoding::UTF_8)
|
58
|
+
write_int(io, new_data.bytesize + 1)
|
59
|
+
io.write(new_data)
|
60
|
+
write_byte(io, 0)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/wolfrpg/map.rb
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
require 'wolfrpg/io'
|
2
|
+
require 'wolfrpg/route'
|
3
|
+
require 'wolfrpg/command'
|
4
|
+
|
5
|
+
module WolfRpg
|
6
|
+
class Map
|
7
|
+
attr_reader :tileset_id
|
8
|
+
attr_reader :width
|
9
|
+
attr_reader :height
|
10
|
+
attr_reader :events
|
11
|
+
|
12
|
+
#DEBUG
|
13
|
+
attr_reader :filename
|
14
|
+
|
15
|
+
def initialize(filename)
|
16
|
+
@filename = File.basename(filename, '.*')
|
17
|
+
File.open(filename, 'rb') do |file|
|
18
|
+
IO.verify(file, MAGIC_NUMBER)
|
19
|
+
|
20
|
+
@tileset_id = IO.read_int(file)
|
21
|
+
|
22
|
+
# Read basic data
|
23
|
+
@width = IO.read_int(file)
|
24
|
+
@height = IO.read_int(file)
|
25
|
+
@events = Array.new(IO.read_int(file))
|
26
|
+
|
27
|
+
# Read tiles
|
28
|
+
#TODO: interpret this data later
|
29
|
+
@tiles = IO.read(file, @width * @height * 3 * 4)
|
30
|
+
|
31
|
+
# Read events
|
32
|
+
while (indicator = IO.read_byte(file)) == 0x6F
|
33
|
+
event = Event.new(file)
|
34
|
+
@events[event.id] = event
|
35
|
+
end
|
36
|
+
if indicator != 0x66
|
37
|
+
raise "unexpected event indicator: #{indicator.to_s(16)}"
|
38
|
+
end
|
39
|
+
unless file.eof?
|
40
|
+
raise "file not fully parsed"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def dump(filename)
|
46
|
+
File.open(filename, 'wb') do |file|
|
47
|
+
IO.write(file, MAGIC_NUMBER)
|
48
|
+
IO.write_int(file, @tileset_id)
|
49
|
+
IO.write_int(file, @width)
|
50
|
+
IO.write_int(file, @height)
|
51
|
+
IO.write_int(file, @events.size)
|
52
|
+
IO.write(file, @tiles)
|
53
|
+
@events.each do |event|
|
54
|
+
IO.write_byte(file, 0x6F)
|
55
|
+
event.dump(file)
|
56
|
+
end
|
57
|
+
IO.write_byte(file, 0x66)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
#DEBUG method that searches for a string somewhere in the map
|
62
|
+
def grep(needle)
|
63
|
+
@events.each do |event|
|
64
|
+
event.pages.each do |page|
|
65
|
+
page.commands.each_with_index do |command, line|
|
66
|
+
command.string_args.each do |arg|
|
67
|
+
if m = arg.match(needle)
|
68
|
+
print "#{@filename}/#{event.id}/#{page.id+1}/#{line+1}: #{command.cid}\n\t#{command.args}\n\t#{command.string_args}\n"
|
69
|
+
break
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def grep_cid(cid)
|
78
|
+
@events.each do |event|
|
79
|
+
event.pages.each do |page|
|
80
|
+
page.commands.each_with_index do |command, line|
|
81
|
+
if command.cid == cid
|
82
|
+
print "#{@filename}/#{event.id}/#{page.id+1}/#{line+1}: #{command.cid}\n\t#{command.args}\n\t#{command.string_args}\n"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class Event
|
90
|
+
attr_accessor :id
|
91
|
+
attr_accessor :name
|
92
|
+
attr_accessor :x
|
93
|
+
attr_accessor :y
|
94
|
+
attr_accessor :pages
|
95
|
+
|
96
|
+
def initialize(file)
|
97
|
+
IO.verify(file, MAGIC_NUMBER1)
|
98
|
+
@id = IO.read_int(file)
|
99
|
+
@name = IO.read_string(file)
|
100
|
+
@x = IO.read_int(file)
|
101
|
+
@y = IO.read_int(file)
|
102
|
+
@pages = Array.new(IO.read_int(file))
|
103
|
+
IO.verify(file, MAGIC_NUMBER2)
|
104
|
+
|
105
|
+
# Read pages
|
106
|
+
page_id = 0
|
107
|
+
while (indicator = IO.read_byte(file)) == 0x79
|
108
|
+
page = Page.new(file, page_id)
|
109
|
+
@pages[page_id] = page
|
110
|
+
page_id += 1
|
111
|
+
end
|
112
|
+
if indicator != 0x70
|
113
|
+
raise "unexpected event page indicator: #{indicator.to_s(16)}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def dump(file)
|
118
|
+
IO.write(file, MAGIC_NUMBER1)
|
119
|
+
IO.write_int(file, @id)
|
120
|
+
IO.write_string(file, @name)
|
121
|
+
IO.write_int(file, @x)
|
122
|
+
IO.write_int(file, @y)
|
123
|
+
IO.write_int(file, @pages.size)
|
124
|
+
IO.write(file, MAGIC_NUMBER2)
|
125
|
+
|
126
|
+
# Write pages
|
127
|
+
@pages.each do |page|
|
128
|
+
IO.write_byte(file, 0x79)
|
129
|
+
page.dump(file)
|
130
|
+
end
|
131
|
+
IO.write_byte(file, 0x70)
|
132
|
+
end
|
133
|
+
|
134
|
+
class Page
|
135
|
+
attr_accessor :id
|
136
|
+
attr_accessor :unknown1
|
137
|
+
attr_accessor :graphic_name
|
138
|
+
attr_accessor :graphic_direction
|
139
|
+
attr_accessor :graphic_frame
|
140
|
+
attr_accessor :graphic_opacity
|
141
|
+
attr_accessor :graphic_render_mode
|
142
|
+
attr_accessor :conditions
|
143
|
+
attr_accessor :movement
|
144
|
+
attr_accessor :flags
|
145
|
+
attr_accessor :route_flags
|
146
|
+
attr_accessor :route
|
147
|
+
attr_accessor :commands
|
148
|
+
attr_accessor :shadow_graphic_num
|
149
|
+
attr_accessor :collision_width
|
150
|
+
attr_accessor :collision_height
|
151
|
+
|
152
|
+
def initialize(file, id)
|
153
|
+
@id = id
|
154
|
+
|
155
|
+
#TODO ???
|
156
|
+
@unknown1 = IO.read_int(file)
|
157
|
+
|
158
|
+
#TODO further abstract graphics options
|
159
|
+
@graphic_name = IO.read_string(file)
|
160
|
+
@graphic_direction = IO.read_byte(file)
|
161
|
+
@graphic_frame = IO.read_byte(file)
|
162
|
+
@graphic_opacity = IO.read_byte(file)
|
163
|
+
@graphic_render_mode = IO.read_byte(file)
|
164
|
+
|
165
|
+
#TODO parse conditions later
|
166
|
+
@conditions = IO.read(file, 1 + 4 + 4*4 + 4*4)
|
167
|
+
#TODO parse movement options later
|
168
|
+
@movement = IO.read(file, 4)
|
169
|
+
|
170
|
+
#TODO further abstract flags
|
171
|
+
@flags = IO.read_byte(file)
|
172
|
+
|
173
|
+
#TODO further abstract flags
|
174
|
+
@route_flags = IO.read_byte(file)
|
175
|
+
|
176
|
+
# Parse move route
|
177
|
+
@route = Array.new(IO.read_int(file))
|
178
|
+
@route.each_index do |i|
|
179
|
+
@route[i] = RouteCommand.create(file)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Parse commands
|
183
|
+
@commands = Array.new(IO.read_int(file))
|
184
|
+
@commands.each_index do |i|
|
185
|
+
@commands[i] = Command.create(file)
|
186
|
+
end
|
187
|
+
IO.verify(file, COMMANDS_TERMINATOR)
|
188
|
+
|
189
|
+
#TODO abstract these options later
|
190
|
+
@shadow_graphic_num = IO.read_byte(file)
|
191
|
+
@collision_width = IO.read_byte(file)
|
192
|
+
@collision_height = IO.read_byte(file)
|
193
|
+
|
194
|
+
if (terminator = IO.read_byte(file)) != 0x7A
|
195
|
+
raise "page terminator not 7A (found #{terminator.to_s(16)})"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def dump(file)
|
200
|
+
IO.write_int(file, @unknown1)
|
201
|
+
IO.write_string(file, @graphic_name)
|
202
|
+
IO.write_byte(file, @graphic_direction)
|
203
|
+
IO.write_byte(file, @graphic_frame)
|
204
|
+
IO.write_byte(file, @graphic_opacity)
|
205
|
+
IO.write_byte(file, @graphic_render_mode)
|
206
|
+
IO.write(file, @conditions)
|
207
|
+
IO.write(file, @movement)
|
208
|
+
IO.write_byte(file, @flags)
|
209
|
+
IO.write_byte(file, @route_flags)
|
210
|
+
IO.write_int(file, @route.size)
|
211
|
+
@route.each do |cmd|
|
212
|
+
cmd.dump(file)
|
213
|
+
end
|
214
|
+
IO.write_int(file, @commands.size)
|
215
|
+
@commands.each do |cmd|
|
216
|
+
cmd.dump(file)
|
217
|
+
end
|
218
|
+
IO.write(file, COMMANDS_TERMINATOR)
|
219
|
+
IO.write_byte(file, @shadow_graphic_num)
|
220
|
+
IO.write_byte(file, @collision_width)
|
221
|
+
IO.write_byte(file, @collision_height)
|
222
|
+
IO.write_byte(file, 0x7A)
|
223
|
+
end
|
224
|
+
|
225
|
+
COMMANDS_TERMINATOR = [
|
226
|
+
0x03, 0x00, 0x00, 0x00,
|
227
|
+
].pack('C*')
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
MAGIC_NUMBER1 = [
|
232
|
+
0x39, 0x30, 0x00, 0x00
|
233
|
+
].pack('C*')
|
234
|
+
MAGIC_NUMBER2 = [
|
235
|
+
0x00, 0x00, 0x00, 0x00
|
236
|
+
].pack('C*')
|
237
|
+
end
|
238
|
+
|
239
|
+
private
|
240
|
+
MAGIC_NUMBER = [
|
241
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
242
|
+
0x57, 0x4F, 0x4C, 0x46, 0x4D, 0x00,
|
243
|
+
0x00, 0x00, 0x00, 0x00,
|
244
|
+
0x64, 0x00, 0x00, 0x00,
|
245
|
+
0x65,
|
246
|
+
0x05, 0x00, 0x00, 0x00,
|
247
|
+
0x82, 0xC8, 0x82, 0xB5, 0x00,
|
248
|
+
].pack('C*')
|
249
|
+
end
|
250
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module WolfRpg
|
2
|
+
class RouteCommand
|
3
|
+
def self.create(file)
|
4
|
+
# Read all data for this movement command from file
|
5
|
+
id = IO.read_byte(file)
|
6
|
+
args = Array.new(IO.read_byte(file))
|
7
|
+
args.each_index do |i|
|
8
|
+
args[i] = IO.read_int(file)
|
9
|
+
end
|
10
|
+
IO.verify(file, TERMINATOR)
|
11
|
+
|
12
|
+
#TODO Create proper route command
|
13
|
+
return RouteCommand.new(id, args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def dump(file)
|
17
|
+
IO.write_byte(file, @id)
|
18
|
+
IO.write_byte(file, @args.size)
|
19
|
+
@args.each do |arg|
|
20
|
+
IO.write_int(file, arg)
|
21
|
+
end
|
22
|
+
IO.write(file, TERMINATOR)
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_accessor :id
|
26
|
+
attr_accessor :args
|
27
|
+
|
28
|
+
def initialize(id, args)
|
29
|
+
@id = id
|
30
|
+
@args = args
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
TERMINATOR = [0x01, 0x00].pack('C*')
|
35
|
+
end
|
36
|
+
end
|
data/lib/wolftrans.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'wolftrans/patch_text'
|
2
|
+
require 'wolftrans/patch_data'
|
3
|
+
require 'wolfrpg'
|
4
|
+
|
5
|
+
module WolfTrans
|
6
|
+
class Patch
|
7
|
+
def initialize(game_path, patch_path)
|
8
|
+
@strings = Hash.new { |hash, key| hash[key] = Hash.new }
|
9
|
+
load_data(game_path)
|
10
|
+
load_patch(patch_path)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Represents a translated string
|
15
|
+
class Translation
|
16
|
+
attr_reader :patch_filename
|
17
|
+
attr_reader :string
|
18
|
+
attr_accessor :autogenerate
|
19
|
+
alias_method :autogenerate?, :autogenerate
|
20
|
+
|
21
|
+
def initialize(patch_filename, string='', autogenerate=true)
|
22
|
+
@patch_filename = patch_filename
|
23
|
+
@string = string
|
24
|
+
@autogenerate = autogenerate
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
@string
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Version; represents a major/minor version scheme
|
33
|
+
class Version
|
34
|
+
include Comparable
|
35
|
+
attr_accessor :major
|
36
|
+
attr_accessor :minor
|
37
|
+
|
38
|
+
def initialize(major: nil, minor: nil, flt: nil, str: nil)
|
39
|
+
# See if we need to parse from a string
|
40
|
+
if flt
|
41
|
+
string = flt.to_s
|
42
|
+
elsif str
|
43
|
+
string = str
|
44
|
+
else
|
45
|
+
string = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# Extract major and minor numbers
|
49
|
+
if string
|
50
|
+
if match = string.match(/(\d+)\.(\d+)/)
|
51
|
+
@major, @minor = match.captures.map { |s| s.to_i }
|
52
|
+
else
|
53
|
+
raise "could not parse version string '#{string}'"
|
54
|
+
end
|
55
|
+
elsif major && minor
|
56
|
+
@major = major
|
57
|
+
@minor = minor
|
58
|
+
else
|
59
|
+
@major = 0
|
60
|
+
@minor = 0
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
"#{@major}.#{@minor}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def <=>(other)
|
69
|
+
case other
|
70
|
+
when Version
|
71
|
+
return 0 if major == other.major && minor == other.minor
|
72
|
+
return 1 if major > other.major || (major == other.major && minor >= other.minor)
|
73
|
+
return -1
|
74
|
+
when String
|
75
|
+
return self == Version.new(str = other)
|
76
|
+
when Float
|
77
|
+
return self == Version.new(flt = other)
|
78
|
+
end
|
79
|
+
return nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# IO functions
|
84
|
+
module IO
|
85
|
+
def self.read_txt(filename)
|
86
|
+
# Read file into memory, forcing UTF-8 without BOM.
|
87
|
+
text = nil
|
88
|
+
File.open(filename, 'rb:UTF-8') do |file|
|
89
|
+
if text = file.read(3)
|
90
|
+
if text == "\xEF\xBB\xBF"
|
91
|
+
raise "UTF-8 BOM detected; refusing to read file"
|
92
|
+
end
|
93
|
+
text << file.read
|
94
|
+
else
|
95
|
+
STDERR.puts "warning: empty patch file '#{filename}'"
|
96
|
+
return ''
|
97
|
+
end
|
98
|
+
end
|
99
|
+
# Convert Windows newlines and return
|
100
|
+
text.gsub(/\r\n?/, "\n")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
#####################
|
105
|
+
# Utility functions #
|
106
|
+
|
107
|
+
# Sanitize a path; i.e., standardize path separators remove trailing separator
|
108
|
+
def self.sanitize_path(path)
|
109
|
+
if File::ALT_SEPARATOR
|
110
|
+
path = path.gsub(File::ALT_SEPARATOR, '/')
|
111
|
+
end
|
112
|
+
path.sub(/\/$/, '')
|
113
|
+
end
|
114
|
+
|
115
|
+
# Get the name of a path case-insensitively
|
116
|
+
def self.join_path_nocase(parent, child)
|
117
|
+
child_case = Dir.entries(parent).select { |e| e.downcase == child }.first
|
118
|
+
return nil unless child_case
|
119
|
+
return "#{parent}/#{child_case}"
|
120
|
+
end
|
121
|
+
|
122
|
+
# Strip all leading/trailing whitespace, including fullwidth spaces
|
123
|
+
def self.full_strip(str)
|
124
|
+
str.strip.sub(/^\u{3000}*/, '').sub(/\u{3000}*$/, '')
|
125
|
+
end
|
126
|
+
|
127
|
+
# Escape a string for use as a path on the filesystem
|
128
|
+
# https://stackoverflow.com/questions/2270635/invalid-chars-filter-for-file-folder-name-ruby
|
129
|
+
def self.escape_path(path)
|
130
|
+
full_strip(path).gsub(/[\x00\/\\:\*\?\"<>\|]/, '_')
|
131
|
+
end
|
132
|
+
|
133
|
+
###################
|
134
|
+
# Debug functions #
|
135
|
+
def self.grep(dir, needle)
|
136
|
+
Find.find(dir) do |path|
|
137
|
+
next if FileTest.directory? path
|
138
|
+
|
139
|
+
basename = File.basename(path)
|
140
|
+
basename_downcase = basename.downcase
|
141
|
+
basename_noext = File.basename(basename_downcase, '.*')
|
142
|
+
parent_path = File.dirname(path)
|
143
|
+
ext = File.extname(basename_downcase)
|
144
|
+
|
145
|
+
if ext.downcase == '.mps'
|
146
|
+
WolfRpg::Map.new(path).grep(needle)
|
147
|
+
elsif ext.downcase == '.project'
|
148
|
+
next if basename_downcase == 'sysdatabasebasic.project'
|
149
|
+
dat_filename = WolfTrans.join_path_nocase(parent_path, "#{basename_noext}.dat")
|
150
|
+
next if dat_filename == nil
|
151
|
+
WolfRpg::Database.new(path, dat_filename).grep(needle)
|
152
|
+
elsif basename_downcase == 'commonevent.dat'
|
153
|
+
WolfRpg::CommonEvents.new(path).grep(needle)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.grep_cid(dir, cid)
|
159
|
+
Find.find(dir) do |path|
|
160
|
+
next if FileTest.directory? path
|
161
|
+
if File.extname(path).downcase == '.mps'
|
162
|
+
WolfRpg::Map.new(path).grep_cid(cid)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Latest patch version format that can be read
|
168
|
+
TXT_VERSION = Version.new(major: 1, minor: 0)
|
169
|
+
end
|