nwn-lib 0.4.6 → 0.4.7

Sign up to get free protection for your applications and to get access to all the features.
data/lib/nwn/res.rb CHANGED
@@ -11,13 +11,15 @@ module NWN
11
11
 
12
12
  # Create a new index to +filename+, optionally specifying +io+.
13
13
  def self.new_from filename, io = nil
14
+ FileTest.exists?(filename) or raise Errno::ENOENT unless io
15
+
14
16
  filename = File.expand_path(filename)
15
17
  base = File.basename(filename).split(".")[0..-2].join(".").downcase
16
- ext = File.extname(filename)[1..-1].downcase
18
+ ext = File.extname(filename)[1..-1].downcase rescue ""
17
19
  res_type = NWN::Resources::Extensions[ext] or raise ArgumentError,
18
20
  "Not a valid extension: #{ext.inspect} (while packing #{filename})"
19
21
 
20
- ContentObject.new(base, res_type, io || filename, 0, io.size || File.stat(filename).size)
22
+ ContentObject.new(base, res_type, io || filename, 0, io ? io.size : File.stat(filename).size)
21
23
  end
22
24
 
23
25
  def initialize resref, res_type, io = nil, offset = nil, size = nil
@@ -39,7 +41,7 @@ module NWN
39
41
  # and do it yourself, observing ContentObject#offset and ContentObject#size.
40
42
  def get
41
43
  if @io.respond_to?(:read)
42
- @io.seek(@offset) if @offset
44
+ @io.seek(@offset ? @offset : 0)
43
45
  d = @io.read(self.size)
44
46
  raise IOError,
45
47
  "not enough data available while reading #{self.filename}" if
@@ -52,7 +54,7 @@ module NWN
52
54
 
53
55
  # Get the canonical filename of this object.
54
56
  def filename
55
- @resref + "." + self.extension
57
+ @resref + "." + (self.extension || "unknown-#{@res_type}")
56
58
  end
57
59
 
58
60
  # Get the extension of this object.
data/lib/nwn/scripting.rb CHANGED
@@ -33,7 +33,7 @@ module NWN::Gff::Scripting
33
33
  fn, hash = $satisfy_loaded[object.object_id]
34
34
  if fn
35
35
  if hash != object.hash
36
- File.open(fn, "w") {|f|
36
+ File.open(fn, "wb") {|f|
37
37
  NWN::Gff.write(f, NWN::Gff.guess_file_format(fn), object)
38
38
  }
39
39
  log "saved #{object.to_s} -> #{fn}"
@@ -103,7 +103,7 @@ module NWN::Gff::Scripting
103
103
  fn = what.shift
104
104
  io = case fn
105
105
  when String
106
- IO.read(fn)
106
+ File.new(fn, "r")
107
107
  when IO
108
108
  fn
109
109
  else
data/lib/nwn/settings.rb CHANGED
@@ -1,3 +1,15 @@
1
+ module NWN
2
+
3
+ # This writes a debug message to stderr if the environment
4
+ # variable +NWN_LIB_DEBUG+ is set to non-nil or $DEBUG is
5
+ # true (ruby -d).
6
+ def self.log_debug msg
7
+ return false unless ENV['NWN_LIB_DEBUG'] || $DEBUG
8
+ $stderr.puts "(nwn-lib debug) %s: %s" % [caller[0].to_s, msg]
9
+ true
10
+ end
11
+ end
12
+
1
13
  if ENV['NWN_LIB_2DA_LOCATION'] && ENV['NWN_LIB_2DA_LOCATION'] != ""
2
14
  NWN::TwoDA::Cache.setup ENV['NWN_LIB_2DA_LOCATION']
3
15
  end
data/lib/nwn/twoda.rb CHANGED
@@ -109,9 +109,6 @@ module NWN
109
109
  # Will raise an ArgumentError if the given +bytes+ do
110
110
  # not contain a valid 2DA header, or the file is so badly
111
111
  # misshaped that it will not ever be parsed correctly by NWN1.
112
- #
113
- # Will complain about everything mismatching it finds
114
- # on $stderr if $DEBUG is set (-d).
115
112
  def parse bytes
116
113
  magic, *data = *bytes.split(/\r?\n/).map {|v| v.strip }
117
114
 
@@ -135,27 +132,27 @@ module NWN
135
132
  data.each_with_index {|row, idx|
136
133
  id = row.shift
137
134
 
138
- $stderr.puts "Warning: invalid ID in line #{idx}: #{id.inspect}" if $DEBUG && $id !~ /^\d+$/
135
+ NWN.log_debug "Warning: invalid ID in line #{idx}: #{id.inspect}" if $id !~ /^\d+$/
139
136
 
140
137
  id = id.to_i + id_offset
141
138
 
142
139
  # Its an empty row - NWN strictly numbers by counted lines - then so do we.
143
140
  while id > idx + idx_offset
144
- $stderr.puts "Warning: missing ID at #{id - id_offset}, fixing that for you." if $DEBUG
141
+ NWN.log_debug "Warning: missing ID at #{id - id_offset}, fixing that for you."
145
142
  idx_offset += 1
146
143
  end
147
144
 
148
145
  # NWN automatically increments duplicate IDs - so do we.
149
146
  while id < idx + idx_offset
150
- $stderr.puts "Warning: duplicate ID found at row #{idx} (id: #{id}); fixing that for you." if $DEBUG
147
+ NWN.log_debug "Warning: duplicate ID found at row #{idx} (id: #{id}); fixing that for you."
151
148
  id_offset += 1
152
149
  id += 1
153
150
  end
154
151
 
155
152
  # NWN fills in missing columns with an empty value - so do we.
156
- $stderr.puts "Warning: row #{id} (real: #{id - id_offset}) misses " +
153
+ NWN.log_debug "Warning: row #{id} (real: #{id - id_offset}) misses " +
157
154
  "#{header.size - row.size} columns at the end, fixed" if
158
- row.size < header.size if $DEBUG
155
+ row.size < header.size
159
156
 
160
157
  row << "" while row.size < header.size
161
158
 
@@ -170,8 +167,8 @@ module NWN
170
167
  end
171
168
  }
172
169
 
173
- $stderr.puts "Warning: row #{idx} has too many cells (has #{k_row.size}, want <= #{header.size})" if
174
- $DEBUG && k_row.size > header.size
170
+ NWN.log_debug "Warning: row #{idx} has too many cells (has #{k_row.size}, want <= #{header.size})" if
171
+ k_row.size > header.size
175
172
 
176
173
  k_row.pop while k_row.size > header.size
177
174
  }
@@ -10,7 +10,7 @@ list += o['Equip_ItemList'].field_value if o['Equip_ItemList']
10
10
  list += o['ItemList'].field_value if o['ItemList']
11
11
  log "#{list.size} items found."
12
12
  list.each_with_index {|item, index|
13
- File.open(fname = "item_#{index}.uti", "w") {|file|
13
+ File.open(fname = "item_#{index}.uti", "wb") {|file|
14
14
  file.write item.to_gff("UTI")
15
15
  log "Written item: #{item['Tag'].field_value} as #{fname}"
16
16
  }
data/scripts/reformat_2da CHANGED
@@ -4,6 +4,6 @@
4
4
  ARGV.each {|f|
5
5
  log "Working on #{f} .."
6
6
  t = TwoDA::Table.parse(IO.read(f))
7
- File.open(f, "w") {|n| n.puts t.to_2da }
7
+ File.open(f, "wb") {|n| n.puts t.to_2da }
8
8
  log "done."
9
9
  }
@@ -0,0 +1,36 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "nwn-dsl" do
4
+ it_should_behave_like "bin helper"
5
+
6
+ it "runs empty scripts" do
7
+ t = Tempfile.new(@tmp)
8
+ t.close
9
+ run(t.path)
10
+ end
11
+
12
+ it "runs want/need scripts" do
13
+ t = Tempfile.new(@tmp)
14
+ t.write("need ARGV.shift, :bic")
15
+ t.close
16
+ run_bin(t.path, WELLFORMED_GFF_PATH) do |i,o,e|
17
+ e = e.read
18
+ o = o.read
19
+ e.should == "#{t.path}: satisfied #{WELLFORMED_GFF_PATH} -> <NWN::Gff::Struct BIC/V3.2, 129 fields>\n"
20
+ o.should == ""
21
+ end
22
+ end
23
+
24
+ it "logs to stderr" do
25
+ t = Tempfile.new(@tmp)
26
+ t.write("log 'hey'")
27
+ t.close
28
+ run_bin(t.path, WELLFORMED_GFF_PATH) do |i,o,e|
29
+ e = e.read
30
+ o = o.read
31
+ e.should == "#{t.path}: hey\n"
32
+ o.should == ""
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,118 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "nwn-erf" do
4
+ it_should_behave_like "bin helper"
5
+
6
+ # Create temporary testcase files.
7
+ before do
8
+ @target = File.join(@tmp, "nwn-lib-spec-target.erf")
9
+
10
+ @tmp0s = []
11
+ @tmp1s = []
12
+ for x in %w{aaa.tga bbbb.erf ccc.bmp 44.txt} do
13
+ File.open(File.join(@tmp, x), "w") do |fn|
14
+ fn.write(x)
15
+ end
16
+ @tmp0s << File.join(@tmp, x)
17
+ end
18
+ for x in %w{bbbbbbbbbbbbbbbbbbbb.bmp} do
19
+ File.open(File.join(@tmp, x), "w") do |fn|
20
+ fn.write(x)
21
+ end
22
+ @tmp1s << File.join(@tmp, x)
23
+ end
24
+ @tmp0s.sort!
25
+ @tmp1s.sort!
26
+ end
27
+
28
+ after do
29
+ @tmp0s.each {|f|
30
+ File.unlink(f.path) rescue nil
31
+ }
32
+ @tmp1s.each {|f|
33
+ File.unlink(f.path) rescue nil
34
+ }
35
+ File.unlink(@target) rescue nil
36
+ end
37
+
38
+ it "packs -0" do
39
+ run("-0", "-c", "-f", @target, *@tmp0s)
40
+ end
41
+
42
+ it "fails to pack -0 with 1.1 filenames" do
43
+ run_fail("-0", "-c", "-f", @target, *(@tmp0s + @tmp1s))
44
+ end
45
+
46
+ it "packs -1" do
47
+ run("-1", "-c", "-f", @target, *(@tmp0s + @tmp1s))
48
+ end
49
+
50
+ it "lists -0" do
51
+ run("-c", "-f", @target, *@tmp0s)
52
+ run_bin("-l", "-f", @target) do |i,o,e|
53
+ o = o.read
54
+ o.split(/\n/).sort.should == @tmp0s.map {|x| File.basename(x) }
55
+ end
56
+ end
57
+
58
+ it "lists -1" do
59
+ run("-1", "-c", "-f", @target, *@tmp1s)
60
+ run_bin("-l", "-f", @target) do |i,o,e|
61
+ o = o.read
62
+ o.split(/\n/).sort.should == @tmp1s.map {|x| File.basename(x) }
63
+ end
64
+ end
65
+
66
+ it "extracts -0" do
67
+ run("-c", "-f", @target, *@tmp0s)
68
+ @tmp0s.each {|f| File.unlink(f) }
69
+ run("-v", "-x", "-f", @target)
70
+ @tmp0s.each {|f| FileTest.exists?(f).should == true }
71
+ @tmp0s.each {|f| IO.read(f).should == File.basename(f) }
72
+ end
73
+
74
+ it "extracts -1" do
75
+ workon = @tmp0s + @tmp1s
76
+ run("-1", "-c", "-f", @target, *workon)
77
+ workon.each {|f| File.unlink(f) }
78
+ run("-v", "-x", "-f", @target)
79
+ workon.each {|f| FileTest.exists?(f).should == true }
80
+ workon.each {|f| IO.read(f).should == File.basename(f) }
81
+ end
82
+
83
+ it "creates haks" do
84
+ run("-H", "-c", "-f", @target, *@tmp0s)
85
+ IO.read(@target).should =~ /^HAK/
86
+ end
87
+
88
+ it "creates mods" do
89
+ run("-M", "-c", "-f", @target, *@tmp0s)
90
+ IO.read(@target).should =~ /^MOD/
91
+ end
92
+
93
+ it "creates erfs (by default)" do
94
+ run("-E", "-c", "-f", @target, *@tmp0s)
95
+ IO.read(@target).should =~ /^ERF/
96
+ run("-c", "-f", @target, *@tmp0s)
97
+ IO.read(@target).should =~ /^ERF/
98
+ end
99
+
100
+ it "adds files from existing archives" do
101
+ run("-c", "-f", @target, *(@tmp0s[1 .. -1]))
102
+ run("-a", "-f", @target, @tmp0s[0])
103
+ run_bin("-l", "-f", @target) do |i,o,e|
104
+ o = o.read
105
+ o.split(/\n/).sort.should == @tmp0s.map {|x| File.basename(x) }
106
+ end
107
+ end
108
+
109
+ it "removes files from existing archives" do
110
+ run("-c", "-f", @target, *@tmp0s)
111
+ run("-r", "-f", @target, File.basename(@tmp0s[0]))
112
+ run_bin("-l", "-f", @target) do |i,o,e|
113
+ o = o.read
114
+ o.split(/\n/).sort.should == @tmp0s[1 .. -1].map {|x| File.basename(x) }
115
+ end
116
+ end
117
+
118
+ end
@@ -0,0 +1,90 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe "nwn-gff" do
4
+ it_should_behave_like "bin helper"
5
+
6
+ NWN::Gff::FileFormats.each do |in_format|
7
+ inf = in_format.to_s
8
+
9
+ NWN::Gff::FileFormats.each do |out_format|
10
+ otf = out_format.to_s
11
+
12
+ it "converts #{inf} to #{otf}" do
13
+ # prepare the temp file
14
+ t = Tempfile.new(@tmp)
15
+ t.close
16
+
17
+ run("-lg", "-i", WELLFORMED_GFF_PATH, "-k", inf, "-o", t.path)
18
+ run("-l", inf, "-i", t.path, "-k", otf)
19
+ t.unlink
20
+ end
21
+ end
22
+ end
23
+
24
+ it "supports none GNU-style backup" do
25
+ t = Tempfile.new(@tmp)
26
+ t.close
27
+ run("-b", "none", "-lg", "-i", WELLFORMED_GFF_PATH, "-kg", "-o", t.path)
28
+ FileTest.exists?(t.path).should == true
29
+ FileTest.exists?(t.path + "~").should == false
30
+ end
31
+
32
+ it "supports numbered GNU-style backup" do
33
+ t = Tempfile.new(@tmp)
34
+ t.close
35
+
36
+ run("-b", "numbered", "-lg", "-i", WELLFORMED_GFF_PATH, "-kg", "-o", t.path)
37
+ FileTest.exists?(t.path).should == true
38
+ FileTest.exists?(t.path + "~0").should == true
39
+ FileTest.exists?(t.path + "~1").should == false
40
+
41
+ run("-b", "numbered", "-lg", "-i", WELLFORMED_GFF_PATH, "-kg", "-o", t.path)
42
+ FileTest.exists?(t.path).should == true
43
+ FileTest.exists?(t.path + "~0").should == true
44
+ FileTest.exists?(t.path + "~1").should == true
45
+ FileTest.exists?(t.path + "~2").should == false
46
+
47
+ File.unlink(t.path + "~0")
48
+ File.unlink(t.path + "~1")
49
+ end
50
+
51
+ it "supports existing with no numbered backups GNU-style backup" do
52
+ t = Tempfile.new(@tmp)
53
+ t.close
54
+
55
+ FileTest.exists?(t.path + "~").should == false
56
+
57
+ run("-b", "existing", "-lg", "-i", WELLFORMED_GFF_PATH, "-kg", "-o", t.path)
58
+ FileTest.exists?(t.path).should == true
59
+ FileTest.exists?(t.path + "~").should == true
60
+
61
+ File.unlink(t.path + "~")
62
+
63
+ FileUtils.cp(t.path, t.path + "~0")
64
+ run("-b", "existing", "-lg", "-i", WELLFORMED_GFF_PATH, "-kg", "-o", t.path)
65
+ FileTest.exists?(t.path).should == true
66
+ FileTest.exists?(t.path + "~").should == false
67
+ FileTest.exists?(t.path + "~0").should == true
68
+ FileTest.exists?(t.path + "~1").should == true
69
+ FileTest.exists?(t.path + "~2").should == false
70
+
71
+ File.unlink( t.path + "~0")
72
+ File.unlink( t.path + "~1")
73
+ end
74
+
75
+
76
+ it "supports simple GNU-style backup" do
77
+ t = Tempfile.new(@tmp)
78
+ t.close
79
+
80
+ FileTest.exists?(t.path + "~").should == false
81
+
82
+ run("-b", "simple", "-lg", "-i", WELLFORMED_GFF_PATH, "-kg", "-o", t.path)
83
+ FileTest.exists?(t.path).should == true
84
+ FileTest.exists?(t.path + "~").should == true
85
+
86
+ File.unlink(t.path + "~")
87
+ end
88
+
89
+
90
+ end
data/spec/erf_spec.rb CHANGED
@@ -1,39 +1,10 @@
1
1
  require File.join(File.dirname(__FILE__), 'spec_helper')
2
2
 
3
- WELLFORMED_ERF = ([
4
- "HAK", "V1.0",
5
- locstr_count = 1, locstr_size = 14,
6
- entry_count = 3,
7
- offset_to_locstr = 160,
8
- offset_to_keys = offset_to_locstr + locstr_size,
9
- offset_to_res = offset_to_locstr + locstr_size + entry_count * 24,
10
-
11
- 100, 126, # year, dayofyear
12
- 0xdeadbeef, "" #description strref, 116 bytes 0-padding
13
- ].pack("a4 a4 VV VV VV VV V a116") + [
14
- 0, 6, "abcdef" # one locstr
15
- ].pack("V V a*") + [
16
- "resref", 0, 10, 0, # keylist: resref.txt, id = 0
17
- "help", 1, 1, 0, # keylist: help.bmp, id = 1
18
- "yegods", 2, 4, 0, # keylist: yegods.wav, id = 2
19
- ].pack("a16 V v v" * entry_count) + [
20
- offset_to_res + entry_count * 8, 6, # offset, size
21
- offset_to_res + entry_count * 8 + 6, 4, # offset, size
22
- offset_to_res + entry_count * 8 + 6 + 4, 6, # offset, size
23
- ].pack("II" * entry_count) + [
24
- "resref", "help", "yegods"
25
- ].pack("a* a* a*")).freeze
26
-
27
-
28
-
29
- describe "Erf::Erf" do
30
-
3
+ describe "Erf::Erf", :shared => true do
31
4
  def wellformed_verify binary, expect_locstr = true
32
- t = nil
33
5
  t = Erf::Erf.new(StringIO.new binary)
34
6
 
35
7
  t.file_type.should == "HAK"
36
- t.file_version.should == "V1.0"
37
8
  if expect_locstr
38
9
  t.localized_strings.should == {0 => "abcdef"}
39
10
  else
@@ -56,32 +27,78 @@ describe "Erf::Erf" do
56
27
  end
57
28
 
58
29
  it "reads wellformed ERF containers" do
59
- wellformed_verify WELLFORMED_ERF
30
+ wellformed_verify @erf
60
31
  end
61
32
 
62
33
  it "reproduces correct ERF binary data" do
63
- t = Erf::Erf.new(StringIO.new WELLFORMED_ERF)
34
+ t = Erf::Erf.new(StringIO.new @erf)
64
35
  io = StringIO.new
65
36
  t.write_to(io)
66
37
  io.seek(0)
67
- proc {
68
- wellformed_verify io.read
69
- }.should_not raise_error IOError
38
+ n = io.read
39
+ wellformed_verify n
40
+ n.should == @erf
70
41
  end
71
42
 
72
- it "does not read ERF with locstr_size = 0 and locstr_count > 0" do
73
- b = WELLFORMED_ERF.dup
74
- b[12,4] = [0].pack("V")
75
- proc {
76
- wellformed_verify b
77
- }.should raise_error IOError
43
+ it "reads ERF with locstr_size = 0 and locstr_count > 0" do
44
+ @erf[12,4] = [0].pack("V")
45
+ wellformed_verify @erf, false
78
46
  end
79
47
 
80
48
  it "reads ERF with locstr_size > 0 and locstr_count = 0" do
81
- b = WELLFORMED_ERF.dup
82
- b[8,4] = [0].pack("V")
83
- proc {
84
- wellformed_verify b, false
85
- }.should_not raise_error IOError
49
+ @erf[8,4] = [0].pack("V")
50
+ wellformed_verify @erf, false
51
+ end
52
+ end
53
+
54
+ describe "Erf V1.0" do
55
+ before do
56
+ @erf = WELLFORMED_ERF.dup
57
+ end
58
+
59
+ it_should_behave_like "Erf::Erf"
60
+
61
+ it "accepts valid filenames" do
62
+ t = Erf::Erf.new(StringIO.new @erf)
63
+ t.add_file("a" * 1 + ".txt", StringIO.new("blargh"))
64
+ t.add_file("a" * 16 + ".txt", StringIO.new("blargh"))
65
+ end
66
+
67
+ it "fails on invalid filenames" do
68
+ t = Erf::Erf.new(StringIO.new @erf)
69
+ proc { t.add_file("a" * 0 + ".txt", StringIO.new("blargh")) }.should raise_error ArgumentError
70
+ proc { t.add_file("a" * 17 + ".txt", StringIO.new("blargh")) }.should raise_error ArgumentError
71
+ end
72
+
73
+ it "returns unknown for unknown-N file types" do
74
+ @erf[174 + 16 + 4, 2] = [9995].pack("v")
75
+ t = Erf::Erf.new(StringIO.new @erf)
76
+ t.content[0].filename.should == "resref.unknown-9995"
77
+ end
78
+ end
79
+
80
+ describe "Erf V1.1" do
81
+ before do
82
+ @erf = WELLFORMED_ERF_11.dup
83
+ end
84
+
85
+ it_should_behave_like "Erf::Erf"
86
+
87
+ it "accepts valid filenames" do
88
+ t = Erf::Erf.new(StringIO.new @erf)
89
+ t.add_file("a" * 1 + ".txt", StringIO.new("blargh"))
90
+ t.add_file("a" * 32 + ".txt", StringIO.new("blargh"))
91
+ end
92
+
93
+ it "fails on invalid filenames" do
94
+ t = Erf::Erf.new(StringIO.new @erf)
95
+ proc { t.add_file("a" * 0 + ".txt", StringIO.new("blargh")) }.should raise_error ArgumentError
96
+ proc { t.add_file("a" * 33 + ".txt", StringIO.new("blargh")) }.should raise_error ArgumentError
97
+ end
98
+
99
+ it "returns unknown for unknown-N file types" do
100
+ @erf[174 + 32 + 4, 2] = [9995].pack("v")
101
+ t = Erf::Erf.new(StringIO.new @erf)
102
+ t.content[0].filename.should == "resref.unknown-9995"
86
103
  end
87
104
  end