wolftrans 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|