diff --git a/src/ZoneLoading/Game/IW4/ZoneLoaderFactoryIW4.cpp b/src/ZoneLoading/Game/IW4/ZoneLoaderFactoryIW4.cpp index 36c032da..062177fb 100644 --- a/src/ZoneLoading/Game/IW4/ZoneLoaderFactoryIW4.cpp +++ b/src/ZoneLoading/Game/IW4/ZoneLoaderFactoryIW4.cpp @@ -278,9 +278,65 @@ std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(const ZoneH // Skip timestamp zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(8)); - if (inspectResult->m_generic_result.m_platform == GamePlatform::XBOX) // TODO: find out what this is + if (inspectResult->m_generic_result.m_platform == GamePlatform::XBOX) { - zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(16)); + + // Xbox fastfiles have additional header data before the auth header: + // - 4 bytes: language flags bitmask + // - 4 bytes: image count + // - sizeof(XAssetStreamFile) * image count (for each language in the bitmask) + // - 4 bytes: unknown + // - 4 bytes: unknown + // struct XAssetStreamFile // sizeof=0xC + // { + // unsigned int fileIndex; + // unsigned int offset; + // unsigned int offsetEnd; + // }; + + struct SkipXAssetStreamFiles : public ILoadingStep + { + void PerformStep(ZoneLoader& zoneLoader, ILoadingStream& stream) override + { + uint32_t languageFlags; + stream.Load(&languageFlags, sizeof(languageFlags)); + languageFlags = endianness::FromBigEndian(languageFlags); + + uint32_t imageCount; + stream.Load(&imageCount, sizeof(imageCount)); + imageCount = endianness::FromBigEndian(imageCount); + + // Count how many languages are set in the bitmask + uint32_t languageCount = 0; + for (int i = 0; i < 15; i++) + { + if (languageFlags & (1 << i)) + { + languageCount++; + } + } + + // Skip image stream file data (12 bytes per image per language) + const size_t imageDataSize = 12 * imageCount * languageCount; + if (imageDataSize > 0) + { + std::vector tempBuffer(std::min(imageDataSize, size_t(8192))); + size_t skipped = 0; + while (skipped < imageDataSize) + { + const size_t toSkip = std::min(imageDataSize - skipped, tempBuffer.size()); + stream.Load(tempBuffer.data(), toSkip); + skipped += toSkip; + } + } + + // Skip the final 8 bytes (2 unknown 4-byte values) + uint8_t finalBytes[8]; + stream.Load(finalBytes, sizeof(finalBytes)); + } + }; + + zoneLoader->AddLoadingStep(std::make_unique()); } // Add steps for loading the auth header which also contain the signature of the zone if it is signed.