nwn-lib 0.4.2 → 0.4.3

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.
Files changed (7) hide show
  1. data/BINARIES +8 -0
  2. data/CHANGELOG +6 -0
  3. data/Rakefile +2 -2
  4. data/bin/nwn-erf +184 -0
  5. data/lib/nwn/all.rb +2 -0
  6. data/lib/nwn/erf.rb +295 -0
  7. metadata +5 -2
data/BINARIES CHANGED
@@ -9,6 +9,14 @@ for help, it should be self-explanatory.
9
9
 
10
10
  There are some usage examples on the CHEATSHEET.
11
11
 
12
+ == nwn-erf
13
+
14
+ nwn-erf can pack/unpack all mod, hak, erf and nwm files.
15
+
16
+ Type
17
+ nwn-erf -h
18
+ for help.
19
+
12
20
  == nwn-irb
13
21
 
14
22
  nwn-irb is a interactive shell preloading all relevant libs, and optionally
data/CHANGELOG CHANGED
@@ -130,3 +130,9 @@ Bernhard Stoeckner <elven@swordcoast.net> (10):
130
130
  add *.itp to GuessFormats
131
131
  Tlk: basic talktable editing and writing
132
132
  0.4.2-rel
133
+
134
+ === 0.4.3
135
+ Bernhard Stoeckner <elven@swordcoast.net> (2):
136
+ NWN::Erf: basic reading/writing library + tar-like binary
137
+ add tlk.rb to nwn/all.rb
138
+ 0.4.3-rel
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ include FileUtils
9
9
  # Configuration
10
10
  ##############################################################################
11
11
  NAME = "nwn-lib"
12
- VERS = "0.4.2"
12
+ VERS = "0.4.3"
13
13
  CLEAN.include ["**/.*.sw?", "pkg", ".config", "rdoc", "coverage"]
14
14
  RDOC_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', \
15
15
  'nwn-lib: a ruby library for accessing NWN resource files', \
@@ -39,7 +39,7 @@ spec = Gem::Specification.new do |s|
39
39
  s.author = "Bernhard Stoeckner"
40
40
  s.email = "elven@swordcoast.net"
41
41
  s.homepage = "http://nwn-lib.elv.es"
42
- s.executables = ["nwn-gff", "nwn-dsl", "nwn-irb"]
42
+ s.executables = ["nwn-gff", "nwn-erf", "nwn-dsl", "nwn-irb"]
43
43
  s.required_ruby_version = ">= 1.8.4"
44
44
  s.files = %w(COPYING CHANGELOG README Rakefile) + Dir.glob("{bin,doc,spec,lib,tools,scripts,data}/**/*")
45
45
  s.require_path = "lib"
data/bin/nwn-erf ADDED
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/ruby
2
+ require 'rubygems'
3
+ require 'optparse'
4
+ require 'nwn/all'
5
+ require 'tempfile'
6
+
7
+ $action = nil
8
+ $verbose = false
9
+ $type = 'ERF'
10
+ $allow_duplicates = false
11
+ $descriptions = {}
12
+
13
+ begin OptionParser.new do |o|
14
+ o.banner = "Usage: nwn-erf <options> [FILEs] ..."
15
+
16
+ o.on "-f FILE", "Specify archive name (instead of stdin/stdout)" do |file|
17
+ $file = file
18
+ end
19
+
20
+ o.separator " "
21
+
22
+ o.on "-t", "--list", "List erf contents. Use -v for attributes." do
23
+ $action = :t
24
+ end
25
+ o.on "-c", "--create", "Create a new archive with the given files as contents." do
26
+ $action = :c
27
+ end
28
+ o.on "-x", "--extract", "Extract all files to current directory." do
29
+ $action = :x
30
+ end
31
+ o.on "-a", "--add", "Add files to the given archive.",
32
+ "This is an expensive operation, a full archive rebuild is required." do
33
+ $action = :a
34
+ end
35
+ o.on "-r", "--remove", "Remove files from the given archive.",
36
+ "This is an expensive operation, a full archive rebuild is required." do
37
+ $action = :r
38
+ end
39
+
40
+ o.separator " "
41
+
42
+ o.on "-D TEXT", "--description", "Set a description TEXT",
43
+ "Only useful with -c, -a, -r." do |text|
44
+ $descriptions[0] = text
45
+ end
46
+
47
+ o.on "--type TYPE", "Specify 3 or 4-letter archive type (defaults to 'HAK')" do |t|
48
+ $type = t
49
+ end
50
+
51
+ o.on "-H" , "--hak", "Shorthand for --type HAK" do
52
+ $type = 'HAK'
53
+ end
54
+
55
+ o.on "-E" , "--erf", "Shorthand for --type ERF (default)" do
56
+ $type = 'ERF'
57
+ end
58
+
59
+ o.on "-M" , "--mod", "Shorthand for --type MOD" do
60
+ $type = 'MOD'
61
+ end
62
+
63
+ o.separator " "
64
+ o.separator "Hacks:"
65
+
66
+ o.on "--allow-duplicates", "Allow packaging of duplicate filenames" do
67
+ $allow_duplicates = true
68
+ end
69
+
70
+ o.separator " "
71
+
72
+ o.on_tail "-h", "--help", "Show this crud" do
73
+ $stderr.puts o
74
+ exit 1
75
+ end
76
+
77
+ o.on_tail "-v", "--verbose", "be verbose" do |v|
78
+ $verbose = v
79
+ end
80
+ end.parse!
81
+ rescue => ee
82
+ $stderr.puts ee.to_s
83
+ $stderr.puts ee.backtrace.join("\n")
84
+ exit 1
85
+ end
86
+
87
+
88
+ def input filename = nil
89
+ if $file.nil?
90
+ yield $stdin
91
+ else
92
+ File.open(filename || $file, "r") {|f| yield f}
93
+ end
94
+ end
95
+
96
+ def output mode = "w", filename = nil
97
+ if $file.nil?
98
+ yield $stdout
99
+ else
100
+ File.open(filename || $file, mode) {|f| yield f}
101
+ end
102
+ end
103
+
104
+ case $action
105
+ when :t
106
+ input {|f|
107
+ erf = NWN::Erf::Erf.new(f)
108
+ $stderr.puts "# %-4s %14s %16s %-10s %s" % %w{type offset size date filename} if $verbose
109
+ erf.content.each {|c|
110
+ if !$verbose
111
+ $stderr.puts "%s" % [c.filename]
112
+ else
113
+ $stderr.puts "%4d %16d %16d %10s %s" % [
114
+ c.res_type, c.offset, c.size,
115
+ Date.ordinal(1900 + erf.year, erf.day_of_year).strftime("%Y-%m-%d"),
116
+ c.filename
117
+ ]
118
+ end
119
+ }
120
+ }
121
+
122
+ when :x
123
+ input {|f|
124
+ erf = NWN::Erf::Erf.new(f)
125
+ erf.content.each {|c|
126
+ $stderr.puts "%s" % [c.filename] if $verbose
127
+ File.open(c.filename, "w") {|ff|
128
+ ff.write(c.get)
129
+ }
130
+ }
131
+ }
132
+
133
+ when :c
134
+ erf = NWN::Erf::Erf.new
135
+ erf.file_type = $type if $type
136
+
137
+ if $descriptions
138
+ erf.localized_strings.merge! $descriptions
139
+ erf.description_str_ref = 0
140
+ end
141
+
142
+ ARGV.each {|a|
143
+ $stderr.puts File.basename(a) if $verbose
144
+ raise ArgumentError, "#{File.basename(a)} already present in erf." if
145
+ !$allow_duplicates && erf.has?(a)
146
+ erf.add a
147
+ }
148
+ output {|f| erf.write_to(f) }
149
+
150
+ when :a, :r
151
+ input do |infile|
152
+ erf = NWN::Erf::Erf.new(infile)
153
+
154
+ if $descriptions
155
+ erf.localized_strings.merge! $descriptions
156
+ erf.description_str_ref = 0
157
+ end
158
+
159
+ ARGV.each {|arg|
160
+ case $action
161
+ when :a
162
+ raise ArgumentError, "#{File.basename(arg)} already present in erf." if
163
+ !$allow_duplicates && erf.has?(arg)
164
+ erf.add arg
165
+
166
+ when :r
167
+ erf.content.reject! {|con|
168
+ con.filename.downcase == arg.downcase
169
+ }
170
+ end
171
+ }
172
+
173
+ tempfile = Tempfile.new("nwn-erf", File.dirname($file || "."))
174
+ output("w", tempfile.path) do |f|
175
+ erf.write_to(f)
176
+ end
177
+
178
+ FileUtils.mv(tempfile.path, $file) unless $file.nil?
179
+ tempfile.close
180
+ end
181
+
182
+ else
183
+ raise ArgumentError, "You need to specify a mode of operation (try -h)."
184
+ end
data/lib/nwn/all.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require 'nwn/twoda'
2
2
  require 'nwn/gff'
3
+ require 'nwn/tlk'
4
+ require 'nwn/erf'
3
5
  require 'nwn/yaml'
4
6
  require 'nwn/kivinen'
5
7
  require 'nwn/scripting'
data/lib/nwn/erf.rb ADDED
@@ -0,0 +1,295 @@
1
+ require 'enumerator'
2
+
3
+ module NWN
4
+ module Erf
5
+ ValidTypes = %w{ERF HAK MOD}
6
+
7
+ Extensions = {
8
+ 'res' => 0,
9
+ 'bmp' => 1,
10
+ 'mve' => 2,
11
+ 'tga' => 3,
12
+ 'wav' => 4,
13
+ 'wfx' => 5,
14
+ 'plt' => 6,
15
+ 'ini' => 7,
16
+ 'mp3' => 8,
17
+ 'mpg' => 9,
18
+ 'txt' => 10,
19
+ 'plh' => 2000,
20
+ 'tex' => 2001,
21
+ 'mdl' => 2002,
22
+ 'thg' => 2003,
23
+ 'fnt' => 2005,
24
+ 'lua' => 2007,
25
+ 'slt' => 2008,
26
+ 'nss' => 2009,
27
+ 'ncs' => 2010,
28
+ 'mod' => 2011,
29
+ 'are' => 2012,
30
+ 'set' => 2013,
31
+ 'ifo' => 2014,
32
+ 'bic' => 2015,
33
+ 'wok' => 2016,
34
+ '2da' => 2017,
35
+ 'tlk' => 2018,
36
+ 'txi' => 2022,
37
+ 'git' => 2023,
38
+ 'bti' => 2024,
39
+ 'uti' => 2025,
40
+ 'btc' => 2026,
41
+ 'utc' => 2027,
42
+ 'dlg' => 2029,
43
+ 'itp' => 2030,
44
+ 'btt' => 2031,
45
+ 'utt' => 2032,
46
+ 'dds' => 2033,
47
+ 'bts' => 2034,
48
+ 'uts' => 2035,
49
+ 'ltr' => 2036,
50
+ 'gff' => 2037,
51
+ 'fac' => 2038,
52
+ 'bte' => 2039,
53
+ 'ute' => 2040,
54
+ 'btd' => 2041,
55
+ 'utd' => 2042,
56
+ 'btp' => 2043,
57
+ 'utp' => 2044,
58
+ 'dft' => 2045,
59
+ 'gic' => 2046,
60
+ 'gui' => 2047,
61
+ 'css' => 2048,
62
+ 'ccs' => 2049,
63
+ 'btm' => 2050,
64
+ 'utm' => 2051,
65
+ 'dwk' => 2052,
66
+ 'pwk' => 2053,
67
+ 'btg' => 2054,
68
+ 'utg' => 2055,
69
+ 'jrl' => 2056,
70
+ 'sav' => 2057,
71
+ 'utw' => 2058,
72
+ '4pc' => 2059,
73
+ 'ssf' => 2060,
74
+ 'hak' => 2061,
75
+ 'nwm' => 2062,
76
+ 'bik' => 2063,
77
+ 'ndb' => 2064,
78
+ 'ptm' => 2065,
79
+ 'ptt' => 2066,
80
+ 'bak' => 2067,
81
+ 'osc' => 3000,
82
+ 'usc' => 3001,
83
+ 'trn' => 3002,
84
+ 'utr' => 3003,
85
+ 'uen' => 3004,
86
+ 'ult' => 3005,
87
+ 'sef' => 3006,
88
+ 'pfx' => 3007,
89
+ 'cam' => 3008,
90
+ 'lfx' => 3009,
91
+ 'bfx' => 3010,
92
+ 'upe' => 3011,
93
+ 'ros' => 3012,
94
+ 'rst' => 3013,
95
+ 'ifx' => 3014,
96
+ 'pfb' => 3015,
97
+ 'zip' => 3016,
98
+ 'wmp' => 3017,
99
+ 'bbx' => 3018,
100
+ 'tfx' => 3019,
101
+ 'wlk' => 3020,
102
+ 'xml' => 3021,
103
+ 'scc' => 3022,
104
+ 'ptx' => 3033,
105
+ 'ltx' => 3034,
106
+ 'trx' => 3035,
107
+ 'mdb' => 4000,
108
+ 'mda' => 4001,
109
+ 'spt' => 4002,
110
+ 'gr2' => 4003,
111
+ 'fxa' => 4004,
112
+ 'fxe' => 4005,
113
+ 'jpg' => 4007,
114
+ 'pwc' => 4008,
115
+ 'ids' => 9996,
116
+ 'erf' => 9997,
117
+ 'bif' => 9998,
118
+ 'key' => 9999,
119
+ }.freeze
120
+
121
+ class Erf
122
+
123
+ attr_accessor :content
124
+ attr_accessor :localized_strings
125
+ attr_accessor :description_str_ref
126
+
127
+ attr_accessor :file_type
128
+ attr_accessor :file_version
129
+ attr_accessor :day_of_year
130
+ attr_accessor :year
131
+
132
+ # Create a new Erf object, optionally reading a existing file from +io+.
133
+ def initialize io = nil
134
+ @content = []
135
+ @localized_strings = {}
136
+ @io = io
137
+ @file_type, @file_version = "ERF", "V1.0"
138
+ @year = Time.now.year
139
+ @description_str_ref = 0xffffffff
140
+ @day_of_year = Time.now.yday # strftime("%j").to_i
141
+ read_from io if io
142
+ end
143
+
144
+ def add filename
145
+ @content << ContentObject.new_from(filename)
146
+ end
147
+
148
+ def has?(filename)
149
+ base = File.basename(filename)
150
+ @content.each {|f|
151
+ return true if f.filename.downcase == base.downcase
152
+ }
153
+ return false
154
+ end
155
+
156
+ class ContentObject
157
+ attr_accessor :resref
158
+ attr_accessor :res_type
159
+ attr_accessor :io
160
+ attr_accessor :offset
161
+ attr_accessor :size_override
162
+
163
+ def self.new_from filename
164
+ stat = File.stat(filename)
165
+ base = File.basename(filename).split(".")[0..-2].join(".")
166
+ ext = File.extname(filename)[1..-1]
167
+ res_type = NWN::Erf::Extensions[ext] or raise ArgumentError,
168
+ "Not a valid extension: #{ext.inspect} (while packing #{filename})"
169
+
170
+ ContentObject.new(base, res_type, filename, 0, stat.size)
171
+ end
172
+
173
+ def initialize resref, res_type, io = nil, offset = nil, size = nil
174
+ @resref, @res_type = resref, res_type
175
+ @io, @offset = io, offset
176
+ @size_override = size
177
+ end
178
+
179
+ # Get the size in bytes of this object.
180
+ def size
181
+ @size_override || (@io.is_a?(IO) ? @io.stat.size : File.stat(@io).size)
182
+ end
183
+
184
+ def get
185
+ if @io.is_a?(IO)
186
+ @io.seek(@offset) if @offset
187
+ @io.read(self.size)
188
+ else
189
+ IO.read(@io)
190
+ end
191
+ end
192
+
193
+ def filename
194
+ @resref + "." + self.extension
195
+ end
196
+
197
+ def extension
198
+ NWN::Erf::Extensions.index(@res_type)
199
+ end
200
+ end
201
+
202
+ private
203
+
204
+ def read_from io
205
+ header = @io.read(160)
206
+ raise IOError, "Cannot read header: Not a erf file?" unless
207
+ header && header.size == 160
208
+
209
+ @file_type, @file_version,
210
+ locstr_count, locstr_size,
211
+ entry_count,
212
+ offset_to_locstr, offset_to_keys,
213
+ offset_to_res,
214
+ @year, @day_of_year, @description_str_ref = header.
215
+ unpack("A4 A4 VV VV VV VV V a116")
216
+
217
+ raise IOError, "Cannot read erf stream: invalid type #{@file_type.inspect}" unless
218
+ NWN::Erf::ValidTypes.index(@file_type)
219
+
220
+ if @file_version == "V1.0"
221
+ @filename_length = 16
222
+ # elsif version == "V1.1"
223
+ # @filename_length = 32
224
+ else
225
+ raise IOError, "Invalid erf version: #{@file_version}"
226
+ end
227
+
228
+ raise IOError, "key list not after locstr list" unless
229
+ offset_to_keys == offset_to_locstr + locstr_size
230
+
231
+ raise IOError, "Offset to locstr list is not after header" if
232
+ offset_to_locstr != 160
233
+
234
+ locstr = @io.read(locstr_size)
235
+ raise IOError, "Cannot read locstr list" unless
236
+ locstr.size == locstr_size
237
+
238
+ locstrs = locstr.unpack("V V/a*" * locstr_count)
239
+ locstrs.each_slice(3) {|lid, strsz, str|
240
+ raise IOError,
241
+ "Cannot read localized strings table: string size not met (want: #{strsz}, got #{str.size})" if
242
+ str.size != strsz
243
+ @localized_strings[lid] = str
244
+ }
245
+
246
+
247
+ keylist_entry_size = @filename_length + 4 + 2 + 2
248
+ keylist = @io.read(keylist_entry_size * entry_count)
249
+ keylist = keylist.unpack("A16 V v v" * entry_count)
250
+ keylist.each_slice(4) {|resref, res_id, res_type, unused|
251
+ @content << ContentObject.new(resref, res_type, @io)
252
+ }
253
+
254
+ resourcelist_entry_size = 4 + 4
255
+ resourcelist = @io.read(resourcelist_entry_size * entry_count)
256
+ resourcelist = resourcelist.unpack("I I" * entry_count)
257
+ _index = -1
258
+ resourcelist.each_slice(2) {|offset, size|
259
+ _index += 1
260
+ @content[_index].offset = offset
261
+ @content[_index].size_override = size
262
+ }
263
+ end
264
+
265
+ public
266
+
267
+ # Writes this Erf to a io stream.
268
+ def write_to io
269
+ locstr = @localized_strings.map {|x| [x[0], x[1].size, x[1]].pack("V V a*") }.join("")
270
+ keylist = @content.map {|c| [c.resref, @content.index(c), c.res_type, 0].pack("a16 V v v") }.join("")
271
+ reslist = @content.map {|c| [c.offset, c.size].pack("V V") }.join("")
272
+
273
+ offset_to_locstr = 160
274
+ offset_to_keylist = offset_to_locstr + locstr.size
275
+ offset_to_resourcelist = offset_to_keylist + keylist.size
276
+
277
+ header = [@file_type, @file_version,
278
+ @localized_strings.size, locstr.size,
279
+ @content.size,
280
+ offset_to_locstr, offset_to_keylist,
281
+ offset_to_resourcelist,
282
+ @year, @day_of_year, @description_str_ref, ""].pack("A4 A4 VV VV VV VV V a116")
283
+
284
+ io.write(header)
285
+ io.write(locstr)
286
+ io.write(keylist)
287
+ io.write(reslist)
288
+
289
+ @content.each {|c|
290
+ io.write(c.get)
291
+ }
292
+ end
293
+ end
294
+ end
295
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nwn-lib
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernhard Stoeckner
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-02-05 00:00:00 +01:00
12
+ date: 2009-02-06 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -17,6 +17,7 @@ description: a ruby library for accessing Neverwinter Nights resource files
17
17
  email: elven@swordcoast.net
18
18
  executables:
19
19
  - nwn-gff
20
+ - nwn-erf
20
21
  - nwn-dsl
21
22
  - nwn-irb
22
23
  extensions: []
@@ -39,12 +40,14 @@ files:
39
40
  - bin/nwn-dsl
40
41
  - bin/nwn-gff
41
42
  - bin/nwn-irb
43
+ - bin/nwn-erf
42
44
  - spec/spec.opts
43
45
  - spec/rcov.opts
44
46
  - lib/nwn
45
47
  - lib/nwn/scripting.rb
46
48
  - lib/nwn/yaml.rb
47
49
  - lib/nwn/twoda.rb
50
+ - lib/nwn/erf.rb
48
51
  - lib/nwn/settings.rb
49
52
  - lib/nwn/gff.rb
50
53
  - lib/nwn/tlk.rb