diff --git a/src/ZoneCommon/Game/IW4/ZoneConstantsIW4.h b/src/ZoneCommon/Game/IW4/ZoneConstantsIW4.h index d405e4cf..af1af733 100644 --- a/src/ZoneCommon/Game/IW4/ZoneConstantsIW4.h +++ b/src/ZoneCommon/Game/IW4/ZoneConstantsIW4.h @@ -18,6 +18,7 @@ namespace IW4 static constexpr const char* MAGIC_IW4X = "IW4x"; static constexpr int ZONE_VERSION = 276; static constexpr int IW4X_ZONE_VERSION = 3; + static constexpr int ZONE_VERSION_XENON = 269; static_assert(std::char_traits::length(MAGIC_SIGNED_INFINITY_WARD) == sizeof(ZoneHeader::m_magic)); static_assert(std::char_traits::length(MAGIC_SIGNED_OAT) == sizeof(ZoneHeader::m_magic)); @@ -40,6 +41,20 @@ namespace IW4 0x77, 0xCD, 0x62, 0x7D, 0x9D, 0x40, 0x26, 0x44, 0x4B, 0x3B, 0x0A, 0x89, 0x02, 0x03, 0x01, 0x00, 0x01, }; + inline static const uint8_t RSA_PUBLIC_KEY_INFINITY_WARD_XENON[270]{ + 48u, 130u, 1u, 10u, 2u, 130u, 1u, 1u, 0u, 205u, 150u, 80u, 200u, 178u, 78u, 16u, 228u, 121u, 5u, 65u, 110u, 155u, 239u, + 206u, 81u, 196u, 45u, 158u, 201u, 209u, 132u, 35u, 240u, 172u, 34u, 36u, 24u, 233u, 157u, 40u, 203u, 175u, 180u, 127u, 96u, 179u, + 103u, 50u, 67u, 252u, 15u, 169u, 112u, 120u, 66u, 204u, 169u, 134u, 64u, 202u, 50u, 202u, 130u, 155u, 12u, 99u, 176u, 81u, 137u, + 20u, 41u, 234u, 21u, 51u, 63u, 123u, 235u, 102u, 237u, 247u, 22u, 247u, 69u, 6u, 198u, 65u, 98u, 222u, 0u, 117u, 250u, 140u, + 143u, 229u, 191u, 115u, 203u, 117u, 44u, 68u, 9u, 80u, 216u, 30u, 81u, 226u, 88u, 178u, 86u, 111u, 229u, 245u, 192u, 32u, 79u, + 95u, 85u, 143u, 141u, 209u, 55u, 204u, 232u, 59u, 9u, 6u, 44u, 93u, 185u, 123u, 142u, 227u, 199u, 131u, 203u, 143u, 64u, 99u, + 137u, 174u, 107u, 15u, 222u, 56u, 94u, 215u, 244u, 101u, 140u, 48u, 90u, 139u, 158u, 162u, 66u, 3u, 247u, 128u, 171u, 223u, 160u, + 143u, 149u, 160u, 187u, 182u, 29u, 163u, 189u, 59u, 111u, 66u, 104u, 221u, 66u, 251u, 230u, 50u, 2u, 59u, 8u, 238u, 52u, 60u, + 143u, 30u, 232u, 89u, 17u, 8u, 9u, 41u, 190u, 105u, 177u, 212u, 85u, 16u, 111u, 243u, 146u, 23u, 9u, 62u, 112u, 160u, 26u, + 230u, 125u, 109u, 49u, 208u, 248u, 51u, 223u, 240u, 41u, 85u, 98u, 29u, 95u, 62u, 141u, 58u, 29u, 90u, 19u, 176u, 249u, 137u, + 137u, 145u, 143u, 6u, 186u, 58u, 55u, 22u, 197u, 125u, 137u, 239u, 239u, 99u, 78u, 243u, 137u, 25u, 6u, 137u, 241u, 123u, 220u, + 245u, 110u, 54u, 10u, 139u, 200u, 196u, 93u, 194u, 125u, 19u, 157u, 2u, 3u, 1u, 0u, 1u}; + static constexpr size_t AUTHED_CHUNK_SIZE = 0x2000; static constexpr unsigned AUTHED_CHUNK_COUNT_PER_GROUP = 256; diff --git a/src/ZoneLoading/Game/IW4/ZoneLoaderFactoryIW4.cpp b/src/ZoneLoading/Game/IW4/ZoneLoaderFactoryIW4.cpp index 78116b19..7ef435f6 100644 --- a/src/ZoneLoading/Game/IW4/ZoneLoaderFactoryIW4.cpp +++ b/src/ZoneLoading/Game/IW4/ZoneLoaderFactoryIW4.cpp @@ -12,6 +12,7 @@ #include "Loading/Processor/ProcessorInflate.h" #include "Loading/Steps/StepAddProcessor.h" #include "Loading/Steps/StepAllocXBlocks.h" +#include "Loading/Steps/StepDumpData.h" #include "Loading/Steps/StepLoadHash.h" #include "Loading/Steps/StepLoadSignature.h" #include "Loading/Steps/StepLoadZoneContent.h" @@ -23,14 +24,26 @@ #include "Loading/Steps/StepVerifyMagic.h" #include "Loading/Steps/StepVerifySignature.h" #include "Utils/ClassUtils.h" +#include "Utils/Endianness.h" #include "Utils/Logging/Log.h" #include #include +#include #include #include using namespace IW4; +namespace fs = std::filesystem; + +class StepLogPosition final : public ILoadingStep +{ +public: + void PerformStep(ZoneLoader& zoneLoader, ILoadingStream& stream) override + { + con::info("Stream position before dump: 0x{:X}", stream.Pos()); + } +}; namespace { @@ -42,13 +55,50 @@ namespace std::optional InspectZoneHeaderIw4(const ZoneHeader& header) { - if (header.m_version != ZoneConstants::ZONE_VERSION) - return std::nullopt; - if (!memcmp(header.m_magic, ZoneConstants::MAGIC_IW4X, std::char_traits::length(ZoneConstants::MAGIC_IW4X))) + if (endianness::FromLittleEndian(header.m_version) == ZoneConstants::ZONE_VERSION) { - if (*reinterpret_cast(&header.m_magic[std::char_traits::length(ZoneConstants::MAGIC_IW4X)]) - == ZoneConstants::IW4X_ZONE_VERSION) + if (!memcmp(header.m_magic, ZoneConstants::MAGIC_IW4X, std::char_traits::length(ZoneConstants::MAGIC_IW4X))) + { + if (*reinterpret_cast(&header.m_magic[std::char_traits::length(ZoneConstants::MAGIC_IW4X)]) + == ZoneConstants::IW4X_ZONE_VERSION) + { + return ZoneLoaderInspectionResultIW4{ + .m_generic_result = + ZoneLoaderInspectionResult{ + .m_game_id = GameId::IW4, + .m_endianness = GameEndianness::LE, + .m_word_size = GameWordSize::ARCH_32, + .m_platform = GamePlatform::PC, + .m_is_official = false, + .m_is_signed = false, + .m_is_encrypted = false, + }, + .m_is_iw4x = true, + }; + } + + return std::nullopt; + } + + if (!memcmp(header.m_magic, ZoneConstants::MAGIC_SIGNED_INFINITY_WARD, std::char_traits::length(ZoneConstants::MAGIC_SIGNED_INFINITY_WARD))) + { + return ZoneLoaderInspectionResultIW4{ + .m_generic_result = + ZoneLoaderInspectionResult{ + .m_game_id = GameId::IW4, + .m_endianness = GameEndianness::LE, + .m_word_size = GameWordSize::ARCH_32, + .m_platform = GamePlatform::PC, + .m_is_official = true, + .m_is_signed = true, + .m_is_encrypted = false, + }, + .m_is_iw4x = false, + }; + } + + if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, std::char_traits::length(ZoneConstants::MAGIC_UNSIGNED))) { return ZoneLoaderInspectionResultIW4{ .m_generic_result = @@ -61,45 +111,44 @@ namespace .m_is_signed = false, .m_is_encrypted = false, }, - .m_is_iw4x = true, + .m_is_iw4x = false, }; } - - return std::nullopt; } - - if (!memcmp(header.m_magic, ZoneConstants::MAGIC_SIGNED_INFINITY_WARD, std::char_traits::length(ZoneConstants::MAGIC_SIGNED_INFINITY_WARD))) + else if (endianness::FromBigEndian(header.m_version) == ZoneConstants::ZONE_VERSION_XENON) { - return ZoneLoaderInspectionResultIW4{ - .m_generic_result = - ZoneLoaderInspectionResult{ - .m_game_id = GameId::IW4, - .m_endianness = GameEndianness::LE, - .m_word_size = GameWordSize::ARCH_32, - .m_platform = GamePlatform::PC, - .m_is_official = true, - .m_is_signed = true, - .m_is_encrypted = false, - }, - .m_is_iw4x = false, - }; - } - - if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, std::char_traits::length(ZoneConstants::MAGIC_UNSIGNED))) - { - return ZoneLoaderInspectionResultIW4{ - .m_generic_result = - ZoneLoaderInspectionResult{ - .m_game_id = GameId::IW4, - .m_endianness = GameEndianness::LE, - .m_word_size = GameWordSize::ARCH_32, - .m_platform = GamePlatform::PC, - .m_is_official = false, - .m_is_signed = false, - .m_is_encrypted = false, - }, - .m_is_iw4x = false, - }; + if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, std::char_traits::length(ZoneConstants::MAGIC_UNSIGNED))) + { + return ZoneLoaderInspectionResultIW4{ + .m_generic_result = + ZoneLoaderInspectionResult{ + .m_game_id = GameId::IW4, + .m_endianness = GameEndianness::BE, + .m_word_size = GameWordSize::ARCH_32, + .m_platform = GamePlatform::XBOX, + .m_is_official = false, + .m_is_signed = false, + .m_is_encrypted = false, + }, + .m_is_iw4x = false, + }; + } + if (!memcmp(header.m_magic, ZoneConstants::MAGIC_SIGNED_INFINITY_WARD, std::char_traits::length(ZoneConstants::MAGIC_SIGNED_INFINITY_WARD))) + { + return ZoneLoaderInspectionResultIW4{ + .m_generic_result = + ZoneLoaderInspectionResult{ + .m_game_id = GameId::IW4, + .m_endianness = GameEndianness::BE, + .m_word_size = GameWordSize::ARCH_32, + .m_platform = GamePlatform::XBOX, + .m_is_official = true, + .m_is_signed = true, + .m_is_encrypted = false, + }, + .m_is_iw4x = false, + }; + } } return std::nullopt; @@ -121,16 +170,27 @@ namespace #undef XBLOCK_DEF } - std::unique_ptr SetupRsa(const bool isOfficial) + std::unique_ptr SetupRsa(const bool isOfficial, const GamePlatform platform) { if (isOfficial) { auto rsa = cryptography::CreateRsa(cryptography::HashingAlgorithm::RSA_HASH_SHA256, cryptography::RsaPaddingMode::RSA_PADDING_PSS); - if (!rsa->SetKey(ZoneConstants::RSA_PUBLIC_KEY_INFINITY_WARD, sizeof(ZoneConstants::RSA_PUBLIC_KEY_INFINITY_WARD))) + if (platform == GamePlatform::PC) { - con::error("Invalid public key for signature checking"); - return nullptr; + if (!rsa->SetKey(ZoneConstants::RSA_PUBLIC_KEY_INFINITY_WARD, sizeof(ZoneConstants::RSA_PUBLIC_KEY_INFINITY_WARD))) + { + con::error("Invalid public key for signature checking"); + return nullptr; + } + } + else if (platform == GamePlatform::XBOX) + { + if (!rsa->SetKey(ZoneConstants::RSA_PUBLIC_KEY_INFINITY_WARD_XENON, sizeof(ZoneConstants::RSA_PUBLIC_KEY_INFINITY_WARD_XENON))) + { + con::error("Invalid public key for signature checking"); + return nullptr; + } } return rsa; @@ -151,7 +211,9 @@ namespace return; // If file is signed setup a RSA instance. - auto rsa = SetupRsa(inspectResult.m_generic_result.m_is_official); + auto rsa = SetupRsa(inspectResult.m_generic_result.m_is_official, inspectResult.m_generic_result.m_platform); + + zoneLoader.AddLoadingStep(std::make_unique()); zoneLoader.AddLoadingStep(step::CreateStepVerifyMagic(ZoneConstants::MAGIC_AUTH_HEADER)); zoneLoader.AddLoadingStep(step::CreateStepSkipBytes(4)); // Skip reserved @@ -227,6 +289,11 @@ std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(const ZoneH // Skip timestamp zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(8)); + if (inspectResult->m_generic_result.m_platform == GamePlatform::XBOX) + { + zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(16)); + } + // Add steps for loading the auth header which also contain the signature of the zone if it is signed. AddAuthHeaderSteps(*inspectResult, *zoneLoader, fileName); @@ -238,21 +305,35 @@ std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(const ZoneH zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(1)); } - // Start of the XFile struct - zoneLoader->AddLoadingStep(step::CreateStepLoadZoneSizes()); - zoneLoader->AddLoadingStep(step::CreateStepAllocXBlocks()); + if (inspectResult->m_generic_result.m_endianness == GameEndianness::LE) + { + // Start of the XFile struct + zoneLoader->AddLoadingStep(step::CreateStepLoadZoneSizes()); + zoneLoader->AddLoadingStep(step::CreateStepAllocXBlocks()); - // Start of the zone content - zoneLoader->AddLoadingStep(step::CreateStepLoadZoneContent( - [zonePtr](ZoneInputStream& stream) - { - return std::make_unique(*zonePtr, stream); - }, - 32u, - ZoneConstants::OFFSET_BLOCK_BIT_COUNT, - ZoneConstants::INSERT_BLOCK, - zonePtr->Memory(), - std::move(progressCallback))); + // Start of the zone content + zoneLoader->AddLoadingStep(step::CreateStepLoadZoneContent( + [zonePtr](ZoneInputStream& stream) + { + return std::make_unique(*zonePtr, stream); + }, + 32u, + ZoneConstants::OFFSET_BLOCK_BIT_COUNT, + ZoneConstants::INSERT_BLOCK, + zonePtr->Memory(), + std::move(progressCallback))); + } + else + { + fs::path dumpFileNamePath = fs::path(fileName).filename(); + dumpFileNamePath.replace_extension(".dat"); + std::string dumpFileName = dumpFileNamePath.string(); + con::warn("Dumping xbox assets is not supported, making a full fastfile data dump to {}", dumpFileName); + + zoneLoader->AddLoadingStep(std::make_unique()); + + zoneLoader->AddLoadingStep(step::CreateStepDumpData(dumpFileName, 0xFFFFFFFF)); + } return zoneLoader; }