nwn-lib 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
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