nwn-lib 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|