-
-
Save DrMcCoy/39d81320fda6ebffce1830f654bb2375 to your computer and use it in GitHub Desktop.
unobb_kotor2.patch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/src/aurora/obbfile.cpp b/src/aurora/obbfile.cpp | |
index a1fdb06..b2a6b62 100644 | |
--- a/src/aurora/obbfile.cpp | |
+++ b/src/aurora/obbfile.cpp | |
@@ -46,11 +46,6 @@ OBBFile::~OBBFile() { | |
} | |
void OBBFile::load(Common::SeekableReadStream &obb) { | |
- /* OBB files have no actual header. But they're made up of zlib compressed chunks, | |
- * so we just check if we find a zlib header at the start of the file. */ | |
- if (obb.readUint16BE() != 0x789C) | |
- throw Common::Exception("No zlib header, this doesn't look like an Aspyr OBB virtual filesystem"); | |
- | |
try { | |
/* Extract the resource index and read the resource list out of it. */ | |
@@ -106,48 +101,27 @@ void OBBFile::readResList(Common::SeekableReadStream &index) { | |
Common::SeekableReadStream *OBBFile::getIndex(Common::SeekableReadStream &obb) { | |
/* Find and decompress the resource index. | |
* | |
- * It's the last compressed chunk in the OBB file, so we're searching | |
- * backwards for 0x78 0x9C (the usual zlib header). That's a bit short, | |
- * and can lead to false positives. | |
- * | |
- * But we also know that each file, after the last chunk, has another | |
- * 16 bytes with some sort of meta data, of which the last four bytes | |
- * are always 0x00. Immediately afterwards, the next compressed chunk | |
- * start. So we can add that to our "marker" to look for. | |
+ * The index is the last block in the OBB file and it's (apparently) always | |
+ * zlib-compressed. Each zlib-compressed file contains 16 bytes of metadata | |
+ * with its offset and uncompressed size. | |
* | |
- * That gives us the start of the compressed resource list. To make | |
- * sure we're not decompression garbage, we're also looking for the | |
- * end of the list. The resource index is always one chunk, and at | |
- * the end, there's the usual 16 bytes. For the resource index, the | |
- * first four of those is the offset of that start of the chunk, and | |
- * the next four bytes are 0x00. We can use that to figure out end. | |
+ * So we seek to the end of the OBB file, read the last 16 bytes and use | |
+ * those to find the start and size of the resource index. | |
* | |
- * With that full range, we can decompress the resource index and | |
- * return the decompressed data. | |
- * | |
- * NOTE: Yes, we're taking quite some shortcuts here. The original | |
- * code probably does it differently and more robust. */ | |
- | |
- static const byte kZlibHeader[6] = { 0x00, 0x00, 0x00, 0x00, 0x78, 0x9C }; | |
- static const size_t kMaxReadBack = 0xFFFFFF; // Should be enough | |
+ * NOTE: Should we ever find an OBB file with uncompressed resource index, | |
+ * this will fail. */ | |
- const size_t lastZlib = Common::searchBackwards(obb, kZlibHeader, sizeof(kZlibHeader), kMaxReadBack); | |
- if (lastZlib == SIZE_MAX) | |
- throw Common::Exception("Couldn't find the last zlib header"); | |
+ obb.seek(-16, Common::SeekableReadStream::kOriginEnd); | |
- byte offsetData[8]; | |
- WRITE_LE_UINT32(offsetData + 0, lastZlib + 4); | |
- WRITE_LE_UINT32(offsetData + 4, 0); | |
+ const uint32_t offset = obb.readUint32LE(); | |
+ obb.skip(4); // Always 0. Possibly space for uint64_t? | |
- Common::SeekableSubReadStream obbZIndexStart(&obb, lastZlib + 4, obb.size()); | |
+ const uint32_t uncompressedSize = obb.readUint32LE(); | |
+ obb.skip(4); // Always 0. Possibly space for uint64_t? | |
- const size_t indexSize = Common::searchBackwards(obbZIndexStart, offsetData, sizeof(offsetData), 0xFFFFFF); | |
- if (indexSize == SIZE_MAX) | |
- throw Common::Exception("Couldn't find the index end marker"); | |
+ Common::SeekableSubReadStream obbZIndex(&obb, offset, obb.size() - 16); | |
- obbZIndexStart.seek(0); | |
- | |
- return Common::decompressDeflateWithoutOutputSize(obbZIndexStart, indexSize, Common::kWindowBitsMax); | |
+ return Common::decompressDeflate(obbZIndex, obbZIndex.size(), uncompressedSize, Common::kWindowBitsMax); | |
} | |
const Archive::ResourceList &OBBFile::getResources() const { | |
@@ -156,7 +130,7 @@ const Archive::ResourceList &OBBFile::getResources() const { | |
const OBBFile::IResource &OBBFile::getIResource(uint32_t index) const { | |
if (index >= _iResources.size()) | |
- throw Common::Exception("Resource index out of range (%u/%u)", index, (uint)_iResources.size()); | |
+ throw Common::Exception("Resource index out of range (%u/%u)", index, static_cast<uint>(_iResources.size())); | |
return _iResources[index]; | |
} | |
@@ -165,48 +139,69 @@ uint32_t OBBFile::getResourceSize(uint32_t index) const { | |
return getIResource(index).uncompressedSize; | |
} | |
-Common::SeekableReadStream *OBBFile::getResource(uint32_t index, bool UNUSED(tryNoCopy)) const { | |
- /* Decompress a single file. | |
- * | |
- * Files in OBB virtual filesystems are split up in zlib compressed chunks. | |
- * Each chunk, after decompression, takes up 4096 bytes (except for the last | |
- * one, which can be shorter). The compressed size of the chunk is variable. | |
+Common::SeekableReadStream *OBBFile::getResource(uint32_t index, bool tryNoCopy) const { | |
+ /* Files in OBB virtual filesystems are either completely uncompressed | |
+ * (if compressedSize == uncompressedSize) or split up in zlib compressed | |
+ * blocks. | |
* | |
- * Since we know the starting offset of the first chunk of the file, and | |
- * the uncompressed data size, we simple decompress one chunk after the | |
- * other, starting with the first of the file. Once we have decompressed | |
- * as many bytes as the uncompressed size, we know we're done. | |
+ * Each compressed block, after decompression takes up 4096 bytes, except | |
+ * for the last one, which can be shorter. The compressed size of a block | |
+ * is variable. If the file is compressed, there's also an additional 16 | |
+ * bytes of metadata which contains the offset of the first block and the | |
+ * uncompressed size of the whole file again. | |
* | |
- * The OBB virtual filesystem also has a chunk list and extra 16 bytes of | |
- * meta data at the end of the last compressed chunk, but we don't really | |
- * care about any of that (also, we don't really know how either of those | |
- * work). | |
+ * Uncompressed files we can just read as is. Compressed files we read | |
+ * block for block, uncompressing them until we have the full uncompressed | |
+ * file. | |
* | |
- * We also can't use the compressed size, because that includes the extra | |
- * 16 bytes. And for the first file in the OBB, it even includes the | |
- * compressed size of the chunk list (which is always the second file | |
- * in the OBB). | |
- * | |
- * NOTE: Again, lots of shortcuts. Unlike what the original does. */ | |
+ * There's also a list of all blocks in an OBB file. We ignore that one | |
+ * for now. */ | |
const IResource &res = getIResource(index); | |
- _obb->seek(res.offset); | |
+ const bool isCompressed = res.compressedSize != res.uncompressedSize; | |
+ if (!isCompressed) { | |
+ if (tryNoCopy) | |
+ return new Common::SeekableSubReadStream(_obb.get(), res.offset, res.offset + res.uncompressedSize); | |
+ | |
+ _obb->seek(res.offset); | |
+ | |
+ return _obb->readStream(res.uncompressedSize); | |
+ } | |
+ | |
+ warning("COMPRESSED"); | |
+ if (res.compressedSize < 16) | |
+ throw Common::Exception("Invalid OBB resource compressed size (%u)", res.compressedSize); | |
+ | |
+ _obb->seek(res.offset + res.compressedSize - 16); | |
+ | |
+ const uint32_t offset = _obb->readUint32LE(); | |
+ _obb->skip(4); // Always 0. Possibly space for uint64_t? | |
+ | |
+ const uint32_t uncompressedSize = _obb->readUint32LE(); | |
+ _obb->skip(4); // Always 0. Possibly space for uint64_t? | |
+ | |
+ if ((offset != res.offset) || (uncompressedSize != res.uncompressedSize)) | |
+ throw Common::Exception("Resource metadata mismatch (%u, %u != %u, %u)", offset, uncompressedSize, res.offset, res.uncompressedSize); | |
std::unique_ptr<byte[]> data = std::make_unique<byte[]>(res.uncompressedSize); | |
- size_t offset = 0; | |
- size_t bytesLeft = res.uncompressedSize; | |
+ size_t readLeft = res.compressedSize - 16; | |
+ size_t writeLeft = res.uncompressedSize; | |
+ size_t writeOffset = 0; | |
- while (bytesLeft > 0) { | |
- const size_t bytesChunk = | |
- Common::decompressDeflateChunk(*_obb, Common::kWindowBitsMax, | |
- data.get() + offset, bytesLeft, 4096); | |
+ while (readLeft > 0) { | |
+ const size_t readSize = std::min<size_t>(readLeft, 4096); | |
+ const size_t bytesChunk = Common::decompressDeflateChunk(*_obb, Common::kWindowBitsMax, data.get() + writeOffset, writeLeft, readSize); | |
- offset += bytesChunk; | |
- bytesLeft -= bytesChunk; | |
+ readLeft -= readSize; | |
+ writeLeft -= bytesChunk; | |
+ writeOffset += bytesChunk; | |
} | |
+ if (writeLeft > 0) | |
+ throw Common::Exception("Exhausted input, but didn't get full output"); | |
+ | |
return new Common::MemoryReadStream(data.release(), res.uncompressedSize, true); | |
} | |
diff --git a/src/aurora/types.h b/src/aurora/types.h | |
index baad301..93d1e6b 100644 | |
--- a/src/aurora/types.h | |
+++ b/src/aurora/types.h | |
@@ -388,6 +388,12 @@ enum FileType { | |
kFileTypeXDS = 30000, ///< Texture. | |
kFileTypeWND = 30001, | |
+ // Found in the Android version of Knights of the Old Republic 2 | |
+ kFileTypeVERT = 31000, ///< Vertex shader. | |
+ kFileTypeFRAG = 31001, ///< Fragment shader. | |
+ kFileTypeGLSL = 31002, ///< OpenGL shader source. | |
+ kFileTypeTLK_CONTROL = 31003, ///< Talk table for extra control strings, plain text. | |
+ | |
// Our own types | |
kFileTypeXEOSITEX = 40000 ///< Intermediate texture. | |
}; | |
diff --git a/src/aurora/util.cpp b/src/aurora/util.cpp | |
index 660c8a4..6683af7 100644 | |
--- a/src/aurora/util.cpp | |
+++ b/src/aurora/util.cpp | |
@@ -348,6 +348,11 @@ const FileTypeManager::Type FileTypeManager::types[] = { | |
{kFileTypeXDS, ".xds"}, | |
{kFileTypeWND, ".wnd"}, | |
+ {kFileTypeVERT, ".vert"}, | |
+ {kFileTypeFRAG, ".frag"}, | |
+ {kFileTypeGLSL, ".glsl"}, | |
+ {kFileTypeTLK_CONTROL, ".tlk_control"}, | |
+ | |
{kFileTypeXEOSITEX, ".xoreositex"} | |
}; | |
diff --git a/src/version/version.cpp b/src/version/version.cpp | |
index 7835160..68ca5aa 100644 | |
--- a/src/version/version.cpp | |
+++ b/src/version/version.cpp | |
@@ -62,6 +62,11 @@ | |
#endif | |
// Distributions may append an extra version string | |
+#ifdef XOREOS_DISTRO | |
+ #undef XOREOS_REV | |
+#endif | |
+#define XOREOS_DISTRO "KOTOR2_ANDROID_UNOBB" | |
+ | |
#ifdef XOREOS_DISTRO | |
#undef XOREOS_REV | |
#define XOREOS_REV XOREOS_DISTRO | |
@@ -81,7 +86,10 @@ static const char *kProjectAuthors = | |
"Please see the AUTHORS file for details.\n" | |
"\n" | |
"This is free software; see the source for copying conditions. There is NO\n" | |
- "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."; | |
+ "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" | |
+ "\n" | |
+ "NOTE: This is a modified and experimental version of xoreos-tools to read\n" | |
+ " unobb archives found in the Android port of KotOR2."; | |
const char *getProjectName() { | |
return kProjectName; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment