Skip to content

Commit

Permalink
AURORA: Add support for ERF V2.1
Browse files Browse the repository at this point in the history
Found in the Xbox version of Dragon Age: Origins.
  • Loading branch information
DrMcCoy committed Aug 19, 2018
1 parent 707eefe commit 84d1c78
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 6 deletions.
76 changes: 72 additions & 4 deletions src/aurora/erffile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ static const uint32 kSAVID = MKTAG('S', 'A', 'V', ' ');
static const uint32 kVersion10 = MKTAG('V', '1', '.', '0');
static const uint32 kVersion11 = MKTAG('V', '1', '.', '1');
static const uint32 kVersion20 = MKTAG('V', '2', '.', '0');
static const uint32 kVersion21 = MKTAG('V', '2', '.', '1');
static const uint32 kVersion22 = MKTAG('V', '2', '.', '2');
static const uint32 kVersion30 = MKTAG('V', '3', '.', '0');

Expand Down Expand Up @@ -162,12 +163,12 @@ void ERFFile::verifyVersion(uint32 id, uint32 version, bool utf16le) {
throw Common::Exception("Not an ERF file (%s)", Common::debugTag(id).c_str());

if ((version != kVersion10) && (version != kVersion11) &&
(version != kVersion20) && (version != kVersion22) &&
(version != kVersion20) && (version != kVersion21) && (version != kVersion22) &&
(version != kVersion30))
throw Common::Exception("Unsupported ERF file version %s", Common::debugTag(version).c_str());

if ((version != kVersion10) && (version != kVersion11) && !utf16le)
throw Common::Exception("ERF file version 2.0+, but not UTF-16LE");
if ((version != kVersion10) && (version != kVersion11) && (version != kVersion21) && !utf16le)
throw Common::Exception("ERF file version 2.0 or 2.2+, but not UTF-16LE");
}

void ERFFile::verifyPasswordDigest() {
Expand Down Expand Up @@ -366,6 +367,23 @@ void ERFFile::readV20Header(Common::SeekableReadStream &erf, ERFHeader &header)
header.offResList = 0x00000020; // Resource list always starts at 0x20 in ERF V2.0
}

void ERFFile::readV21Header(Common::SeekableReadStream &erf, ERFHeader &header) {
erf.skip(8); // Unknown, always 0x00000000 0x00000000?

header.resCount = erf.readUint32LE(); // Number of resources in the ERF

erf.skip(4); // Unknown, always 0x00000000?

header.buildYear = erf.readUint16LE() + 1900;
header.buildDay = erf.readUint16LE();

erf.skip(4); // Unknown, always 0xFFFF0000?

header.offResList = 0x00000020; // Resource list always starts at 0x20 in ERF V2.1

header.compression = kCompressionStandardZlib;
}

void ERFFile::readV22Header(Common::SeekableReadStream &erf, ERFHeader &header, uint32 &flags) {
header.resCount = erf.readUint32LE(); // Number of resources in the ERF

Expand Down Expand Up @@ -449,10 +467,17 @@ void ERFFile::readERFHeader(Common::SeekableReadStream &erf, ERFHeader &header,
} else if (version == kVersion20) {

/* Version 2.0:
* Unencrypted data in Dragon Age: Origins. */
* Unencrypted data in Dragon Age: Origins (PC). */

readV20Header(erf, header);

} else if (version == kVersion21) {

/* Version 2.1:
* Unencrypted data in Dragon Age: Origins (Xbox). */

readV21Header(erf, header);

} else if (version == kVersion22) {

/* Version 2.2:
Expand Down Expand Up @@ -509,6 +534,11 @@ void ERFFile::readResources(Common::SeekableReadStream &erf, const ERFHeader &he
// Read the resource list
readV20ResList(erf, header);

} else if (_version == kVersion21) {

// Read the resource list
readV21ResList(erf, header);

} else if (_version == kVersion22) {

// Read the resource list
Expand Down Expand Up @@ -577,6 +607,26 @@ void ERFFile::readV20ResList(Common::SeekableReadStream &erf, const ERFHeader &h

}

void ERFFile::readV21ResList(Common::SeekableReadStream &erf, const ERFHeader &header) {
erf.seek(header.offResList);

uint32 index = 0;
ResourceList::iterator res = _resources.begin();
IResourceList::iterator iRes = _iResources.begin();
for (; (res != _resources.end()) && (iRes != _iResources.end()); ++index, ++res, ++iRes) {
Common::UString name = Common::readStringFixed(erf, Common::kEncodingASCII, 32);

res->name = TypeMan.setFileType(name, kFileTypeNone);
res->type = TypeMan.getFileType(name);
res->index = index;

iRes->offset = erf.readUint32LE();
iRes->packedSize = erf.readUint32LE();
iRes->unpackedSize = erf.readUint32LE();
}

}

void ERFFile::readV22ResList(Common::SeekableReadStream &erf, const ERFHeader &header) {
erf.seek(header.offResList);

Expand Down Expand Up @@ -731,6 +781,9 @@ Common::SeekableReadStream *ERFFile::decompress(Common::MemoryReadStream *packed
case kCompressionHeaderlessZlib:
return decompressHeaderlessZlib(stream.release(), unpackedSize);

case kCompressionStandardZlib:
return decompressStandardZlib(stream.release(), unpackedSize);

default:
break;
}
Expand Down Expand Up @@ -768,6 +821,21 @@ Common::SeekableReadStream *ERFFile::decompressHeaderlessZlib(Common::MemoryRead
return decompressZlib(compressedData, packedSize, unpackedSize, Common::kWindowBitsMax);
}

Common::SeekableReadStream *ERFFile::decompressStandardZlib(Common::MemoryReadStream *packedStream,
uint32 unpackedSize) const {

/* Decompress using raw inflate. Use the default maximum window size (15), and with zlib header. */

assert(packedStream);

Common::ScopedPtr<Common::MemoryReadStream> stream(packedStream);

const byte * const compressedData = stream->getData();
const uint32 packedSize = stream->size();

return decompressZlib(compressedData, packedSize, unpackedSize, -Common::kWindowBitsMax);
}

Common::SeekableReadStream *ERFFile::decompressZlib(const byte *compressedData, uint32 packedSize,
uint32 unpackedSize, int windowBits) const {

Expand Down
17 changes: 15 additions & 2 deletions src/aurora/erffile.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,14 @@ namespace Aurora {
* - Extension saved as a Type ID
* - Includes a localized description text field
* - 2.0:
* - Used in Dragon Age: Origins
* - Used in Dragon Age: Origins (PC)
* - 32 UTF-16 characters per resource name
* - Resource name includes extension and path
* - 2.1:
* - Used in Dragon Age: Origins (Xbox)
* - 32 UTF-16 characters per resource name
* - Resource name includes extension and path
* - DEFLATE compressed
* - 2.2:
* - Used in Dragon Age: Origins
* - Optionally Blowfish encrypted
Expand Down Expand Up @@ -150,7 +155,8 @@ class ERFFile : public Archive, public AuroraFile {
enum Compression {
kCompressionNone = 0, ///< No compression as all.
kCompressionBioWareZlib = 1, ///< Compression using DEFLATE with an extra header byte.
kCompressionHeaderlessZlib = 7 ///< Compression using DEFLATE with default parameters.
kCompressionHeaderlessZlib = 7, ///< Compression using DEFLATE with default parameters.
kCompressionStandardZlib = 8 ///< Compression using DEFLATE, standard zlib chunk.
};

/** The header of an ERF file. */
Expand Down Expand Up @@ -245,6 +251,11 @@ class ERFFile : public Archive, public AuroraFile {
void readV20ResList(Common::SeekableReadStream &erf, const ERFHeader &header);
// '---

// .--- V2.1
static void readV21Header(Common::SeekableReadStream &erf, ERFHeader &header);
void readV21ResList(Common::SeekableReadStream &erf, const ERFHeader &header);
// '---

// .--- V2.2
static void readV22Header(Common::SeekableReadStream &erf, ERFHeader &header, uint32 &flags);
void readV22ResList(Common::SeekableReadStream &erf, const ERFHeader &header);
Expand Down Expand Up @@ -286,6 +297,8 @@ class ERFFile : public Archive, public AuroraFile {
uint32 unpackedSize) const;
Common::SeekableReadStream *decompressHeaderlessZlib(Common::MemoryReadStream *packedStream,
uint32 unpackedSize) const;
Common::SeekableReadStream *decompressStandardZlib (Common::MemoryReadStream *packedStream,
uint32 unpackedSize) const;

Common::SeekableReadStream *decompressZlib(const byte *compressedData, uint32 packedSize,
uint32 unpackedSize, int windowBits) const;
Expand Down

0 comments on commit 84d1c78

Please sign in to comment.