From a862393a32fdbc6bec5f9111ab69dbefc2cba1a2 Mon Sep 17 00:00:00 2001 From: novicepie <1287918131@qq.com> Date: Sat, 10 Oct 2026 07:02:45 +0800 Subject: [PATCH 1/2] tank: support trg version 14 2.25 bumps the trg version to 14. the header layout and the blocks we parse (packages, skins) are unchanged from 13. also fix header detection for unencrypted trgs: the version byte is at the top of the magic there, not the bottom, so TryReadHeader never matched and fell through to "TRG header not recognised". Co-Authored-By: Claude Opus 5.5 --- TACTLib/Core/Product/Tank/ResourceGraph.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/TACTLib/Core/Product/Tank/ResourceGraph.cs b/TACTLib/Core/Product/Tank/ResourceGraph.cs index 9822148..fd3e14c 100644 --- a/TACTLib/Core/Product/Tank/ResourceGraph.cs +++ b/TACTLib/Core/Product/Tank/ResourceGraph.cs @@ -284,7 +284,8 @@ private static bool TryReadHeader(Stream stream, Func readHea stream.Position = 0; header = readHeader(stream); - var magic = header.m_footerMagic >> 8; + // encrypted magic is byte-swapped, so the version byte is at the bottom instead of the top + var magic = header.IsEncrypted() ? header.m_footerMagic >> 8 : header.m_footerMagic & 0xFFFFFF; if (magic != TRGHeader.ENCRYPTED_MAGIC && magic != TRGHeader.UNENCRYPTED_MAGIC) { // footer magic isn't in the right place, we can't check the version @@ -334,8 +335,8 @@ public ResourceGraph(ClientHandler client, Stream stream, string name) { m_header = ReadHeader(stream); var version = m_header.GetVersion(); - if (version is < 5 or > 13) { - throw new UnsupportedBuildVersionException($"unable to parse TRG. invalid version {version}, expected 5, 6, 7, 8, 9, 10, 11, 12 or 13"); + if (version is < 5 or > 14) { + throw new UnsupportedBuildVersionException($"unable to parse TRG. invalid version {version}, expected 5, 6, 7, 8, 9, 10, 11, 12, 13 or 14"); } // version 7: type bundle index added From 5489e9704c333ec3e4f618585119c7ad558737cf Mon Sep 17 00:00:00 2001 From: novicepie <1287918131@qq.com> Date: Sat, 10 Oct 2026 07:02:45 +0800 Subject: [PATCH 2/2] [pro] cmf for 154088 2.25.0.0.154461N (CN client), manifest build 154088. same key/iv loops as 142923, new keytable. decrypts all encrypted cmfs and the trg. Co-Authored-By: Claude Opus 5.5 --- .../Core/Product/Tank/CMF/ProCMF_154088.cs | 72 +++++++++++++++++++ .../Core/Product/Tank/TRG/ProTRG_154088.cs | 72 +++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 TACTLib/Core/Product/Tank/CMF/ProCMF_154088.cs create mode 100644 TACTLib/Core/Product/Tank/TRG/ProTRG_154088.cs diff --git a/TACTLib/Core/Product/Tank/CMF/ProCMF_154088.cs b/TACTLib/Core/Product/Tank/CMF/ProCMF_154088.cs new file mode 100644 index 0000000..7a307f6 --- /dev/null +++ b/TACTLib/Core/Product/Tank/CMF/ProCMF_154088.cs @@ -0,0 +1,72 @@ +using static TACTLib.Core.Product.Tank.ManifestCryptoHandler; +using static TACTLib.Core.Product.Tank.ContentManifestFile; + +namespace TACTLib.Core.Product.Tank.CMF +{ + [ManifestCrypto(AutoDetectVersion = true, Product = TACTProduct.Overwatch)] + public class ProCMF_154088 : ICMFEncryptionProc + { + public byte[] Key(CMFHeader header, int length) + { + byte[] buffer = new byte[length]; + uint kidx, okidx; + kidx = okidx = (uint)(length * header.m_buildVersion); + for (uint i = 0; i != length; ++i) + { + buffer[i] = Keytable[SignedMod(kidx, 512)]; + kidx += (header.m_buildVersion * (uint)header.m_dataCount) % 7; + } + return buffer; + } + + public byte[] IV(CMFHeader header, byte[] digest, int length) + { + byte[] buffer = new byte[length]; + uint kidx, okidx; + kidx = okidx = Keytable[(digest[7] * Keytable[0]) & 511]; + for (int i = 0; i != length; ++i) + { + buffer[i] = Keytable[SignedMod(kidx, 512)]; + kidx += 3; + buffer[i] ^= digest[SignedMod(kidx - i, SHA1_DIGESTSIZE)]; + } + return buffer; + } + + private static readonly byte[] Keytable = + { + 0xBC, 0x63, 0x89, 0x14, 0x11, 0x19, 0x93, 0x89, 0x51, 0xFB, 0x72, 0xCB, 0x04, 0x5C, 0x49, 0xA3, + 0x96, 0x7C, 0x68, 0xCB, 0x5C, 0x7D, 0xB6, 0xE1, 0x8E, 0xC9, 0x02, 0x62, 0x1C, 0x96, 0x02, 0xDA, + 0xFE, 0xBE, 0x3D, 0xDF, 0xBA, 0x2F, 0x6E, 0xB9, 0x2E, 0x11, 0xCD, 0xC3, 0x2D, 0xC6, 0x94, 0x91, + 0x98, 0x75, 0x8B, 0x3D, 0x71, 0xC0, 0x55, 0xA0, 0xA3, 0x10, 0xE4, 0xE3, 0xC6, 0x61, 0x16, 0x2E, + 0xD4, 0x9A, 0x8C, 0xA9, 0x78, 0x6D, 0xAF, 0xF7, 0x7F, 0x5C, 0x12, 0x65, 0x1D, 0x26, 0xCD, 0xFA, + 0x90, 0x76, 0x69, 0x12, 0x51, 0x86, 0x63, 0x8A, 0xC0, 0x43, 0xCF, 0xF6, 0xC2, 0xF0, 0xC5, 0x62, + 0x8A, 0x69, 0x2E, 0xAA, 0x78, 0x04, 0x5A, 0xB5, 0x65, 0x94, 0xC3, 0x4D, 0x4A, 0x60, 0x1C, 0x03, + 0x26, 0x6C, 0xC4, 0xAC, 0xEE, 0x0D, 0xE2, 0xED, 0xD0, 0xF1, 0x99, 0x74, 0x06, 0xD2, 0x1C, 0xC4, + 0x21, 0xE8, 0x66, 0xFC, 0x7B, 0xF4, 0x67, 0xEA, 0x92, 0xF2, 0x33, 0xF6, 0xE9, 0x21, 0x80, 0xC1, + 0x0E, 0x9E, 0x1B, 0xAE, 0xAF, 0xB1, 0xBA, 0xA4, 0xBC, 0xB1, 0x7F, 0x96, 0x85, 0xA5, 0x7E, 0xBB, + 0x4D, 0xC5, 0x06, 0x1B, 0xA9, 0xE1, 0xE2, 0xEB, 0xAF, 0x67, 0xDA, 0xE5, 0xF5, 0x3A, 0xD2, 0x8D, + 0x90, 0x0E, 0x11, 0xC1, 0x68, 0xB4, 0xEC, 0x06, 0x80, 0xCE, 0x94, 0x63, 0xBB, 0x24, 0xB8, 0xE9, + 0x6B, 0x9E, 0x07, 0x2D, 0x3A, 0xF1, 0xDD, 0x2D, 0x28, 0x44, 0x6C, 0xA1, 0x6D, 0xFE, 0x9D, 0xC1, + 0x69, 0xE3, 0x0E, 0xBC, 0xFE, 0x1F, 0xF5, 0x16, 0x41, 0x8A, 0x5B, 0x86, 0x92, 0x67, 0x25, 0xE2, + 0x0A, 0x0E, 0x56, 0xC5, 0xB6, 0x2A, 0x34, 0xEF, 0xF0, 0x11, 0xFD, 0xD3, 0x3F, 0x16, 0x87, 0x18, + 0x94, 0x5D, 0x4E, 0x8C, 0x2F, 0x87, 0x2B, 0x16, 0x5C, 0x88, 0xE4, 0x60, 0x59, 0x33, 0x39, 0x29, + 0x70, 0x11, 0xB9, 0x3E, 0x89, 0xE6, 0x14, 0xF2, 0x1D, 0x20, 0xE6, 0xA3, 0xD2, 0x50, 0x5F, 0x76, + 0x2E, 0x85, 0x5A, 0x18, 0x41, 0xF6, 0x99, 0xA3, 0x0D, 0x29, 0x68, 0x6B, 0xAA, 0x1C, 0x78, 0xA8, + 0xCB, 0xB3, 0x26, 0x10, 0x5A, 0x38, 0x5F, 0x56, 0xB4, 0x5A, 0xE6, 0xF9, 0x8D, 0x97, 0xDB, 0x84, + 0x71, 0xDD, 0x9C, 0xA4, 0xF7, 0xEF, 0x01, 0x29, 0x4D, 0x98, 0xAB, 0x7C, 0xE6, 0x08, 0xCE, 0x32, + 0x5C, 0xDD, 0x4D, 0x31, 0x8F, 0x49, 0x71, 0x52, 0x3F, 0x8D, 0x88, 0x38, 0x44, 0x3C, 0x17, 0x01, + 0x6A, 0x01, 0x77, 0xFC, 0x0E, 0xEB, 0x68, 0xD3, 0x85, 0x6C, 0x87, 0x1D, 0xED, 0x3C, 0x65, 0x38, + 0x9A, 0x10, 0xD7, 0xA6, 0x9C, 0x89, 0x34, 0x70, 0x3C, 0x29, 0x2E, 0xBD, 0xB0, 0xBC, 0x4F, 0xB5, + 0xE9, 0xA2, 0x5B, 0x51, 0xE7, 0xA8, 0xCB, 0x02, 0xEC, 0xAA, 0xB9, 0x8C, 0x94, 0x14, 0x8B, 0x0B, + 0xEB, 0xC1, 0x6B, 0x81, 0xA0, 0x54, 0x93, 0x6A, 0xCB, 0x00, 0x2B, 0x9C, 0xB7, 0xC7, 0x5F, 0x50, + 0x74, 0x45, 0xFE, 0x3C, 0x97, 0xD4, 0x43, 0x8E, 0xA1, 0xFD, 0x08, 0xA4, 0x8B, 0x63, 0x80, 0x0F, + 0x69, 0xB6, 0x67, 0x78, 0x94, 0x98, 0xE1, 0x32, 0xED, 0x2F, 0x87, 0x1A, 0x62, 0xF9, 0x4D, 0x99, + 0xB2, 0x9F, 0xEB, 0x37, 0xD9, 0x9C, 0x13, 0x3E, 0xD7, 0xD7, 0x0D, 0x35, 0x5D, 0x61, 0xD2, 0x4C, + 0x8D, 0x30, 0xD1, 0x7F, 0xC4, 0xCC, 0x5D, 0x24, 0xEA, 0x70, 0x2E, 0x43, 0xB5, 0x1A, 0x51, 0x77, + 0x95, 0x28, 0x0C, 0x3F, 0xFA, 0x07, 0xF3, 0x9A, 0xC9, 0x05, 0x71, 0xB1, 0x51, 0x85, 0xC9, 0xA3, + 0x64, 0xA4, 0x99, 0x0C, 0xFF, 0xFD, 0xBE, 0xBC, 0x53, 0xF5, 0x95, 0x80, 0x77, 0x37, 0xAD, 0xCA, + 0xEB, 0x16, 0x59, 0x74, 0xE1, 0x42, 0xA7, 0xC0, 0x9F, 0x27, 0xF6, 0xA3, 0x4F, 0x22, 0x8E, 0x69 + }; + } +} diff --git a/TACTLib/Core/Product/Tank/TRG/ProTRG_154088.cs b/TACTLib/Core/Product/Tank/TRG/ProTRG_154088.cs new file mode 100644 index 0000000..23c9435 --- /dev/null +++ b/TACTLib/Core/Product/Tank/TRG/ProTRG_154088.cs @@ -0,0 +1,72 @@ +using static TACTLib.Core.Product.Tank.ManifestCryptoHandler; +using static TACTLib.Core.Product.Tank.ResourceGraph; + +namespace TACTLib.Core.Product.Tank.TRG +{ + [ManifestCrypto(AutoDetectVersion = true, Product = TACTProduct.Overwatch)] + public class ProTRG_154088 : ITRGEncryptionProc + { + public byte[] Key(TRGHeader header, int length) + { + byte[] buffer = new byte[length]; + uint kidx, okidx; + kidx = okidx = (uint)(length * header.m_buildVersion); + for (uint i = 0; i != length; ++i) + { + buffer[i] = Keytable[SignedMod(kidx, 512)]; + kidx += (header.m_buildVersion * (uint)header.m_skinCount) % 7; + } + return buffer; + } + + public byte[] IV(TRGHeader header, byte[] digest, int length) + { + byte[] buffer = new byte[length]; + uint kidx, okidx; + kidx = okidx = Keytable[(digest[3] * Keytable[111]) & 511]; + for (int i = 0; i != length; ++i) + { + buffer[i] = Keytable[SignedMod(kidx, 512)]; + kidx += 3; + buffer[i] ^= digest[SignedMod(kidx - i, SHA1_DIGESTSIZE)]; + } + return buffer; + } + + private static readonly byte[] Keytable = + { + 0xBC, 0x63, 0x89, 0x14, 0x11, 0x19, 0x93, 0x89, 0x51, 0xFB, 0x72, 0xCB, 0x04, 0x5C, 0x49, 0xA3, + 0x96, 0x7C, 0x68, 0xCB, 0x5C, 0x7D, 0xB6, 0xE1, 0x8E, 0xC9, 0x02, 0x62, 0x1C, 0x96, 0x02, 0xDA, + 0xFE, 0xBE, 0x3D, 0xDF, 0xBA, 0x2F, 0x6E, 0xB9, 0x2E, 0x11, 0xCD, 0xC3, 0x2D, 0xC6, 0x94, 0x91, + 0x98, 0x75, 0x8B, 0x3D, 0x71, 0xC0, 0x55, 0xA0, 0xA3, 0x10, 0xE4, 0xE3, 0xC6, 0x61, 0x16, 0x2E, + 0xD4, 0x9A, 0x8C, 0xA9, 0x78, 0x6D, 0xAF, 0xF7, 0x7F, 0x5C, 0x12, 0x65, 0x1D, 0x26, 0xCD, 0xFA, + 0x90, 0x76, 0x69, 0x12, 0x51, 0x86, 0x63, 0x8A, 0xC0, 0x43, 0xCF, 0xF6, 0xC2, 0xF0, 0xC5, 0x62, + 0x8A, 0x69, 0x2E, 0xAA, 0x78, 0x04, 0x5A, 0xB5, 0x65, 0x94, 0xC3, 0x4D, 0x4A, 0x60, 0x1C, 0x03, + 0x26, 0x6C, 0xC4, 0xAC, 0xEE, 0x0D, 0xE2, 0xED, 0xD0, 0xF1, 0x99, 0x74, 0x06, 0xD2, 0x1C, 0xC4, + 0x21, 0xE8, 0x66, 0xFC, 0x7B, 0xF4, 0x67, 0xEA, 0x92, 0xF2, 0x33, 0xF6, 0xE9, 0x21, 0x80, 0xC1, + 0x0E, 0x9E, 0x1B, 0xAE, 0xAF, 0xB1, 0xBA, 0xA4, 0xBC, 0xB1, 0x7F, 0x96, 0x85, 0xA5, 0x7E, 0xBB, + 0x4D, 0xC5, 0x06, 0x1B, 0xA9, 0xE1, 0xE2, 0xEB, 0xAF, 0x67, 0xDA, 0xE5, 0xF5, 0x3A, 0xD2, 0x8D, + 0x90, 0x0E, 0x11, 0xC1, 0x68, 0xB4, 0xEC, 0x06, 0x80, 0xCE, 0x94, 0x63, 0xBB, 0x24, 0xB8, 0xE9, + 0x6B, 0x9E, 0x07, 0x2D, 0x3A, 0xF1, 0xDD, 0x2D, 0x28, 0x44, 0x6C, 0xA1, 0x6D, 0xFE, 0x9D, 0xC1, + 0x69, 0xE3, 0x0E, 0xBC, 0xFE, 0x1F, 0xF5, 0x16, 0x41, 0x8A, 0x5B, 0x86, 0x92, 0x67, 0x25, 0xE2, + 0x0A, 0x0E, 0x56, 0xC5, 0xB6, 0x2A, 0x34, 0xEF, 0xF0, 0x11, 0xFD, 0xD3, 0x3F, 0x16, 0x87, 0x18, + 0x94, 0x5D, 0x4E, 0x8C, 0x2F, 0x87, 0x2B, 0x16, 0x5C, 0x88, 0xE4, 0x60, 0x59, 0x33, 0x39, 0x29, + 0x70, 0x11, 0xB9, 0x3E, 0x89, 0xE6, 0x14, 0xF2, 0x1D, 0x20, 0xE6, 0xA3, 0xD2, 0x50, 0x5F, 0x76, + 0x2E, 0x85, 0x5A, 0x18, 0x41, 0xF6, 0x99, 0xA3, 0x0D, 0x29, 0x68, 0x6B, 0xAA, 0x1C, 0x78, 0xA8, + 0xCB, 0xB3, 0x26, 0x10, 0x5A, 0x38, 0x5F, 0x56, 0xB4, 0x5A, 0xE6, 0xF9, 0x8D, 0x97, 0xDB, 0x84, + 0x71, 0xDD, 0x9C, 0xA4, 0xF7, 0xEF, 0x01, 0x29, 0x4D, 0x98, 0xAB, 0x7C, 0xE6, 0x08, 0xCE, 0x32, + 0x5C, 0xDD, 0x4D, 0x31, 0x8F, 0x49, 0x71, 0x52, 0x3F, 0x8D, 0x88, 0x38, 0x44, 0x3C, 0x17, 0x01, + 0x6A, 0x01, 0x77, 0xFC, 0x0E, 0xEB, 0x68, 0xD3, 0x85, 0x6C, 0x87, 0x1D, 0xED, 0x3C, 0x65, 0x38, + 0x9A, 0x10, 0xD7, 0xA6, 0x9C, 0x89, 0x34, 0x70, 0x3C, 0x29, 0x2E, 0xBD, 0xB0, 0xBC, 0x4F, 0xB5, + 0xE9, 0xA2, 0x5B, 0x51, 0xE7, 0xA8, 0xCB, 0x02, 0xEC, 0xAA, 0xB9, 0x8C, 0x94, 0x14, 0x8B, 0x0B, + 0xEB, 0xC1, 0x6B, 0x81, 0xA0, 0x54, 0x93, 0x6A, 0xCB, 0x00, 0x2B, 0x9C, 0xB7, 0xC7, 0x5F, 0x50, + 0x74, 0x45, 0xFE, 0x3C, 0x97, 0xD4, 0x43, 0x8E, 0xA1, 0xFD, 0x08, 0xA4, 0x8B, 0x63, 0x80, 0x0F, + 0x69, 0xB6, 0x67, 0x78, 0x94, 0x98, 0xE1, 0x32, 0xED, 0x2F, 0x87, 0x1A, 0x62, 0xF9, 0x4D, 0x99, + 0xB2, 0x9F, 0xEB, 0x37, 0xD9, 0x9C, 0x13, 0x3E, 0xD7, 0xD7, 0x0D, 0x35, 0x5D, 0x61, 0xD2, 0x4C, + 0x8D, 0x30, 0xD1, 0x7F, 0xC4, 0xCC, 0x5D, 0x24, 0xEA, 0x70, 0x2E, 0x43, 0xB5, 0x1A, 0x51, 0x77, + 0x95, 0x28, 0x0C, 0x3F, 0xFA, 0x07, 0xF3, 0x9A, 0xC9, 0x05, 0x71, 0xB1, 0x51, 0x85, 0xC9, 0xA3, + 0x64, 0xA4, 0x99, 0x0C, 0xFF, 0xFD, 0xBE, 0xBC, 0x53, 0xF5, 0x95, 0x80, 0x77, 0x37, 0xAD, 0xCA, + 0xEB, 0x16, 0x59, 0x74, 0xE1, 0x42, 0xA7, 0xC0, 0x9F, 0x27, 0xF6, 0xA3, 0x4F, 0x22, 0x8E, 0x69 + }; + } +}