Skip to content

Commit 32c7006

Browse files
author
Matthew Lyons
committed
Extra formats, 1:1 fixes and diff tool
1 parent 05a5d5a commit 32c7006

11 files changed

+142
-68
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
/spec/reports/
88
/tmp/
99
Data*/*
10+
Scripts/*
11+
12+
ins1
13+
ins2
1014

1115
# rspec failure tracking
1216
.rspec_status

.vscode/launch.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"type": "Ruby",
1010
"request": "launch",
1111
"program": "${workspaceRoot}/bin/rmxp_extractor",
12-
"args": ["import"]
12+
"args": ["export", "rb"]
1313
}
1414
]
1515
}

Gemfile

+4-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@ gem "oj"
77
gem "ruby-progressbar"
88
gem "fileutils"
99
gem "pathname"
10-
gem "json"
1110
gem "zlib"
11+
gem "toml-rb"
12+
gem "json"
13+
gem "yaml"
14+
gem "amazing_print"

Gemfile.lock

+8
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,31 @@ PATH
1212
GEM
1313
remote: https://rubygems.org/
1414
specs:
15+
amazing_print (1.4.0)
16+
citrus (3.0.2)
1517
fileutils (1.6.0)
1618
json (2.6.1)
1719
oj (3.13.11)
1820
pathname (0.2.0)
1921
ruby-progressbar (1.11.0)
22+
toml-rb (2.1.2)
23+
citrus (~> 3.0, > 3.0)
24+
yaml (0.1.1)
2025
zlib (2.1.1)
2126

2227
PLATFORMS
2328
x86_64-linux
2429

2530
DEPENDENCIES
31+
amazing_print
2632
fileutils
2733
json
2834
oj
2935
pathname
3036
rmxp_extractor!
3137
ruby-progressbar
38+
toml-rb
39+
yaml
3240
zlib
3341

3442
BUNDLED WITH

README.md

+18-13
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
# RMXP-Extractor
22

3-
A tool for extracting rxdata to a more usable format, namely JSON. You could probably get it working with XML more easily.
4-
I made this a while back, so I don't really remember how it works, other than it was pretty clunky and relied heavily on iteration.
3+
A tool designed to dump rxdata to various git compatible formats. Some work better than others, though.
4+
Toml is extremely slow in comparison to json or yaml. I'd suggest sticking to JSON generally.
5+
Originally, I used a complex monkey patched system to dump things to a hash that was very slow and imprecise. Now, it uses
6+
some neat tricks to do it relatively efficiently. Only problem is, classes dumped or loaded dump their instance variables, meaning that
7+
classes are **not** initialized with their default values at all.
8+
This is important if you are handwriting the config files for whatever reason.
59

6-
Usage:
7-
8-
`rmxp_extractor import|export|sccripts`
10+
Pretty print is also a format, but it uses eval for loading. There's likely a better way of doing this out there, but eh, I'm not bothered.
11+
It's fairly readable though.
912

10-
# Import
11-
Import will import all files in Data_JSON into the rxdata format. It'll take a bit.
13+
There is a minor problem right now with string encoding, especially with oneshot since there's some foul text that's encoded weirdly.
14+
I'll try to fix that later.
1215

13-
# Export
14-
Export will export all files in Data into json. It's a bit faster than JSON.
16+
Usage:
1517

18+
`rmxp_extractor import | export <type> | scripts"`
1619
# Script
1720

1821
Allows you to export Scripts/xScripts to a specified folder. You can also import said specified folder back into Scripts/xScripts.
1922
The last argument `[x]` is optional. Placing just `x` there will extract Scripts.
2023

21-
# Is it flawless
24+
# Is it flawless?
2225

23-
Running export followed by inport should produce an almost identical file with some minor differences.
24-
Move routes will display as blank since they have some extra serialization I don't understand yet, although they still work nontheless.
25-
You may or may not find some files are also a few bytes longer, I'm unsure why. RPG Maker XP probably doesn't exactly follow Marshal spec and cuts some corners.
26+
Running export followed by import should produce an almost identical file with some minor differences.
27+
As far as I'm aware from my testing, a file diff may say the the two are different, but loading them via marshal provides the exact same instance give or take.
28+
Ruby reports the original and imported instances as being different when loaded from Marshal, but pretty printing each instance to a file shows this is not the case.
29+
RPG Maker XP probably doesn't exactly follow Marshal spec and cuts some corners, which is why the file diff shows up differently.
30+
A diff check tool is provided if you want to try it yourself.

diff_check.rb

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
require_relative "lib/rmxp_extractor/classnames"
2+
require "amazing_print"
3+
4+
f = File.open("Data_OLD/Map266.rxdata")
5+
ins1 = Marshal.load(f)
6+
7+
f2 = File.open("Data/Map266.rxdata")
8+
ins2 = Marshal.load(f2)
9+
10+
f = File.open("ins1", "w")
11+
f2 = File.open("ins2", "w")
12+
f.puts ins1.ai(plain: true, raw: true, object_id: false)
13+
f2.puts ins2.ai(plain: true, raw: true, object_id: false)

lib/rmxp_extractor.rb

+23-12
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,40 @@ module RMXPExtractor
44
require "rmxp_extractor/script_handler"
55
require "rmxp_extractor/version"
66

7+
FORMATS = ["toml", "json", "yaml", "rb"]
8+
79
def self.usage
8-
STDERR.puts "usage: rmxp_extractor import|export|scripts"
10+
STDERR.puts "usage: rmxp_extractor import/export <type = json> | scripts"
911
exit 1
1012
end
1113

1214
def self.process(type)
1315
RMXPExtractor.usage if type.length < 1
1416

15-
if type[0] == "import"
16-
RMXPExtractor.import
17-
elsif type[0] == "export"
18-
RMXPExtractor.export
19-
elsif type[0] == "scripts"
20-
if type[3] == "x"
21-
RMXPExtractor.rpgscript(type[2], type[1], true)
22-
elsif type.length < 3 || type.length > 4
23-
STDERR.puts "usage: rmxp_extractor scripts scripts_dir game_dir [x]"
17+
case type[0]
18+
when "import"
19+
check_format(type[1])
20+
import(type[1])
21+
when "export"
22+
check_format(type[1])
23+
export(type[1])
24+
when "scripts"
25+
if type.length < 4 || type.length > 5
26+
STDERR.puts "usage: rmxp_extractor scripts scripts_dir scripts_name game_dir [x]"
2427
exit 1
25-
elsif type[3] == nil
26-
RMXPExtractor.rpgscript(type[2], type[1], false)
28+
else
29+
RMXPExtractor.rpgscript(type[3], type[4], type[5] == "x")
2730
end
2831
else
2932
RMXPExtractor.usage
3033
end
3134
end
35+
36+
def self.check_format(format)
37+
format = "json" if format.nil?
38+
unless FORMATS.include?(format)
39+
warn "Allowed formats: #{FORMATS.to_s}"
40+
exit 1
41+
end
42+
end
3243
end

lib/rmxp_extractor/classnames.rb

+16-2
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ def create_from_rmxp_serialize(obj)
99
ret = {}
1010
# Hashes have no map method. Why? I dunno!
1111
obj.each do |key, value|
12-
if key.start_with?("class ")
12+
if key.is_a?(String) && key.start_with?("class ")
1313
const = const_from_string(key.delete_prefix("class "))
1414
ret = const.new.from_rmxp_serialize(value)
1515
else
16-
if key =~ /\A[0-9]+\z/ # Key is an integer
16+
if key.is_a?(String) && key =~ /\A[0-9]+\z/ # Key is an integer
1717
key = key.to_i
1818
end
1919
ret[key] = create_from_rmxp_serialize(value)
@@ -108,6 +108,20 @@ def _dump(limit)
108108
def self._load(obj)
109109
data = obj.unpack("VVVVVv*")
110110
@num_of_dimensions, @xsize, @ysize, @zsize, @num_of_elements, *@elements = *data
111+
if @num_of_dimensions > 1
112+
if @xsize > 1
113+
@elements = @elements.each_slice(@xsize).to_a
114+
else
115+
@elements = @elements.map { |element| [element] }
116+
end
117+
end
118+
if @num_of_dimensions > 2
119+
if @ysize > 1
120+
@elements = @elements.each_slice(@ysize).to_a
121+
else
122+
@elements = @elements.map { |element| [element] }
123+
end
124+
end
111125
end
112126
end
113127

lib/rmxp_extractor/data_export.rb

+25-13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
module RMXPExtractor
2-
def self.export
2+
def self.export(format)
33
STDERR.puts "No Data Directory!" unless Dir.exists? "./Data"
44
exit 1 unless Dir.exists? "./Data"
55

66
require "json"
7+
require "toml-rb"
8+
require "yaml"
9+
require "amazing_print"
710
require "ruby-progressbar"
811
require "fileutils"
912
require "pathname"
@@ -12,18 +15,19 @@ def self.export
1215
require_relative "script_handler"
1316

1417
window_size = 120
18+
progress_format = "%a /%e |%B| %p%% %c/%C %r files/sec %t, currently processing: "
1519
progress = ProgressBar.create(
16-
format: "%a /%e |%B| %p%% %c/%C %r files/sec %t",
20+
format: progress_format,
1721
starting_at: 0,
1822
total: nil,
1923
output: $stderr,
2024
length: window_size,
21-
title: "Exported",
25+
title: "exported",
2226
remainder_mark: "\e[0;30m█\e[0m",
2327
progress_mark: "█",
2428
unknown_progress_animation_steps: ["==>", ">==", "=>="],
2529
)
26-
Dir.mkdir "./Data_JSON" unless Dir.exists? "./Data_JSON"
30+
Dir.mkdir "./Data_#{format.upcase}" unless Dir.exists? "./Data_#{format.upcase}"
2731
paths = Pathname.glob(("./Data/" + ("*" + ".rxdata")))
2832
count = paths.size
2933
progress.total = count
@@ -32,19 +36,27 @@ def self.export
3236

3337
name = path.basename(".rxdata")
3438
rxdata = Marshal.load(path.read(mode: "rb"))
35-
#puts name.to_s
39+
progress.format = progress_format + name.to_s
3640
case name.to_s
37-
when "xScripts"
38-
RMXPExtractor.rpgscript("./", "./Scripts", true)
39-
content[:version] = VERSION
41+
when "xScripts", "Scripts"
42+
RMXPExtractor.rpgscript("./", "./Scripts", "#{name.to_s}.rxdata", true)
43+
content["version"] = VERSION
4044
else
41-
content[:data] = rxdata.rmxp_serialize
42-
content[:version] = VERSION
45+
content["data"] = rxdata.rmxp_serialize
46+
content["version"] = VERSION
4347
end
4448

45-
json = File.open("./Data_JSON/" + name.sub_ext(".json").to_s, "wb")
46-
#puts content
47-
json.puts JSON.pretty_generate(content)
49+
file = File.open("./Data_#{format.upcase}/" + name.sub_ext(".#{format}").to_s, "wb")
50+
file.puts case format
51+
when "yaml"
52+
content.to_yaml
53+
when "json"
54+
JSON.pretty_generate(content)
55+
when "toml"
56+
TomlRB.dump(content)
57+
when "rb"
58+
content.ai(index: false, indent: 2, plain: true)
59+
end
4860

4961
progress.increment
5062
end

lib/rmxp_extractor/data_import.rb

+28-12
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
module RMXPExtractor
2-
def self.import
3-
STDERR.puts "No Data_JSON Directory!" unless Dir.exists? "./Data_JSON"
4-
exit 1 unless Dir.exists? "./Data_JSON"
2+
def self.import(format)
3+
STDERR.puts "No Data_JSON Directory!" unless Dir.exists? "./Data_#{format.upcase}"
4+
exit 1 unless Dir.exists? "./Data_#{format.upcase}"
55

66
require "oj"
7+
require "toml-rb"
8+
require "yaml"
9+
# require "active_support"
10+
# require "active_support/core_ext"
711
require "ruby-progressbar"
812
require "fileutils"
913
require "pathname"
@@ -12,34 +16,46 @@ def self.import
1216
require_relative "script_handler"
1317

1418
window_size = 120
19+
progress_format = "%a /%e |%B| %p%% %c/%C %r files/sec %t, currently processing: "
1520
progress = ProgressBar.create(
16-
format: "%a /%e |%B| %p%% %c/%C %r files/sec %t",
21+
format: progress_format,
1722
starting_at: 0,
1823
total: nil,
1924
output: $stderr,
2025
length: window_size,
21-
title: "Imported",
26+
title: "imported",
2227
remainder_mark: "\e[0;30m█\e[0m",
2328
progress_mark: "█",
2429
unknown_progress_animation_steps: ["==>", ">==", "=>="],
2530
)
2631
Dir.mkdir "./Data" unless Dir.exists? "./Data"
27-
paths = Pathname.glob(("./Data_JSON/" + ("*" + ".json")))
32+
paths = Pathname.glob(("./Data_#{format.upcase}/" + ("*" + ".#{format}")))
2833
count = paths.size
2934
progress.total = count
3035
paths.each_with_index do |path, i|
31-
name = path.basename(".json")
32-
json = Oj.load path.read(mode: "rb")
36+
name = path.basename(".#{format}")
37+
progress.format = progress_format + name.to_s
38+
file_contents = path.read(mode: "rb")
39+
hash = case format
40+
when "json"
41+
Oj.load file_contents
42+
when "yaml"
43+
YAML.load file_contents
44+
when "toml"
45+
TomlRB.parse file_contents
46+
when "rb"
47+
eval file_contents # Yes, this SHOULD work. Is it bad? Yes.
48+
end
3349

34-
puts "\n\e[33;1mWARNING: Incompatible version format in #{name.to_s}!\e[0m\n" if json["version"] != VERSION
50+
warn "\n\e[33;1mWARNING: Incompatible version format in #{name.to_s}!\e[0m\n" if hash["version"] != VERSION
3551

3652
case name.to_s
37-
when "xScripts"
38-
RMXPExtractor.rpgscript("./", "./Scripts")
53+
when "xScripts", "Scripts"
54+
RMXPExtractor.rpgscript("./", "./Scripts", "#{name.to_s}")
3955
progress.increment
4056
return
4157
else
42-
content = create_from_rmxp_serialize(json["data"])
58+
content = create_from_rmxp_serialize(hash["data"])
4359
end
4460

4561
rxdata = File.open("./Data/" + name.sub_ext(".rxdata").to_s, "wb")

lib/rmxp_extractor/script_handler.rb

+2-14
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,15 @@
22
require "zlib"
33

44
module RMXPExtractor
5-
def self.rpgscript(game_dir, scripts_dir, extract = false)
5+
def self.rpgscript(game_dir, scripts_dir, script_name, extract = false)
66
# Determine version of game engine
77
game_data_dir = File.join(game_dir, "Data")
88
unless Dir.exist? game_data_dir
99
STDERR.puts "error: #{game_dir} does not have a Data subdirectory"
1010
exit 1
1111
end
1212

13-
target_path = nil
14-
Dir.entries(game_data_dir).each do |e|
15-
ext = File.extname(e)
16-
if ext =~ /\.r[xv]data2?/
17-
target_path = File.join(game_data_dir, "xScripts" + ext)
18-
break
19-
end
20-
end
21-
22-
unless target_path
23-
STDERR.puts "warning: could not determine game engine version, assuming XP"
24-
target_path = File.join(game_data_dir, "xScripts.rxdata")
25-
end
13+
target_path = File.join(game_data_dir, script_name)
2614

2715
# Generate path of script list
2816
list_path = File.join(scripts_dir, "_scripts.txt")

0 commit comments

Comments
 (0)