xoreos  0.0.5
ssffile.cpp
Go to the documentation of this file.
1 /* xoreos - A reimplementation of BioWare's Aurora engine
2  *
3  * xoreos is the legal property of its developers, whose names
4  * can be found in the AUTHORS file distributed with this source
5  * distribution.
6  *
7  * xoreos is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 3
10  * of the License, or (at your option) any later version.
11  *
12  * xoreos is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with xoreos. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
25 /* See BioWare's own specs released for Neverwinter Nights modding
26  * (<https://github.com/xoreos/xoreos-docs/tree/master/specs/bioware>)
27  */
28 
29 #include "src/common/scopedptr.h"
30 #include "src/common/readstream.h"
31 #include "src/common/writestream.h"
32 #include "src/common/writefile.h"
33 #include "src/common/util.h"
34 #include "src/common/strutil.h"
35 #include "src/common/error.h"
36 #include "src/common/encoding.h"
37 
38 #include "src/aurora/resman.h"
39 #include "src/aurora/ssffile.h"
40 
41 static const uint32 kSSFID = MKTAG('S', 'S', 'F', ' ');
42 static const uint32 kVersion10 = MKTAG('V', '1', '.', '0');
43 static const uint32 kVersion11 = MKTAG('V', '1', '.', '1');
44 
45 namespace Aurora {
46 
47 SSFFile::Sound::Sound(const Common::UString &f, uint32 s) : soundFile(f), strRef(s) {
48 }
49 
50 
52 }
53 
55  load(ssf);
56 }
57 
60  if (!res)
61  throw Common::Exception("No such SSF \"%s\"", ssf.c_str());
62 
63  load(*res);
64 }
65 
67 }
68 
70  try {
71  size_t entryCount, offEntryTable;
72  Version version = readSSFHeader(ssf, entryCount, offEntryTable);
73 
74  _sounds.resize(entryCount);
75 
76  readEntries(ssf, version, offEntryTable);
77 
78  } catch (Common::Exception &e) {
79  e.add("Failed reading SSF file");
80  throw;
81  }
82 }
83 
85  size_t &entryCount, size_t &offEntryTable) {
86  readHeader(ssf);
87 
88  if (_id != kSSFID)
89  throw Common::Exception("Not a SSF file (%s)", Common::debugTag(_id).c_str());
90 
91  if ((_version != kVersion10) && (_version != kVersion11))
92  throw Common::Exception("Unsupported SSF file version %s", Common::debugTag(_version).c_str());
93 
94  entryCount = ssf.readUint32LE();
95  offEntryTable = ssf.readUint32LE();
96 
97  if (entryCount >= UINT32_MAX)
98  throw Common::Exception("Invalid SSF header");
99 
100  // Plain old version V1.0 used in NWN (and NWN2)
101  if (_version == kVersion10)
102  return kVersion10_NWN;
103 
104  // NWN2's V1.1
105  if ((offEntryTable < ssf.size()) && ((ssf.size() - offEntryTable) >= ((4 + 32 + 4) * entryCount)))
106  return kVersion11_NWN2;
107 
108  offEntryTable = entryCount;
109  entryCount = (ssf.size() - offEntryTable) / 4;
110 
111  // Sanity check
112  if ((offEntryTable > ssf.size()) || (((ssf.size() - offEntryTable) % 4) != 0))
113  throw Common::Exception("Invalid SSF header (%u, %u)", (uint32) ssf.size(), (uint32) offEntryTable);
114 
115  // KotOR's V1.1
116  return kVersion11_KotOR;
117 }
118 
119 void SSFFile::readEntries(Common::SeekableReadStream &ssf, Version version, size_t offset) {
120  ssf.seek(offset);
121 
122  switch (version) {
123  case kVersion10_NWN:
124  readEntriesNWN(ssf, 16);
125  break;
126 
127  case kVersion11_NWN2:
128  readEntriesNWN(ssf, 32);
129  break;
130 
131  case kVersion11_KotOR:
132  readEntriesKotOR(ssf);
133  break;
134 
135  default:
136  break;
137  }
138 }
139 
140 void SSFFile::readEntriesNWN(Common::SeekableReadStream &ssf, size_t soundFileLen) {
141  /* The NWN/NWN2 versions of an SSF file (V1.0 and V1.1) begin with a list of
142  offsets to the data entries. Each of these contains a ResRef of a sound
143  file and a StrRef of a text. */
144 
145  size_t count = _sounds.size();
146 
147  std::vector<uint32> offsets;
148 
149  offsets.resize(count);
150 
151  for (size_t i = 0; i < count; i++)
152  offsets[i] = ssf.readUint32LE();
153 
154  for (size_t i = 0; i < count; i++) {
155  ssf.seek(offsets[i]);
156 
157  _sounds[i].soundFile = Common::readStringFixed(ssf, Common::kEncodingASCII, soundFileLen);
158  _sounds[i].strRef = ssf.readUint32LE();
159  }
160 }
161 
163  /* The KotOR/KotOR2 version of an SSF file (V1.1) is just a list of StrRefs. */
164 
165  for (SoundSet::iterator sound = _sounds.begin(); sound != _sounds.end(); ++sound)
166  sound->strRef = ssf.readUint32LE();
167 }
168 
169 size_t SSFFile::getSoundCount() const {
170  return _sounds.size();
171 }
172 
173 const Common::UString &SSFFile::getSoundFile(size_t index) const {
174  static const Common::UString kEmptyString = "";
175 
176  if (index >= _sounds.size())
177  return kEmptyString;
178 
179  return _sounds[index].soundFile;
180 }
181 
182 uint32 SSFFile::getStrRef(size_t index) const {
183  if (index >= _sounds.size())
184  return kStrRefInvalid;
185 
186  return _sounds[index].strRef;
187 }
188 
189 void SSFFile::getSound(size_t index, Common::UString &soundFile, uint32 &strRef) const {
190  if (index >= _sounds.size()) {
191  soundFile.clear();
192  strRef = kStrRefInvalid;
193 
194  return;
195  }
196 
197  soundFile = _sounds[index].soundFile;
198  strRef = _sounds[index].strRef;
199 }
200 
201 void SSFFile::setSoundFile(size_t index, const Common::UString &soundFile) {
202  if (index >= UINT32_MAX)
203  throw Common::Exception("Sound index out of range");
204 
205  if (_sounds.size() <= index)
206  _sounds.resize(index + 1);
207 
208  _sounds[index].soundFile = soundFile;
209 }
210 
211 void SSFFile::setStrRef(size_t index, uint32 strRef) {
212  if (index >= UINT32_MAX)
213  throw Common::Exception("Sound index out of range");
214 
215  if (_sounds.size() <= index)
216  _sounds.resize(index + 1);
217 
218  _sounds[index].strRef = strRef;
219 }
220 
221 void SSFFile::setSound(size_t index, const Common::UString &soundFile, uint32 strRef) {
222  if (index >= UINT32_MAX)
223  throw Common::Exception("Sound index out of range");
224 
225  if (_sounds.size() <= index)
226  _sounds.resize(index + 1);
227 
228  _sounds[index].soundFile = soundFile;
229  _sounds[index].strRef = strRef;
230 }
231 
233  size_t maxSoundFileLen = 0;
234  for (SoundSet::const_iterator s = _sounds.begin(); s != _sounds.end(); ++s)
235  maxSoundFileLen = MAX(maxSoundFileLen, s->soundFile.size());
236 
237  return maxSoundFileLen;
238 }
239 
241  for (SoundSet::const_iterator s = _sounds.begin(); s != _sounds.end(); ++s)
242  for (Common::UString::iterator c = s->soundFile.begin(); c != s->soundFile.end(); ++c)
243  if (!Common::UString::isASCII(*c))
244  return true;
245 
246  return false;
247 }
248 
250  assert(_sounds.size() < UINT32_MAX);
251 
253  throw Common::Exception("SSF files do not support non-ASCII sound filenames");
254 
255  const size_t maxFileLen = getMaxSoundFileLen();
256 
257  switch (version) {
258  case kVersion10_NWN:
259  if (maxFileLen > 16)
260  throw Common::Exception("Sound filenames in SSF V1.0 need to be 16 characters or less");
261  break;
262 
263  case kVersion11_NWN2:
264  if (maxFileLen > 32)
265  throw Common::Exception("Sound filenames in SSF V1.1 (NWN2) need to be 32 characters or less");
266  break;
267 
268  case kVersion11_KotOR:
269  if (maxFileLen > 0)
270  throw Common::Exception("SSF V1.1 (KotOR/KotOR2) does not support sound filenames");
271  break;
272 
273  default:
274  throw Common::Exception("Invalid SSF version");
275  }
276 }
277 
279  switch (game) {
280  case kGameIDNWN:
281  return kVersion10_NWN;
282 
283  case kGameIDNWN2:
284  if (getMaxSoundFileLen() > 16)
285  return kVersion11_NWN2;
286  return kVersion10_NWN;
287 
288  case kGameIDKotOR:
289  case kGameIDKotOR2:
290  return kVersion11_KotOR;
291 
292  default:
293  break;
294  }
295 
296  throw Common::Exception("This game does not support SSF files");
297 }
298 
299 void SSFFile::writeSSF(Common::WriteStream &out, Version version) const {
300  checkVersionFeatures(version);
301 
302  switch (version) {
303  case kVersion10_NWN:
304  writeNWN(out);
305  break;
306 
307  case kVersion11_NWN2:
308  writeNWN2(out);
309  break;
310 
311  case kVersion11_KotOR:
312  writeKotOR(out);
313  break;
314  }
315 }
316 
317 bool SSFFile::writeSSF(const Common::UString &fileName, Version version) const {
318  Common::WriteFile file;
319  if (!file.open(fileName))
320  return false;
321 
322  writeSSF(file, version);
323  file.close();
324 
325  return true;
326 }
327 
328 void SSFFile::writeNWN(Common::WriteStream &out, size_t soundFileLen) const {
329  static const size_t kOffsetEntryTable = 0x28;
330 
331  out.writeUint32LE(_sounds.size());
332  out.writeUint32LE(kOffsetEntryTable);
333 
334  // Reserved
335  for (size_t i = 16; i < kOffsetEntryTable; i++)
336  out.writeByte(0);
337 
338  size_t offsetData = kOffsetEntryTable + _sounds.size() * 4;
339 
340  for (SoundSet::const_iterator s = _sounds.begin(); s != _sounds.end();
341  ++s, offsetData += soundFileLen + 4) {
342 
343  if (offsetData >= UINT32_MAX)
345 
346  out.writeUint32LE(offsetData);
347  }
348 
349  for (SoundSet::const_iterator s = _sounds.begin(); s != _sounds.end(); ++s) {
350  Common::writeStringFixed(out, s->soundFile, Common::kEncodingASCII, soundFileLen);
351 
352  out.writeUint32LE(s->strRef);
353  }
354 }
355 
357  out.writeString("SSF V1.0");
358 
359  writeNWN(out, 16);
360 }
361 
363  out.writeString("SSF V1.1");
364 
365  writeNWN(out, 32);
366 }
367 
369  out.writeString("SSF V1.1");
370 
371  out.writeUint32LE(0x0C); // Offset to the entries
372 
373  for (SoundSet::const_iterator s = _sounds.begin(); s != _sounds.end(); ++s)
374  out.writeUint32LE(s->strRef);
375 }
376 
377 } // End of namespace Aurora
#define ResMan
Shortcut for accessing the sound manager.
Definition: resman.h:557
void setStrRef(size_t index, uint32 strRef)
Set the string reference of the text to display for this sound.
Definition: ssffile.cpp:211
Version determineVersionForGame(GameID game) const
Determine the best version to save this SSF file in, for the specified game.
Definition: ssffile.cpp:278
#define MKTAG(a0, a1, a2, a3)
A wrapper macro used around four character constants, like &#39;DATA&#39;, to ensure portability.
Definition: endianness.h:140
const Common::UString & getSoundFile(size_t index) const
Return the sound file to play for this sound.
Definition: ssffile.cpp:173
Version readSSFHeader(Common::SeekableReadStream &ssf, size_t &entryCount, size_t &offEntryTable)
Read the header of an SSF file and determine the version.
Definition: ssffile.cpp:84
Handling BioWare&#39;s SSFs (sound set file).
GameID
Definition: types.h:393
void add(const char *s,...) GCC_PRINTF(2
Definition: error.cpp:58
uint32 readUint32LE()
Read an unsigned 32-bit word stored in little endian (LSB first) order from the stream and return it...
Definition: readstream.h:133
A class holding an UTF-8 string.
Definition: ustring.h:48
virtual size_t seek(ptrdiff_t offset, Origin whence=kOriginBegin)=0
Sets the stream position indicator for the stream.
void writeString(const UString &str)
Write the given string to the stream, encoded as UTF-8.
Definition: writestream.cpp:94
Version
The different versions of SSF files that exists.
Definition: ssffile.h:58
size_t getMaxSoundFileLen() const
Return the maximum length of a sound filename in characters.
Definition: ssffile.cpp:232
static const uint32 kVersion10
Definition: erfwriter.cpp:31
Utility templates and functions for working with strings and streams.
Exception that provides a stack of explanations.
Definition: error.h:36
static void readHeader(Common::ReadStream &stream, uint32 &id, uint32 &version, bool &utf16le)
Read the header out of a stream.
Definition: aurorafile.cpp:53
A simple scoped smart pointer template.
void setSoundFile(size_t index, const Common::UString &soundFile)
Set the sound file to play for this sound.
Definition: ssffile.cpp:201
SSF V1.0, as found in NWN and NWN2.
Definition: ssffile.h:59
Basic exceptions to throw.
static const uint32 kSSFID
Definition: ssffile.cpp:41
utf8::iterator< std::string::const_iterator > iterator
Definition: ustring.h:50
const char * c_str() const
Return the (utf8 encoded) string data.
Definition: ustring.cpp:249
bool open(const UString &fileName)
Try to open the file with the given fileName.
Definition: writefile.cpp:50
void load(Common::SeekableReadStream &ssf)
Definition: ssffile.cpp:69
Star Wars: Knights of the Old Republic.
Definition: types.h:397
Utility templates and functions.
void writeSSF(Common::WriteStream &out, Version version) const
Write the SSF into a stream, as the specified version.
Definition: ssffile.cpp:299
SoundSet _sounds
Definition: ssffile.h:111
Basic writing stream interfaces.
void writeKotOR(Common::WriteStream &out) const
Write this SSF into a stream as a V1.1 (KotOR/KotOR2).
Definition: ssffile.cpp:368
static const uint32 kStrRefInvalid
Definition: types.h:444
SSF V1.1, as found in KotOR and KotOR.
Definition: ssffile.h:61
Utility functions for working with differing string encodings.
uint32 _id
The file&#39;s ID.
Definition: aurorafile.h:77
Sound Set File.
Definition: types.h:126
StackException Exception
Definition: error.h:59
static const uint32 kVersion11
Definition: ssffile.cpp:43
uint32 _version
The file&#39;s version.
Definition: aurorafile.h:78
void writeByte(byte value)
Definition: writestream.h:88
virtual size_t size() const =0
Obtains the total size of the stream, measured in bytes.
Neverwinter Nights 2.
Definition: types.h:396
Basic reading stream interfaces.
Generic interface for a writable data stream.
Definition: writestream.h:64
void readEntriesKotOR(Common::SeekableReadStream &ssf)
Read the data entries of the KotOR version.
Definition: ssffile.cpp:162
void readEntries(Common::SeekableReadStream &ssf, Version version, size_t offset)
Read the data entries, depending on the version.
Definition: ssffile.cpp:119
void writeNWN2(Common::WriteStream &out) const
Write this SSF into a stream as a V1.1 (NWN2).
Definition: ssffile.cpp:362
Plain, unextended ASCII (7bit clean).
Definition: encoding.h:40
Sound(const Common::UString &f="", uint32 s=kStrRefInvalid)
Definition: ssffile.cpp:47
void close()
Close the file, if open.
Definition: writefile.cpp:69
static bool isASCII(uint32 c)
Is the character an ASCII character?
Definition: ustring.cpp:785
const Exception kSeekError("Seek error")
Exception when seeking a stream failed.
Definition: error.h:63
uint32_t uint32
Definition: types.h:204
UString debugTag(uint32 tag, bool trim)
Create an elaborate string from an integer tag, for debugging purposes.
Definition: strutil.cpp:117
static const uint32 kVersion10
Definition: ssffile.cpp:42
#define UINT32_MAX
Definition: types.h:231
Implementing the stream writing interfaces for files.
void setSound(size_t index, const Common::UString &soundFile, uint32 strRef)
Set both the sound file and the string reference for this sound.
Definition: ssffile.cpp:221
void checkVersionFeatures(Version version) const
Make sure this SSF files fits the requirements for specified SSF version.
Definition: ssffile.cpp:249
SSF V1.1, as found in NWN2.
Definition: ssffile.h:60
UString readStringFixed(SeekableReadStream &stream, Encoding encoding, size_t length)
Read length bytes as a string with the given encoding out of a stream.
Definition: encoding.cpp:297
Neverwinter Nights.
Definition: types.h:395
static const Common::UString kEmptyString
Definition: talkman.cpp:129
void readEntriesNWN(Common::SeekableReadStream &ssf, size_t soundFileLen)
Read the data entries of the NWN versions.
Definition: ssffile.cpp:140
void getSound(size_t index, Common::UString &soundFile, uint32 &strRef) const
Return both the sound file and the string reference for this sound.
Definition: ssffile.cpp:189
Star Wars: Knights of the Old Republic II - The Sith Lords.
Definition: types.h:398
A simple streaming file writing class.
Definition: writefile.h:40
T MAX(T a, T b)
Definition: util.h:71
void clear()
Clear the string&#39;s contents.
Definition: ustring.cpp:236
Interface for a seekable & readable data stream.
Definition: readstream.h:265
void writeUint32LE(uint32 value)
Definition: writestream.h:104
The global resource manager for Aurora resources.
size_t getSoundCount() const
Return the number of sounds in this SSF file.
Definition: ssffile.cpp:169
bool existNonASCIISoundFile() const
Is there a sound filename with non-ASCII characters?
Definition: ssffile.cpp:240
uint32 getStrRef(size_t index) const
Return the string reference of the text to display for this sound.
Definition: ssffile.cpp:182
void writeNWN(Common::WriteStream &out) const
Write this SSF into a stream as a V1.0 (NWN).
Definition: ssffile.cpp:356
void writeStringFixed(WriteStream &stream, const Common::UString &str, Encoding encoding, size_t length)
Write a string into a stream with a given encoding and fixed length in bytes.
Definition: encoding.cpp:347