nwn-lib 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +15 -0
- data/README +56 -0
- data/Rakefile +104 -0
- data/bin/nwn-gff-print +87 -0
- data/bin/nwn-gff-set +87 -0
- data/lib/nwn/gff.rb +733 -0
- data/lib/nwn/twoda.rb +9 -0
- data/spec/rcov.opts +0 -0
- data/spec/spec.opts +0 -0
- metadata +64 -0
data/COPYING
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Copyright (C) 2008 Bernhard Stoeckner <elven@swordcoast.net>
|
2
|
+
|
3
|
+
This program is free software; you can redistribute it and/or modify
|
4
|
+
it under the terms of the GNU General Public License as published by
|
5
|
+
the Free Software Foundation; either version 2 of the License, or
|
6
|
+
(at your option) any later version.
|
7
|
+
|
8
|
+
This program is distributed in the hope that it will be useful,
|
9
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
GNU General Public License for more details.
|
12
|
+
|
13
|
+
You should have received a copy of the GNU General Public License along
|
14
|
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
15
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
data/README
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
== A library for Neverwinter Nights 1/2 resource files
|
2
|
+
|
3
|
+
This package provides a library for reading, changing, and writing common file formats that are found with NWN[http://nwn.bioware.com/], a Bioware game.
|
4
|
+
|
5
|
+
They should work with NWN2 just as well, since the file format specifications did not change.
|
6
|
+
|
7
|
+
There are various things included in this distribution:
|
8
|
+
* some binaries under bin/
|
9
|
+
* the actual library under lib/nwn
|
10
|
+
|
11
|
+
== Supplied binaries
|
12
|
+
* nwn-gff-print
|
13
|
+
|
14
|
+
== Quickstart
|
15
|
+
|
16
|
+
It is easiest to just use the gem, which is available through rubyforge (<tt>sudo gem install nwn-lib</tt>).
|
17
|
+
|
18
|
+
As an alternative, fetch the latest development files with git[http://git.swordcoast.net/?p=nwn/nwn-lib.git;a=summary].
|
19
|
+
|
20
|
+
require 'rubygems'
|
21
|
+
require 'nwn/gff'
|
22
|
+
|
23
|
+
# Read a GFF file
|
24
|
+
o = NWN::Gff::Reader.read(IO.read('/tmp/test-creature.gff'))
|
25
|
+
|
26
|
+
# Do some modifications to it
|
27
|
+
o['/Tag'] = 'testtag'
|
28
|
+
|
29
|
+
# And write it somewhere else
|
30
|
+
bytes = NWN::Gff::Writer.dump(o)
|
31
|
+
|
32
|
+
File.open('/tmp/test-creature-2.gff', 'w') {|f| f.write(bytes) }
|
33
|
+
|
34
|
+
Modification API is fairly limited, for now, but will improve soon.
|
35
|
+
|
36
|
+
== GFF data structures: Quick Intro
|
37
|
+
|
38
|
+
Ruby GFF data structures are nearly a 1:1 wrapper around the known format.
|
39
|
+
|
40
|
+
Each GFF object has a root structure with a +struct_id+ of 0.
|
41
|
+
|
42
|
+
=== Label
|
43
|
+
A Label is a string of up to 16 bytes, and occurs as keys in Structs.
|
44
|
+
|
45
|
+
=== Struct
|
46
|
+
A struct is a hash. It is a unordered collection of key => value pairs.
|
47
|
+
Keys are Labels, values are either:
|
48
|
+
* Structs
|
49
|
+
* Lists
|
50
|
+
* Elements
|
51
|
+
|
52
|
+
=== List
|
53
|
+
A list is an *ordered* array of Structs.
|
54
|
+
|
55
|
+
=== Element
|
56
|
+
A Element is a distinct value of a given type. For all possible types, see the +Types+ hash in the NWN::Gff module.
|
data/Rakefile
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
require "rake"
|
2
|
+
require "rake/clean"
|
3
|
+
require "rake/gempackagetask"
|
4
|
+
require "rake/rdoctask"
|
5
|
+
require "fileutils"
|
6
|
+
include FileUtils
|
7
|
+
|
8
|
+
##############################################################################
|
9
|
+
# Configuration
|
10
|
+
##############################################################################
|
11
|
+
NAME = "nwn-lib"
|
12
|
+
VERS = "0.1"
|
13
|
+
CLEAN.include ["**/.*.sw?", "pkg", ".config", "rdoc", "coverage"]
|
14
|
+
RDOC_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', \
|
15
|
+
'nwn-lib: a ruby library for accessing NWN resource files', \
|
16
|
+
'--main', 'README']
|
17
|
+
|
18
|
+
Rake::RDocTask.new do |rdoc|
|
19
|
+
rdoc.rdoc_dir = "rdoc"
|
20
|
+
rdoc.options += RDOC_OPTS
|
21
|
+
rdoc.rdoc_files.add ["README", "COPYING", "CHANGELOG", "doc/*.rdoc", "lib/**/*.rb"]
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "Packages up nwn-lib"
|
25
|
+
task :package => [:clean]
|
26
|
+
|
27
|
+
spec = Gem::Specification.new do |s|
|
28
|
+
s.name = NAME
|
29
|
+
s.rubyforge_project = 'nwn-lib'
|
30
|
+
s.version = VERS
|
31
|
+
s.platform = Gem::Platform::RUBY
|
32
|
+
s.has_rdoc = true
|
33
|
+
s.extra_rdoc_files = ["README", "COPYING"] + Dir["doc/*.rdoc"]
|
34
|
+
s.rdoc_options += RDOC_OPTS + ["--exclude", "^(examples|extras)\/"]
|
35
|
+
s.summary = "a ruby library for accessing Neverwinter Nights resource files"
|
36
|
+
s.description = s.summary
|
37
|
+
s.author = "Bernhard Stoeckner"
|
38
|
+
s.email = "elven@swordcoast.net"
|
39
|
+
s.homepage = "http://nwn-lib.elv.es"
|
40
|
+
s.executables = ["nwn-gff-print"]
|
41
|
+
s.required_ruby_version = ">= 1.8.4"
|
42
|
+
s.files = %w(COPYING README Rakefile) + Dir.glob("{bin,doc,spec,lib}/**/*")
|
43
|
+
s.require_path = "lib"
|
44
|
+
s.bindir = "bin"
|
45
|
+
end
|
46
|
+
|
47
|
+
Rake::GemPackageTask.new(spec) do |p|
|
48
|
+
p.need_tar = true
|
49
|
+
p.gem_spec = spec
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "Install nwn-lib gem"
|
53
|
+
task :install do
|
54
|
+
sh %{rake package}
|
55
|
+
sh %{sudo gem install pkg/#{NAME}-#{VERS}}
|
56
|
+
end
|
57
|
+
|
58
|
+
desc "Install nwn-lib gem without docs"
|
59
|
+
task :install_no_docs do
|
60
|
+
sh %{rake package}
|
61
|
+
sh %{sudo gem install pkg/#{NAME}-#{VERS} --no-rdoc --no-ri}
|
62
|
+
end
|
63
|
+
|
64
|
+
desc "Uninstall nwn-lib gem"
|
65
|
+
task :uninstall => [:clean] do
|
66
|
+
sh %{sudo gem uninstall #{NAME}}
|
67
|
+
end
|
68
|
+
|
69
|
+
desc "Upload nwn-lib gem to rubyforge"
|
70
|
+
task :release => [:package] do
|
71
|
+
sh %{rubyforge login}
|
72
|
+
sh %{rubyforge add_release nwn-lib #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.tgz}
|
73
|
+
sh %{rubyforge add_file nwn-lib #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.gem}
|
74
|
+
end
|
75
|
+
|
76
|
+
require "spec/rake/spectask"
|
77
|
+
|
78
|
+
desc "Run specs with coverage"
|
79
|
+
Spec::Rake::SpecTask.new("spec") do |t|
|
80
|
+
t.spec_files = FileList["spec/*_spec.rb"]
|
81
|
+
t.spec_opts = File.read("spec/spec.opts").split("\n")
|
82
|
+
t.rcov_opts = File.read("spec/rcov.opts").split("\n")
|
83
|
+
t.rcov = true
|
84
|
+
end
|
85
|
+
|
86
|
+
desc "Run specs without coverage"
|
87
|
+
task :default => [:spec_no_cov]
|
88
|
+
Spec::Rake::SpecTask.new("spec_no_cov") do |t|
|
89
|
+
t.spec_files = FileList["spec/*_spec.rb"]
|
90
|
+
t.spec_opts = File.read("spec/spec.opts").split("\n")
|
91
|
+
end
|
92
|
+
|
93
|
+
desc "Run rcov only"
|
94
|
+
Spec::Rake::SpecTask.new("rcov") do |t|
|
95
|
+
t.rcov_opts = File.read("spec/rcov.opts").split("\n")
|
96
|
+
t.spec_opts = File.read("spec/spec.opts").split("\n")
|
97
|
+
t.spec_files = FileList["spec/*_spec.rb"]
|
98
|
+
t.rcov = true
|
99
|
+
end
|
100
|
+
|
101
|
+
desc "check documentation coverage"
|
102
|
+
task :dcov do
|
103
|
+
sh "find lib -name '*.rb' | xargs dcov"
|
104
|
+
end
|
data/bin/nwn-gff-print
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'optparse'
|
4
|
+
require 'nwn/gff'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
format = nil
|
8
|
+
|
9
|
+
OptionParser.new do |o|
|
10
|
+
o.banner = "Usage: nwn-gff-print [options] file/- [path]"
|
11
|
+
o.on "-y", "--yaml", "Dump as yaml" do
|
12
|
+
format = :yaml
|
13
|
+
end
|
14
|
+
o.on "-k", "--kivinen", "Dump as kivinens dump format (like the perl tools)" do
|
15
|
+
format = :kivinen
|
16
|
+
end
|
17
|
+
end.parse!
|
18
|
+
|
19
|
+
file = ARGV.shift or begin
|
20
|
+
$stderr.puts "Required argument: filename to process, or - for stdin (try -h)."
|
21
|
+
exit 1
|
22
|
+
end
|
23
|
+
|
24
|
+
path = ARGV.shift
|
25
|
+
|
26
|
+
if file == "-"
|
27
|
+
bytes = $stdin.read
|
28
|
+
else
|
29
|
+
bytes = IO.read(file)
|
30
|
+
end
|
31
|
+
|
32
|
+
g = NWN::Gff::Reader.read(bytes)
|
33
|
+
|
34
|
+
if path
|
35
|
+
begin
|
36
|
+
g = g[path]
|
37
|
+
rescue Exception => e
|
38
|
+
$stderr.puts "Error: " + e.to_s
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def k_format_struct s, prefix = "/", &block
|
44
|
+
if s.is_a?(NWN::Gff::Gff)
|
45
|
+
s = NWN::Gff::Element.new("", :struct, s.root_struct)
|
46
|
+
end
|
47
|
+
if s.is_a?(Array)
|
48
|
+
v = NWN::Gff::Element.new("(unlabeled list)", :list, s)
|
49
|
+
end
|
50
|
+
if s.is_a?(NWN::Gff::Struct)
|
51
|
+
s = NWN::Gff::Element.new("(unlabeled struct)", :struct, s)
|
52
|
+
end
|
53
|
+
case s.type
|
54
|
+
when :struct
|
55
|
+
s.value.each {|k,v|
|
56
|
+
k_format_struct v, prefix + s.label + (s.label == "" ? "" : "/") do |l,v|
|
57
|
+
yield(l, v)
|
58
|
+
end
|
59
|
+
}
|
60
|
+
when :cexolocstr
|
61
|
+
s.value.each {|vv|
|
62
|
+
yield(prefix + s.label + "/" + vv.language.to_s, vv.text)
|
63
|
+
}
|
64
|
+
yield(prefix + s.label + ". ___string_ref", s._str_ref)
|
65
|
+
when :list
|
66
|
+
s.value.each_with_index {|vv, idx|
|
67
|
+
vv.each {|kkk, vvv|
|
68
|
+
k_format_struct vvv, prefix + s.label + "[#{idx}]/" do |l,v|
|
69
|
+
yield(l,v)
|
70
|
+
end
|
71
|
+
}
|
72
|
+
}
|
73
|
+
else
|
74
|
+
yield(prefix + s.label, s.value)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
case format
|
79
|
+
when :yaml
|
80
|
+
y g
|
81
|
+
when :kivinen
|
82
|
+
k_format_struct g do |label, value|
|
83
|
+
puts "%s:\t%s" % [label, value]
|
84
|
+
end
|
85
|
+
else
|
86
|
+
puts "Unknown format; try -h"
|
87
|
+
end
|
data/bin/nwn-gff-set
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'optparse'
|
4
|
+
require 'nwn/gff'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
format = nil
|
8
|
+
|
9
|
+
OptionParser.new do |o|
|
10
|
+
o.banner = "Usage: nwn-gff-print [options] file/- [path]"
|
11
|
+
o.on "-y", "--yaml", "Dump as yaml" do
|
12
|
+
format = :yaml
|
13
|
+
end
|
14
|
+
o.on "-k", "--kivinen", "Dump as kivinens dump format (like the perl tools)" do
|
15
|
+
format = :kivinen
|
16
|
+
end
|
17
|
+
end.parse!
|
18
|
+
|
19
|
+
file = ARGV.shift or begin
|
20
|
+
$stderr.puts "Required argument: filename to process, or - for stdin (try -h)."
|
21
|
+
exit 1
|
22
|
+
end
|
23
|
+
|
24
|
+
path = ARGV.shift
|
25
|
+
|
26
|
+
if file == "-"
|
27
|
+
bytes = $stdin.read
|
28
|
+
else
|
29
|
+
bytes = IO.read(file)
|
30
|
+
end
|
31
|
+
|
32
|
+
g = NWN::Gff::Reader.read(bytes)
|
33
|
+
|
34
|
+
if path
|
35
|
+
begin
|
36
|
+
g = g[path]
|
37
|
+
rescue Exception => e
|
38
|
+
$stderr.puts "Error: " + e.to_s
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def k_format_struct s, prefix = "/", &block
|
44
|
+
if s.is_a?(NWN::Gff::Gff)
|
45
|
+
s = NWN::Gff::Element.new("", :struct, s.root_struct)
|
46
|
+
end
|
47
|
+
if s.is_a?(Array)
|
48
|
+
v = NWN::Gff::Element.new("(unlabeled list)", :list, s)
|
49
|
+
end
|
50
|
+
if s.is_a?(NWN::Gff::Struct)
|
51
|
+
s = NWN::Gff::Element.new("(unlabeled struct)", :struct, s)
|
52
|
+
end
|
53
|
+
case s.type
|
54
|
+
when :struct
|
55
|
+
s.value.each {|k,v|
|
56
|
+
k_format_struct v, prefix + s.label + (s.label == "" ? "" : "/") do |l,v|
|
57
|
+
yield(l, v)
|
58
|
+
end
|
59
|
+
}
|
60
|
+
when :cexolocstr
|
61
|
+
s.value.each {|vv|
|
62
|
+
yield(prefix + s.label + "/" + vv.language.to_s, vv.text)
|
63
|
+
}
|
64
|
+
yield(prefix + s.label + ". ___string_ref", s._str_ref)
|
65
|
+
when :list
|
66
|
+
s.value.each_with_index {|vv, idx|
|
67
|
+
vv.each {|kkk, vvv|
|
68
|
+
k_format_struct vvv, prefix + s.label + "[#{idx}]/" do |l,v|
|
69
|
+
yield(l,v)
|
70
|
+
end
|
71
|
+
}
|
72
|
+
}
|
73
|
+
else
|
74
|
+
yield(prefix + s.label, s.value)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
case format
|
79
|
+
when :yaml
|
80
|
+
y g
|
81
|
+
when :kivinen
|
82
|
+
k_format_struct g do |label, value|
|
83
|
+
puts "%s:\t%s" % [label, value]
|
84
|
+
end
|
85
|
+
else
|
86
|
+
puts "Unknown format; try -h"
|
87
|
+
end
|
data/lib/nwn/gff.rb
ADDED
@@ -0,0 +1,733 @@
|
|
1
|
+
module NWN
|
2
|
+
module Gff
|
3
|
+
# This error gets thrown if reading or writing fails.
|
4
|
+
class GffError < Exception; end
|
5
|
+
|
6
|
+
# This error gets thrown if a supplied value does not
|
7
|
+
# fit into the given data type, or you are trying to assign
|
8
|
+
# a type to something that does not hold type.
|
9
|
+
#
|
10
|
+
# Example: You're trying to pass a value greater than 2**32
|
11
|
+
# into a int.
|
12
|
+
class GffTypeError < Exception; end
|
13
|
+
|
14
|
+
# Gets raised if you are trying to access a path that does
|
15
|
+
# not exist.
|
16
|
+
class GffPathInvalidError < Exception; end
|
17
|
+
|
18
|
+
# This hash lists all possible NWN::Gff::Element types.
|
19
|
+
Types = {
|
20
|
+
0 => :byte,
|
21
|
+
1 => :char,
|
22
|
+
2 => :word,
|
23
|
+
3 => :short,
|
24
|
+
4 => :dword,
|
25
|
+
5 => :int,
|
26
|
+
6 => :dword64,
|
27
|
+
7 => :int64,
|
28
|
+
8 => :float,
|
29
|
+
9 => :double,
|
30
|
+
10 => :cexostr,
|
31
|
+
11 => :resref,
|
32
|
+
12 => :cexolocstr,
|
33
|
+
13 => :void,
|
34
|
+
14 => :struct,
|
35
|
+
15 => :list,
|
36
|
+
}.freeze
|
37
|
+
|
38
|
+
#:stopdoc:
|
39
|
+
# Used internally to figure out if a field is stored directly
|
40
|
+
# or by reference.
|
41
|
+
ComplexTypes = [6, 7, 9, 10, 11, 12, 13, 14, 15].freeze
|
42
|
+
SimpleTypes = (Types.keys - ComplexTypes)
|
43
|
+
SimpleTypes.freeze
|
44
|
+
#:startdoc:
|
45
|
+
|
46
|
+
Formats = {
|
47
|
+
:byte => "Cxxx",
|
48
|
+
:char => "Cxxx",
|
49
|
+
:word => 'Sxx',
|
50
|
+
:short => 'sxx',
|
51
|
+
:dword => 'I',
|
52
|
+
:int => 'i',
|
53
|
+
:dword64 => 'II',
|
54
|
+
:int64 => 'q',
|
55
|
+
:float => 'f',
|
56
|
+
:double => 'd',
|
57
|
+
}.freeze
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# A GFF object encapsulates a whole GFF identity, with a type,
|
62
|
+
# version, and a root structure.
|
63
|
+
# This object also provides easy accessors for labels and values.
|
64
|
+
class NWN::Gff::Gff
|
65
|
+
include NWN::Gff
|
66
|
+
|
67
|
+
attr_accessor :type
|
68
|
+
attr_accessor :version
|
69
|
+
|
70
|
+
# Create a new GFF object from the given +struct+.
|
71
|
+
# This is normally not needed unless you are creating
|
72
|
+
# GFF objects entirely from hand.
|
73
|
+
#
|
74
|
+
# See NWN::Gff::Reader.
|
75
|
+
def initialize struct, type, version = "V3.2"
|
76
|
+
@hash = struct
|
77
|
+
@type = type
|
78
|
+
@version = version
|
79
|
+
end
|
80
|
+
|
81
|
+
# Return the root struct of this GFF.
|
82
|
+
def root_struct
|
83
|
+
@hash
|
84
|
+
end
|
85
|
+
|
86
|
+
# A simple accessor that can be used to
|
87
|
+
# retrieve or set properties in the struct, delimited by slashes.
|
88
|
+
#
|
89
|
+
# Will raise a GffPathInvalidError if the given path cannot be found,
|
90
|
+
# and GffTypeError if some type fails to validate.
|
91
|
+
#
|
92
|
+
# Examples (with +gff+ assumed to be a item):
|
93
|
+
# gff['/Tag']
|
94
|
+
# will retrieve the Tag of the given object
|
95
|
+
# gff['/Tag'] = 'Test'
|
96
|
+
# Set the Tag to 'Test'
|
97
|
+
# gff['/PropertiesList']
|
98
|
+
# will retrieve an array of Gff::Elements
|
99
|
+
# gff['/PropertiesList[1]']
|
100
|
+
# will yield element 2 in the list
|
101
|
+
# gff['/'] = NWN::Gff::Element.new('Property', :byte, 14)
|
102
|
+
# will add a new property at the root struct with the name of 'Property', or
|
103
|
+
# overwrite an existing one with the same label.
|
104
|
+
# gff['/PropertiesList[0]'] = 'Test'
|
105
|
+
# This will raise an error (obviously)
|
106
|
+
def get_or_set k, new_value = nil, new_type = nil, new_label = nil, new_str_ref = nil
|
107
|
+
puts "get_or_set(#{k} = #{new_value})"
|
108
|
+
h = self.root_struct
|
109
|
+
path = []
|
110
|
+
value_path = [h]
|
111
|
+
current_value = nil
|
112
|
+
|
113
|
+
k.split('/').each {|v|
|
114
|
+
next if v == ""
|
115
|
+
path << v
|
116
|
+
|
117
|
+
if current_value.is_a?(Gff::Element) && current_value.type == :list # && v =~ /\[(\d+)\]$/
|
118
|
+
puts "value = #{$1}"
|
119
|
+
current_value = current_value.value[$1.to_i]
|
120
|
+
end
|
121
|
+
|
122
|
+
if h.is_a?(Gff::Element)
|
123
|
+
case h.type
|
124
|
+
when :cexolocstr
|
125
|
+
current_value = h.value.select {|vx| vx.language.to_i == v.to_i}
|
126
|
+
current_value = current_value[0] != nil ? current_value[0].text : ''
|
127
|
+
|
128
|
+
when :list
|
129
|
+
raise GffPathInvalidError, "List-selector access not implemented yet."
|
130
|
+
|
131
|
+
else
|
132
|
+
raise GffPathInvalidError,
|
133
|
+
"Tried to access sub-label of a non-complex field: /#{path.join('/')}"
|
134
|
+
|
135
|
+
end
|
136
|
+
elsif h.is_a?(Gff::Struct)
|
137
|
+
|
138
|
+
if v =~ /^(.+?)\[(\d+)\]$/
|
139
|
+
current_value = h[$1.to_s]
|
140
|
+
if current_value.is_a?(Gff::Element) && !current_value.type == :list
|
141
|
+
raise GffPathInvalidError, "Tried to access list-index of a non-list at /#{path.join('/')}"
|
142
|
+
end
|
143
|
+
current_value = current_value.value[$2.to_i]
|
144
|
+
else
|
145
|
+
current_value = h[v]
|
146
|
+
end
|
147
|
+
else
|
148
|
+
raise GffPathInvalidError, "Unknown sub-field type #{h.class.to_s} at /#{path.join('/')}"
|
149
|
+
end
|
150
|
+
|
151
|
+
value_path << current_value
|
152
|
+
h = current_value
|
153
|
+
|
154
|
+
raise GffPathInvalidError,
|
155
|
+
"Cannot find path: /#{path.join('/')}" if current_value.nil? && !new_value.is_a?(Gff::Element)
|
156
|
+
}
|
157
|
+
|
158
|
+
if path.size == 0
|
159
|
+
if new_value.is_a?(Gff::Element)
|
160
|
+
value_path << h
|
161
|
+
else
|
162
|
+
raise GffPathInvalidError, "Do not operate on the root struct unless through adding items."
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
old_value = current_value.nil? ? nil : current_value.dup
|
167
|
+
|
168
|
+
if new_value.is_a?(Gff::Element)
|
169
|
+
new_value.validate
|
170
|
+
value_path[-2].delete(current_value)
|
171
|
+
value_path[-2][new_value.label] = new_value
|
172
|
+
else
|
173
|
+
|
174
|
+
if !new_label.nil?
|
175
|
+
# Set a new label
|
176
|
+
value_path[-2].delete(current_value.label)
|
177
|
+
current_value.label = new_label
|
178
|
+
value_path[-2][new_label] = current_value
|
179
|
+
end
|
180
|
+
|
181
|
+
if !new_type.nil?
|
182
|
+
# Set a new datatype
|
183
|
+
raise GffTypeError, "Cannot set a type on a non-element." unless current_value.is_a?(Gff::Element)
|
184
|
+
test = current_value.dup
|
185
|
+
test.type = new_type
|
186
|
+
test.validate
|
187
|
+
|
188
|
+
current_value.type = new_type
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
if !new_str_ref.nil?
|
193
|
+
# Set a new str_ref
|
194
|
+
raise GffTypeError, "specified path is not a CExoStr" unless current_value.is_a?(Gff::CExoString)
|
195
|
+
current_value._str_ref = new_str_ref.to_i
|
196
|
+
end
|
197
|
+
|
198
|
+
if !new_value.nil?
|
199
|
+
|
200
|
+
case current_value
|
201
|
+
when Gff::Element
|
202
|
+
test = current_value.dup
|
203
|
+
test.value = new_value
|
204
|
+
test.validate
|
205
|
+
current_value.value = new_value
|
206
|
+
|
207
|
+
when String #means: cexolocstr assignment
|
208
|
+
if value_path[-2].is_a?(Gff::Element) && value_path[-2].type == :cexolocstr
|
209
|
+
value_path[-2].value.select{|xy| xy.language == path[-1].to_i }[0].text = new_value
|
210
|
+
else
|
211
|
+
raise GffPathInvalidError, "Dont know how to set #{new_value.class} on #{path.inspect}."
|
212
|
+
end
|
213
|
+
else
|
214
|
+
raise GffPathInvalidError, "Don't know what to do with #{current_value.class} -> #{new_value.class} at /#{path.join('/')}"
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
old_value
|
221
|
+
end
|
222
|
+
|
223
|
+
def [] k
|
224
|
+
get_or_set k
|
225
|
+
end
|
226
|
+
|
227
|
+
def []= k, v
|
228
|
+
get_or_set k, v
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
# A Element wraps a GFF label->value pair,
|
234
|
+
# provides a +.type+ and, optionally,
|
235
|
+
# a +._str_ref+ for CExoLocStrings.
|
236
|
+
#
|
237
|
+
# Fields:
|
238
|
+
# [+label+] The label of this element, for reference.
|
239
|
+
# [+type+] The type of this element. (See NWN::Gff)
|
240
|
+
# [+value+] The value of this element.
|
241
|
+
class NWN::Gff::Element
|
242
|
+
attr_accessor :label, :type, :value
|
243
|
+
attr_accessor :_str_ref
|
244
|
+
|
245
|
+
def initialize label = nil, type = nil, value = nil
|
246
|
+
@label, @type, @value = label, type, value
|
247
|
+
end
|
248
|
+
|
249
|
+
def validate path_prefix = "/"
|
250
|
+
raise NWN::Gff::GffTypeError, "#{path_prefix}#{self.label}: New value #{self.value} is not compatible with the current type #{self.type}" unless
|
251
|
+
self.class.valid_for?(self.value, self.type)
|
252
|
+
end
|
253
|
+
|
254
|
+
# 0 => :byte,
|
255
|
+
# 1 => :char,
|
256
|
+
# 2 => :word,
|
257
|
+
# 3 => :short,
|
258
|
+
# 4 => :dword,
|
259
|
+
# 5 => :int,
|
260
|
+
# 6 => :dword64,
|
261
|
+
# 7 => :int64,
|
262
|
+
# 8 => :float,
|
263
|
+
# 9 => :double,
|
264
|
+
# 10 => :cexostr,
|
265
|
+
# 11 => :resref,
|
266
|
+
# 12 => :cexolocstr,
|
267
|
+
# 13 => :void,
|
268
|
+
# 14 => :struct,
|
269
|
+
# 15 => :list,
|
270
|
+
|
271
|
+
# Validate if +value+ is within bounds of +type+.
|
272
|
+
def self.valid_for? value, type
|
273
|
+
case type
|
274
|
+
when :char, :byte
|
275
|
+
value.is_a?(Fixnum)
|
276
|
+
when :short, :word
|
277
|
+
value.is_a?(Fixnum)
|
278
|
+
when :int, :dword
|
279
|
+
value.is_a?(Fixnum)
|
280
|
+
when :int64, :dword64
|
281
|
+
value.is_a?(Fixnum)
|
282
|
+
when :float, :double
|
283
|
+
value.is_a?(Float)
|
284
|
+
when :resref
|
285
|
+
value.is_a?(String) && (1..16).member?(value.size)
|
286
|
+
when :cexostr
|
287
|
+
value.is_a?(String)
|
288
|
+
when :cexolocstr
|
289
|
+
value.is_a?(Array)
|
290
|
+
when :struct, :list
|
291
|
+
value.is_a?(Array)
|
292
|
+
when :void
|
293
|
+
true
|
294
|
+
else
|
295
|
+
false
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
end
|
300
|
+
|
301
|
+
# A Gff::Struct is a hash of label->Element pairs,
|
302
|
+
# with an added +.struct_id+.
|
303
|
+
class NWN::Gff::Struct < Hash
|
304
|
+
attr_accessor :struct_id
|
305
|
+
def initialize *a
|
306
|
+
@struct_id = 0
|
307
|
+
super
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
# A CExoLocString is a localised CExoString.
|
312
|
+
#
|
313
|
+
# Attributes:
|
314
|
+
# [+language+] The language ID
|
315
|
+
# [+text+] The text for this language.
|
316
|
+
#
|
317
|
+
# ExoLocStrings in the wild are usually arrays of NWN::Gff:CExoLocString
|
318
|
+
# (one for each language supplied).
|
319
|
+
# Note that a CExoLocString is NOT a GFF list, although both are
|
320
|
+
# represented as arrays.
|
321
|
+
class NWN::Gff::CExoLocString < Struct.new(:language, :text)
|
322
|
+
end
|
323
|
+
|
324
|
+
# A class that parses binary GFF bytes into ruby-friendly data structures.
|
325
|
+
class NWN::Gff::Reader
|
326
|
+
include NWN::Gff
|
327
|
+
|
328
|
+
attr_reader :hash
|
329
|
+
attr_reader :gff
|
330
|
+
|
331
|
+
# Create a new Reader with the given +bytes+ and immediately parse it.
|
332
|
+
# This is not needed usually; use Reader.read instead.
|
333
|
+
def initialize bytes
|
334
|
+
@bytes = bytes
|
335
|
+
read_all
|
336
|
+
end
|
337
|
+
|
338
|
+
# Reads +bytes+ as gff data and returns a NWN::Gff:Gff object.
|
339
|
+
def self.read bytes
|
340
|
+
self.new(bytes).gff
|
341
|
+
end
|
342
|
+
|
343
|
+
private
|
344
|
+
|
345
|
+
def read_all
|
346
|
+
type, version,
|
347
|
+
struct_offset, struct_count,
|
348
|
+
field_offset, field_count,
|
349
|
+
label_offset, label_count,
|
350
|
+
field_data_offset, field_data_count,
|
351
|
+
field_indices_offset, field_indices_count,
|
352
|
+
list_indices_offset, list_indices_count =
|
353
|
+
@bytes.unpack("a4a4 VV VV VV VV VV VV")
|
354
|
+
|
355
|
+
raise GffError, "Unknown version #{@version}; not a gff?" unless
|
356
|
+
version == "V3.2"
|
357
|
+
|
358
|
+
raise GffError, "struct offset at wrong place, not a gff?" unless
|
359
|
+
struct_offset == 56
|
360
|
+
|
361
|
+
struct_len = struct_count * 12
|
362
|
+
field_len = field_count * 16
|
363
|
+
label_len = label_count * 16
|
364
|
+
|
365
|
+
@structs = @bytes[struct_offset, struct_len].unpack("V*")
|
366
|
+
@fields = @bytes[field_offset, field_len].unpack("V*")
|
367
|
+
@labels = @bytes[label_offset, label_len].unpack("A16" * label_count)
|
368
|
+
@field_data = @bytes[field_data_offset, field_data_count]
|
369
|
+
@field_indices = @bytes[field_indices_offset, field_indices_count].unpack("V*")
|
370
|
+
@list_indices = @bytes[list_indices_offset, list_indices_count].unpack("V*")
|
371
|
+
# puts "FieldDataOffset = #{field_data_offset}, Count = #{field_data_count}"
|
372
|
+
@hash = read_struct 0
|
373
|
+
@gff = Gff.new(@hash, type, version)
|
374
|
+
end
|
375
|
+
|
376
|
+
# This iterates through a struct and reads all fields into a hash, which it returns.
|
377
|
+
def read_struct index
|
378
|
+
struct = Gff::Struct.new
|
379
|
+
|
380
|
+
type = @structs[index * 3]
|
381
|
+
data_or_offset = @structs[index * 3 + 1]
|
382
|
+
count = @structs[index * 3 + 2]
|
383
|
+
|
384
|
+
raise GffError, "struct index #{index} outside of struct_array" if
|
385
|
+
index * 3 + 3 > @structs.size + 1
|
386
|
+
|
387
|
+
if count == 1
|
388
|
+
lbl, vl = * read_field(data_or_offset)
|
389
|
+
struct[lbl] = vl
|
390
|
+
else
|
391
|
+
return 1 if count == 0
|
392
|
+
raise GffError, "struct index not divisable by 4" if
|
393
|
+
data_or_offset % 4 != 0
|
394
|
+
data_or_offset /= 4
|
395
|
+
for i in data_or_offset...(data_or_offset+count)
|
396
|
+
lbl, vl = * read_field(@field_indices[i])
|
397
|
+
struct[lbl] = vl
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
struct.struct_id = type
|
402
|
+
struct
|
403
|
+
end
|
404
|
+
|
405
|
+
# Reads the field at +index+ and returns [label_name, Gff::Element]
|
406
|
+
def read_field index
|
407
|
+
gff = {}
|
408
|
+
|
409
|
+
field = Element.new
|
410
|
+
|
411
|
+
index *= 3
|
412
|
+
type = @fields[index]
|
413
|
+
label_index = @fields[index + 1]
|
414
|
+
data_or_offset = @fields[index + 2]
|
415
|
+
# puts "Reading field #{index}"
|
416
|
+
# puts "Label_index = #{label_index}, label = #{@labels[label_index]}"
|
417
|
+
# puts "type = #{type}, data_or_offset = #{data_or_offset}"
|
418
|
+
raise GffError, "Label index #{label_index} outside of label array" if
|
419
|
+
label_index > @labels.size
|
420
|
+
|
421
|
+
raise GffError, "Field data offset #{data_or_offset} outside of field data block (#{@field_data.size})" if
|
422
|
+
ComplexTypes.index(type) && data_or_offset > @field_data.size
|
423
|
+
|
424
|
+
raise GffError, "Unknown field type #{type}." unless Types[type]
|
425
|
+
type = Types[type]
|
426
|
+
|
427
|
+
label = @labels[label_index]
|
428
|
+
|
429
|
+
value = case type
|
430
|
+
when :byte, :char
|
431
|
+
data_or_offset & 0xff
|
432
|
+
|
433
|
+
when :word
|
434
|
+
data_or_offset & 0xffff
|
435
|
+
|
436
|
+
when :short
|
437
|
+
[(data_or_offset & 0xffff)].pack("S").unpack("s")[0]
|
438
|
+
|
439
|
+
when :dword
|
440
|
+
data_or_offset
|
441
|
+
|
442
|
+
when :int
|
443
|
+
[data_or_offset].pack("I").unpack("i")[0]
|
444
|
+
|
445
|
+
when :float
|
446
|
+
[data_or_offset].pack("V").unpack("f")[0]
|
447
|
+
|
448
|
+
when :dword64
|
449
|
+
len = 8
|
450
|
+
v1, v2 = @field_data[data_or_offset, len].unpack("II")
|
451
|
+
v1 * (2**32) + v2
|
452
|
+
|
453
|
+
when :int64
|
454
|
+
len = 8
|
455
|
+
@field_data[data_or_offset, len].unpack("q")[0]
|
456
|
+
|
457
|
+
when :double
|
458
|
+
len = 8
|
459
|
+
@field_data[data_or_offset, len].unpack("d")[0]
|
460
|
+
|
461
|
+
when :cexostr
|
462
|
+
len = @field_data[data_or_offset, 4].unpack("V")[0]
|
463
|
+
@field_data[data_or_offset + 4, len]
|
464
|
+
|
465
|
+
when :resref
|
466
|
+
len = @field_data[data_or_offset, 1].unpack("C")[0]
|
467
|
+
@field_data[data_or_offset + 1, len]
|
468
|
+
|
469
|
+
when :cexolocstr
|
470
|
+
total_size, str_ref, str_count =
|
471
|
+
@field_data[data_or_offset, 12].unpack("VVV")
|
472
|
+
all = @field_data[data_or_offset + 12, total_size]
|
473
|
+
field._str_ref = str_ref
|
474
|
+
|
475
|
+
r = []
|
476
|
+
str_count.times {
|
477
|
+
id, len = all.unpack("VV")
|
478
|
+
str = all[8, len].unpack("a*")[0]
|
479
|
+
all = all[(8 + len)..-1]
|
480
|
+
r << Gff::CExoLocString.new(id, str)
|
481
|
+
}
|
482
|
+
len = total_size + 4
|
483
|
+
r
|
484
|
+
|
485
|
+
when :void
|
486
|
+
len = @field_data[data_or_offset, 4].unpack("V")
|
487
|
+
void = @field_data[data_or_offset + 4, len].unpack("H*")
|
488
|
+
raise "void: #{void.inspect}"
|
489
|
+
|
490
|
+
when :struct
|
491
|
+
read_struct data_or_offset
|
492
|
+
|
493
|
+
when :list
|
494
|
+
list = []
|
495
|
+
|
496
|
+
raise GffError, "List index not divisable by 4" unless
|
497
|
+
data_or_offset % 4 == 0
|
498
|
+
|
499
|
+
data_or_offset /= 4
|
500
|
+
|
501
|
+
raise GffError, "List index outside list indices" if
|
502
|
+
data_or_offset > @list_indices.size
|
503
|
+
|
504
|
+
count = @list_indices[data_or_offset]
|
505
|
+
|
506
|
+
raise GffError, "List index overflow the list indices array" if
|
507
|
+
data_or_offset + count > @list_indices.size
|
508
|
+
|
509
|
+
data_or_offset += 1
|
510
|
+
|
511
|
+
for i in data_or_offset...(data_or_offset + count)
|
512
|
+
list << read_struct(@list_indices[i])
|
513
|
+
end
|
514
|
+
|
515
|
+
list
|
516
|
+
|
517
|
+
end
|
518
|
+
|
519
|
+
raise GffError, "Field data overflows from the field data block area\
|
520
|
+
offset = #{data_or_offset + len}, len = #{@field_data.size}" if
|
521
|
+
len && data_or_offset + len > @field_data.size
|
522
|
+
|
523
|
+
field.label = label
|
524
|
+
field.type = type
|
525
|
+
field.value = value
|
526
|
+
|
527
|
+
[label, field] #::Gff::Element.new(type,label,value)
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
|
532
|
+
class NWN::Gff::Writer
|
533
|
+
include NWN::Gff
|
534
|
+
|
535
|
+
attr_reader :bytes
|
536
|
+
|
537
|
+
def initialize(gff)
|
538
|
+
@gff = gff
|
539
|
+
|
540
|
+
@structs = []
|
541
|
+
@fields = []
|
542
|
+
@labels = []
|
543
|
+
@field_indices = []
|
544
|
+
@list_indices = []
|
545
|
+
@field_data = ""
|
546
|
+
|
547
|
+
write_all
|
548
|
+
end
|
549
|
+
|
550
|
+
# Takes a NWN::Gff::Gff object and dumps it as raw bytes,
|
551
|
+
# including the header.
|
552
|
+
def self.dump(gff)
|
553
|
+
self.new(gff).bytes
|
554
|
+
end
|
555
|
+
|
556
|
+
private
|
557
|
+
|
558
|
+
def get_label_id_for_label str
|
559
|
+
@labels << str unless @labels.index(str)
|
560
|
+
@labels.index(str)
|
561
|
+
end
|
562
|
+
|
563
|
+
def add_data_field type, label, content
|
564
|
+
label_id = get_label_id_for_label label
|
565
|
+
@fields.push Types.index(type), label_id, content
|
566
|
+
(@fields.size - 1) / 3
|
567
|
+
end
|
568
|
+
|
569
|
+
def write_all
|
570
|
+
data = []
|
571
|
+
write_struct @gff.root_struct
|
572
|
+
|
573
|
+
c_offset = 0
|
574
|
+
data << [
|
575
|
+
@gff.type,
|
576
|
+
@gff.version,
|
577
|
+
|
578
|
+
# Offset of Struct array as bytes from the beginning of the file
|
579
|
+
c_offset += 56,
|
580
|
+
# Number of elements in Struct array
|
581
|
+
@structs.size / 3,
|
582
|
+
|
583
|
+
# Offset of Field array as bytes from the beginning of the file
|
584
|
+
fields_start = c_offset += @structs.size / 3 * 12,
|
585
|
+
# Number of elements in Field array
|
586
|
+
@fields.size / 3,
|
587
|
+
|
588
|
+
# Offset of Label array as bytes from the beginning of the file
|
589
|
+
c_offset += @fields.size / 3 * 12,
|
590
|
+
# Number of elements in Label array
|
591
|
+
@labels.size,
|
592
|
+
|
593
|
+
# Offset of Field Data as bytes from the beginning of the file
|
594
|
+
c_offset += @labels.size * 16,
|
595
|
+
# Number of bytes in Field Data block
|
596
|
+
@field_data.size,
|
597
|
+
|
598
|
+
# Offset of Field Indices array as bytes from the beginning of the file
|
599
|
+
c_offset += @field_data.size,
|
600
|
+
# Number of bytes in Field Indices array
|
601
|
+
@field_indices.size * 4,
|
602
|
+
|
603
|
+
# Offset of List Indices array as bytes from the beginning of the file
|
604
|
+
c_offset += @field_indices.size * 4,
|
605
|
+
# Number of bytes in List Indices array
|
606
|
+
@list_indices.size * 4
|
607
|
+
|
608
|
+
].pack("a4a4 VV VV VV VV VV VV")
|
609
|
+
|
610
|
+
data << @structs.pack("V*")
|
611
|
+
data << @fields.pack("V*")
|
612
|
+
data << @labels.pack("a16" * @labels.size)
|
613
|
+
data << @field_data
|
614
|
+
data << @field_indices.pack("V*")
|
615
|
+
data << @list_indices.pack("V*")
|
616
|
+
|
617
|
+
@bytes = data.join("")
|
618
|
+
end
|
619
|
+
|
620
|
+
def write_struct struct
|
621
|
+
raise GffError, "struct invalid: #{struct.inspect}" unless struct.is_a?(Gff::Struct)
|
622
|
+
raise GffError, "struct_id missing from struct" unless struct.struct_id
|
623
|
+
|
624
|
+
# This holds all field label ids this struct has as a member
|
625
|
+
fields_of_this_struct = []
|
626
|
+
|
627
|
+
# This will hold the index of this struct
|
628
|
+
index = @structs.size / 3
|
629
|
+
|
630
|
+
@structs.push struct.struct_id, 0, 0
|
631
|
+
|
632
|
+
struct.sort.each {|k,v|
|
633
|
+
raise GffError, "Empty label." if !k || k == ""
|
634
|
+
|
635
|
+
case v.type
|
636
|
+
# simple data types
|
637
|
+
when :byte, :char, :word, :short, :dword, :int, :float
|
638
|
+
format = Formats[v.type]
|
639
|
+
fields_of_this_struct << add_data_field(v.type, k, [v.value].pack(format).unpack("V")[0])
|
640
|
+
|
641
|
+
# complex data types
|
642
|
+
when :dword64, :int64, :double, :void
|
643
|
+
$stderr.puts "Warning: complex datatypes dword64, int64, double and void are untested."
|
644
|
+
|
645
|
+
fields_of_this_struct << add_data_field(v.type, k, @field_data.size)
|
646
|
+
format = Formats[v.type]
|
647
|
+
@field_data << case v.type
|
648
|
+
when :dword64
|
649
|
+
[
|
650
|
+
( v.value / (2**32) ) & 0xffffffff,
|
651
|
+
v.value % (2**32)
|
652
|
+
].pack("II")
|
653
|
+
when :void
|
654
|
+
[ v.value.size, v.value ].pack("VH*")
|
655
|
+
else
|
656
|
+
[v.value].pack(format)
|
657
|
+
end
|
658
|
+
|
659
|
+
raise GffError, "unhandled complex datatype #{v.type}"
|
660
|
+
|
661
|
+
when :struct
|
662
|
+
raise GffError, "type = struct, but value not a hash" unless
|
663
|
+
v.value.is_a?(Gff::Struct)
|
664
|
+
|
665
|
+
fields_of_this_struct << add_data_field(v.type, k, write_struct(v.value))
|
666
|
+
|
667
|
+
when :list
|
668
|
+
raise GffError, "type = list, but value not an array" unless
|
669
|
+
v.value.is_a?(Array)
|
670
|
+
|
671
|
+
fields_of_this_struct << add_data_field(v.type, k, 4 * @list_indices.size)
|
672
|
+
|
673
|
+
count = v.value.size
|
674
|
+
tmp = @list_indices.size
|
675
|
+
@list_indices << count
|
676
|
+
count.times {
|
677
|
+
@list_indices << 0
|
678
|
+
}
|
679
|
+
|
680
|
+
v.value.each_with_index do |kk, idx|
|
681
|
+
vv = write_struct(kk)
|
682
|
+
@list_indices[ idx + tmp + 1 ] = vv
|
683
|
+
end
|
684
|
+
|
685
|
+
when :resref
|
686
|
+
fields_of_this_struct << add_data_field(v.type, k, @field_data.size)
|
687
|
+
@field_data << [v.value.size, v.value].pack("Ca*")
|
688
|
+
|
689
|
+
when :cexostr
|
690
|
+
fields_of_this_struct << add_data_field(v.type, k, @field_data.size)
|
691
|
+
@field_data << [v.value.size, v.value].pack("Va*")
|
692
|
+
|
693
|
+
when :cexolocstr
|
694
|
+
raise GffError, "type = cexolocstr, but value not an array" unless
|
695
|
+
v.value.is_a?(Array)
|
696
|
+
|
697
|
+
fields_of_this_struct << add_data_field(v.type, k, @field_data.size)
|
698
|
+
|
699
|
+
# total size (4), str_ref (4), str_count (4)
|
700
|
+
total_size = 8 + v.value.inject(0) {|t,x| t + x.text.size + 8}
|
701
|
+
@field_data << [
|
702
|
+
total_size,
|
703
|
+
v._str_ref,
|
704
|
+
v.value.size
|
705
|
+
].pack("VVV")
|
706
|
+
|
707
|
+
v.value.each {|s|
|
708
|
+
@field_data << [s.language, s.text.size, s.text].pack("VVa*")
|
709
|
+
}
|
710
|
+
|
711
|
+
|
712
|
+
else
|
713
|
+
raise GffError, "Unknown data type: #{v.type}"
|
714
|
+
end
|
715
|
+
}
|
716
|
+
|
717
|
+
# id/type, data_or_offset, nr_of_fields
|
718
|
+
@structs[3 * (index) + 2] = fields_of_this_struct.size
|
719
|
+
|
720
|
+
if fields_of_this_struct.size < 1
|
721
|
+
elsif fields_of_this_struct.size == 1
|
722
|
+
@structs[3 * (index) + 1] = fields_of_this_struct[0]
|
723
|
+
else
|
724
|
+
# Offset into field_indices starting where are number of nr_of_fields
|
725
|
+
# dwords as indexes into @fields
|
726
|
+
@structs[3 * (index) + 1] = 4 * (@field_indices.size)
|
727
|
+
@field_indices.push *fields_of_this_struct
|
728
|
+
end
|
729
|
+
|
730
|
+
index
|
731
|
+
end
|
732
|
+
|
733
|
+
end
|
data/lib/nwn/twoda.rb
ADDED
data/spec/rcov.opts
ADDED
File without changes
|
data/spec/spec.opts
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.0
|
3
|
+
specification_version: 1
|
4
|
+
name: nwn-lib
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: "0.1"
|
7
|
+
date: 2008-06-28 00:00:00 +02:00
|
8
|
+
summary: a ruby library for accessing Neverwinter Nights resource files
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: elven@swordcoast.net
|
12
|
+
homepage: http://nwn-lib.elv.es
|
13
|
+
rubyforge_project: nwn-lib
|
14
|
+
description: a ruby library for accessing Neverwinter Nights resource files
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.8.4
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Bernhard Stoeckner
|
31
|
+
files:
|
32
|
+
- COPYING
|
33
|
+
- README
|
34
|
+
- Rakefile
|
35
|
+
- bin/nwn-gff-print
|
36
|
+
- bin/nwn-gff-set
|
37
|
+
- spec/spec.opts
|
38
|
+
- spec/rcov.opts
|
39
|
+
- lib/nwn
|
40
|
+
- lib/nwn/twoda.rb
|
41
|
+
- lib/nwn/gff.rb
|
42
|
+
test_files: []
|
43
|
+
|
44
|
+
rdoc_options:
|
45
|
+
- --quiet
|
46
|
+
- --line-numbers
|
47
|
+
- --inline-source
|
48
|
+
- --title
|
49
|
+
- "nwn-lib: a ruby library for accessing NWN resource files"
|
50
|
+
- --main
|
51
|
+
- README
|
52
|
+
- --exclude
|
53
|
+
- ^(examples|extras)/
|
54
|
+
extra_rdoc_files:
|
55
|
+
- README
|
56
|
+
- COPYING
|
57
|
+
executables:
|
58
|
+
- nwn-gff-print
|
59
|
+
extensions: []
|
60
|
+
|
61
|
+
requirements: []
|
62
|
+
|
63
|
+
dependencies: []
|
64
|
+
|