rvpacker-ng 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/.prettierrc +9 -0
- data/Gemfile +4 -0
- data/LICENSE +17 -0
- data/README.md +107 -0
- data/Rakefile +2 -0
- data/bin/rvpacker +39 -0
- data/lib/RGSS/BasicCoder.rb +53 -0
- data/lib/RGSS/serialize.rb +448 -0
- data/lib/RGSS.rb +409 -0
- data/lib/RPG.rb +60 -0
- data/lib/rvpacker/version.rb +3 -0
- data/rvpacker.gemspec +23 -0
- metadata +144 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 548008eb85d312d1f74cf7be57e145797b3a6e0506c66e7768159ab74dd26ced
|
4
|
+
data.tar.gz: f8ead7347cdc744c982395ec592021ee48c24643e752b809ed24b9aea78cf8b2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8f298da9896242cacd9616f932cd17dfc5cf392d2e0810e055663d6b6995fd660559c77e201dc60f250c47f67536fc7fdf1e63e501b6dc74422213e0c17d62d7
|
7
|
+
data.tar.gz: 6a6520720f0eba8d9db96455caaaee8d0a11b7a8125a21de150196bca1fa5aedb4ca3701dda293e7b1f47bccd0dfa2322d401ec9046f3b71b3376c121b40e2e9
|
data/.gitignore
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Specific to RubyMotion:
|
13
|
+
.dat*
|
14
|
+
.repl_history
|
15
|
+
build/
|
16
|
+
|
17
|
+
## Documentation cache and generated files:
|
18
|
+
/.yardoc/
|
19
|
+
/_yardoc/
|
20
|
+
/doc/
|
21
|
+
/rdoc/
|
22
|
+
|
23
|
+
## Environment normalisation:
|
24
|
+
/.bundle/
|
25
|
+
/lib/bundler/man/
|
26
|
+
|
27
|
+
# for a library or gem, you might want to ignore these files since the code is
|
28
|
+
# intended to run in multiple environments; otherwise, check them in:
|
29
|
+
Gemfile.lock
|
30
|
+
.ruby-version
|
31
|
+
.ruby-gemset
|
32
|
+
|
33
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
34
|
+
.rvmrc
|
data/.prettierrc
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Copyright (c) 2013 Howard Jeng
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
4
|
+
software and associated documentation files (the "Software"), to deal in the Software
|
5
|
+
without restriction, including without limitation the rights to use, copy, modify, merge,
|
6
|
+
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
7
|
+
to whom the Software is furnished to do so, subject to the following conditions:
|
8
|
+
|
9
|
+
The above copyright notice and this permission notice shall be included in all copies or
|
10
|
+
substantial portions of the Software.
|
11
|
+
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
13
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
14
|
+
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
15
|
+
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
17
|
+
DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# rvpacker
|
2
|
+
|
3
|
+
A tool to unpack & pack RPGMaker data files into text so they can be version controlled & collaborated on. It's a port of Ruby 1.9.x's rvpacker to Ruby 3.x.
|
4
|
+
|
5
|
+
rvpacker consists of 3 parts:
|
6
|
+
|
7
|
+
* RPG library (stub classes for serialization of RPGMaker game data)
|
8
|
+
* RGSS library (some more classes for RPGMaker serialization)
|
9
|
+
* rvpacker (the script you call on the frontend)
|
10
|
+
|
11
|
+
# Installation
|
12
|
+
|
13
|
+
```
|
14
|
+
$ gem install rvpacker-ng
|
15
|
+
```
|
16
|
+
|
17
|
+
Usage
|
18
|
+
=====
|
19
|
+
|
20
|
+
```
|
21
|
+
$ rvpacker --help
|
22
|
+
Options:
|
23
|
+
-a, --action=<s> Action to perform on project (unpack|pack)
|
24
|
+
-d, --project=<s> RPG Maker Project directory
|
25
|
+
-f, --force Update target even when source is older than target
|
26
|
+
-t, --project-type=<s> Project type (vx|ace|xp)
|
27
|
+
-V, --verbose Print verbose information while processing
|
28
|
+
-D, --database=<s> Only work on the given database
|
29
|
+
-h, --help Show this message
|
30
|
+
```
|
31
|
+
|
32
|
+
For example, to unpack a RPG Maker VX Ace project in ~/Documents/RPGVXAce/Project1:
|
33
|
+
|
34
|
+
```
|
35
|
+
$ rvpacker --action unpack --project ~/Documents/RPGVXAce/Project1 --project-type ace
|
36
|
+
```
|
37
|
+
|
38
|
+
This will expand all Data/* files into (PROJECT)/YAML/ as YAML files (YAML is used because the object serialization data is retained, which ruby's YAML parser is very good at - otherwise I would have changed it to JSON). The Scripts will be unpacked as individual .rb files into (PROJECT)/Scripts/.
|
39
|
+
|
40
|
+
To take a previously unpacked project, and pack it back up:
|
41
|
+
|
42
|
+
```
|
43
|
+
$ rvpacker --action pack --project ~/Documents/RPGVXAce/Project1 --project-type ace
|
44
|
+
```
|
45
|
+
|
46
|
+
This will take all of the yaml files in (PROJECT)/YAML and all the scripts in (PROJECT)/Scripts, and repack all of your (PROJECT)/Data/* files. You can trust this to completely reassemble your Data/ directory, so long as the Scripts/ and YAML/ directories remain intact.
|
47
|
+
|
48
|
+
## FAQ
|
49
|
+
|
50
|
+
### General
|
51
|
+
|
52
|
+
This is great for teams that are collaborating on an RPG Maker project. Just add a few steps to your existing workflow:
|
53
|
+
|
54
|
+
* Checkout the project from version control
|
55
|
+
* Run 'rvpacker --action pack' on the project to repack it for the RPG Maker tool
|
56
|
+
* Load up RPG Maker and do whatever you're going to do; save the project
|
57
|
+
* Run 'rvpacker --action unpack' on the project
|
58
|
+
* Commit everything to version control (ignore the Data directory since you don't need it anymore; use .gitignore or .hgignore or whatever)
|
59
|
+
|
60
|
+
Now your project can be forked/merged in a much more safe/sane way, and you don't have to have someone bottlenecking the entire process.
|
61
|
+
|
62
|
+
### Avoiding Map Collisions
|
63
|
+
|
64
|
+
One thing that rvpacker really can't help you with right now (and, ironically, probably one of the reasons you want it) is map collisions. Consider this situation:
|
65
|
+
|
66
|
+
* The project has 10 maps in it, total.
|
67
|
+
* Developer A makes a new map. It gets saved by the editor as Map011
|
68
|
+
* Developer B makes a new map, in a different branch. It also gets saved by the editor as Map011.
|
69
|
+
* Developer A and Developer B attempt to merge their changes. The merge fails because of the collision on the Map011 file.
|
70
|
+
|
71
|
+
The best way to avoid this that I can see is to use blocks of pre-allocated maps. You appoint one person in your project to be principally responsible for the map assets. It then becomes this person's responsibility to allocate maps in "blocks", so that people can work on maps in a distributed way without clobbering one another. The workflow looks like this:
|
72
|
+
|
73
|
+
* The project has 10 maps in it, total.
|
74
|
+
* Developer A needs to make 4 maps. He sends a request to the "map owner", requesting a block of 4 maps.
|
75
|
+
* The map owner creates 4 default, blank maps, and names them all "Request #12345" for Developer A
|
76
|
+
* Developer A starts working on his maps
|
77
|
+
* Developer B needs to make 6 maps. He sends a request to the "map owner", requesting a block of 6 maps.
|
78
|
+
* The map owner 4 default, blank maps, and names them all "Request #12346" or something similar for Developer B
|
79
|
+
* Developer B starts working on his maps
|
80
|
+
|
81
|
+
Using this workflow, it doesn't matter what order Developers A and B request their map blocks, or what order the map owner creates their map blocks. By giving the map owner the authority to create the map blocks, the individual developers can work freely in their map blocks. They can rename them, reorder them, change all of the map attributes (Size, tileset, whatever), without getting in danger of a map collision.
|
82
|
+
|
83
|
+
While this may seem like unnecessary process, it is a reasonable workaround. For a better explanation of why rvpacker can't do this for you, read the next section.
|
84
|
+
|
85
|
+
### Automatic ID generation
|
86
|
+
|
87
|
+
You can add new elements to the YAML files manually, and leave their 'id:' field set to 'null'. This will cause the rvpacker pack action to automatically assign them a new ID number at the end of the sequence (e.g., if you have 17 items, the new one becomes ID 18). This is mainly handy for adding new scripts to the project without having to open the RPG maker and paste the script in; just make the new script file, add its entry in YAML/Scripts.yaml, and the designer will have your script accessible the next time they repack and open the project.
|
88
|
+
|
89
|
+
Also, the rvpacker tool sets the ID of script files to an autoincrementing integer. The scripts exist in the database with a magic number that I can't recreate, and nothing in the editor (RPG VX Ace anyway) seems to care if the magic number changes. It doesn't even affect the ordering. So in order to support adding new scripts with null IDs, like everything else, the magic numbers on scripts are disregarded and a new ID number is forced on the scripts when the rvpacker pack action occurs.
|
90
|
+
|
91
|
+
Note that this does not apply to Map files. Do not try changing the map ID numbers manually (see the "Avoiding Map Collisions" workflow, above, and "Why rvpacker can't help with map collisions", below).
|
92
|
+
|
93
|
+
### Why rvpacker can't help with map collisions
|
94
|
+
|
95
|
+
If you look at the map collision problem described above, the way out of this situation might seem obvious: "Rename Map011.yaml in one of the branches to Map012.yaml, and problem solved. However, there are several significant problems with this approach:
|
96
|
+
|
97
|
+
* The ID numbers on the map files correspond to ID number entries in MapInfos.yaml (and the corresponding MapInfos.rvdata objects)
|
98
|
+
* The ID numbers are used to specify a parent/child relationship between one or more maps
|
99
|
+
* The ID numbers are used to specify the target of a map transition/warp event in event scripting
|
100
|
+
|
101
|
+
This means that changing the ID number assigned to a map (and, thereby, making it possible to merge 2 maps with the same ID number) becomes very nontrivial. The event scripting portion, especially, presents a difficult problem for rvpacker to overcome. It is simple enough for rvpacker to change the IDs of any new map created, and to change the reference to that ID number from any child maps. However, the events are where it gets sticky. The format of event calls in RPG Maker map files is not terribly well defined, and even if it was, I sincerely doubt that you want rvpacker tearing around in the guts of your map events.
|
102
|
+
|
103
|
+
## Credit to SiCrane
|
104
|
+
|
105
|
+
The RPG and RGSS libraries were originally taken from SiCrane's YAML importer/exporter on the gamedev forums. I initially just put them in github so I wouldn't lose them, and added the rvpacker script frontend. They are starting to drift a bit, but SiCrane still gets original credit for the grand majority of the work that rvpacker does.
|
106
|
+
|
107
|
+
http://www.gamedev.net/topic/646333-rpg-maker-vx-ace-data-conversion-utility/
|
data/Rakefile
ADDED
data/bin/rvpacker
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
require 'optimist'
|
5
|
+
require 'RGSS'
|
6
|
+
|
7
|
+
opts =
|
8
|
+
Optimist.options do
|
9
|
+
opt :action,
|
10
|
+
'Action to perform on project (unpack|pack)',
|
11
|
+
short: 'a',
|
12
|
+
type: String
|
13
|
+
opt :project, 'RPG Maker Project directory', short: 'd', type: String
|
14
|
+
opt :force,
|
15
|
+
'Update target even when source is older than target',
|
16
|
+
short: 'f'
|
17
|
+
opt :project_type, 'Project type (vx|ace|xp)', short: 't', type: String
|
18
|
+
opt :verbose, 'Print verbose information while processing', short: 'V'
|
19
|
+
opt :database,
|
20
|
+
'Only work on the given database',
|
21
|
+
short: 'D',
|
22
|
+
type: String
|
23
|
+
end
|
24
|
+
|
25
|
+
directions = { 'unpack' => :all_bin_to_text, 'pack' => :all_text_to_bin }
|
26
|
+
projecttypes = { 'vx' => :vx, 'ace' => :ace, 'xp' => :xp }
|
27
|
+
$VERBOSE = opts[:verbose]
|
28
|
+
|
29
|
+
RGSS.serialize(
|
30
|
+
projecttypes[opts[:project_type]],
|
31
|
+
directions[opts[:action]],
|
32
|
+
opts[:project],
|
33
|
+
{
|
34
|
+
force: (opts[:force] ? true : false),
|
35
|
+
line_width: -1,
|
36
|
+
table_width: -1,
|
37
|
+
database: opts[:database]
|
38
|
+
}
|
39
|
+
)
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module RGSS
|
2
|
+
module BasicCoder
|
3
|
+
def encode_with(coder)
|
4
|
+
ivars.each do |var|
|
5
|
+
name = var.to_s.sub(/^@/, '')
|
6
|
+
value = instance_variable_get(var)
|
7
|
+
coder[name] = encode(name, value)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def encode(name, value)
|
12
|
+
return value
|
13
|
+
end
|
14
|
+
|
15
|
+
def init_with(coder)
|
16
|
+
coder.map.each do |key, value|
|
17
|
+
sym = "@#{key}".to_sym
|
18
|
+
instance_variable_set(sym, decode(key, value))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def decode(name, value)
|
23
|
+
return value
|
24
|
+
end
|
25
|
+
|
26
|
+
def ivars
|
27
|
+
return instance_variables
|
28
|
+
end
|
29
|
+
|
30
|
+
INCLUDED_CLASSES = []
|
31
|
+
def self.included(mod)
|
32
|
+
INCLUDED_CLASSES.push(mod)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.set_ivars_methods(version)
|
36
|
+
INCLUDED_CLASSES.each do |c|
|
37
|
+
if version == :ace
|
38
|
+
RGSS.reset_method(
|
39
|
+
c,
|
40
|
+
:ivars,
|
41
|
+
-> { return instance_variables }
|
42
|
+
)
|
43
|
+
else
|
44
|
+
RGSS.reset_method(
|
45
|
+
c,
|
46
|
+
:ivars,
|
47
|
+
-> { return instance_variables.sort }
|
48
|
+
)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,448 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (c) 2013 Howard Jeng
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
5
|
+
software and associated documentation files (the "Software"), to deal in the Software
|
6
|
+
without restriction, including without limitation the rights to use, copy, modify, merge,
|
7
|
+
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
8
|
+
to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all copies or
|
11
|
+
substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
14
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
15
|
+
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
16
|
+
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
17
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
18
|
+
DEALINGS IN THE SOFTWARE.
|
19
|
+
=end
|
20
|
+
|
21
|
+
require 'psych'
|
22
|
+
require 'fileutils'
|
23
|
+
require 'zlib'
|
24
|
+
require 'pp'
|
25
|
+
require 'formatador'
|
26
|
+
|
27
|
+
module RGSS
|
28
|
+
def self.change_extension(file, new_ext)
|
29
|
+
return File.basename(file, '.*') + new_ext
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.sanitize_filename(filename)
|
33
|
+
return filename.gsub(/[^0-9A-Za-z]+/, '_')
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.files_with_extension(directory, extension)
|
37
|
+
return(
|
38
|
+
Dir
|
39
|
+
.entries(directory)
|
40
|
+
.select { |file| File.extname(file) == extension }
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.inflate(str)
|
45
|
+
text = Zlib::Inflate.inflate(str)
|
46
|
+
return text.force_encoding('UTF-8')
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.deflate(str)
|
50
|
+
return Zlib::Deflate.deflate(str, Zlib::BEST_COMPRESSION)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.dump_data_file(file, data, time, options)
|
54
|
+
File.open(file, 'wb') { |f| Marshal.dump(data, f) }
|
55
|
+
File.utime(time, time, file)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.dump_yaml_file(file, data, time, options)
|
59
|
+
File.open(file, 'wb') { |f| Psych.dump(data, f, options) }
|
60
|
+
File.utime(time, time, file)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.dump_save(file, data, time, options)
|
64
|
+
File.open(file, 'wb') do |f|
|
65
|
+
data.each { |chunk| Marshal.dump(chunk, f) }
|
66
|
+
end
|
67
|
+
File.utime(time, time, file)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.dump_raw_file(file, data, time, options)
|
71
|
+
File.open(file, 'wb') { |f| f.write(data) }
|
72
|
+
File.utime(time, time, file)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.dump(dumper, file, data, time, options)
|
76
|
+
self.method(dumper).call(file, data, time, options)
|
77
|
+
rescue StandardError
|
78
|
+
warn "Exception dumping #{file}"
|
79
|
+
raise
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.load_data_file(file)
|
83
|
+
File.open(file, 'rb') { |f| return Marshal.load(f) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.load_yaml_file(file)
|
87
|
+
formatador = Formatador.new
|
88
|
+
obj = nil
|
89
|
+
File.open(file, 'rb') { |f| obj = Psych.load(f) }
|
90
|
+
max = 0
|
91
|
+
return obj unless obj.kind_of?(Array)
|
92
|
+
seen = {}
|
93
|
+
idx =
|
94
|
+
obj.each do |elem|
|
95
|
+
next if elem.nil?
|
96
|
+
if elem.instance_variable_defined?('@id')
|
97
|
+
id = elem.instance_variable_get('@id')
|
98
|
+
else
|
99
|
+
id = nil
|
100
|
+
elem.instance_variable_set('@id', nil)
|
101
|
+
end
|
102
|
+
next if id.nil?
|
103
|
+
|
104
|
+
if seen.has_key?(id)
|
105
|
+
formatador.display_line(
|
106
|
+
"[red]#{file}: Duplicate ID #{id}[/]"
|
107
|
+
)
|
108
|
+
formatador.indent do
|
109
|
+
formatador.indent do
|
110
|
+
elem
|
111
|
+
.pretty_inspect
|
112
|
+
.split(/\n/)
|
113
|
+
.each do |line|
|
114
|
+
formatador.display_line("[red]#{line}[/]")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
formatador.display_line
|
118
|
+
formatador.display_line("[red]Last seen at:\n[/]")
|
119
|
+
formatador.indent do
|
120
|
+
elem
|
121
|
+
.pretty_inspect
|
122
|
+
.split(/\n/)
|
123
|
+
.each do |line|
|
124
|
+
formatador.display_line("[red]#{line}[/]")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
exit
|
129
|
+
end
|
130
|
+
seen[id] = elem
|
131
|
+
max = ((id + 1) unless id < max)
|
132
|
+
end
|
133
|
+
obj.each do |elem|
|
134
|
+
next if elem.nil?
|
135
|
+
id = elem.instance_variable_get('@id')
|
136
|
+
if id.nil?
|
137
|
+
elem.instance_variable_set('@id', max)
|
138
|
+
max += 1
|
139
|
+
end
|
140
|
+
end
|
141
|
+
return obj
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.load_raw_file(file)
|
145
|
+
File.open(file, 'rb') { |f| return f.read }
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.load_save(file)
|
149
|
+
File.open(file, 'rb') do |f|
|
150
|
+
data = []
|
151
|
+
while not f.eof?
|
152
|
+
o = Marshal.load(f)
|
153
|
+
data.push(o)
|
154
|
+
end
|
155
|
+
return data
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def self.load(loader, file)
|
160
|
+
return self.method(loader).call(file)
|
161
|
+
rescue StandardError
|
162
|
+
warn "Exception loading #{file}"
|
163
|
+
raise
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.scripts_to_text(dirs, src, dest, options)
|
167
|
+
formatador = Formatador.new
|
168
|
+
src_file = File.join(dirs[:data], src)
|
169
|
+
dest_file = File.join(dirs[:yaml], dest)
|
170
|
+
raise "Missing #{src}" unless File.exist?(src_file)
|
171
|
+
|
172
|
+
script_entries = load(:load_data_file, src_file)
|
173
|
+
check_time = !options[:force] && File.exist?(dest_file)
|
174
|
+
oldest_time = File.mtime(dest_file) if check_time
|
175
|
+
|
176
|
+
file_map, script_index, script_code = Hash.new(-1), [], {}
|
177
|
+
|
178
|
+
idx = 0
|
179
|
+
script_entries.each do |script|
|
180
|
+
idx += 1
|
181
|
+
magic_number, script_name, code = idx, script[1], inflate(script[2])
|
182
|
+
script_name.force_encoding('UTF-8')
|
183
|
+
|
184
|
+
if code.length > 0
|
185
|
+
filename =
|
186
|
+
if script_name.empty?
|
187
|
+
'blank'
|
188
|
+
else
|
189
|
+
sanitize_filename(script_name)
|
190
|
+
end
|
191
|
+
key = filename.upcase
|
192
|
+
value = (file_map[key] += 1)
|
193
|
+
actual_filename =
|
194
|
+
filename + (value == 0 ? '' : ".#{value}") + RUBY_EXT
|
195
|
+
script_index.push([magic_number, script_name, actual_filename])
|
196
|
+
full_filename = File.join(dirs[:script], actual_filename)
|
197
|
+
script_code[full_filename] = code
|
198
|
+
check_time = false unless File.exist?(full_filename)
|
199
|
+
oldest_time = [
|
200
|
+
File.mtime(full_filename),
|
201
|
+
oldest_time
|
202
|
+
].min if check_time
|
203
|
+
else
|
204
|
+
script_index.push([magic_number, script_name, nil])
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
src_time = File.mtime(src_file)
|
209
|
+
if check_time && (src_time - 1) < oldest_time
|
210
|
+
if $VERBOSE
|
211
|
+
formatador.display_line('[yellow]Skipping scripts to text[/]')
|
212
|
+
end
|
213
|
+
else
|
214
|
+
if $VERBOSE
|
215
|
+
formatador.display_line('[green]Converting scripts to text[/]')
|
216
|
+
end
|
217
|
+
dump(:dump_yaml_file, dest_file, script_index, src_time, options)
|
218
|
+
script_code.each do |file, code|
|
219
|
+
dump(:dump_raw_file, file, code, src_time, options)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def self.scripts_to_binary(dirs, src, dest, options)
|
225
|
+
formatador = Formatador.new
|
226
|
+
src_file = File.join(dirs[:yaml], src)
|
227
|
+
dest_file = File.join(dirs[:data], dest)
|
228
|
+
raise "Missing #{src}" unless File.exist?(src_file)
|
229
|
+
check_time = !options[:force] && File.exist?(dest_file)
|
230
|
+
newest_time = File.mtime(src_file) if check_time
|
231
|
+
|
232
|
+
index = load(:load_yaml_file, src_file)
|
233
|
+
script_entries = []
|
234
|
+
index.each do |entry|
|
235
|
+
magic_number, script_name, filename = entry
|
236
|
+
code = ''
|
237
|
+
if filename
|
238
|
+
full_filename = File.join(dirs[:script], filename)
|
239
|
+
unless File.exist?(full_filename)
|
240
|
+
raise "Missing script file #{filename}"
|
241
|
+
end
|
242
|
+
newest_time = [
|
243
|
+
File.mtime(full_filename),
|
244
|
+
newest_time
|
245
|
+
].max if check_time
|
246
|
+
code = load(:load_raw_file, full_filename)
|
247
|
+
end
|
248
|
+
script_entries.push([magic_number, script_name, deflate(code)])
|
249
|
+
end
|
250
|
+
if check_time && (newest_time - 1) < File.mtime(dest_file)
|
251
|
+
if $VERBOSE
|
252
|
+
formatador.display_line('[yellow]Skipping scripts to binary[/]')
|
253
|
+
end
|
254
|
+
else
|
255
|
+
if $VERBOSE
|
256
|
+
formatador.display_line(
|
257
|
+
'[green]Converting scripts to binary[/]'
|
258
|
+
)
|
259
|
+
end
|
260
|
+
dump(
|
261
|
+
:dump_data_file,
|
262
|
+
dest_file,
|
263
|
+
script_entries,
|
264
|
+
newest_time,
|
265
|
+
options
|
266
|
+
)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def self.process_file(
|
271
|
+
file,
|
272
|
+
src_file,
|
273
|
+
dest_file,
|
274
|
+
dest_ext,
|
275
|
+
loader,
|
276
|
+
dumper,
|
277
|
+
options
|
278
|
+
)
|
279
|
+
formatador = Formatador.new
|
280
|
+
fbase = File.basename(file, File.extname(file))
|
281
|
+
if (!options[:database].nil?) and
|
282
|
+
(options[:database].downcase != fbase.downcase)
|
283
|
+
return
|
284
|
+
end
|
285
|
+
src_time = File.mtime(src_file)
|
286
|
+
if !options[:force] && File.exist?(dest_file) &&
|
287
|
+
(src_time - 1) < File.mtime(dest_file)
|
288
|
+
formatador.display_line("[yellow]Skipping #{file}[/]") if $VERBOSE
|
289
|
+
else
|
290
|
+
if $VERBOSE
|
291
|
+
formatador.display_line(
|
292
|
+
"[green]Converting #{file} to #{dest_ext}[/]"
|
293
|
+
)
|
294
|
+
end
|
295
|
+
data = load(loader, src_file)
|
296
|
+
dump(dumper, dest_file, data, src_time, options)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def self.convert(src, dest, options)
|
301
|
+
files = files_with_extension(src[:directory], src[:ext])
|
302
|
+
files -= src[:exclude]
|
303
|
+
|
304
|
+
files.each do |file|
|
305
|
+
src_file = File.join(src[:directory], file)
|
306
|
+
dest_file =
|
307
|
+
File.join(dest[:directory], change_extension(file, dest[:ext]))
|
308
|
+
|
309
|
+
process_file(
|
310
|
+
file,
|
311
|
+
src_file,
|
312
|
+
dest_file,
|
313
|
+
dest[:ext],
|
314
|
+
src[:load_file],
|
315
|
+
dest[:dump_file],
|
316
|
+
options
|
317
|
+
)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def self.convert_saves(base, src, dest, options)
|
322
|
+
save_files = files_with_extension(base, src[:ext])
|
323
|
+
save_files.each do |file|
|
324
|
+
src_file = File.join(base, file)
|
325
|
+
dest_file = File.join(base, change_extension(file, dest[:ext]))
|
326
|
+
|
327
|
+
process_file(
|
328
|
+
file,
|
329
|
+
src_file,
|
330
|
+
dest_file,
|
331
|
+
dest[:ext],
|
332
|
+
src[:load_save],
|
333
|
+
dest[:dump_save],
|
334
|
+
options
|
335
|
+
)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# [version] one of :ace, :vx, :xp
|
340
|
+
# [direction] one of :data_bin_to_text, :data_text_to_bin, :save_bin_to_text,
|
341
|
+
# :save_text_to_bin, :scripts_bin_to_text, :scripts_text_to_bin,
|
342
|
+
# :all_text_to_bin, :all_bin_to_text
|
343
|
+
# [directory] directory that project file is in
|
344
|
+
# [options] :force - ignores file dates when converting (default false)
|
345
|
+
# :round_trip - create yaml data that matches original marshalled data skips
|
346
|
+
# data cleanup operations (default false)
|
347
|
+
# :line_width - line width form YAML files, -1 for no line width limit
|
348
|
+
# (default 130)
|
349
|
+
# :table_width - maximum number of entries per row for table data, -1 for no
|
350
|
+
# table row limit (default 20)
|
351
|
+
def self.serialize(version, direction, directory, options = {})
|
352
|
+
raise "#{directory} not found" unless File.exist?(directory)
|
353
|
+
|
354
|
+
setup_classes(version, options)
|
355
|
+
options = options.clone
|
356
|
+
options[:sort] = true if %i[vx xp].include?(version)
|
357
|
+
options[:flow_classes] = FLOW_CLASSES
|
358
|
+
options[:line_width] ||= 130
|
359
|
+
|
360
|
+
table_width = options[:table_width]
|
361
|
+
RGSS.reset_const(Table, :MAX_ROW_LENGTH, table_width ? table_width : 20)
|
362
|
+
|
363
|
+
base = File.realpath(directory)
|
364
|
+
|
365
|
+
dirs = {
|
366
|
+
base: base,
|
367
|
+
data: get_data_directory(base),
|
368
|
+
yaml: get_yaml_directory(base),
|
369
|
+
script: get_script_directory(base)
|
370
|
+
}
|
371
|
+
|
372
|
+
dirs.values.each { |d| FileUtils.mkdir(d) unless File.exist?(d) }
|
373
|
+
|
374
|
+
exts = { ace: ACE_DATA_EXT, vx: VX_DATA_EXT, xp: XP_DATA_EXT }
|
375
|
+
|
376
|
+
yaml_scripts = SCRIPTS_BASE + YAML_EXT
|
377
|
+
yaml = {
|
378
|
+
directory: dirs[:yaml],
|
379
|
+
exclude: [yaml_scripts],
|
380
|
+
ext: YAML_EXT,
|
381
|
+
load_file: :load_yaml_file,
|
382
|
+
dump_file: :dump_yaml_file,
|
383
|
+
load_save: :load_yaml_file,
|
384
|
+
dump_save: :dump_yaml_file
|
385
|
+
}
|
386
|
+
|
387
|
+
scripts = SCRIPTS_BASE + exts[version]
|
388
|
+
data = {
|
389
|
+
directory: dirs[:data],
|
390
|
+
exclude: [scripts],
|
391
|
+
ext: exts[version],
|
392
|
+
load_file: :load_data_file,
|
393
|
+
dump_file: :dump_data_file,
|
394
|
+
load_save: :load_save,
|
395
|
+
dump_save: :dump_save
|
396
|
+
}
|
397
|
+
|
398
|
+
if options[:database].nil? or options[:database].downcase == 'scripts'
|
399
|
+
convert_scripts = true
|
400
|
+
else
|
401
|
+
convert_scripts = false
|
402
|
+
end
|
403
|
+
if options[:database].nil? or options[:database].downcase == 'saves'
|
404
|
+
convert_saves = true
|
405
|
+
else
|
406
|
+
convert_saves = false
|
407
|
+
end
|
408
|
+
|
409
|
+
case direction
|
410
|
+
when :data_bin_to_text
|
411
|
+
convert(data, yaml, options)
|
412
|
+
if convert_scripts
|
413
|
+
scripts_to_text(dirs, scripts, yaml_scripts, options)
|
414
|
+
end
|
415
|
+
when :data_text_to_bin
|
416
|
+
convert(yaml, data, options)
|
417
|
+
if convert_scripts
|
418
|
+
scripts_to_binary(dirs, yaml_scripts, scripts, options)
|
419
|
+
end
|
420
|
+
when :save_bin_to_text
|
421
|
+
convert_saves(base, data, yaml, options) if convert_saves
|
422
|
+
when :save_text_to_bin
|
423
|
+
convert_saves(base, yaml, data, options) if convert_saves
|
424
|
+
when :scripts_bin_to_text
|
425
|
+
if convert_scripts
|
426
|
+
scripts_to_text(dirs, scripts, yaml_scripts, options)
|
427
|
+
end
|
428
|
+
when :scripts_text_to_bin
|
429
|
+
if convert_scripts
|
430
|
+
scripts_to_binary(dirs, yaml_scripts, scripts, options)
|
431
|
+
end
|
432
|
+
when :all_bin_to_text
|
433
|
+
convert(data, yaml, options)
|
434
|
+
if convert_scripts
|
435
|
+
scripts_to_text(dirs, scripts, yaml_scripts, options)
|
436
|
+
end
|
437
|
+
convert_saves(base, data, yaml, options) if convert_saves
|
438
|
+
when :all_text_to_bin
|
439
|
+
convert(yaml, data, options)
|
440
|
+
if convert_scripts
|
441
|
+
scripts_to_binary(dirs, yaml_scripts, scripts, options)
|
442
|
+
end
|
443
|
+
convert_saves(base, yaml, data, options) if convert_saves
|
444
|
+
else
|
445
|
+
raise "Unrecognized direction :#{direction}"
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
data/lib/RGSS.rb
ADDED
@@ -0,0 +1,409 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (c) 2013 Howard Jeng
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
5
|
+
software and associated documentation files (the "Software"), to deal in the Software
|
6
|
+
without restriction, including without limitation the rights to use, copy, modify, merge,
|
7
|
+
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
8
|
+
to whom the Software is furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all copies or
|
11
|
+
substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
14
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
15
|
+
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
16
|
+
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
17
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
18
|
+
DEALINGS IN THE SOFTWARE.
|
19
|
+
=end
|
20
|
+
|
21
|
+
require 'scanf'
|
22
|
+
|
23
|
+
class Table
|
24
|
+
def initialize(bytes)
|
25
|
+
@dim, @x, @y, @z, items, *@data = bytes.unpack('L5 S*')
|
26
|
+
unless items == @data.length
|
27
|
+
raise 'Size mismatch loading Table from data'
|
28
|
+
end
|
29
|
+
unless @x * @y * @z == items
|
30
|
+
raise 'Size mismatch loading Table from data'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
MAX_ROW_LENGTH = 20
|
35
|
+
|
36
|
+
def encode_with(coder)
|
37
|
+
coder.style = Psych::Nodes::Mapping::BLOCK
|
38
|
+
|
39
|
+
coder['dim'] = @dim
|
40
|
+
coder['x'] = @x
|
41
|
+
coder['y'] = @y
|
42
|
+
coder['z'] = @z
|
43
|
+
|
44
|
+
if @x * @y * @z > 0
|
45
|
+
stride = @x < 2 ? (@y < 2 ? @z : @y) : @x
|
46
|
+
rows = @data.each_slice(stride).to_a
|
47
|
+
if MAX_ROW_LENGTH != -1 && stride > MAX_ROW_LENGTH
|
48
|
+
block_length = (stride + MAX_ROW_LENGTH - 1) / MAX_ROW_LENGTH
|
49
|
+
row_length = (stride + block_length - 1) / block_length
|
50
|
+
rows =
|
51
|
+
rows
|
52
|
+
.collect { |x| x.each_slice(row_length).to_a }
|
53
|
+
.flatten(1)
|
54
|
+
end
|
55
|
+
rows = rows.collect { |x| x.collect { |y| '%04x' % y }.join(' ') }
|
56
|
+
coder['data'] = rows
|
57
|
+
else
|
58
|
+
coder['data'] = []
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def init_with(coder)
|
63
|
+
@dim = coder['dim']
|
64
|
+
@x = coder['x']
|
65
|
+
@y = coder['y']
|
66
|
+
@z = coder['z']
|
67
|
+
@data =
|
68
|
+
coder['data'].collect { |x| x.split(' ').collect(&:hex) }.flatten
|
69
|
+
items = @x * @y * @z
|
70
|
+
unless items == @data.length
|
71
|
+
raise 'Size mismatch loading Table from YAML'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def _dump(*ignored)
|
76
|
+
return [@dim, @x, @y, @z, @x * @y * @z, *@data].pack('L5 S*')
|
77
|
+
end
|
78
|
+
|
79
|
+
def self._load(bytes)
|
80
|
+
Table.new(bytes)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class Color
|
85
|
+
def initialize(bytes)
|
86
|
+
@r, @g, @b, @a = *bytes.unpack('D4')
|
87
|
+
end
|
88
|
+
|
89
|
+
def _dump(*ignored)
|
90
|
+
return [@r, @g, @b, @a].pack('D4')
|
91
|
+
end
|
92
|
+
|
93
|
+
def self._load(bytes)
|
94
|
+
Color.new(bytes)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class Tone
|
99
|
+
def initialize(bytes)
|
100
|
+
@r, @g, @b, @a = *bytes.unpack('D4')
|
101
|
+
end
|
102
|
+
|
103
|
+
def _dump(*ignored)
|
104
|
+
return [@r, @g, @b, @a].pack('D4')
|
105
|
+
end
|
106
|
+
|
107
|
+
def self._load(bytes)
|
108
|
+
Tone.new(bytes)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class Rect
|
113
|
+
def initialize(bytes)
|
114
|
+
@x, @y, @width, @height = *bytes.unpack('i4')
|
115
|
+
end
|
116
|
+
|
117
|
+
def _dump(*ignored)
|
118
|
+
return [@x, @y, @width, @height].pack('i4')
|
119
|
+
end
|
120
|
+
|
121
|
+
def self._load(bytes)
|
122
|
+
Rect.new(bytes)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
module RGSS
|
127
|
+
def self.remove_defined_method(scope, name)
|
128
|
+
if scope.instance_methods(false).include?(name)
|
129
|
+
scope.send(:remove_method, name)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.reset_method(scope, name, method)
|
134
|
+
remove_defined_method(scope, name)
|
135
|
+
scope.send(:define_method, name, method)
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.reset_const(scope, sym, value)
|
139
|
+
scope.send(:remove_const, sym) if scope.const_defined?(sym)
|
140
|
+
scope.send(:const_set, sym, value)
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.array_to_hash(arr, &block)
|
144
|
+
h = {}
|
145
|
+
arr.each_with_index do |val, index|
|
146
|
+
r = block_given? ? block.call(val) : val
|
147
|
+
h[index] = r unless r.nil?
|
148
|
+
end
|
149
|
+
if arr.length > 0
|
150
|
+
last = arr.length - 1
|
151
|
+
h[last] = nil unless h.has_key?(last)
|
152
|
+
end
|
153
|
+
return h
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.hash_to_array(hash)
|
157
|
+
arr = []
|
158
|
+
hash.each { |k, v| arr[k] = v }
|
159
|
+
return arr
|
160
|
+
end
|
161
|
+
|
162
|
+
require 'RGSS/BasicCoder'
|
163
|
+
require 'RPG'
|
164
|
+
|
165
|
+
# creates an empty class in a potentially nested scope
|
166
|
+
def self.process(root, name, *args)
|
167
|
+
if args.length > 0
|
168
|
+
process(root.const_get(name), *args)
|
169
|
+
else
|
170
|
+
unless root.const_defined?(name, false)
|
171
|
+
root.const_set(name, Class.new)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# other classes that don't need definitions
|
177
|
+
[
|
178
|
+
# RGSS data structures
|
179
|
+
%i[RPG Actor],
|
180
|
+
%i[RPG Animation],
|
181
|
+
%i[RPG Animation Frame],
|
182
|
+
%i[RPG Animation Timing],
|
183
|
+
%i[RPG Area],
|
184
|
+
%i[RPG Armor],
|
185
|
+
%i[RPG AudioFile],
|
186
|
+
%i[RPG BaseItem],
|
187
|
+
%i[RPG BaseItem Feature],
|
188
|
+
%i[RPG BGM],
|
189
|
+
%i[RPG BGS],
|
190
|
+
%i[RPG Class],
|
191
|
+
%i[RPG Class Learning],
|
192
|
+
%i[RPG CommonEvent],
|
193
|
+
%i[RPG Enemy],
|
194
|
+
%i[RPG Enemy Action],
|
195
|
+
%i[RPG Enemy DropItem],
|
196
|
+
%i[RPG EquipItem],
|
197
|
+
%i[RPG Event],
|
198
|
+
%i[RPG Event Page],
|
199
|
+
%i[RPG Event Page Condition],
|
200
|
+
%i[RPG Event Page Graphic],
|
201
|
+
%i[RPG Item],
|
202
|
+
%i[RPG Map],
|
203
|
+
%i[RPG Map Encounter],
|
204
|
+
%i[RPG MapInfo],
|
205
|
+
%i[RPG ME],
|
206
|
+
%i[RPG MoveCommand],
|
207
|
+
%i[RPG MoveRoute],
|
208
|
+
%i[RPG SE],
|
209
|
+
%i[RPG Skill],
|
210
|
+
%i[RPG State],
|
211
|
+
%i[RPG System Terms],
|
212
|
+
%i[RPG System TestBattler],
|
213
|
+
%i[RPG System Vehicle],
|
214
|
+
%i[RPG System Words],
|
215
|
+
%i[RPG Tileset],
|
216
|
+
%i[RPG Troop],
|
217
|
+
%i[RPG Troop Member],
|
218
|
+
%i[RPG Troop Page],
|
219
|
+
%i[RPG Troop Page Condition],
|
220
|
+
%i[RPG UsableItem],
|
221
|
+
%i[RPG UsableItem Damage],
|
222
|
+
%i[RPG UsableItem Effect],
|
223
|
+
%i[RPG Weapon],
|
224
|
+
# Script classes serialized in save game files
|
225
|
+
[:Game_ActionResult],
|
226
|
+
[:Game_Actor],
|
227
|
+
[:Game_Actors],
|
228
|
+
[:Game_BaseItem],
|
229
|
+
[:Game_BattleAction],
|
230
|
+
[:Game_CommonEvent],
|
231
|
+
[:Game_Enemy],
|
232
|
+
[:Game_Event],
|
233
|
+
[:Game_Follower],
|
234
|
+
[:Game_Followers],
|
235
|
+
[:Game_Interpreter],
|
236
|
+
[:Game_Map],
|
237
|
+
[:Game_Message],
|
238
|
+
[:Game_Party],
|
239
|
+
[:Game_Picture],
|
240
|
+
[:Game_Pictures],
|
241
|
+
[:Game_Player],
|
242
|
+
[:Game_System],
|
243
|
+
[:Game_Timer],
|
244
|
+
[:Game_Troop],
|
245
|
+
[:Game_Screen],
|
246
|
+
[:Game_Vehicle],
|
247
|
+
[:Interpreter]
|
248
|
+
].each { |x| process(Object, *x) }
|
249
|
+
|
250
|
+
def self.setup_system(version, options)
|
251
|
+
# convert variable and switch name arrays to a hash when serialized
|
252
|
+
# if round_trip isn't set change version_id to fixed number
|
253
|
+
if options[:round_trip]
|
254
|
+
iso = ->(val) { return val }
|
255
|
+
reset_method(RPG::System, :reduce_string, iso)
|
256
|
+
reset_method(RPG::System, :map_version, iso)
|
257
|
+
reset_method(Game_System, :map_version, iso)
|
258
|
+
else
|
259
|
+
reset_method(
|
260
|
+
RPG::System,
|
261
|
+
:reduce_string,
|
262
|
+
->(str) do
|
263
|
+
return nil if str.nil?
|
264
|
+
stripped = str.strip
|
265
|
+
return stripped.empty? ? nil : stripped
|
266
|
+
end
|
267
|
+
)
|
268
|
+
|
269
|
+
# These magic numbers should be different. If they are the same, the saved version
|
270
|
+
# of the map in save files will be used instead of any updated version of the map
|
271
|
+
reset_method(
|
272
|
+
RPG::System,
|
273
|
+
:map_version,
|
274
|
+
->(ignored) { return 12_345_678 }
|
275
|
+
)
|
276
|
+
reset_method(
|
277
|
+
Game_System,
|
278
|
+
:map_version,
|
279
|
+
->(ignored) { return 87_654_321 }
|
280
|
+
)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def self.setup_interpreter(version)
|
285
|
+
# Game_Interpreter is marshalled differently in VX Ace
|
286
|
+
if version == :ace
|
287
|
+
reset_method(Game_Interpreter, :marshal_dump, -> { return @data })
|
288
|
+
reset_method(
|
289
|
+
Game_Interpreter,
|
290
|
+
:marshal_load,
|
291
|
+
->(obj) { @data = obj }
|
292
|
+
)
|
293
|
+
else
|
294
|
+
remove_defined_method(Game_Interpreter, :marshal_dump)
|
295
|
+
remove_defined_method(Game_Interpreter, :marshal_load)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def self.setup_event_command(version, options)
|
300
|
+
# format event commands to flow style for the event codes that aren't move commands
|
301
|
+
if options[:round_trip]
|
302
|
+
reset_method(RPG::EventCommand, :clean, -> { })
|
303
|
+
else
|
304
|
+
reset_method(
|
305
|
+
RPG::EventCommand,
|
306
|
+
:clean,
|
307
|
+
-> { @parameters[0].rstrip! if @code == 401 }
|
308
|
+
)
|
309
|
+
end
|
310
|
+
reset_const(
|
311
|
+
RPG::EventCommand,
|
312
|
+
:MOVE_LIST_CODE,
|
313
|
+
version == :xp ? 209 : 205
|
314
|
+
)
|
315
|
+
end
|
316
|
+
|
317
|
+
def self.setup_classes(version, options)
|
318
|
+
setup_system(version, options)
|
319
|
+
setup_interpreter(version)
|
320
|
+
setup_event_command(version, options)
|
321
|
+
BasicCoder.set_ivars_methods(version)
|
322
|
+
end
|
323
|
+
|
324
|
+
FLOW_CLASSES = [Color, Tone, RPG::BGM, RPG::BGS, RPG::MoveCommand, RPG::SE]
|
325
|
+
|
326
|
+
SCRIPTS_BASE = 'Scripts'
|
327
|
+
|
328
|
+
ACE_DATA_EXT = '.rvdata2'
|
329
|
+
VX_DATA_EXT = '.rvdata'
|
330
|
+
XP_DATA_EXT = '.rxdata'
|
331
|
+
YAML_EXT = '.yaml'
|
332
|
+
RUBY_EXT = '.rb'
|
333
|
+
|
334
|
+
def self.get_data_directory(base)
|
335
|
+
return File.join(base, 'Data')
|
336
|
+
end
|
337
|
+
|
338
|
+
def self.get_yaml_directory(base)
|
339
|
+
return File.join(base, 'YAML')
|
340
|
+
end
|
341
|
+
|
342
|
+
def self.get_script_directory(base)
|
343
|
+
return File.join(base, 'Scripts')
|
344
|
+
end
|
345
|
+
|
346
|
+
class Game_Switches
|
347
|
+
include RGSS::BasicCoder
|
348
|
+
|
349
|
+
def encode(name, value)
|
350
|
+
return array_to_hash(value)
|
351
|
+
end
|
352
|
+
|
353
|
+
def decode(name, value)
|
354
|
+
return hash_to_array(value)
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
class Game_Variables
|
359
|
+
include RGSS::BasicCoder
|
360
|
+
|
361
|
+
def encode(name, value)
|
362
|
+
return array_to_hash(value)
|
363
|
+
end
|
364
|
+
|
365
|
+
def decode(name, value)
|
366
|
+
return hash_to_array(value)
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
class Game_SelfSwitches
|
371
|
+
include RGSS::BasicCoder
|
372
|
+
|
373
|
+
def encode(name, value)
|
374
|
+
return(
|
375
|
+
Hash[
|
376
|
+
value.collect do |pair|
|
377
|
+
key, value = pair
|
378
|
+
next ['%03d %03d %s' % key, value]
|
379
|
+
end
|
380
|
+
]
|
381
|
+
)
|
382
|
+
end
|
383
|
+
|
384
|
+
def decode(name, value)
|
385
|
+
return(
|
386
|
+
Hash[
|
387
|
+
value.collect do |pair|
|
388
|
+
key, value = pair
|
389
|
+
next [key.scanf('%d %d %s'), value]
|
390
|
+
end
|
391
|
+
]
|
392
|
+
)
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
class Game_System
|
397
|
+
include RGSS::BasicCoder
|
398
|
+
|
399
|
+
def encode(name, value)
|
400
|
+
if name == 'version_id'
|
401
|
+
return map_version(value)
|
402
|
+
else
|
403
|
+
return value
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
require 'RGSS/serialize'
|
409
|
+
end
|
data/lib/RPG.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'RGSS'
|
2
|
+
module RPG
|
3
|
+
class System
|
4
|
+
include RGSS::BasicCoder
|
5
|
+
HASHED_VARS = %w[variables switches]
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.array_to_hash(arr, &block)
|
9
|
+
h = {}
|
10
|
+
arr.each_with_index do |val, index|
|
11
|
+
r = block_given? ? block.call(val) : val
|
12
|
+
h[index] = r unless r.nil?
|
13
|
+
end
|
14
|
+
if arr.length > 0
|
15
|
+
last = arr.length - 1
|
16
|
+
h[last] = nil unless h.has_key?(last)
|
17
|
+
end
|
18
|
+
return h
|
19
|
+
end
|
20
|
+
|
21
|
+
def encode(name, value)
|
22
|
+
if HASHED_VARS.include?(name)
|
23
|
+
return array_to_hash(value) { |val| reduce_string(val) }
|
24
|
+
elsif name == 'version_id'
|
25
|
+
return map_version(value)
|
26
|
+
else
|
27
|
+
return value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def decode(name, value)
|
32
|
+
if HASHED_VARS.include?(name)
|
33
|
+
return hash_to_array(value)
|
34
|
+
else
|
35
|
+
return value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class EventCommand
|
40
|
+
def encode_with(coder)
|
41
|
+
if instance_variables.length != 3
|
42
|
+
raise 'Unexpected number of instance variables'
|
43
|
+
end
|
44
|
+
clean
|
45
|
+
|
46
|
+
case @code
|
47
|
+
when MOVE_LIST_CODE
|
48
|
+
# move list
|
49
|
+
coder.style = Psych::Nodes::Mapping::BLOCK
|
50
|
+
else
|
51
|
+
coder.style = Psych::Nodes::Mapping::FLOW
|
52
|
+
end
|
53
|
+
coder['i'], coder['c'], coder['p'] = @indent, @code, @parameters
|
54
|
+
end
|
55
|
+
|
56
|
+
def init_with(coder)
|
57
|
+
@indent, @code, @parameters = coder['i'], coder['c'], coder['p']
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/rvpacker.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative 'lib/rvpacker/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'rvpacker-ng'
|
5
|
+
spec.version = RVPACKER::VERSION
|
6
|
+
spec.authors = ['Howard Jeng', 'Andrew Kesterson', 'Solistra', 'Darkness9724']
|
7
|
+
spec.email = ['darkness9724@gmail.com']
|
8
|
+
spec.summary = 'Pack and unpack any RPG Maker VX Ace data files'
|
9
|
+
spec.homepage = 'https://gitlab.com/Darkness9724/rvpacker'
|
10
|
+
spec.license = 'MIT'
|
11
|
+
|
12
|
+
spec.files = `git ls-files -z`.split("\x0")
|
13
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
14
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
15
|
+
spec.require_paths = ['lib']
|
16
|
+
|
17
|
+
spec.add_development_dependency 'bundler', '2.3.10'
|
18
|
+
spec.add_development_dependency 'rake', '13.0.6'
|
19
|
+
spec.add_dependency 'optimist', '3.0.1'
|
20
|
+
spec.add_dependency 'psych', '4.0.3'
|
21
|
+
spec.add_dependency 'formatador', '0.3.0'
|
22
|
+
spec.add_dependency 'scanf', '1.0.0'
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rvpacker-ng
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Howard Jeng
|
8
|
+
- Andrew Kesterson
|
9
|
+
- Solistra
|
10
|
+
- Darkness9724
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2022-03-29 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: bundler
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - '='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.3.10
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - '='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.3.10
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - '='
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 13.0.6
|
37
|
+
type: :development
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - '='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 13.0.6
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: optimist
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - '='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 3.0.1
|
51
|
+
type: :runtime
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - '='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 3.0.1
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: psych
|
60
|
+
requirement: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - '='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: 4.0.3
|
65
|
+
type: :runtime
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - '='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: 4.0.3
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: formatador
|
74
|
+
requirement: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - '='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: 0.3.0
|
79
|
+
type: :runtime
|
80
|
+
prerelease: false
|
81
|
+
version_requirements: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - '='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 0.3.0
|
86
|
+
- !ruby/object:Gem::Dependency
|
87
|
+
name: scanf
|
88
|
+
requirement: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - '='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 1.0.0
|
93
|
+
type: :runtime
|
94
|
+
prerelease: false
|
95
|
+
version_requirements: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - '='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: 1.0.0
|
100
|
+
description:
|
101
|
+
email:
|
102
|
+
- darkness9724@gmail.com
|
103
|
+
executables:
|
104
|
+
- rvpacker
|
105
|
+
extensions: []
|
106
|
+
extra_rdoc_files: []
|
107
|
+
files:
|
108
|
+
- ".gitignore"
|
109
|
+
- ".prettierrc"
|
110
|
+
- Gemfile
|
111
|
+
- LICENSE
|
112
|
+
- README.md
|
113
|
+
- Rakefile
|
114
|
+
- bin/rvpacker
|
115
|
+
- lib/RGSS.rb
|
116
|
+
- lib/RGSS/BasicCoder.rb
|
117
|
+
- lib/RGSS/serialize.rb
|
118
|
+
- lib/RPG.rb
|
119
|
+
- lib/rvpacker/version.rb
|
120
|
+
- rvpacker.gemspec
|
121
|
+
homepage: https://gitlab.com/Darkness9724/rvpacker
|
122
|
+
licenses:
|
123
|
+
- MIT
|
124
|
+
metadata: {}
|
125
|
+
post_install_message:
|
126
|
+
rdoc_options: []
|
127
|
+
require_paths:
|
128
|
+
- lib
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
requirements: []
|
140
|
+
rubygems_version: 3.3.8
|
141
|
+
signing_key:
|
142
|
+
specification_version: 4
|
143
|
+
summary: Pack and unpack any RPG Maker VX Ace data files
|
144
|
+
test_files: []
|