From e65c61b500df343098c17ca21f45ce2ffff4b92d Mon Sep 17 00:00:00 2001 From: Undead <110314402+undead2146@users.noreply.github.com> Date: Sun, 30 Aug 2026 18:51:59 +0000 Subject: [PATCH 1/3] feat(manifest): modernize ManifestGenerationService with authoritative CSV catalog resolution - Embed GameInstallationFilesRegistry CSV and index catalog as assembly resources in GenHub.Core - Implement authoritative CSV catalog resolution in ManifestGenerationService supporting Generals 1.08 and Zero Hour 1.04 - Enhance IContentManifestBuilder and ContentManifestBuilder to accept precomputed hashes, sizes, and requirement flags - Add multi-language detection, normalization, and filtering to game installation manifest generation - Implement cross-platform case-insensitive path resolution and pristine vanilla manifest enforcement (excluding untracked non-vanilla mod files) - Add comprehensive unit tests covering CSV authority, multi-language filtering, backup file handling, and non-vanilla file exclusion --- .../Assets/Registries/Generals-1.08.csv | 165 ++++++ .../Assets/Registries/ZeroHour-1.04.csv | 176 ++++++ .../GenHub.Core/Assets/Registries/index.json | 39 ++ GenHub/GenHub.Core/GenHub.Core.csproj | 4 +- .../Manifest/IContentManifestBuilder.cs | 12 +- .../Manifest/IManifestGenerationService.cs | 16 +- .../ManifestGenerationServiceTests.cs | 163 +++++- .../Manifest/ContentManifestBuilder.cs | 51 +- .../Manifest/ManifestGenerationService.cs | 500 +++++++++++------- 9 files changed, 933 insertions(+), 193 deletions(-) create mode 100644 GenHub/GenHub.Core/Assets/Registries/Generals-1.08.csv create mode 100644 GenHub/GenHub.Core/Assets/Registries/ZeroHour-1.04.csv create mode 100644 GenHub/GenHub.Core/Assets/Registries/index.json diff --git a/GenHub/GenHub.Core/Assets/Registries/Generals-1.08.csv b/GenHub/GenHub.Core/Assets/Registries/Generals-1.08.csv new file mode 100644 index 000000000..ab1d6be0c --- /dev/null +++ b/GenHub/GenHub.Core/Assets/Registries/Generals-1.08.csv @@ -0,0 +1,165 @@ +relativePath,size,md5,sha256,gameType,language,isRequired,metadata,downloadUrl +00000000.016,153716,aebed2f8fa6f42b8c76929dfc8f90a00,ef61474057b21db70ae4356c3f22c088e583b3fd00c0b21da0c298949e8c3d62,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +00000000.256,308276,e8caed1e8eb521287cad86cbcb6edf5c,2d1f66e232557e775e636f8b8976422e330223e70a50f0535cc6b2e5ea03903f,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Audio.big,127940044,c4d80fc87dc13eacd9dbf2a981b194a3,d522df264149e3e27fd46f027548cc38bd60614a43666555219b7b1d45fb3bad,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +AudioEnglish.big,104744592,fc015ddbe16ac6b4d39a85f5612d7233,39d67dba96111178fcceefae2bedb2dc65b55b968741162c714b333c0b0f5f2e,Generals,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +BINKW32.DLL,358963,e58a20c9e7b342d5ca1f5ba75f1d1108,892a51c4056efcb22297a3b44a3491e3f5888f28b08ed1b17030f24acffedb44,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +BrowserEngine.dll,356352,df2ad4b243d68d74d6c35525d24c56ed,3653210a9e021be8c28144db1420320bc999a1c9ca13084a056f4b51ff03bd8a,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCAttack_S.ani,1666,548a3180cc4fadcdf669ea455e6e1921,8e1a57bd031bc8565775f78a162eb0b2f81444d464466e57d43838a116b38ab2,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/sccattack.ani,6398,6d899deaefe2228081f28073a9485ade,fa13992a0603390b0c8fa4200cfca7ee9eb0744cb55b87f49aebee55e5711216,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCAttMov_S.ani,1670,3bd9ee4b858660bbf45fa393dc1c04c5,b9c38e1c1a9294b3afcd029162a79bbbfc285770b5e1ff82998a604755ad7e19,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCAttMov.ani,5644,f06b765c53fda761aebe4f8b4045cd08,c6b2ac92c9d8d8a15bd411dc3e3cf6e276b081cef20035ca82647f0168085714,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCCashHack.ani,7896,19efb6c96fb3b30adfcf6d8ad6fa7981,b41565b664457a00d87efbd24b5681155967620df5ae64a54d28aa9780279385,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCEnter_S.ani,2446,f48af1584768ade129a8a6c4f049b452,858479b74fafd998f1496b69edf2e44245a0c2f8bc8dc6c7ecb9b90d62c7370f,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCEnter.ani,2440,20f9bc7c814947b4cec1349c4bb8bfb7,037ab0691bebf9b4a985d194d0619e9fefa0330a07d4c85bb5047c26d10c5787,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCExit.ani,2436,acf7171dfbe1f72d64c9c08a6a190469,590d419191ec781d97fa7e375498eb99270eeccf8d9ee75bb5c3c67e9dcb620a,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCFriendly_S.ani,6358,5578eda24455b8416ac223d29b4ce475,488b83907f2b12c35d1e3d688a3564acd309d4f2afa1185cade157aee40fc238,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCFriendly.ani,6344,9f7a4f16f5cafbf36e865285ec360638,f7b424afb5575067fffe02cf1afdeb53f62f81ee85a12d581e2a76b5ab418652,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCGuard.ani,1616,1b24328b2926ca7bede732be68b4a4c1,fa8c51b348106c3ede587fc0a13deb63aa54a5db38ce8f582f03b761d57f6150,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCHeal.ani,2478,d12a7573375158375a33131339598d51,32997e09311a684687ab8a339d63d3f075eab98def7e8e0c47b4519b9763a695,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCHostile_S.ani,1674,aa715bed9e28d9dd5a13a56b8fec2e90,73f2fb4c8871afbaa49d6a5cbd34c53e4f29cead68f571d7aab5a5b406c893ea,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCHostile.ani,3270,bf03d453be48a42b218c084cbe19e578,78133602f6b1eceb6257895e9eaeadeb07ddbc9e969f6ac69b6bcbe16b5244ed,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCHostile2.ani,8634,f46b0d70e7c29ddb6b24a14dc053fb95,69a82c2f23d5608f039ce47cde2fe955ec1e4dc9971082ef2c24035f04d0738c,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCHostile3.ani,25792,92918392b8969da0d4562b5636df397c,b86c5be45dbf421172de0d12f4f1b237ce1844ad67ce614620490d60a5cbc5b5,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCKnifeAttack.ani,3966,eb68b3991a575c6ee2247c66b9ff01da,922ba292078be4b9f697450bcf68e23b6bcd9344f433b64641c856f15cc040a1,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCMove_S.ani,1664,3b33efc0b64e1b35f07afb8e098d5cf8,0a60c8f9b6da230767f10275ca97a57347d65a39468f953db122e0f93b07793c,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/sccmove.ani,5638,728cd67cd5a9c8c133f4a05d0a7424ea,edcc9a98a24e0e21bd89e45db2fcdf04c5d3ac359616db96ea6c33407df04c82,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCNoAction_S.ani,892,d9612607ce26ba18da3c98187ba8f60a,2cfa77a51eb15ab54359c13c27b926d7b0f948f2c91d4a1928d139d70ceacb02,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCNoAction.ani,886,cc4e179474a670631835d81725830882,116f51bd9073187b7b2fba0ce08bbe3417cd5daacec7b2af6b78a60afd07d6ff,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCNoBomb.ani,886,48237559686d7aaf721d2cb5109efa67,03e4cd887ab8283a8594b4ae54c0f73b159dce2ebf2df069923531de89be0d7a,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCNoEntry_S.ani,1682,121ffb04e7785ef03dba6de7930d5899,2b76be67e3d75d0cc428efb1563c08c0a6f717d95ffdeb1696c70ba446c92c09,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCNoEntry.ani,1676,bf2c1b618dff02c56bed38e7c69b19ba,ab8fed4bf8bf5bf6ff8e79a40bc89afe9c6bc7c13e2b1f5577506bb48d017803,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCNoKnife.ani,886,973eebf5e3893f3e1ce04ebab5f2b5eb,49d8a05655d9af4bf5c6ab3c7f15e2c86dc1037cc2c016c093d22eada35d75f0,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCOutrange.ani,16460,f18891ca6f6be7f2bc8feeeda196ac80,8539c4a441775dd7ccf5c1faf0cb79fdd5fb6c6797f7d7d3ca98768348429741,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCPlace.ani,1680,dc039f3410c790f3666ac7dc611ef801,a149ac8f358c82b6fd89e2e3fef32b6b1d11adcfd846c3560fd388d75c166e0b,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCPlaceBeacon.ani,7078,82c144ddda013cf670954944a79b2196,d32b4336cbe21101cb872fe9e650f0cad9c77f86fdc26286484448c3fc00b8a6,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/sccpointer.ani,884,54199240e4efefa204418c6c973221b2,ad3829b3e6262f8881ad2a3ddc90e7add848ca5b6bb8fda76facd6ea3a98df00,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCRallyPnt_S.ani,1668,1b3d007599e9307b758c28748321c10b,cfb1cd7baed30b94d5b47a659296738376cafeb4dc492dda6b2c9e5de534d1b9,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCRallyPnt.ani,4064,cec0a2074598b34bebe51d14f7783d31,9d15def280ae76cc3aff9f5ae61547f035b9841ed42e647d13d4cae596a2065d,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCRemoteChg.ani,9412,f8a2450fc0f82f28f584af001dbfbc30,d60b49ac06ae56b56fc03e6cbbf6d1bdbf0bc66eaacec621d7cd0caffd300d6d,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCRepair.ani,5640,a7a77357ec53203c1af9164acf6abff7,baaaa22fb2b331660321833659967d8d057cb50ab31cd2b927459ff0c548ecb6,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCResumeC.ani,17970,a5941a513b20dc2165e7ed9fd2cfdf12,cea73ea5cf0dc34d8299d42ec965eaf2c98a7a501ab1bd5599407f3af91f7e85,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/sccscroll0.ani,888,69a1fbc2773ec932f6a5a7fb37f7f12f,0560079fe4782ac89942eb45e94de7011eb1f4ebb39230890e4917d612bc5d01,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/sccscroll1.ani,888,6aacbae0874a6c9aa335e35947a8ebd0,c48c8691866637e0fa9fc98bb40e2d8240370b4cafd7b767c1595b58789eb06c,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/sccscroll2.ani,888,43f6019d566d0aa8759a42d0a5bec5cd,e62fe7da074898004ee7ef81626830fa94332b3a9d4fe8987e948cea0e2ac1f9,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/sccscroll3.ani,888,6f2e50e14ff2776eae76d961a4a5bfc7,f9c2a000c946a6f6bb420d9671c9ba21a9fc14821a5692a5f2e5b4755fdebb8d,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCScroll4.ani,888,a169748d79da826c10d7e5149c7fd41e,ec77136c9768ca3c50216ae92269aa5fae4f29197a13a355c6f92fcf53ecb279,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCScroll5.ani,888,f17a6fc61a7b944bf5eed66d8242d5ca,2ccb9140e23faf6cc98560e545a54b83efce54dd1bfb01ea84119732d0f04433,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCScroll6.ani,888,33618b4ed32fcec63df6fea44b98bac8,46186f3e8e9243edda6e0baf00e0a0fcaaf5e5d9b4d4531bf13393fdda67acc9,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCScroll7.ani,888,eaae99658bb1a5d14ea89ae89aaf32e0,00904ceec2de7e828c1a5fbe224b1ebf6cb19543f3d24381b64d4329a510e46b,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCSDIUplink.ani,16460,1443e1a4d8c5665e6a88288e1b03e7b0,11de6fad707df80023f91c011569de86705b2770cd1df5bb0394481bf5794fb5,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCSelect.ani,6388,21d58f3402a7f33ca5422fc865d2d602,42840130f31fa90c880ba2855fecff0c4a1cd3e903e6eea862ab95b8dc876427,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCSell.ani,1672,21f1c178ab89de9b364cc8709ad0dce8,3a9dcadef511ad9a2dd1ca9b31eac33a7aa58b652554cd1d18c628c87fab1a4d,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCSniper.ani,15636,29c81451b838097f4baed9b00c3f2873,258ce022aa05087bd0772484d3aceff49b9beb532cd8a8d524f9eb97be57de80,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCSpyDrone.ani,1632,b30aaf4f9ef73831165b1e280050da14,6a4302a4a897d6ded38ee0216bc45975406d40a73270eed12d13cb73c868ba34,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCStop.ani,882,28eb1d384c4ff53a0b44352fd62455b3,ebd5c2968465f5e3898d38d3a737f2bda387be6be6ee246712a5081d703150a1,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCTimedChg.ani,8634,c84d13defbb211c95a8015cfc743320c,727c0863496e777929a86425fcc849e7041b77e3209151f566fe233bf8a2bce6,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCTNTAttack.ani,4744,23569a40a3dbe637a6fdd839d25f58ec,e5c03a381c120e26d04c5c7495c88bf43224f51d89e9ca9d7dea84a5903285fd,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCWaypoint_S.ani,1682,b7566c5d6d3bc8e604f6c07b9dedf4ff,1c585e4c98e05ad6fa916f6d496dcac8673ee651a8cd6743319850e3d14bd4bc,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Cursors/SCCWaypoint.ani,1676,c76b8d01c707588e02aea0c9ae052c54,6335fdebaa2ce98e17fec156d75ff9b089ab0f42b70170e5f18383abc73f1e88,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/english/Movies/EA_LOGO.bik,1480980,adbd4c3a5abce41bb190430acb4ff29e,f8beb9cbc902cdd90f94563df1246d4df5769f8335a2dcc1f85916d446e3a8c0,Generals,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/english/Movies/EA_LOGO640.bik,1480980,adbd4c3a5abce41bb190430acb4ff29e,f8beb9cbc902cdd90f94563df1246d4df5769f8335a2dcc1f85916d446e3a8c0,Generals,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/english/Movies/sizzle_review.bik,19887184,a5da392e70910f56ffa563e8720296d6,9bed2259d7088cc4cfa437e3b0ac54449a2069956e3f033ca37d0c2c88274869,Generals,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/english/Movies/sizzle_review640.bik,15345648,1b9993508acd143a86d8e0682db4ca99,0709f9a04903ae915eed68e7bf883b51c0fa22b50096ba9c334e4e0b113087a4,Generals,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/CHINA_end.bik,21096880,131d68acde339d1f204de92733c2d5ae,63553a1c27e4677e381b222fd711a71f72e8289ceeaf0ac0ac07a90a7b4a883a,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/CHINA_end640.bik,16030972,e9356c5ba8b2f15c8eb05a300851fb50,886ad6a33edff45b0f41e3060904dd4e4df7bc9bbf08933fc86b34a1d47183f0,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/China01_Final_00s.bik,9462664,d2ad7d6128e139113c364d26da6f85e0,a183045298d219626b3681adeb77a26b74ca0ecf7ce3daf4a7aa41a11178e237,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/China02_Final_00s.bik,9416184,eabc0320e25a3214936fe7a664a5ac28,2332508999e8ba62c531d3763c9b2cc5627fa059f7bb504b419f089ef2d8dfff,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/China03_Final_00s.bik,9466800,4983a2ca739a4869f60346855a5e4b85,f6554d863a1e47d374e490161c39200df6786478674a9bb4c4a8542362b3a259,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/China04_Final_00s.bik,9304904,989929b5f0f10a943162e6d7659382cd,b28c8a1f63a5e01df3189d6b537fab52cf7a3ff209d845256f3b9fef21d2315a,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/China05_Final_00s.bik,9434164,e90bb8585ec81807290394c39ab66dca,7c520491d39aa3e50a895e8e34bace4dbdf2cd424b507461b3f1959e3c2224e8,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/China06_Final_00s.bik,9484312,a298248160b6a42b7fc4cfa8e2615dad,e232468337b3573151ffa953c5e357f8fc144951114a8db1441dec41414d3c5a,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/China07_Final_00s.bik,9480224,b1dc505e4e57322c651d5ec8071dbf12,aca8ce932d965cd7514a3d5ec50c6a5bf0673409ab316d6b6a429d1f09e6f8b2,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/GLA_end.bik,18464604,705f46b48604eb83418f51f233d3fa5c,141553b0ff8f27dccd6a36008bf4cf547117da09e3b13de29e56b55a7f3b5bf3,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/GLA_end640.bik,14030116,28bc6598a0816b49270dc3374e2fda7b,266765c6f199a662c3bcb4b9c1bbb6564d25766b77b164c4bc3d014e593375f7,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/GLA01_Final_00s.bik,9380716,9c8328840cab3d83a10cfd9b5f2b2d0d,985d0e9c3e1d2f1e37fc3842827763f584955ca1e929b308f78a92c9d074faf7,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/GLA02_Final_00s.bik,9434596,6fe1a7e3518f9b4d9d4d63cad0b1c243,bf95e38a1d1bc15ef9457b461f80d840a9ad019d45ade6dc2a3070c4a6d24655,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/GLA03_Final_00s.bik,9366612,947d5947b7657072219e9b07137eb678,f9ad9839558a23d34e56a34167773ab4e00ffaf5bcb4b34017408f13449a3fb8,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/GLA04_Final_00s.bik,9479028,ce76409306e2450955170ac4f6cc9ad4,60a3713905f240a645aaa9d28f95141876afd2d2c13db66a3ac6449a0c1aef13,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/GLA05_Final_00s.bik,9476400,438e155a4fdf8b6a337e806eaae946b6,6cb3a3a63b0970b72151d88c8635713bb6c279747fdc829647a98d3b869d9655,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/GLA06_Final_00s.bik,9380596,9b2c80a2d661edc49cabdb2f9f34376f,2a2574025f5dc703026d94f5fb373721cd9440c71ae0a5ac6930a4a3151c36e5,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/GLA07_Final_00s.bik,9314264,9e6f4ca61154a9ce1bf654e8abeec032,613de8f96bca17a350e6f8a2abd902e6d8bfcd6b1f0ceac299baf086d90922aa,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/GLA08_Final_00s.bik,9454488,9c20c612eaccc90324b01b1263b533d9,25ede4b757b4aadae00dcd7fd3e7632b553dc9443894be0363e3f23ed49638be,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/Training_Final_00s.bik,9497308,c97f7aea6379f1abb2fa1352dda02cbf,5a46ab6aa21bb760faf3f6d8172d7ab75c009ad9adc95e01c40ebd81d76aeeb9,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/USA_end.bik,20857148,8b13a3c745df210b2df2147750922414,e2149c34fca288f8f86b43466a4a6aa8cb33a97d4ca8075628d77191c08051cf,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/USA_end640.bik,15464260,49b90621d514e06334573c87fafa9e10,f39bc09b30d23ab47b52fa0c97d07375bcfcc75a5097cd45a23c4f1262891682,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/USA01_Final_00s.bik,9440016,bc0908dbfd543f6f816146f4d4794e2e,416f0acdf3a36b4009389e55e8a27001df336bf40921413892f40e923bd78cf4,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/USA02_Final_00s.bik,9484920,99f59a5108b72b46afd223d3bd3b1439,ce76cc66cbdf911e1e6283673b4c26b38196ee0c9e05274f26e5c92f1e363456,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/USA03_Final_00s.bik,9358748,5367c7faac2a8639cb5101f969c82442,51a7f8c59cb3f4cee9c2af1e670549907eb81e8806a5023db20d6a3c211f8a46,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/USA04_Final_00s.bik,9416664,5f5ecb41dcaeb833dbdc82790ee4f7cf,3b183b059e43c1d1184f7206d0d542d94ae58a736035e12cd943c470038ca5c2,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/USA06_Final_00s.bik,9258892,05880cd511c0658537edb5238e7e6e31,45fdd0345c8461c6ae8eae614e540ccd37b3ce6a2345cc9a7ae0e0dea760b0d9,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/USA07_Final_00s.bik,9466124,31977bea9ee3ebd1c5fa0f5d612074da,ddbbe1be231a7ffdaa3235981bb1936e4c1c5dcd08284f0b262095010970806e,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Movies/USA08_Final_00s.bik,9420052,20c9e77027b4a0e245bcbebaac11fff3,59e6ede70c0f8c0f8557d063356f6ccc61cbc829adb1f619ce9aaec0d4d98eb6,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Scripts/MultiplayerScripts.scb,6915,1c9afbc46dad13f55c317a162255d6ae,86c6a3b82a4188ba3c7abf1388f9d5ee503f06634466be095256a2aefb4ae06b,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/Scripts/SkirmishScripts.scb,467525,c8e1e11e697da5adf6abdcdf1290c578,9eedadc0d8d9deb241d56a3db148720029e119c0cfc03a3ef74cade9da3106a5,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust00.tga,16428,9f2d9b6133b6e4d78b409ca8303cab85,d4b2fa073a52734658fed6b780de379fe866b956aec8c95525457232e7d1b636,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust01.tga,16428,5b257180aef56e05b007be93eb64e527,67116bb0c18491cf403d5706e4d6c005d43ea77e034cb889d60465501d504156,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust02.tga,16428,6802068b10f1f8ac64646d54124b7e96,2718869ffb24f789a912b5fe0dcd94067db1a06aa144e1a1ac0d81cd6b5c8fbe,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust03.tga,16428,af760ffba0eff9fe49ace5e8e82de122,6e7c3733f937534800dfc1ac4632c34dd4c873d6d9e55f13dcedf738462d470d,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust04.tga,16428,3068c2e091b0c52c93b0d83d18b04ea3,7123520dc1d22b0279e24f50f4dd5c62d54b2185883acadfd723e19eda7d5c0e,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust05.tga,16428,4e6a03367c3282c939f1002f826c6549,7a44d215ad75268319abcef7cd2ac89dfc68cf7a4ff090cb354db287971764a4,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust06.tga,16428,daefb72301f19803058795c9d4eeb833,c1fba0eef7d968bdd1866fdc13495e0c3ef5f5171f6a6c70f4e73e7097dc6d52,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust07.tga,16428,8b8ccacbcdf9cdef9481cd9428760c44,b84e11ade33a67bce27c7c2dfcbcc569045e41609332f6f62ef12c675db7baa8,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust08.tga,16428,0def5b045f0e0ddabf002d68efb8d347,a1e37542342ecb0d7c482512b3f98195b1f17706a0f1579221fa78f3644d64ba,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust09.tga,16428,f89c4c80fb4d69d23c0755787d18f12b,48608dff4b30b2b25a5fa0c450f744c56e37f007f01b351018c82df7e7198ea4,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust10.tga,16428,44baf98408c4f629a79495ca88669bec,c2e1c7d5594681269641e08442334eee3d0da26b7c385036427dd33aa56ecedb,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust11.tga,16428,db0ad392bd51d1fe89ee050e7e9a430b,1bb9d5b5edd128e315bba2f24ed00be12e4df7ce8ca37f7c7d84ef666e8ac72c,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust12.tga,16428,0416996a4b5ba4c7a60f0456cf1c5a76,dfa05cbcf7d32b8057bf57fa0c7b05e24839205845e66ce06d612130a3f31376,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust13.tga,16428,9016d60dfcd7fa826edd07464a1cc1cb,5baa929913b5ce1a1cbec97f6cd9090dfa3d78ebc0e000d1ee6b8e0f45591255,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust14.tga,16428,deb81cd26d0d67043fd6338c691338f3,29c941490d668adb8cff8927ca5e6694847dffc4198739e88956356e6f2a3094,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust15.tga,16428,d10b07457cc172d05b8508b427e24187,f87addd5360edc1bd5822bcc2818dda27eb727054783b346ddbea26b78e82ff2,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust16.tga,16428,d75dfff45be2c47b5f3c115f9d78bfe2,90dee0bb7198e675772d81e30327531f8bbb535d299b8dd613466c44cd7235f7,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust17.tga,16428,bc83a41cb453dfe12d378ecb8c799d49,f053a13e73c8ce938938feea573141a0e22b50eeaec7f101f4636004f8ba554f,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust18.tga,16428,040c0932fd2705ea0a37e360c57b1cec,c26f3a040e1284a99e06b18953df40efbeabe1e0947783c797b363364ebb99f1,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust19.tga,16428,85e1680cea2bb20578e4f1aa19dfa67e,04915f82f986d72f6c999583b957bb735ba221890bec61aeeb1a16abb1d4352e,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust20.tga,16428,d5ac8f99b83fc92ba4e2f356c6816c21,1a27a58af21b14b8da96728ba62725626085f6b50399d99ff990fd2bcde3e725,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust21.tga,16428,ab1231ec264d2b93cfca0cadc52554f4,8c0e3d19126da509f7cdea23e98142b489479019095ae7cfc00024921523ad5f,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust22.tga,16428,f349cba69868d4da452fa37ec21f860e,fbdbef23579e743b027ce6177e3e15edd3df2794ae49bfc6144c81b777d395a0,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust23.tga,16428,79cb087169f45f2d2b121762b0175b5e,478d7d5e09a60624104647106b05c02d6831a68a7aa14590359d3d00fa02accc,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust24.tga,16428,64e7f0a3fafb344ff8772d2b47aabdb2,e19606c3677d773c9f39681bc0a03c49376292d7f0bc5ce340ccab19c1f09166,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust25.tga,16428,255ca010b8657f14c85bc72028472bf6,b29b4df4052bfde94ce82617f647c02e3864c1f730e9d91f880bb43bbb325f5e,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust26.tga,16428,e79bc7f009b31a48137b5e59fb704ffd,4fd4406232083d148b97cb1de257f5674023c5ecb92b4418291f3ab6667a8c69,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust27.tga,16428,4f7c2763c2f1b4ea4ef99c2bf6496404,becbb96d56fb5a22bc809c2100ee7beecab9470593feec4bf82354d81aeccbc5,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust28.tga,16428,9f399179b32616aebff3a1638973de15,2f0f5c933ff40b9c959c484a317a2e0568392fb2347e76bdbaac2e850e1de642,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust29.tga,16428,cc2998d2d0044e1762d794ed38930cc2,7437ed2c75b3f791372ed914c692e0cb36387d4586089a818c9f33be38a41c32,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust30.tga,16428,88da7064ccdee97387ee50c6c678ce7b,11a82a9b483dcf824e8e86997decc89286e6cc5af71624c1186a186aa5721a35,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Data/WaterPlane/caust31.tga,16428,418b9f65207e4d7321c5e87f98f3084d,6b49361ac4c0709a3dbf15e3ec537aa616e0c324bfa6bc490d28cee5c3fa7cd6,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +dbghelp.dll,163088,13fbc2e8b37ddf28181dd6d8081c2b8e,a29056a9810ff08c708505f1ac20d0263d5d894a223696e20217c0e9d132bf84,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +DrvMgt.dll,23552,cfac34e9b742612844204f42fe76baa4,1366302382f84813e3f6d097e8e401b17ac3887ca7bd9f71827763aa4fc23916,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +English.big,2774948,a12512c67dac5bcc81a2be26461001b5,218440f15bd2f718c6897f631eeacb8a42378679bcc0a6aa8a9cf83d0798f543,Generals,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +game.dat,5701632,a7cc739a1657edd542312a4db719b7ab,a2c697ba74f1ab224a72e0f175b0a8e924a5bf15b4d3c0d1a8a312e68c04b8dd,Generals,All,True,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Generals.dat,56,55645a20899cb8cb37449f3f989e6127,ef6a721d84a7cac5afaf7bfe36402ef07764560215f68b45d7e1f5d8c7ef5aab,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +generals.exe,57392,fd658a62512db722448f4924259d0a0a,e253361f457f2ec3290ccf4088aa5c4022fc4772a769fff5fb2fa8b9e5df842d,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Generals.ico,21630,29f24ae15cc2c4c1deff5a9904000637,e2f762b9279981c2021e891bd11677caf447e11ca3d039a5e0b9173418aceac6,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +generals.lcf,144,2438db32c55a1551d1daba2e9adc0f40,49ac9bf59939df878d2ad3a130bb2ae6ea6f3c6ada56d0bd35d5aa891853de0f,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +gensec.big,787464,ac9e5838f587d75f40b0c064cceeef05,99c6f200392488aeace0ecf7a0833c9426f068b698b5fb3e70ecd1b0d410913f,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +INI.big,7607479,ecdd6e48060398b207f70a6d40917e97,bff8d621088b25fd8b041c8acca020a020fabc66f972ab2bd131fc67d905a72c,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Install_Final.bmp,1440056,ff426b5b7fb72dcb3e0d38846f7ac128,05fe660e4794ce97752e15074ad61f0ef5508484a776be97bd1f0d46f4786baa,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +langdata.dat,25398,2e7d20210b21b5fe40e1bb44af63c1c2,b964985085af30ff170d25cdbf97a000db1f52d99e9e4cda293a7761cc5a2616,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +launcher.bmp,39608,4ad9b62f4ab1fababf35c616b6e7285f,4d61a1b74377b4a42353d17b10b72a1757f6da4efae3b79a355f38a500051922,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Launcher.txt,22690,9df9e806744c78e0d860c2cc0e6ec1f6,0065e33171eac7670d506b1c2b3c1bfdb5c4ab121fb3b5e465d3481376d37f13,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +maps.big,23554152,728bf83395aa64dce52d8d27b5f88a30,8a241df0c87ea47f6ad992984ea73dabf4f2ac5f96f8924f30bb0bf5fc4ac4d1,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +MSS/mssa3d.m3d,83456,e089ce52b0617a6530069f22e0bdba2a,41ccd5e30475ef7b40e68aa8c5c0ce18e804179fcaa77ba42e6ffa4f438d9a24,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +MSS/mssds3d.m3d,70656,85267776d45dbf5475c7d9882f08117c,a2926f4e2a094a99508c05adaf86c5710ad3cff8bbcf247821feb0e6977f547c,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +MSS/mssdsp.flt,93696,cb71b1791009eca618e9b1ad4baa4fa9,e035db7c2a4a2378156f096a1450faec425fd8b89bffb886f68c655480bfff52,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +MSS/mssdx7.m3d,80896,2727e2671482a55b2f1f16aa88d2780f,e6c928729db1d7c62d684962f4ecfd6bb039504897af53b72b2a66d32f1bc6b0,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +MSS/msseax.m3d,103424,788bd950efe89fa5166292bd6729fa62,62e0e34435b9705eedc73660e64564138d8276dcf7b08dfbaac05c592b67e6d3,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +MSS/mssmp3.asi,125952,189576dfe55af3b70db7e3e2312cd0fd,121be91fd21c80396cb5cc46c245d9b3f67a26f8cec4d0ebd03f17cd13508b0d,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +MSS/mssrsx.m3d,354816,7fae15b559eb91f491a5f75cfa103cd4,f983c72977f19fb7bdfeaec4db1ee1e169a18cc58499452a5bab9fa2447f68ce,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +MSS/msssoft.m3d,67072,bdc9ad58ade17dbd939522eee447416f,5dcbf188c30ae1ac6a3d5b7fac4a25e831c9a495683b044f5141c4bdbc83f607,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +MSS/mssvoice.asi,197120,3d5342edebe722748ace78c930f4d8a5,72bac1b0d0d3bfcc235a74c06c3fc62043f197a2bd8ebcf8a89652d78f23157b,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +mss32.dll,349696,6400e224b8b44ece59a992e6d8233719,441b290e7dc6334eb5023cd9b7937739298fdd66c104d4c96e5edcf642ae912d,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Music.big,158818808,5b65772e6097d206c0fe326452624637,c1e162b8a7575d98d9e20c7ef582e3edc96b35e3d071d821b01c5426d3c55450,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +P2XDLL.DLL,519168,f8e5e9d283c5f7ca528777ddbb5d6e48,15dad960f53ba3238564a10678b993ddbe9964b4c5033d905337e0bfb79039d2,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Patch.big,1462590,7ab5492ae04a8657feb4ec83aae80280,28dc194412f96dc1f66412430cf74f2d89ad0cdabf70d2c8d1179d8e51743494,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +patchget.dat,122880,13282bada64a35b59f9281ab73932ca0,ab1a8576ce19b00bad8988212d54a9f3da1b30c5ceed38807fed315670b3a252,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +patchw32.dll,185344,6ec517e866e476401755281837295579,0ec6e25234ad74489eb1890d4de57bb6140bb8196bdc4a5dcac90dd9d16eb2dd,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +SECDRV.SYS,12464,890cada2ab7acf53a5f9cce7515522a2,78f1de7b1f3fbae009fe818d5bf3c4e0f109c4c8dff87a385921575c133b4b25,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +shaders.big,1200,b587ef873d3f5cd475b156d4236c1e5e,b982d3a99c8fae32a6d07ab0994274c1754a0fb08f69646f96d698b3986fe2a5,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Speech.big,13479230,0866ad595a5098345653097dca1afda6,5106e92a91b1159fd861d5e4475beb6e7e05b0d5ac43d65520e02d89e39e33d4,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +SpeechEnglish.big,269104188,ead55e76d6b86944aed95b94f631c8d6,b48ede709de86437a9cff23bd27586a03f86e2b009e0773816af48496bb10ae2,Generals,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Terrain.big,48342856,9fa5a8c692d6122a20032f3a59a70d2c,4c203b31ccbf7f4a41ca0288d3e782a356a0f75f3315c05a83d7364e19f86f71,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Textures.big,333031108,90a934df85a1d628fc79f440068ce0d5,1303e92c57c9cf4e24bf85b342bd58799924565796f3a4aef65dd4a9967aad5b,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +W3D.big,184549391,3e1ddef647cf5b590d2290f797401b15,87727b698089cdc32bc378b1746bf315c2a920d5df33cace0d7085e027b67d36,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +Window.big,7962700,6681234d7a863f5f2841efe1b0e3b773,344f830ce00eabc247524b5e8b1305f10fe8c742a667e238d3a9c1c63fbe6479,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv +WorldBuilder.exe,6885376,b63f9648c5373b0868611931a88a7721,1b5c2c634c5b2f1c1ec53d49ae8a35da5d397c4566c42b406920cada0b7368d9,Generals,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv diff --git a/GenHub/GenHub.Core/Assets/Registries/ZeroHour-1.04.csv b/GenHub/GenHub.Core/Assets/Registries/ZeroHour-1.04.csv new file mode 100644 index 000000000..20d71ca5e --- /dev/null +++ b/GenHub/GenHub.Core/Assets/Registries/ZeroHour-1.04.csv @@ -0,0 +1,176 @@ +relativePath,size,md5,sha256,gameType,language,isRequired,metadata,downloadUrl +00000000.016,153720,614156f3dc3ada5d21a5f0cb60ce5bf1,a85138edc09cd8c51b337674999b9cea16ef1a25259c7f4f7810edb3add55a0b,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +00000000.256,308276,5d8160359b90e0dd13d191e87fd84010,6b4db9327a858fd6a8ee77362b7fc0b6457279a57d942d5143fb7a0230e0b7e3,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +AudioEnglishZH.big,58326522,5e6d3b22c4dc45b421417feb21a89427,85109b5cb4a5ef75c5fdd1fe1a66957d951f98f028e10e3729dddc2309402914,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +AudioZH.big,23965542,03edcec3e410dd2e6bfb99f92087ad39,6fbd05e43491bfd5f56c9250c3f9c30fc3061cd5867cb912c46486fc78f5e8c7,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +BINKW32.DLL,358963,e58a20c9e7b342d5ca1f5ba75f1d1108,892a51c4056efcb22297a3b44a3491e3f5888f28b08ed1b17030f24acffedb44,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCAttack_S.ani,1666,548a3180cc4fadcdf669ea455e6e1921,8e1a57bd031bc8565775f78a162eb0b2f81444d464466e57d43838a116b38ab2,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/sccattack.ani,6398,6d899deaefe2228081f28073a9485ade,fa13992a0603390b0c8fa4200cfca7ee9eb0744cb55b87f49aebee55e5711216,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCAttMov_S.ani,1670,3bd9ee4b858660bbf45fa393dc1c04c5,b9c38e1c1a9294b3afcd029162a79bbbfc285770b5e1ff82998a604755ad7e19,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCAttMov.ani,5644,f06b765c53fda761aebe4f8b4045cd08,c6b2ac92c9d8d8a15bd411dc3e3cf6e276b081cef20035ca82647f0168085714,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCCashHack.ani,7896,19efb6c96fb3b30adfcf6d8ad6fa7981,b41565b664457a00d87efbd24b5681155967620df5ae64a54d28aa9780279385,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCEnter_S.ani,2446,f48af1584768ade129a8a6c4f049b452,858479b74fafd998f1496b69edf2e44245a0c2f8bc8dc6c7ecb9b90d62c7370f,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCEnter.ani,2440,20f9bc7c814947b4cec1349c4bb8bfb7,037ab0691bebf9b4a985d194d0619e9fefa0330a07d4c85bb5047c26d10c5787,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCExit.ani,2436,acf7171dfbe1f72d64c9c08a6a190469,590d419191ec781d97fa7e375498eb99270eeccf8d9ee75bb5c3c67e9dcb620a,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCFriendly_S.ani,6358,5578eda24455b8416ac223d29b4ce475,488b83907f2b12c35d1e3d688a3564acd309d4f2afa1185cade157aee40fc238,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCFriendly.ani,6344,9f7a4f16f5cafbf36e865285ec360638,f7b424afb5575067fffe02cf1afdeb53f62f81ee85a12d581e2a76b5ab418652,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCGuard.ani,1616,1b24328b2926ca7bede732be68b4a4c1,fa8c51b348106c3ede587fc0a13deb63aa54a5db38ce8f582f03b761d57f6150,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCHeal.ani,2478,d12a7573375158375a33131339598d51,32997e09311a684687ab8a339d63d3f075eab98def7e8e0c47b4519b9763a695,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCHostile_S.ani,1674,aa715bed9e28d9dd5a13a56b8fec2e90,73f2fb4c8871afbaa49d6a5cbd34c53e4f29cead68f571d7aab5a5b406c893ea,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCHostile.ani,3270,bf03d453be48a42b218c084cbe19e578,78133602f6b1eceb6257895e9eaeadeb07ddbc9e969f6ac69b6bcbe16b5244ed,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCHostile2.ani,8634,f46b0d70e7c29ddb6b24a14dc053fb95,69a82c2f23d5608f039ce47cde2fe955ec1e4dc9971082ef2c24035f04d0738c,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCHostile3.ani,25792,92918392b8969da0d4562b5636df397c,b86c5be45dbf421172de0d12f4f1b237ce1844ad67ce614620490d60a5cbc5b5,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCKnifeAttack.ani,3966,eb68b3991a575c6ee2247c66b9ff01da,922ba292078be4b9f697450bcf68e23b6bcd9344f433b64641c856f15cc040a1,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCMove_S.ani,1664,3b33efc0b64e1b35f07afb8e098d5cf8,0a60c8f9b6da230767f10275ca97a57347d65a39468f953db122e0f93b07793c,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/sccmove.ani,5638,728cd67cd5a9c8c133f4a05d0a7424ea,edcc9a98a24e0e21bd89e45db2fcdf04c5d3ac359616db96ea6c33407df04c82,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCNoAction_S.ani,892,d9612607ce26ba18da3c98187ba8f60a,2cfa77a51eb15ab54359c13c27b926d7b0f948f2c91d4a1928d139d70ceacb02,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCNoAction.ani,886,cc4e179474a670631835d81725830882,116f51bd9073187b7b2fba0ce08bbe3417cd5daacec7b2af6b78a60afd07d6ff,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCNoBomb.ani,886,48237559686d7aaf721d2cb5109efa67,03e4cd887ab8283a8594b4ae54c0f73b159dce2ebf2df069923531de89be0d7a,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCNoEntry_S.ani,1682,121ffb04e7785ef03dba6de7930d5899,2b76be67e3d75d0cc428efb1563c08c0a6f717d95ffdeb1696c70ba446c92c09,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCNoEntry.ani,1676,bf2c1b618dff02c56bed38e7c69b19ba,ab8fed4bf8bf5bf6ff8e79a40bc89afe9c6bc7c13e2b1f5577506bb48d017803,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCNoKnife.ani,886,973eebf5e3893f3e1ce04ebab5f2b5eb,49d8a05655d9af4bf5c6ab3c7f15e2c86dc1037cc2c016c093d22eada35d75f0,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCOutrange.ani,16460,f18891ca6f6be7f2bc8feeeda196ac80,8539c4a441775dd7ccf5c1faf0cb79fdd5fb6c6797f7d7d3ca98768348429741,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCPlace.ani,1680,dc039f3410c790f3666ac7dc611ef801,a149ac8f358c82b6fd89e2e3fef32b6b1d11adcfd846c3560fd388d75c166e0b,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCPlaceBeacon.ani,7078,82c144ddda013cf670954944a79b2196,d32b4336cbe21101cb872fe9e650f0cad9c77f86fdc26286484448c3fc00b8a6,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/sccpointer.ani,884,54199240e4efefa204418c6c973221b2,ad3829b3e6262f8881ad2a3ddc90e7add848ca5b6bb8fda76facd6ea3a98df00,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCRallyPnt_S.ani,1668,1b3d007599e9307b758c28748321c10b,cfb1cd7baed30b94d5b47a659296738376cafeb4dc492dda6b2c9e5de534d1b9,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCRallyPnt.ani,4064,cec0a2074598b34bebe51d14f7783d31,9d15def280ae76cc3aff9f5ae61547f035b9841ed42e647d13d4cae596a2065d,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCRemoteChg.ani,9412,f8a2450fc0f82f28f584af001dbfbc30,d60b49ac06ae56b56fc03e6cbbf6d1bdbf0bc66eaacec621d7cd0caffd300d6d,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCRepair.ani,5640,a7a77357ec53203c1af9164acf6abff7,baaaa22fb2b331660321833659967d8d057cb50ab31cd2b927459ff0c548ecb6,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCResumeC.ani,17970,a5941a513b20dc2165e7ed9fd2cfdf12,cea73ea5cf0dc34d8299d42ec965eaf2c98a7a501ab1bd5599407f3af91f7e85,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/sccscroll0.ani,888,69a1fbc2773ec932f6a5a7fb37f7f12f,0560079fe4782ac89942eb45e94de7011eb1f4ebb39230890e4917d612bc5d01,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/sccscroll1.ani,888,6aacbae0874a6c9aa335e35947a8ebd0,c48c8691866637e0fa9fc98bb40e2d8240370b4cafd7b767c1595b58789eb06c,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/sccscroll2.ani,888,43f6019d566d0aa8759a42d0a5bec5cd,e62fe7da074898004ee7ef81626830fa94332b3a9d4fe8987e948cea0e2ac1f9,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/sccscroll3.ani,888,6f2e50e14ff2776eae76d961a4a5bfc7,f9c2a000c946a6f6bb420d9671c9ba21a9fc14821a5692a5f2e5b4755fdebb8d,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCScroll4.ani,888,a169748d79da826c10d7e5149c7fd41e,ec77136c9768ca3c50216ae92269aa5fae4f29197a13a355c6f92fcf53ecb279,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCScroll5.ani,888,f17a6fc61a7b944bf5eed66d8242d5ca,2ccb9140e23faf6cc98560e545a54b83efce54dd1bfb01ea84119732d0f04433,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCScroll6.ani,888,33618b4ed32fcec63df6fea44b98bac8,46186f3e8e9243edda6e0baf00e0a0fcaaf5e5d9b4d4531bf13393fdda67acc9,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCScroll7.ani,888,eaae99658bb1a5d14ea89ae89aaf32e0,00904ceec2de7e828c1a5fbe224b1ebf6cb19543f3d24381b64d4329a510e46b,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCSDIUplink.ani,16460,1443e1a4d8c5665e6a88288e1b03e7b0,11de6fad707df80023f91c011569de86705b2770cd1df5bb0394481bf5794fb5,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCSelect.ani,6388,21d58f3402a7f33ca5422fc865d2d602,42840130f31fa90c880ba2855fecff0c4a1cd3e903e6eea862ab95b8dc876427,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCSell.ani,1672,21f1c178ab89de9b364cc8709ad0dce8,3a9dcadef511ad9a2dd1ca9b31eac33a7aa58b652554cd1d18c628c87fab1a4d,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCSniper.ani,15636,29c81451b838097f4baed9b00c3f2873,258ce022aa05087bd0772484d3aceff49b9beb532cd8a8d524f9eb97be57de80,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCSpyDrone.ani,1632,b30aaf4f9ef73831165b1e280050da14,6a4302a4a897d6ded38ee0216bc45975406d40a73270eed12d13cb73c868ba34,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCStop.ani,882,28eb1d384c4ff53a0b44352fd62455b3,ebd5c2968465f5e3898d38d3a737f2bda387be6be6ee246712a5081d703150a1,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCTimedChg.ani,8634,c84d13defbb211c95a8015cfc743320c,727c0863496e777929a86425fcc849e7041b77e3209151f566fe233bf8a2bce6,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCTNTAttack.ani,4744,23569a40a3dbe637a6fdd839d25f58ec,e5c03a381c120e26d04c5c7495c88bf43224f51d89e9ca9d7dea84a5903285fd,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCWaypoint_S.ani,1682,b7566c5d6d3bc8e604f6c07b9dedf4ff,1c585e4c98e05ad6fa916f6d496dcac8673ee651a8cd6743319850e3d14bd4bc,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Cursors/SCCWaypoint.ani,1676,c76b8d01c707588e02aea0c9ae052c54,6335fdebaa2ce98e17fec156d75ff9b089ab0f42b70170e5f18383abc73f1e88,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_AirGen_000.bik,346496,13ea362d7b47cd7247af5e433e31f16b,2d40193202eb94c4b61d504541e914453c72734ab69777f8805c900607d9b44e,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_AirGen_inv_000.bik,151536,e72d42f551369f6f827009696642e7e2,9eaeb8644884408b0b12f4f635939d013baea7d17813794a8760c81bab10e375,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_BossGen_000.bik,346964,06e4f22ea5b688b3b8704dd9a2224484,2c5b1fc04a58ab23a140ef5f604370200754db649726fb1f07ed51fd1e34f91a,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_BossGen_inv_000.bik,346988,26a765a7dbe257ff065b6a1db6c215a9,113b95f527ab763bb48d598095c0dd570dff925f097ea231d3f18b696d06c620,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_DemolGen_000.bik,346204,79f69494f98c180251dc20d4942d3693,43bff2a3b1b88e986038e497080f153a2aa3cfa8ca858bd4c179433ea71a9820,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_DemolGen_inv_000.bik,346196,50f505f6a145b77b922c1ce1fc121fec,787436021a38fc4390ef1d9f4672eb05cc5d1da07df81f4989f8bea0289565c8,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_InfantryGen_000.bik,346660,909cd4a92955a09cfa5a5d757986cba6,c5dec5ffc859340b06b97bc6e3f50ed48d3aee4022808dae26f52dd32566ce25,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_InfantryGen_inv_000.bik,346644,beb578d242b377c7ad85772888c5f3e6,f9c6a21d40ecce522b25e31e3759b75c5d0b2aa9654e89cb922b8109b1ccd259,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_LaserGen_000.bik,346720,def7d95800704d912857326956621e34,52c33f472f294b243051d7a8f7e16f84256ef5f83e669ecbd63d6e779cf80289,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_LaserGen_inv_000.bik,152152,c6b2fd8c8584e726eb22c83ce47f0bbf,4db8ef4958b397ce3ff6d70bd5559fe52dcae55e5cae11ec383ba678c166a134,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_NukeGen_000.bik,346788,0980302323d3e57b0e8a10a13b2d0484,e42916ce2dbc58c626e8faf4d71ed870d000e0c31e3cacca3eed9a213bf5ccfa,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_NukeGen_inv_000.bik,346844,8091a036c4013b40914e3e353ec3066b,c2221846962aa329fe184867effd9b58a755b4ef088c799235ce2a6cfeb5a92d,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_StealthGen_000.bik,346780,a2805a2af305c3dd696034ec3092757e,b98d23db78e1c5fcb4a4c821210e0209aba982f567c5dd1169d75138df8b0ad8,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_StealthGen_inv_000.bik,346576,95f63b145cfbea47c8b2b43dcc5e6e6c,13367541c8eef647f944b689027de3b9e05527fb0c01fea3db76442e96c5594b,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_SuperGen_000.bik,346716,c41c7c55a307b7348816c65ab8615318,48f5c1b8e1a4b8385742b9f4186ef3245d081c6d81a57309f9d6dd787adae5e2,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_SuperGen_inv_000.bik,346760,99f88fab06fbf9450a78a9d769c52a12,4c532ac82135eb891aace70cbad07de9e0dbb8b829b4c855122cf0024fd49c06,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_TankGen_000.bik,346692,fa21d37a99b75bd6f05999bf8b54e075,d38eda028458e75806d52aad0d8d91a6f9a5de9ecd61ddb41914be89aa06c9ee,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_TankGen_inv_000.bik,346824,854ca0f56855864f577bdd0e32bf0eb9,39cd6967d21b3480fbd87750052ffe9305adde460a2c66cfdc199b4adec1ac8b,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_ThraxGen_000.bik,346192,73cb03cab7bd8e822d46f61250865d77,78995480ccf11157e5a65fb553371683de8b6a34dbba7cc756e55fd1bdf39311,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/Comp_ThraxGen_inv_000.bik,346212,c52f4f03b949d321e126104500167a3a,f33251a5fa071bfc0326b4cbe7ba5c423c8de886f98dc7fe22659e247ed99725,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/EA_LOGO.BIK,1480980,adbd4c3a5abce41bb190430acb4ff29e,f8beb9cbc902cdd90f94563df1246d4df5769f8335a2dcc1f85916d446e3a8c0,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/EA_LOGO640.BIK,1480980,adbd4c3a5abce41bb190430acb4ff29e,f8beb9cbc902cdd90f94563df1246d4df5769f8335a2dcc1f85916d446e3a8c0,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/MD_China01_0.bik,18455164,58b447f059cc41672f4463533cedf269,1c46249e9145606f09346dff88616e0ac95d3d40d193d53e134594e4693553b5,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/MD_China02_0.bik,13208784,7ca5f9a4e00e740d8bafbf8a9f7b5a79,09696f151d47db3c8f99fa1f35e862f803e6b1f7b77613a32fc1d674ae589e62,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/MD_China03_0.bik,14858580,abe073e844cda10163b6b3c8f15bcd80,192ca2cb20e4d7f7b37234a61146854dbae50dedf3405f2bfa89c0d304239c2c,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/MD_China04_0.bik,16527788,78e2d3e6562d093e2bb3e906f2cd0a45,43f130f39cded46e84b49bff079f791e8e3566e95037547618da0cb9bb89de7c,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/MD_China05_0.bik,15937612,8de7f4742ea9b8a7e124a664dff9898e,2341a9531021808560fdac241d14ecc2a678e86c013af74161a484cd5a05713c,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/MD_GLA01_0.bik,15523480,40e2374e0e7b19d54ce433ccbb3b2dea,da4e5a0d5aad89aed9b0643d66c6533fac1e127a3f20e37595223797603f2d57,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/MD_GLA02_0.bik,13984196,7b94d3111786099a03ada61269c6e40a,55ab92bf9ed77be8c6ad7c167634714634d2b5ba156e5a276d6cc3fb181a91f6,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/MD_GLA03_0.bik,15558184,ebcb01cfbbd41687a93c5192dcdbdad6,aef36d070fa054a9a595cb3579c93131c2a7f936208b817313d4170c0903665c,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/MD_GLA04_0.bik,13278492,e1e64e5524e143db38565f92ba95a4e6,dc2aec472a42a85d0307855cfb53e374f81f814db1cc25ae005d3d148874ecc6,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/MD_GLA05_0.bik,18456064,609304c5ac3e35097c11c45d99a5d391,90a51d388e7662488b236833382e342a5c77db218ef02709ddb6a7f21c178a3d,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/MD_USA01_0.bik,23077588,ab98eb72484f7380cba94368394a08fc,81810d0e1505611aad402c6ba02e8f18f1224fa5fa7bde40cf769a5d15f3de1d,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/MD_USA02_0.bik,15983780,d63e78fd37fc7b0788a40fedc537c625,01f8b53b61aa9009c4a8b9092ffdac874c2c280023e2a9311fd358e5b455bf4d,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/MD_USA03_0.bik,13706924,921b27dd9f2deb9317a377aa9d3d7f26,5471d66f13206ac39bd435d53a2c924eb4cf38af9e06a22f4ca17e142f885173,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/MD_USA04_0.bik,16062316,0534b9b0ae2407cb501e3c0696b489ea,1a46805c6a117ffc1d14843d5073f5b246924d413cdbd6448141e1b1ed1941ea,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/MD_USA05_0.bik,20291044,a6e969dd759dea02a3b7609d1a5cef38,97abc54134f15db7359d9509dfbde9e6c9d6382edc5979e66da8dc763119f74c,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/sizzle_review.bik,23891876,4238a81d8ccbaf8324bdf1994ddeade7,66a52a06953255390ca584c385e1f5524864ee48d206713577c172a5aacf1ab6,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/English/Movies/sizzle_review640.bik,17220424,e464f33ae298e75d3439756819786136,01ba21833f311e39660b704d9fbe88cc4cc60c641d1f0a699035647c8007c3f5,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Movies/GC_Background.bik,149700,bd8fb9e5d3982d9c86c817512bcf17eb,fd997a3b763c8a6e3a5655eaaa9d105b88471487ee639c71cff23bc3d62e8acb,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Movies/VS_small.bik,310128,6b56f1c09cdba7a363c66873ba385cb4,f0e4c07b1041dd535298a42eb64b8f734560e33d76902f340d4e820704baf993,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Scripts/MultiplayerScripts.scb,10381,2938ee364c4fc60f608d223046ae20f6,86d6bd295dd56dc17c6c1289f9a530506c755b0dbc3e868448d93dd468738ab6,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Scripts/Scripts.ini,68743,e37952e22da90832511f99dbcea732f5,2c72d91a77929fd5f2b6f590b4af5ef1c099379bf9c6e39b0f249022a54b7554,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/Scripts/SkirmishScripts.scb,2272629,0635cfc535efd6f0a09495326bf5d8e5,8f93862b751f289b052206b87170cc840044cb66660fbf6ae30d5782c1d73776,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust00.tga,16428,9f2d9b6133b6e4d78b409ca8303cab85,d4b2fa073a52734658fed6b780de379fe866b956aec8c95525457232e7d1b636,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust01.tga,16428,5b257180aef56e05b007be93eb64e527,67116bb0c18491cf403d5706e4d6c005d43ea77e034cb889d60465501d504156,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust02.tga,16428,6802068b10f1f8ac64646d54124b7e96,2718869ffb24f789a912b5fe0dcd94067db1a06aa144e1a1ac0d81cd6b5c8fbe,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust03.tga,16428,af760ffba0eff9fe49ace5e8e82de122,6e7c3733f937534800dfc1ac4632c34dd4c873d6d9e55f13dcedf738462d470d,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust04.tga,16428,3068c2e091b0c52c93b0d83d18b04ea3,7123520dc1d22b0279e24f50f4dd5c62d54b2185883acadfd723e19eda7d5c0e,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust05.tga,16428,4e6a03367c3282c939f1002f826c6549,7a44d215ad75268319abcef7cd2ac89dfc68cf7a4ff090cb354db287971764a4,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust06.tga,16428,daefb72301f19803058795c9d4eeb833,c1fba0eef7d968bdd1866fdc13495e0c3ef5f5171f6a6c70f4e73e7097dc6d52,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust07.tga,16428,8b8ccacbcdf9cdef9481cd9428760c44,b84e11ade33a67bce27c7c2dfcbcc569045e41609332f6f62ef12c675db7baa8,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust08.tga,16428,0def5b045f0e0ddabf002d68efb8d347,a1e37542342ecb0d7c482512b3f98195b1f17706a0f1579221fa78f3644d64ba,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust09.tga,16428,f89c4c80fb4d69d23c0755787d18f12b,48608dff4b30b2b25a5fa0c450f744c56e37f007f01b351018c82df7e7198ea4,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust10.tga,16428,44baf98408c4f629a79495ca88669bec,c2e1c7d5594681269641e08442334eee3d0da26b7c385036427dd33aa56ecedb,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust11.tga,16428,db0ad392bd51d1fe89ee050e7e9a430b,1bb9d5b5edd128e315bba2f24ed00be12e4df7ce8ca37f7c7d84ef666e8ac72c,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust12.tga,16428,0416996a4b5ba4c7a60f0456cf1c5a76,dfa05cbcf7d32b8057bf57fa0c7b05e24839205845e66ce06d612130a3f31376,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust13.tga,16428,9016d60dfcd7fa826edd07464a1cc1cb,5baa929913b5ce1a1cbec97f6cd9090dfa3d78ebc0e000d1ee6b8e0f45591255,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust14.tga,16428,deb81cd26d0d67043fd6338c691338f3,29c941490d668adb8cff8927ca5e6694847dffc4198739e88956356e6f2a3094,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust15.tga,16428,d10b07457cc172d05b8508b427e24187,f87addd5360edc1bd5822bcc2818dda27eb727054783b346ddbea26b78e82ff2,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust16.tga,16428,d75dfff45be2c47b5f3c115f9d78bfe2,90dee0bb7198e675772d81e30327531f8bbb535d299b8dd613466c44cd7235f7,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust17.tga,16428,bc83a41cb453dfe12d378ecb8c799d49,f053a13e73c8ce938938feea573141a0e22b50eeaec7f101f4636004f8ba554f,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust18.tga,16428,040c0932fd2705ea0a37e360c57b1cec,c26f3a040e1284a99e06b18953df40efbeabe1e0947783c797b363364ebb99f1,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust19.tga,16428,85e1680cea2bb20578e4f1aa19dfa67e,04915f82f986d72f6c999583b957bb735ba221890bec61aeeb1a16abb1d4352e,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust20.tga,16428,d5ac8f99b83fc92ba4e2f356c6816c21,1a27a58af21b14b8da96728ba62725626085f6b50399d99ff990fd2bcde3e725,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust21.tga,16428,ab1231ec264d2b93cfca0cadc52554f4,8c0e3d19126da509f7cdea23e98142b489479019095ae7cfc00024921523ad5f,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust22.tga,16428,f349cba69868d4da452fa37ec21f860e,fbdbef23579e743b027ce6177e3e15edd3df2794ae49bfc6144c81b777d395a0,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust23.tga,16428,79cb087169f45f2d2b121762b0175b5e,478d7d5e09a60624104647106b05c02d6831a68a7aa14590359d3d00fa02accc,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust24.tga,16428,64e7f0a3fafb344ff8772d2b47aabdb2,e19606c3677d773c9f39681bc0a03c49376292d7f0bc5ce340ccab19c1f09166,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust25.tga,16428,255ca010b8657f14c85bc72028472bf6,b29b4df4052bfde94ce82617f647c02e3864c1f730e9d91f880bb43bbb325f5e,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust26.tga,16428,e79bc7f009b31a48137b5e59fb704ffd,4fd4406232083d148b97cb1de257f5674023c5ecb92b4418291f3ab6667a8c69,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust27.tga,16428,4f7c2763c2f1b4ea4ef99c2bf6496404,becbb96d56fb5a22bc809c2100ee7beecab9470593feec4bf82354d81aeccbc5,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust28.tga,16428,9f399179b32616aebff3a1638973de15,2f0f5c933ff40b9c959c484a317a2e0568392fb2347e76bdbaac2e850e1de642,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust29.tga,16428,cc2998d2d0044e1762d794ed38930cc2,7437ed2c75b3f791372ed914c692e0cb36387d4586089a818c9f33be38a41c32,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust30.tga,16428,88da7064ccdee97387ee50c6c678ce7b,11a82a9b483dcf824e8e86997decc89286e6cc5af71624c1186a186aa5721a35,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Data/WaterPlane/caust31.tga,16428,418b9f65207e4d7321c5e87f98f3084d,6b49361ac4c0709a3dbf15e3ec537aa616e0c324bfa6bc490d28cee5c3fa7cd6,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +dbghelp.dll,163088,13fbc2e8b37ddf28181dd6d8081c2b8e,a29056a9810ff08c708505f1ac20d0263d5d894a223696e20217c0e9d132bf84,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +DrvMgt.dll,41472,b725c0fc7139ba5e16759cffd44c8944,cbae2ee6166028bcd26768e0ab375e29b007b06208291347cd80c0e676ca4570,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +EnglishZH.big,80472928,b88cc553608289160da1cc7af4829a82,d3904216ac210a363d9e0e46980c6ef70232046ba713ea137e8a87cbd57a32d6,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +game.dat,6483968,0fafcfa2cfbcff3c5ed5b209c306d64d,3ca248389ab7b562a1f7e99875af4b8282a5b3e459945e3b1b9a0fdbacbd282c,ZeroHour,All,True,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Generals.dat,56,bec512bdd269c5541c20f96c7cb1b930,2fb113e18fa5ff72885c5c9a94d95b3ce739088cea95582096b93c9ff3a0b6e4,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +generals.exe,6480431,328413da608c14b7f402ec61a0067956,ad4ef0c12ab41d6534a3ac0ee1364e4c1be93eaad6f2c5bf559869abd642594c,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Generals.ico,21630,1e0020da079a9aaf973a0cd622ff4bb7,dabd138c378c1dea55f3019420682789a68f4fc515cfa37c6cd665d8e413e504,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +generals.lcf,267,9648766e3d377cc7f544fab225c3da15,9b30cc82856514f1af345701f317b84481a6fa877751e7cd71fd5e60c462a790,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +GeneralsZH.ico,21630,1e0020da079a9aaf973a0cd622ff4bb7,dabd138c378c1dea55f3019420682789a68f4fc515cfa37c6cd665d8e413e504,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +GensecZH.big,787464,c375e701ef869d86e936f01df365c49f,ac2aaf5536a2f748dc99178aa431280f26d9d85179e6c2abedf5adfabc971b15,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +INIZH.big,18764687,aeec332104db082612d162ec41913986,1a6d41a7a2cb31e67ad2f868aca9264ad069c275e0074f8a0d970a336071e9a0,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Install_Final.bmp,1440056,3ae2a6b7a00941e85d979c5842ed8d95,d2bad77cdec7269346dbf2b7c2c06505f220670352bddb242f6a839f0f1f2401,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +langdata.dat,25398,2e7d20210b21b5fe40e1bb44af63c1c2,b964985085af30ff170d25cdbf97a000db1f52d99e9e4cda293a7761cc5a2616,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +launcher.bmp,39604,bb58f29acaeee51836e0261f03ebd576,9c2c1a24c2972f239adf53f96aa53e4e3288bd37d42531fd4d37d39ab84b2df6,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Launcher.txt,13028,ddd7d6df5ad443d3a4bebacf16b225df,8aadc18dc84587cdea6dca1b75a71be3836b0a3dd2259d39c4ff38f57e91d551,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +MapsZH.big,39749312,ba7e68e1c67416fa651f74a3f099245a,35cc8947f34f363d69f5045b9ac65f6ee164b8d5a382a1f7af213dbc2a744b96,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +MSS/mssa3d.m3d,83456,e089ce52b0617a6530069f22e0bdba2a,41ccd5e30475ef7b40e68aa8c5c0ce18e804179fcaa77ba42e6ffa4f438d9a24,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +MSS/mssds3d.m3d,70656,85267776d45dbf5475c7d9882f08117c,a2926f4e2a094a99508c05adaf86c5710ad3cff8bbcf247821feb0e6977f547c,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +MSS/mssdsp.flt,93696,cb71b1791009eca618e9b1ad4baa4fa9,e035db7c2a4a2378156f096a1450faec425fd8b89bffb886f68c655480bfff52,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +MSS/mssdx7.m3d,80896,2727e2671482a55b2f1f16aa88d2780f,e6c928729db1d7c62d684962f4ecfd6bb039504897af53b72b2a66d32f1bc6b0,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +MSS/msseax.m3d,103424,788bd950efe89fa5166292bd6729fa62,62e0e34435b9705eedc73660e64564138d8276dcf7b08dfbaac05c592b67e6d3,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +MSS/mssmp3.asi,125952,189576dfe55af3b70db7e3e2312cd0fd,121be91fd21c80396cb5cc46c245d9b3f67a26f8cec4d0ebd03f17cd13508b0d,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +MSS/mssrsx.m3d,354816,7fae15b559eb91f491a5f75cfa103cd4,f983c72977f19fb7bdfeaec4db1ee1e169a18cc58499452a5bab9fa2447f68ce,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +MSS/msssoft.m3d,67072,bdc9ad58ade17dbd939522eee447416f,5dcbf188c30ae1ac6a3d5b7fac4a25e831c9a495683b044f5141c4bdbc83f607,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +MSS/mssvoice.asi,197120,3d5342edebe722748ace78c930f4d8a5,72bac1b0d0d3bfcc235a74c06c3fc62043f197a2bd8ebcf8a89652d78f23157b,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +mss32.dll,349696,6400e224b8b44ece59a992e6d8233719,441b290e7dc6334eb5023cd9b7937739298fdd66c104d4c96e5edcf642ae912d,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +Music.big,786724,54d9b5b40a97e9770670ec5a9e0cafad,3f2af9c6dcc2b35852556bdc020c95e43c2f43127c50dbff018e12a2bed6116f,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +MusicZH.big,34741916,17c178f302bba43ca5c66efa7e14efab,5aa7b8408e5cf61fd53633f6e6310c76d79e74d75dd3286b6e9b9c718d9c5b5e,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +P2XDLL.DLL,519168,f8e5e9d283c5f7ca528777ddbb5d6e48,15dad960f53ba3238564a10678b993ddbe9964b4c5033d905337e0bfb79039d2,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +patchget.dat,122880,cb197b8fa2ba2bde0eb382f862d8e3d5,fd84c2ce09d574653ade6aae0081b3ea033b230e5e841d6c45fd98d10d47a9f7,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +patchw32.dll,185344,6ec517e866e476401755281837295579,0ec6e25234ad74489eb1890d4de57bb6140bb8196bdc4a5dcac90dd9d16eb2dd,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +PatchZH.big,118822,c190bbaa94d8f940259b6bdc9d611deb,450276fbabd19f79dc0143f70fe755e44a99b22c552bc00c5854fc810e615722,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +SECDRV.SYS,12400,ba0d892d2f786bcebdf03b0a252b47f3,4ed103bd45ece4d2b6029c36d0e209c8a6f1c34e0f72b01553742773cb1f43a1,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +ShadersZH.big,996,299c959957eac0d8348ad96479ea00c8,6246a6906f93261669c8c19e021c8d3484a6f7449bd5e253c8e998c1a9d31d6e,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +SpeechEnglishZH.big,254275694,42dece807234bacad9c58328e37af0ce,5b3c8b1819b1ddfeaf6045a4a5dfb9cb47ad16a508dbf17c317e11b9d310a14f,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +SpeechZH.big,6174552,f01b53214e75950749c1e1e0d1dfa105,4498097a3c294bc638e72e09e967bb417b0c9b171b4eee95e2c6c0bffef68fa2,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +TerrainZH.big,8660432,cd7d1e946232307d7b1c089e3af8708e,501ef71d12e7a1398800231f2526d47e8e583607f843a2ebcb0c622e26fc73b3,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +TexturesZH.big,222753036,8be93eafc51909aec552827000a940f1,aa975400abd70e45e13eacf4ab22505ea322941dfc492097c68245cd35d4b791,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +W3DEnglishZH.big,2696872,31d49330fb96abd8efadb7f713c8e238,f5d164d1e294f26744e2b87ecfbe13e68affaf5ceb4cf12f43005beb8f54defd,ZeroHour,EN,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +W3DZH.big,189741064,39a344cef260aeb608c49b0739bfafd4,e308c3aeb49d023ffd98b50400f24643a782a68ac4d2e53171c17fc45d73fdc6,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +WindowZH.big,8493653,11ba0050700bd5f91a146acb8c0293bb,68b519f28d012cd297fae2663d431a4b0e6e416aa7bb1a57a79e5500ebcc14b4,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv +WorldBuilder.exe,10604603,1b5e7d5779ceb561e4d69b17a42d38e1,2ce0541cf2510713b7e98873d1f0fac0301cdb770df0d95bbfabd55155a96ddd,ZeroHour,All,False,"{""category"":""other""}",https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv diff --git a/GenHub/GenHub.Core/Assets/Registries/index.json b/GenHub/GenHub.Core/Assets/Registries/index.json new file mode 100644 index 000000000..7de03bcff --- /dev/null +++ b/GenHub/GenHub.Core/Assets/Registries/index.json @@ -0,0 +1,39 @@ +{ + "version": "1.0.0", + "lastUpdated": "2026-08-30T17:40:00Z", + "description": "Index of CSV registries for Command & Conquer Generals and Zero Hour validation", + "registries": [ + { + "id": "generals-1.08", + "gameType": "Generals", + "version": "1.08", + "url": "https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv", + "fileCount": 164, + "totalSizeBytes": 48166, + "languages": ["All", "EN", "DE", "FR", "ES", "IT", "KO", "PL", "PT-BR", "ZH-CN", "ZH-TW"], + "checksum": { + "md5": "41e3f06a608156eaea960d432d6be682", + "sha256": "97c72beab9b92918ccf2629cf104034007337873783f2f7f03855e9857fa6267" + }, + "generatedAt": "2025-09-17T09:15:00Z", + "generatorVersion": "1.0.0", + "isActive": true + }, + { + "id": "zerohour-1.04", + "gameType": "ZeroHour", + "version": "1.04", + "url": "https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv", + "fileCount": 175, + "totalSizeBytes": 51722, + "languages": ["All", "EN", "DE", "FR", "ES", "IT", "KO", "PL", "PT-BR", "ZH-CN", "ZH-TW"], + "checksum": { + "md5": "f2dc5255bc48636449e146666fb1b53a", + "sha256": "41bd40d8f477eeba73c5c7543a902542394034d995c8efc27467d5938b9836c6" + }, + "generatedAt": "2025-09-17T09:20:00Z", + "generatorVersion": "1.0.0", + "isActive": true + } + ] +} \ No newline at end of file diff --git a/GenHub/GenHub.Core/GenHub.Core.csproj b/GenHub/GenHub.Core/GenHub.Core.csproj index 2dd9fe5dc..bbd7d8006 100644 --- a/GenHub/GenHub.Core/GenHub.Core.csproj +++ b/GenHub/GenHub.Core/GenHub.Core.csproj @@ -1,4 +1,4 @@ - + net8.0 enable @@ -18,5 +18,7 @@ + + diff --git a/GenHub/GenHub.Core/Interfaces/Manifest/IContentManifestBuilder.cs b/GenHub/GenHub.Core/Interfaces/Manifest/IContentManifestBuilder.cs index 58985d1e0..ec01a5563 100644 --- a/GenHub/GenHub.Core/Interfaces/Manifest/IContentManifestBuilder.cs +++ b/GenHub/GenHub.Core/Interfaces/Manifest/IContentManifestBuilder.cs @@ -167,8 +167,18 @@ IContentManifestBuilder AddDependency( /// The source path of the file in the game installation. /// Whether the file is executable. /// File permissions. + /// Optional pre-computed SHA256 content hash. + /// Optional file size in bytes. + /// Whether the file is required. /// A task that yields the instance for chaining upon completion. - Task AddGameInstallationFileAsync(string relativePath, string sourcePath, bool isExecutable = false, FilePermissions? permissions = null); + Task AddGameInstallationFileAsync( + string relativePath, + string sourcePath, + bool isExecutable = false, + FilePermissions? permissions = null, + string? hash = null, + long? size = null, + bool isRequired = true); /// /// Adds a content-addressable file from the CAS system. diff --git a/GenHub/GenHub.Core/Interfaces/Manifest/IManifestGenerationService.cs b/GenHub/GenHub.Core/Interfaces/Manifest/IManifestGenerationService.cs index 280d43697..f15f6a9cd 100644 --- a/GenHub/GenHub.Core/Interfaces/Manifest/IManifestGenerationService.cs +++ b/GenHub/GenHub.Core/Interfaces/Manifest/IManifestGenerationService.cs @@ -15,8 +15,14 @@ public interface IManifestGenerationService /// The game type (Generals, ZeroHour). /// The installation type (Steam, EaApp). /// The manifest version (e.g., "1.08", "1.04", or integer like 0, 1, 2). If null, defaults to 0. + /// Optional explicit language code (e.g., "EN", "DE"). If null, language is detected automatically. /// A that returns a configured manifest builder. - Task CreateGameInstallationManifestAsync(string gameInstallationPath, GameType gameType, GameInstallationType installationType, string? manifestVersion = null); + Task CreateGameInstallationManifestAsync( + string gameInstallationPath, + GameType gameType, + GameInstallationType installationType, + string? manifestVersion = null, + string? language = null); /// /// Creates a manifest builder for a game installation with integer version. @@ -25,8 +31,14 @@ public interface IManifestGenerationService /// The game type (Generals, ZeroHour). /// The installation type (Steam, EaApp). /// The manifest version (e.g., 1, 2, 20). Defaults to 0 for first version. + /// Optional explicit language code (e.g., "EN", "DE"). If null, language is detected automatically. /// A that returns a configured manifest builder. - Task CreateGameInstallationManifestAsync(string gameInstallationPath, GameType gameType, GameInstallationType installationType, int manifestVersion = 0); + Task CreateGameInstallationManifestAsync( + string gameInstallationPath, + GameType gameType, + GameInstallationType installationType, + int manifestVersion = 0, + string? language = null); /// /// Creates a manifest builder for any content type (mod, patch, addon, etc). diff --git a/GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Manifest/ManifestGenerationServiceTests.cs b/GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Manifest/ManifestGenerationServiceTests.cs index 094c1c886..95a3b2d65 100644 --- a/GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Manifest/ManifestGenerationServiceTests.cs +++ b/GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Manifest/ManifestGenerationServiceTests.cs @@ -260,7 +260,7 @@ public async Task CreateGameInstallationManifestAsync_UsesCsvWhenAvailableAsync( var installationPath = Path.Combine(_tempDirectory, "GeneralsInstall"); Directory.CreateDirectory(installationPath); - // Create some files that are in the generals.csv + // Create some files that are in the Generals registry await File.WriteAllTextAsync(Path.Combine(installationPath, "generals.exe"), "dummy"); await File.WriteAllTextAsync(Path.Combine(installationPath, "AudioEnglish.big"), "dummy"); @@ -275,6 +275,167 @@ public async Task CreateGameInstallationManifestAsync_UsesCsvWhenAvailableAsync( Assert.Contains(manifest.Files, f => f.RelativePath == "AudioEnglish.big"); } + /// + /// Tests that CreateGameInstallationManifestAsync generates authoritative manifest for Generals with SHA256 hashes and proper source type. + /// + /// A representing the asynchronous test operation. + [Fact] + public async Task CreateGameInstallationManifestAsync_Generals_PopulatesAuthoritativeMetadataAsync() + { + // Arrange + var installationPath = Path.Combine(_tempDirectory, "GeneralsAuthoritative"); + Directory.CreateDirectory(installationPath); + + await File.WriteAllTextAsync(Path.Combine(installationPath, "generals.exe"), "executable binary"); + await File.WriteAllTextAsync(Path.Combine(installationPath, "binkw32.dll"), "bink dll"); + await File.WriteAllTextAsync(Path.Combine(installationPath, "Audio.big"), "audio archive"); + + // Act + var builder = await _service.CreateGameInstallationManifestAsync( + installationPath, GameType.Generals, GameInstallationType.Steam, "1.08", "EN"); + var manifest = builder.Build(); + + // Assert + Assert.NotNull(manifest); + var exeFile = manifest.Files.FirstOrDefault(f => f.RelativePath == "generals.exe"); + Assert.NotNull(exeFile); + Assert.True(exeFile.IsExecutable); + Assert.Equal(GenHub.Core.Models.Enums.ContentSourceType.GameInstallation, exeFile.SourceType); + Assert.Equal("hash_generals.exe", exeFile.Hash); + + var dllFile = manifest.Files.FirstOrDefault(f => f.RelativePath.Equals("binkw32.dll", StringComparison.OrdinalIgnoreCase)); + Assert.NotNull(dllFile); + Assert.False(dllFile.IsExecutable); + Assert.Equal("hash_binkw32.dll", dllFile.Hash); + } + + /// + /// Tests that CreateGameInstallationManifestAsync generates authoritative manifest for Zero Hour. + /// + /// A representing the asynchronous test operation. + [Fact] + public async Task CreateGameInstallationManifestAsync_ZeroHour_ResolvesAuthoritativeFilesAsync() + { + // Arrange + var installationPath = Path.Combine(_tempDirectory, "ZeroHourInstall"); + Directory.CreateDirectory(installationPath); + + await File.WriteAllTextAsync(Path.Combine(installationPath, "generals.exe"), "zh exe"); + await File.WriteAllTextAsync(Path.Combine(installationPath, "AudioZH.big"), "zh audio"); + await File.WriteAllTextAsync(Path.Combine(installationPath, "SpeechZH.big"), "zh speech"); + + // Act + var builder = await _service.CreateGameInstallationManifestAsync( + installationPath, GameType.ZeroHour, GameInstallationType.EaApp, "1.04", "EN"); + var manifest = builder.Build(); + + // Assert + Assert.NotNull(manifest); + Assert.Contains(manifest.Files, f => f.RelativePath == "generals.exe"); + Assert.Contains(manifest.Files, f => f.RelativePath == "AudioZH.big"); + Assert.Contains(manifest.Files, f => f.RelativePath == "SpeechZH.big"); + } + + /// + /// Tests that CreateGameInstallationManifestAsync filters language files according to requested language. + /// + /// A representing the asynchronous test operation. + [Fact] + public async Task CreateGameInstallationManifestAsync_LanguageFiltering_IncludesMatchingLanguageOnlyAsync() + { + // Arrange + var installationPath = Path.Combine(_tempDirectory, "LanguageGenerals"); + Directory.CreateDirectory(installationPath); + + await File.WriteAllTextAsync(Path.Combine(installationPath, "generals.exe"), "exe"); + await File.WriteAllTextAsync(Path.Combine(installationPath, "AudioEnglish.big"), "english audio"); + await File.WriteAllTextAsync(Path.Combine(installationPath, "English.big"), "english text"); + + // Act 1: Request English (EN) + var enBuilder = await _service.CreateGameInstallationManifestAsync( + installationPath, GameType.Generals, GameInstallationType.Steam, "1.08", "EN"); + var enManifest = enBuilder.Build(); + + // Assert 1: English files are included + Assert.NotNull(enManifest); + Assert.Contains(enManifest.Files, f => f.RelativePath == "AudioEnglish.big"); + Assert.Contains(enManifest.Files, f => f.RelativePath == "English.big"); + + // Act 2: Request German (DE) for the same folder + var deBuilder = await _service.CreateGameInstallationManifestAsync( + installationPath, GameType.Generals, GameInstallationType.Steam, "1.08", "DE"); + var deManifest = deBuilder.Build(); + + // Assert 2: English-specific files are excluded when German is requested + Assert.NotNull(deManifest); + Assert.DoesNotContain(deManifest.Files, f => f.RelativePath == "AudioEnglish.big"); + Assert.DoesNotContain(deManifest.Files, f => f.RelativePath == "English.big"); + } + + /// + /// Tests that CreateGameInstallationManifestAsync excludes extra non-vanilla files from the core manifest. + /// + /// A representing the asynchronous test operation. + [Fact] + public async Task CreateGameInstallationManifestAsync_ExcludesExtraNonVanillaFilesAsync() + { + // Arrange + var installationPath = Path.Combine(_tempDirectory, "ModdedGenerals"); + Directory.CreateDirectory(installationPath); + + // Vanilla files + await File.WriteAllTextAsync(Path.Combine(installationPath, "generals.exe"), "exe"); + await File.WriteAllTextAsync(Path.Combine(installationPath, "AudioEnglish.big"), "english audio"); + + // Extra non-vanilla mod files + await File.WriteAllTextAsync(Path.Combine(installationPath, "!Gentool.dll"), "gentool mod dll"); + await File.WriteAllTextAsync(Path.Combine(installationPath, "CustomShockwave.big"), "mod archive"); + await File.WriteAllTextAsync(Path.Combine(installationPath, "test_custom_config.ini"), "custom ini"); + + // Act + var builder = await _service.CreateGameInstallationManifestAsync( + installationPath, GameType.Generals, GameInstallationType.Steam, "1.08", "EN"); + var manifest = builder.Build(); + + // Assert + Assert.NotNull(manifest); + Assert.Contains(manifest.Files, f => f.RelativePath == "generals.exe"); + Assert.Contains(manifest.Files, f => f.RelativePath == "AudioEnglish.big"); + + // Untracked/mod files should NOT be in the pristine vanilla manifest + Assert.DoesNotContain(manifest.Files, f => f.RelativePath == "!Gentool.dll"); + Assert.DoesNotContain(manifest.Files, f => f.RelativePath == "CustomShockwave.big"); + Assert.DoesNotContain(manifest.Files, f => f.RelativePath == "test_custom_config.ini"); + } + + /// + /// Tests that CreateGameInstallationManifestAsync uses backup (.bak) file if available. + /// + /// A representing the asynchronous test operation. + [Fact] + public async Task CreateGameInstallationManifestAsync_UsesBackupFileWhenPresentAsync() + { + // Arrange + var installationPath = Path.Combine(_tempDirectory, "BackupTest"); + Directory.CreateDirectory(installationPath); + + var originalExe = Path.Combine(installationPath, "generals.exe"); + var backupExe = Path.Combine(installationPath, "generals.exe.bak"); + await File.WriteAllTextAsync(originalExe, "modified exe"); + await File.WriteAllTextAsync(backupExe, "pristine original backup exe"); + + // Act + var builder = await _service.CreateGameInstallationManifestAsync( + installationPath, GameType.Generals, GameInstallationType.Steam, "1.08", "EN"); + var manifest = builder.Build(); + + // Assert + Assert.NotNull(manifest); + var exeFile = manifest.Files.FirstOrDefault(f => f.RelativePath == "generals.exe"); + Assert.NotNull(exeFile); + Assert.Equal(backupExe, exeFile.SourcePath); + } + /// /// Cleans up temporary test files. /// diff --git a/GenHub/GenHub/Features/Manifest/ContentManifestBuilder.cs b/GenHub/GenHub/Features/Manifest/ContentManifestBuilder.cs index 828107c9c..0be224c51 100644 --- a/GenHub/GenHub/Features/Manifest/ContentManifestBuilder.cs +++ b/GenHub/GenHub/Features/Manifest/ContentManifestBuilder.cs @@ -504,19 +504,34 @@ public async Task AddRemoteFileAsync( /// The source path of the file in the game installation. /// Whether the file is executable. /// File permissions. + /// Optional pre-computed SHA256 content hash. + /// Optional file size in bytes. + /// Whether the file is required. /// A task that yields the instance for chaining upon completion. public async Task AddGameInstallationFileAsync( string relativePath, string sourcePath, bool isExecutable = false, - FilePermissions? permissions = null) + FilePermissions? permissions = null, + string? hash = null, + long? size = null, + bool isRequired = true) { if (string.IsNullOrEmpty(sourcePath)) { throw new ArgumentException("sourcePath cannot be null or empty for game installation files.", nameof(sourcePath)); } - return await AddFileAsync(relativePath, sourcePath, ContentSourceType.GameInstallation, string.Empty, isExecutable, permissions); + return await AddFileAsync( + relativePath, + sourcePath, + ContentSourceType.GameInstallation, + string.Empty, + isExecutable, + permissions, + hash, + size, + isRequired); } /// @@ -865,6 +880,9 @@ private ContentInstallTarget DetermineInstallTarget(string relativePath) /// Download URL. /// Is executable. /// File permissions. + /// Optional pre-computed SHA256 content hash. + /// Optional file size in bytes. + /// Whether the file is required. /// The builder instance. private async Task AddFileAsync( string relativePath, @@ -872,7 +890,10 @@ private async Task AddFileAsync( ContentSourceType sourceType = ContentSourceType.ContentAddressable, string downloadUrl = "", bool isExecutable = false, - FilePermissions? permissions = null) + FilePermissions? permissions = null, + string? hash = null, + long? size = null, + bool isRequired = true) { var installTarget = DetermineInstallTarget(relativePath); @@ -884,19 +905,29 @@ private async Task AddFileAsync( IsExecutable = isExecutable, DownloadUrl = downloadUrl, InstallTarget = installTarget, + IsRequired = isRequired, Permissions = permissions ?? new FilePermissions { UnixPermissions = isExecutable ? "755" : "644", }, }; - var shouldComputeHash = false; + if (size.HasValue) + { + manifestFile.Size = size.Value; + } + + if (!string.IsNullOrEmpty(hash)) + { + manifestFile.Hash = hash; + } + if (!string.IsNullOrEmpty(sourcePath) && File.Exists(sourcePath)) { var fileInfo = new FileInfo(sourcePath); - manifestFile.Size = fileInfo.Length; + if (!size.HasValue) + { + manifestFile.Size = fileInfo.Length; + } - // Always compute hash for executable files (critical for GameClient integrity validation) - // For non-executable GameInstallation files, skip hash (CSV-based authority from GitHub planned) - shouldComputeHash = isExecutable || sourceType != ContentSourceType.GameInstallation; - if (shouldComputeHash) + if (string.IsNullOrEmpty(manifestFile.Hash)) { manifestFile.Hash = await _hashProvider.ComputeFileHashAsync(sourcePath); } @@ -913,7 +944,7 @@ private async Task AddFileAsync( } _manifest.Files.Add(manifestFile); - logger.LogDebug("Added file: {RelativePath} (Source: {SourceType}, Hashed: {Hashed})", relativePath, sourceType, shouldComputeHash); + logger.LogDebug("Added file: {RelativePath} (Source: {SourceType}, Hash: {Hash})", relativePath, sourceType, manifestFile.Hash); return this; } } diff --git a/GenHub/GenHub/Features/Manifest/ManifestGenerationService.cs b/GenHub/GenHub/Features/Manifest/ManifestGenerationService.cs index ea609583a..492760037 100644 --- a/GenHub/GenHub/Features/Manifest/ManifestGenerationService.cs +++ b/GenHub/GenHub/Features/Manifest/ManifestGenerationService.cs @@ -1,15 +1,21 @@ using CsvHelper; using CsvHelper.Configuration; using GenHub.Core.Constants; +using GenHub.Core.Features.GameInstallations; using GenHub.Core.Interfaces.Common; +using GenHub.Core.Interfaces.Content; using GenHub.Core.Interfaces.Manifest; using GenHub.Core.Interfaces.Tools; +using GenHub.Core.Models.Content; using GenHub.Core.Models.Enums; using GenHub.Core.Models.Manifest; +using GenHub.Core.Models.Results.Content; using GenHub.Core.Utilities; +using GenHub.Features.Content.Services.ContentResolvers; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; @@ -31,17 +37,29 @@ public class ManifestGenerationService( IFileHashProvider hashProvider, IManifestIdService manifestIdService, IDownloadService downloadService, - IConfigurationProviderService configurationProvider) : IManifestGenerationService + IConfigurationProviderService configurationProvider, + ILanguageDetector? languageDetector = null, + CsvResolver? csvResolver = null) : IManifestGenerationService { - private static readonly JsonSerializerOptions _jsonSerializerOptions = new() + private static readonly CsvConfiguration CsvConfig = new(CultureInfo.InvariantCulture) + { + HasHeaderRecord = true, + MissingFieldFound = null, + HeaderValidated = null, + BadDataFound = null, + }; + + private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; - private static readonly string[] SupportedLanguages = ["EN", "DE", "FR", "ES", "IT", "KO", "PL", "PT-BR", "ZH-CN", "ZH-TW"]; + private readonly ILanguageDetector _languageDetector = languageDetector ?? new LanguageDetector(); - private int _fileCount = 0; + private readonly CsvResolver? _resolvedCsvResolver = csvResolver; + + private int _fileCount; /// /// Creates a manifest builder for a game installation with string version normalization. @@ -50,12 +68,14 @@ public class ManifestGenerationService( /// The game type (Generals, ZeroHour). /// The installation type (Steam, EaApp). /// The manifest version (e.g., "1.08", "1.04", or integer like 0, 1, 2). If null, defaults to 0. + /// Optional explicit language code (e.g., "EN", "DE"). If null, language is detected automatically. /// A that returns a configured manifest builder. public async Task CreateGameInstallationManifestAsync( string gameInstallationPath, GameType gameType, GameInstallationType installationType, - string? manifestVersion = null) + string? manifestVersion = null, + string? language = null) { try { @@ -81,7 +101,7 @@ public async Task CreateGameInstallationManifestAsync( builder.WithPublisher(publisher.Name, publisher.Website, publisher.SupportUrl, string.Empty, publisher.PublisherType); // Add essential game files - await AddGameFilesToManifest(builder, gameInstallationPath, gameType); + await AddGameFilesToManifest(builder, gameInstallationPath, gameType, manifestVersion, language); logger.LogInformation( "Created GameInstallation manifest for {InstallationType} {GameType} (Publisher: {PublisherName})", @@ -109,14 +129,16 @@ public async Task CreateGameInstallationManifestAsync( /// The game type (Generals, ZeroHour). /// The installation type (Steam, EaApp). /// The manifest version (e.g., 1, 2, 20). Defaults to 0 for first version. + /// Optional explicit language code (e.g., "EN", "DE"). If null, language is detected automatically. /// A that returns a configured manifest builder. public async Task CreateGameInstallationManifestAsync( string gameInstallationPath, GameType gameType, GameInstallationType installationType, - int manifestVersion = 0) + int manifestVersion = 0, + string? language = null) { - return await CreateGameInstallationManifestAsync(gameInstallationPath, gameType, installationType, manifestVersion.ToString()); + return await CreateGameInstallationManifestAsync(gameInstallationPath, gameType, installationType, manifestVersion.ToString(), language); } /// @@ -333,7 +355,7 @@ public async Task SaveManifestAsync(ContentManifest manifest, string outputPath) Directory.CreateDirectory(directory); } - var options = _jsonSerializerOptions; + var options = JsonOptions; await using var stream = File.Create(outputPath); await JsonSerializer.SerializeAsync(stream, manifest, options); @@ -498,135 +520,226 @@ private static bool ShouldSkipFile(string relativePath) relativePath.EndsWith(SteamConstants.ProxyLauncherFileName, StringComparison.OrdinalIgnoreCase); } + private static List FilterEntriesByGameAndLanguage( + IEnumerable records, + string targetGame, + string targetLanguage) + { + var result = new List(); + foreach (var record in records) + { + if (string.IsNullOrWhiteSpace(record.RelativePath)) + { + continue; + } + + if (!string.IsNullOrWhiteSpace(targetGame) && + !string.Equals(record.GameType, targetGame, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (MatchesLanguage(record.Language, targetLanguage)) + { + result.Add(record); + } + } + + return result; + } + + private static bool MatchesLanguage(string? entryLanguage, string targetLanguage) + { + if (string.Equals(targetLanguage, CsvConstants.AllLanguagesFilter, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (string.IsNullOrWhiteSpace(entryLanguage) || + string.Equals(entryLanguage, CsvConstants.AllLanguagesFilter, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + var normalizedEntryLang = ContentSearchQuery.NormalizeLanguage(entryLanguage); + return string.Equals(normalizedEntryLang, targetLanguage, StringComparison.OrdinalIgnoreCase); + } + /// - /// Adds game files to a manifest builder. + /// Finds a file in the installation directory using case-insensitive path resolution. /// - /// The manifest builder. - /// The installation path. - /// The game type. - /// A task representing the asynchronous operation. - /// - /// Scans the game installation directory and adds all files to the manifest, - /// excluding known non-game directories (RedistInstallers, Manuals, etc.). - /// This will be used for manual content packaging of game installations until we have CSV feature implemented that will act as a source of truth to generate the manifest. - /// - private async Task AddGameFilesToManifest(IContentManifestBuilder builder, string installationPath, GameType gameType) + private static string? FindFileCaseInsensitive(string installationPath, string relativePath) { - try + var exactPath = Path.Combine(installationPath, relativePath.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar)); + if (File.Exists(exactPath)) { - // Reset file counter for this manifest generation - _fileCount = 0; - - logger.LogInformation("Starting manifest generation for {GameType} at {InstallationPath}", gameType, installationPath); + return exactPath; + } - // Add essential executable files - var executableName = gameType == GameType.Generals ? GameClientConstants.GeneralsExecutable : GameClientConstants.ZeroHourExecutable; - var executablePath = Path.Combine(installationPath, executableName); + var segments = relativePath.Split(['/', '\\'], StringSplitOptions.RemoveEmptyEntries); + var currentDir = installationPath; - if (File.Exists(executablePath)) + for (int i = 0; i < segments.Length - 1; i++) + { + if (!Directory.Exists(currentDir)) { - var sourcePath = ResolveSourcePathWithBackup(executablePath, executableName); - await builder.AddGameInstallationFileAsync(executableName, sourcePath, isExecutable: true); + return null; } - // Add common game files including DLLs and .big archives which are required for the game to run - var commonFiles = new[] - { - "*.exe", - "*.dll", - "*.dat", - "*.ini", - "*.cfg", - "*.big", // Essential: Archive files containing game assets, textures, audio, etc. - "*.txt", // Essential: Text files like steam_appid.txt - }; + var segment = segments[i]; + var matchingDir = Directory.EnumerateDirectories(currentDir) + .FirstOrDefault(d => string.Equals(Path.GetFileName(d), segment, StringComparison.OrdinalIgnoreCase)); - foreach (var pattern in commonFiles) + if (matchingDir == null) { - try - { - var files = Directory.GetFiles(installationPath, pattern, SearchOption.TopDirectoryOnly); - foreach (var file in files) - { - var relativePath = Path.GetFileName(file); + return null; + } - // Skip backup files and the proxy launcher itself - if (ShouldSkipFile(relativePath)) - { - continue; - } + currentDir = matchingDir; + } - // Skip the main executable as it was already added with backup handling - if (relativePath.Equals(executableName, StringComparison.OrdinalIgnoreCase)) - { - continue; - } + if (!Directory.Exists(currentDir)) + { + return null; + } - await builder.AddGameInstallationFileAsync(relativePath, file); - } + var targetFileName = segments[^1]; + return Directory.EnumerateFiles(currentDir) + .FirstOrDefault(f => string.Equals(Path.GetFileName(f), targetFileName, StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Counts extra non-vanilla files in the installation directory that are excluded from the manifest. + /// + private static int CountExtraNonVanillaFiles(string installationPath, ISet authoritativePaths) + { + try + { + var localFiles = Directory.GetFiles(installationPath, "*", SearchOption.AllDirectories); + int extraCount = 0; + + foreach (var localFile in localFiles) + { + var relPath = Path.GetRelativePath(installationPath, localFile).Replace('\\', '/'); + if (ShouldSkipFile(relPath)) + { + continue; } - catch (Exception ex) + + if (!authoritativePaths.Contains(relPath)) { - logger.LogWarning(ex, "Failed to enumerate files with pattern {Pattern} at {InstallationPath}", pattern, installationPath); + extraCount++; } } - // Add all subdirectories except known non-game directories - // PRIORITY: Use CSV-based manifest generation if available - await AddFilesFromCsvAsync(builder, installationPath, gameType); - - logger.LogInformation("Completed manifest generation for {GameType}: {TotalFiles} files added", gameType, _fileCount); - logger.LogDebug("Added game files to manifest for {GameType} at {InstallationPath}", gameType, installationPath); + return extraCount; } - catch (Exception ex) + catch { - logger.LogError(ex, "Error adding game files to manifest"); + return 0; } } /// - /// Recursively adds all files from a directory to the manifest. + /// Adds authoritative vanilla game files to a manifest builder using the CSV catalog authority. /// /// The manifest builder. - /// The root installation path. - /// The directory to scan. + /// The installation path. + /// The game type. + /// Optional manifest version. + /// Optional explicit language code. /// A task representing the asynchronous operation. - private async Task AddDirectoryFilesRecursivelyAsync(IContentManifestBuilder builder, string installationPath, string directoryPath) + private async Task AddGameFilesToManifest( + IContentManifestBuilder builder, + string installationPath, + GameType gameType, + string? manifestVersion, + string? language) { try { - // Add all files in this directory (run synchronously on background thread) - var files = await Task.Run(() => Directory.GetFiles(directoryPath, "*.*", SearchOption.TopDirectoryOnly)); - foreach (var file in files) + _fileCount = 0; + + var detectedLanguage = string.IsNullOrWhiteSpace(language) + ? await _languageDetector.DetectAsync(installationPath) + : language; + + var normalizedLanguage = ContentSearchQuery.NormalizeLanguage(detectedLanguage); + var version = !string.IsNullOrWhiteSpace(manifestVersion) && manifestVersion != "0" + ? manifestVersion + : (gameType == GameType.Generals ? ManifestConstants.GeneralsManifestVersion : ManifestConstants.ZeroHourManifestVersion); + + logger.LogInformation( + "Starting authoritative manifest generation for {GameType} v{Version} ({Language}) at {InstallationPath}", + gameType, + version, + normalizedLanguage, + installationPath); + + var authoritativeEntries = await GetAuthoritativeEntriesAsync(gameType, version, normalizedLanguage); + + if (authoritativeEntries.Count == 0) { - var relativePath = Path.GetRelativePath(installationPath, file); + logger.LogWarning( + "No authoritative CSV entries found for {GameType} v{Version} ({Language})", + gameType, + version, + normalizedLanguage); + return; + } - // Skip backup files and the proxy launcher itself - if (ShouldSkipFile(relativePath)) + var authoritativePathSet = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var entry in authoritativeEntries) + { + if (string.IsNullOrWhiteSpace(entry.RelativePath)) { continue; } - await builder.AddGameInstallationFileAsync(relativePath, file); + authoritativePathSet.Add(entry.RelativePath); - // Report progress every 50 files - _fileCount++; - if (_fileCount % 50 == 0) + var resolvedFilePath = FindFileCaseInsensitive(installationPath, entry.RelativePath); + if (resolvedFilePath == null || !File.Exists(resolvedFilePath)) { - logger.LogInformation("Scanning game files: {FileCount} files processed...", _fileCount); + if (entry.IsRequired) + { + logger.LogDebug( + "Required vanilla file missing from installation: {RelativePath}", + entry.RelativePath); + } + + continue; } - } - // Recursively process subdirectories (run synchronously on background thread) - var subdirectories = await Task.Run(() => Directory.GetDirectories(directoryPath, "*", SearchOption.TopDirectoryOnly)); - foreach (var subdir in subdirectories) - { - await AddDirectoryFilesRecursivelyAsync(builder, installationPath, subdir); + var sourcePath = ResolveSourcePathWithBackup(resolvedFilePath, entry.RelativePath); + var isExecutable = ExecutableFileClassifier.RequiresExecutePermission(entry.RelativePath, sourcePath); + var fileInfo = new FileInfo(sourcePath); + var localHash = await hashProvider.ComputeFileHashAsync(sourcePath); + + await builder.AddGameInstallationFileAsync( + entry.RelativePath, + sourcePath, + isExecutable, + permissions: null, + hash: localHash, + size: fileInfo.Length, + isRequired: entry.IsRequired); + + _fileCount++; } + + // Exclude extra/non-vanilla files from the core manifest to keep it completely pristine + var extraFileCount = CountExtraNonVanillaFiles(installationPath, authoritativePathSet); + logger.LogInformation( + "Completed authoritative manifest generation for {GameType}: {TotalFiles} vanilla files added, {ExtraCount} extra non-vanilla files excluded", + gameType, + _fileCount, + extraFileCount); } catch (Exception ex) { - logger.LogWarning(ex, "Failed to recursively add files from {DirectoryPath}", directoryPath); + logger.LogError(ex, "Error adding authoritative game files to manifest for {GameType}", gameType); } } @@ -881,98 +994,138 @@ private async Task AddClientFilesToManifest(IContentManifestBuilder builder, str } /// - /// Adds files to the manifest using a CSV source of truth. + /// Resolves authoritative CSV entries for the specified game type, version, and language. /// - private async Task AddFilesFromCsvAsync(IContentManifestBuilder builder, string installationPath, GameType gameType) + private async Task> GetAuthoritativeEntriesAsync( + GameType gameType, + string version, + string language) { - try - { - var csvResourceName = gameType == GameType.Generals ? "GenHub.Core.Assets.Manifests.generals.csv" : "GenHub.Core.Assets.Manifests.zerohour.csv"; - var assembly = Assembly.Load("GenHub.Core"); - using var stream = assembly.GetManifestResourceStream(csvResourceName); + var gameTypeStr = gameType == GameType.ZeroHour ? CsvConstants.ZeroHourGameType : CsvConstants.GeneralsGameType; + var csvFileName = gameType == GameType.ZeroHour ? "ZeroHour-1.04.csv" : "Generals-1.08.csv"; - if (stream == null) + // 1. Try CSV resolver if available + if (_resolvedCsvResolver != null) + { + try { - logger.LogWarning("Embedded resource {ResourceName} not found", csvResourceName); - return false; - } + var searchResult = new ContentSearchResult + { + Id = $"csvregistry.gameinstallation.{gameTypeStr.ToLowerInvariant()}{version.Replace(".", string.Empty)}{language.ToLowerInvariant()}", + Name = $"{gameTypeStr} {version} ({language})", + Version = version, + TargetGame = gameType, + ContentType = ContentType.GameInstallation, + SourceUrl = CsvConstants.DefaultIndexFileUrl, + ResolverId = CsvConstants.ResolverId, + ResolverMetadata = + { + [CsvConstants.GameTypeMetadataKey] = gameTypeStr, + [CsvConstants.VersionMetadataKey] = version, + [CsvConstants.LanguageMetadataKey] = language, + [CsvConstants.CsvUrlMetadataKey] = csvFileName, + }, + }; - using var reader = new StreamReader(stream); - var config = new CsvConfiguration(CultureInfo.InvariantCulture) + var resolveResult = await _resolvedCsvResolver.ResolveAsync(searchResult); + if (resolveResult.Success && resolveResult.Data?.Files != null && resolveResult.Data.Files.Count > 0) + { + logger.LogDebug( + "Resolved {Count} authoritative files via CSV resolver for {GameType} v{Version} ({Language})", + resolveResult.Data.Files.Count, + gameType, + version, + language); + + return resolveResult.Data.Files.Select(f => new CsvCatalogEntry + { + RelativePath = f.RelativePath, + Size = f.Size, + Sha256 = f.Hash, + GameType = gameTypeStr, + Language = language, + IsRequired = f.IsRequired, + DownloadUrl = f.DownloadUrl, + }).ToList(); + } + } + catch (Exception ex) { - HasHeaderRecord = true, - }; - using var csv = new CsvReader(reader, config); + logger.LogWarning(ex, "Failed to resolve CSV catalog via resolver for {GameType} ({Language})", gameType, language); + } + } - var records = csv.GetRecords().ToList(); - var installationFiles = Directory.GetFiles(installationPath, "*", SearchOption.AllDirectories) - .Select(f => Path.GetRelativePath(installationPath, f)) - .ToHashSet(StringComparer.OrdinalIgnoreCase); + // 2. Fallback to embedded assembly or local file + return LoadAuthoritativeEntriesFromFallback(gameType, version, language); + } - logger.LogInformation("Processing {Count} entries from CSV for {GameType}", records.Count, gameType); + /// + /// Loads authoritative CSV entries from embedded assembly assets or local registry files. + /// + private IReadOnlyList LoadAuthoritativeEntriesFromFallback( + GameType gameType, + string version, + string language) + { + var gameTypeStr = gameType == GameType.ZeroHour ? CsvConstants.ZeroHourGameType : CsvConstants.GeneralsGameType; + var csvFileName = gameType == GameType.ZeroHour ? "ZeroHour-1.04.csv" : "Generals-1.08.csv"; - foreach (var record in records) + // Try embedded resource from GenHub.Core + try + { + var assembly = typeof(CsvConstants).Assembly; + var resourceName = $"GenHub.Core.Assets.Registries.{csvFileName}"; + using var stream = assembly.GetManifestResourceStream(resourceName); + if (stream != null) { - if (string.IsNullOrEmpty(record.RelativePath)) continue; - - var finalPath = record.RelativePath; - var found = false; - - // 1. Check if the exact file exists - if (installationFiles.Contains(finalPath)) + using var reader = new StreamReader(stream); + using var csv = new CsvReader(reader, CsvConfig); + var records = csv.GetRecords().ToList(); + var filtered = FilterEntriesByGameAndLanguage(records, gameTypeStr, language); + if (filtered.Count > 0) { - found = true; + logger.LogDebug("Loaded {Count} authoritative entries from embedded resource {Resource}", filtered.Count, resourceName); + return filtered; } + } + } + catch (Exception ex) + { + logger.LogWarning(ex, "Failed to load authoritative CSV from embedded resource for {GameType}", gameType); + } - // 2. If it's language-specific and exact file NOT found, try to resolve other language variants - else if (!string.IsNullOrEmpty(record.Language)) - { - // Attempt to find any language-pivoted version of this file - foreach (var lang in SupportedLanguages) - { - var pivotedPath = record.RelativePath.Replace(record.Language, lang, StringComparison.OrdinalIgnoreCase); - if (installationFiles.Contains(pivotedPath)) - { - finalPath = pivotedPath; - found = true; - logger.LogDebug("Resolved language file {Original} to {Pivoted}", record.RelativePath, pivotedPath); - break; - } - } - } - - if (found) - { - var fullPath = Path.Combine(installationPath, finalPath); - - fullPath = ResolveSourcePathWithBackup(fullPath, finalPath); - - // .dat is data, not code. It was previously marked executable because - // the Steam layout launches game.dat through a proxy, which is a launch - // strategy rather than a property of the file, and it forced - // SteamManifestPatcher to keep flipping the flag by hand. - var isExecutable = ExecutableFileClassifier.RequiresExecutePermission(finalPath, fullPath); + // Try local disk path if running from repo or development tree + try + { + var possiblePaths = new[] + { + Path.Combine(AppContext.BaseDirectory, "docs", "GameInstallationFilesRegistry", csvFileName), + Path.Combine(Directory.GetCurrentDirectory(), "docs", "GameInstallationFilesRegistry", csvFileName), + Path.Combine(AppContext.BaseDirectory, csvFileName), + }; - await builder.AddGameInstallationFileAsync(finalPath, fullPath, isExecutable); - _fileCount++; - } - else + foreach (var path in possiblePaths) + { + if (File.Exists(path)) { - // If it's a core file (no language), log as missing - if (string.IsNullOrEmpty(record.Language)) + using var reader = new StreamReader(path); + using var csv = new CsvReader(reader, CsvConfig); + var records = csv.GetRecords().ToList(); + var filtered = FilterEntriesByGameAndLanguage(records, gameTypeStr, language); + if (filtered.Count > 0) { - logger.LogDebug("Core file {File} missing from installation", finalPath); + logger.LogDebug("Loaded {Count} authoritative entries from local path {Path}", filtered.Count, path); + return filtered; } } } - - return true; } catch (Exception ex) { - logger.LogError(ex, "Failed to add files from CSV for {GameType}", gameType); - return false; + logger.LogWarning(ex, "Failed to load authoritative CSV from local disk for {GameType}", gameType); } + + return []; } /// @@ -987,22 +1140,13 @@ private string ResolveSourcePathWithBackup(string filePath, string manifestFileN return backupPath; } - return filePath; - } + var legacyBackupPath = filePath + ".bak"; + if (File.Exists(legacyBackupPath)) + { + logger.LogInformation("Using backup file {Backup} as source for {File} in manifest", Path.GetFileName(legacyBackupPath), manifestFileName); + return legacyBackupPath; + } - /// - /// Represents a file entry in the manifest CSV. - /// - private class ManifestFileEntry - { - /// - /// Gets or sets the relative path of the file. - /// - public string RelativePath { get; set; } = string.Empty; - - /// - /// Gets or sets the language of the file (optional). - /// - public string Language { get; set; } = string.Empty; + return filePath; } } From 99f355cfefa86eec4c5168ecb71e4c05451061d2 Mon Sep 17 00:00:00 2001 From: Undead <110314402+undead2146@users.noreply.github.com> Date: Sun, 30 Aug 2026 19:18:02 +0000 Subject: [PATCH 2/3] fix(manifest): address review feedback on authoritative CSV manifest resolution - Centralize CSV registry filenames, resource namespace, and URLs in CsvConstants - Add path traversal protection and directory boundary verification in FindFileCaseInsensitive - Use authoritative SHA256 hashes and file sizes from CSV catalog entries - Map catalog filename dynamically from game type and version - Handle specific exceptions (IOException, UnauthorizedAccessException, CsvHelperException) instead of generic catches - Simplify ternary expressions and update unit tests to verify authoritative hashes --- GenHub/GenHub.Core/Constants/CsvConstants.cs | 35 +++ .../ManifestGenerationServiceTests.cs | 4 +- .../Manifest/ManifestGenerationService.cs | 223 +++++++++++------- 3 files changed, 180 insertions(+), 82 deletions(-) diff --git a/GenHub/GenHub.Core/Constants/CsvConstants.cs b/GenHub/GenHub.Core/Constants/CsvConstants.cs index 65f6407c0..ec7be534a 100644 --- a/GenHub/GenHub.Core/Constants/CsvConstants.cs +++ b/GenHub/GenHub.Core/Constants/CsvConstants.cs @@ -127,4 +127,39 @@ public static class CsvConstants /// Canonical language code for Traditional Chinese. /// public const string LanguageZhTw = "ZH-TW"; + + /// + /// File name for Generals 1.08 authoritative CSV registry. + /// + public const string GeneralsCsvFileName = "Generals-1.08.csv"; + + /// + /// File name for Zero Hour 1.04 authoritative CSV registry. + /// + public const string ZeroHourCsvFileName = "ZeroHour-1.04.csv"; + + /// + /// File name for game installation files index JSON. + /// + public const string RegistryIndexFileName = "index.json"; + + /// + /// Documentation folder containing game installation files registry. + /// + public const string RegistryDocsFolder = "GameInstallationFilesRegistry"; + + /// + /// Embedded resource namespace in GenHub.Core containing authoritative registries. + /// + public const string EmbeddedResourceNamespace = "GenHub.Core.Assets.Registries"; + + /// + /// Default remote URL for Generals 1.08 CSV. + /// + public const string DefaultGeneralsCsvUrl = "https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/Generals-1.08.csv"; + + /// + /// Default remote URL for Zero Hour 1.04 CSV. + /// + public const string DefaultZeroHourCsvUrl = "https://raw.githubusercontent.com/community-outpost/GenHub/main/docs/GameInstallationFilesRegistry/ZeroHour-1.04.csv"; } diff --git a/GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Manifest/ManifestGenerationServiceTests.cs b/GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Manifest/ManifestGenerationServiceTests.cs index 95a3b2d65..bad46ee3c 100644 --- a/GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Manifest/ManifestGenerationServiceTests.cs +++ b/GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Manifest/ManifestGenerationServiceTests.cs @@ -301,12 +301,12 @@ public async Task CreateGameInstallationManifestAsync_Generals_PopulatesAuthorit Assert.NotNull(exeFile); Assert.True(exeFile.IsExecutable); Assert.Equal(GenHub.Core.Models.Enums.ContentSourceType.GameInstallation, exeFile.SourceType); - Assert.Equal("hash_generals.exe", exeFile.Hash); + Assert.Equal("e253361f457f2ec3290ccf4088aa5c4022fc4772a769fff5fb2fa8b9e5df842d", exeFile.Hash); var dllFile = manifest.Files.FirstOrDefault(f => f.RelativePath.Equals("binkw32.dll", StringComparison.OrdinalIgnoreCase)); Assert.NotNull(dllFile); Assert.False(dllFile.IsExecutable); - Assert.Equal("hash_binkw32.dll", dllFile.Hash); + Assert.Equal("892a51c4056efcb22297a3b44a3491e3f5888f28b08ed1b17030f24acffedb44", dllFile.Hash); } /// diff --git a/GenHub/GenHub/Features/Manifest/ManifestGenerationService.cs b/GenHub/GenHub/Features/Manifest/ManifestGenerationService.cs index 492760037..91c02c1ab 100644 --- a/GenHub/GenHub/Features/Manifest/ManifestGenerationService.cs +++ b/GenHub/GenHub/Features/Manifest/ManifestGenerationService.cs @@ -19,6 +19,7 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Net.Http; using System.Reflection; using System.Text.Json; using System.Threading.Tasks; @@ -520,6 +521,29 @@ private static bool ShouldSkipFile(string relativePath) relativePath.EndsWith(SteamConstants.ProxyLauncherFileName, StringComparison.OrdinalIgnoreCase); } + private static string? GetCatalogFileName(GameType gameType, string version) + { + if (gameType == GameType.Generals) + { + return version switch + { + "1.08" or "1.8" => CsvConstants.GeneralsCsvFileName, + _ => null, + }; + } + + if (gameType == GameType.ZeroHour) + { + return version switch + { + "1.04" or "1.4" => CsvConstants.ZeroHourCsvFileName, + _ => null, + }; + } + + return null; + } + private static List FilterEntriesByGameAndLanguage( IEnumerable records, string targetGame, @@ -570,14 +594,34 @@ private static bool MatchesLanguage(string? entryLanguage, string targetLanguage /// private static string? FindFileCaseInsensitive(string installationPath, string relativePath) { - var exactPath = Path.Combine(installationPath, relativePath.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar)); + if (string.IsNullOrWhiteSpace(relativePath) || Path.IsPathRooted(relativePath)) + { + return null; + } + + // Prevent directory traversal + var normalizedRelative = relativePath.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar); + if (normalizedRelative.Contains(".." + Path.DirectorySeparatorChar) || normalizedRelative.StartsWith("..", StringComparison.Ordinal)) + { + return null; + } + + var fullInstallationPath = Path.GetFullPath(installationPath); + var exactPath = Path.GetFullPath(Path.Combine(fullInstallationPath, normalizedRelative)); + + // Ensure target is strictly within the installation directory + if (!exactPath.StartsWith(fullInstallationPath, StringComparison.OrdinalIgnoreCase)) + { + return null; + } + if (File.Exists(exactPath)) { return exactPath; } var segments = relativePath.Split(['/', '\\'], StringSplitOptions.RemoveEmptyEntries); - var currentDir = installationPath; + var currentDir = fullInstallationPath; for (int i = 0; i < segments.Length - 1; i++) { @@ -587,6 +631,11 @@ private static bool MatchesLanguage(string? entryLanguage, string targetLanguage } var segment = segments[i]; + if (segment == "." || segment == "..") + { + return null; + } + var matchingDir = Directory.EnumerateDirectories(currentDir) .FirstOrDefault(d => string.Equals(Path.GetFileName(d), segment, StringComparison.OrdinalIgnoreCase)); @@ -634,7 +683,7 @@ private static int CountExtraNonVanillaFiles(string installationPath, ISet(StringComparer.OrdinalIgnoreCase); - if (authoritativeEntries.Count == 0) + foreach (var entry in authoritativeEntries) + { + if (string.IsNullOrWhiteSpace(entry.RelativePath)) { - logger.LogWarning( - "No authoritative CSV entries found for {GameType} v{Version} ({Language})", - gameType, - version, - normalizedLanguage); - return; + continue; } - var authoritativePathSet = new HashSet(StringComparer.OrdinalIgnoreCase); + authoritativePathSet.Add(entry.RelativePath); - foreach (var entry in authoritativeEntries) + var resolvedFilePath = FindFileCaseInsensitive(installationPath, entry.RelativePath); + if (resolvedFilePath == null || !File.Exists(resolvedFilePath)) { - if (string.IsNullOrWhiteSpace(entry.RelativePath)) + if (entry.IsRequired) { - continue; + logger.LogWarning( + "Required vanilla file missing from installation: {RelativePath}", + entry.RelativePath); } - authoritativePathSet.Add(entry.RelativePath); + continue; + } - var resolvedFilePath = FindFileCaseInsensitive(installationPath, entry.RelativePath); - if (resolvedFilePath == null || !File.Exists(resolvedFilePath)) - { - if (entry.IsRequired) - { - logger.LogDebug( - "Required vanilla file missing from installation: {RelativePath}", - entry.RelativePath); - } + var sourcePath = ResolveSourcePathWithBackup(resolvedFilePath, entry.RelativePath); + var isExecutable = ExecutableFileClassifier.RequiresExecutePermission(entry.RelativePath, sourcePath); - continue; - } + var catalogHash = !string.IsNullOrWhiteSpace(entry.Sha256) + ? entry.Sha256 + : await hashProvider.ComputeFileHashAsync(sourcePath); - var sourcePath = ResolveSourcePathWithBackup(resolvedFilePath, entry.RelativePath); - var isExecutable = ExecutableFileClassifier.RequiresExecutePermission(entry.RelativePath, sourcePath); - var fileInfo = new FileInfo(sourcePath); - var localHash = await hashProvider.ComputeFileHashAsync(sourcePath); - - await builder.AddGameInstallationFileAsync( - entry.RelativePath, - sourcePath, - isExecutable, - permissions: null, - hash: localHash, - size: fileInfo.Length, - isRequired: entry.IsRequired); - - _fileCount++; - } + var catalogSize = entry.Size > 0 + ? entry.Size + : new FileInfo(sourcePath).Length; - // Exclude extra/non-vanilla files from the core manifest to keep it completely pristine - var extraFileCount = CountExtraNonVanillaFiles(installationPath, authoritativePathSet); - logger.LogInformation( - "Completed authoritative manifest generation for {GameType}: {TotalFiles} vanilla files added, {ExtraCount} extra non-vanilla files excluded", - gameType, - _fileCount, - extraFileCount); - } - catch (Exception ex) - { - logger.LogError(ex, "Error adding authoritative game files to manifest for {GameType}", gameType); + await builder.AddGameInstallationFileAsync( + entry.RelativePath, + sourcePath, + isExecutable, + permissions: null, + hash: catalogHash, + size: catalogSize, + isRequired: entry.IsRequired); + + _fileCount++; } + + // Exclude extra/non-vanilla files from the core manifest to keep it completely pristine + var extraFileCount = CountExtraNonVanillaFiles(installationPath, authoritativePathSet); + logger.LogInformation( + "Completed authoritative manifest generation for {GameType}: {TotalFiles} vanilla files added, {ExtraCount} extra non-vanilla files excluded", + gameType, + _fileCount, + extraFileCount); } /// @@ -873,7 +929,7 @@ private async Task AddGeneralsOnlineClientFilesToManifest(IContentManifestBuilde /// The installation path. /// The game type. /// The full path to the game executable. - /// The publisher name. + /// The publisher name (for publisher-specific logic). /// A task representing the asynchronous operation. private async Task AddClientFilesToManifest(IContentManifestBuilder builder, string installationPath, GameType gameType, string executablePath, string publisherName) { @@ -1002,7 +1058,14 @@ private async Task> GetAuthoritativeEntriesAsync( string language) { var gameTypeStr = gameType == GameType.ZeroHour ? CsvConstants.ZeroHourGameType : CsvConstants.GeneralsGameType; - var csvFileName = gameType == GameType.ZeroHour ? "ZeroHour-1.04.csv" : "Generals-1.08.csv"; + var csvFileName = GetCatalogFileName(gameType, version); + if (string.IsNullOrEmpty(csvFileName)) + { + logger.LogWarning("No authoritative CSV catalog configured for {GameType} version {Version}", gameType, version); + return []; + } + + var csvRemoteUrl = gameType == GameType.ZeroHour ? CsvConstants.DefaultZeroHourCsvUrl : CsvConstants.DefaultGeneralsCsvUrl; // 1. Try CSV resolver if available if (_resolvedCsvResolver != null) @@ -1016,7 +1079,7 @@ private async Task> GetAuthoritativeEntriesAsync( Version = version, TargetGame = gameType, ContentType = ContentType.GameInstallation, - SourceUrl = CsvConstants.DefaultIndexFileUrl, + SourceUrl = csvRemoteUrl, ResolverId = CsvConstants.ResolverId, ResolverMetadata = { @@ -1049,14 +1112,14 @@ private async Task> GetAuthoritativeEntriesAsync( }).ToList(); } } - catch (Exception ex) + catch (Exception ex) when (ex is HttpRequestException or IOException or InvalidOperationException) { - logger.LogWarning(ex, "Failed to resolve CSV catalog via resolver for {GameType} ({Language})", gameType, language); + logger.LogWarning(ex, "Failed to resolve CSV catalog via resolver for {GameType} ({Language}), falling back to local/embedded registry", gameType, language); } } // 2. Fallback to embedded assembly or local file - return LoadAuthoritativeEntriesFromFallback(gameType, version, language); + return LoadAuthoritativeEntriesFromFallback(gameType, version, language, csvFileName); } /// @@ -1065,16 +1128,16 @@ private async Task> GetAuthoritativeEntriesAsync( private IReadOnlyList LoadAuthoritativeEntriesFromFallback( GameType gameType, string version, - string language) + string language, + string csvFileName) { var gameTypeStr = gameType == GameType.ZeroHour ? CsvConstants.ZeroHourGameType : CsvConstants.GeneralsGameType; - var csvFileName = gameType == GameType.ZeroHour ? "ZeroHour-1.04.csv" : "Generals-1.08.csv"; // Try embedded resource from GenHub.Core try { var assembly = typeof(CsvConstants).Assembly; - var resourceName = $"GenHub.Core.Assets.Registries.{csvFileName}"; + var resourceName = $"{CsvConstants.EmbeddedResourceNamespace}.{csvFileName}"; using var stream = assembly.GetManifestResourceStream(resourceName); if (stream != null) { @@ -1089,7 +1152,7 @@ private IReadOnlyList LoadAuthoritativeEntriesFromFallback( } } } - catch (Exception ex) + catch (Exception ex) when (ex is IOException or FormatException or CsvHelperException) { logger.LogWarning(ex, "Failed to load authoritative CSV from embedded resource for {GameType}", gameType); } @@ -1099,8 +1162,8 @@ private IReadOnlyList LoadAuthoritativeEntriesFromFallback( { var possiblePaths = new[] { - Path.Combine(AppContext.BaseDirectory, "docs", "GameInstallationFilesRegistry", csvFileName), - Path.Combine(Directory.GetCurrentDirectory(), "docs", "GameInstallationFilesRegistry", csvFileName), + Path.Combine(AppContext.BaseDirectory, "docs", CsvConstants.RegistryDocsFolder, csvFileName), + Path.Combine(Directory.GetCurrentDirectory(), "docs", CsvConstants.RegistryDocsFolder, csvFileName), Path.Combine(AppContext.BaseDirectory, csvFileName), }; @@ -1120,7 +1183,7 @@ private IReadOnlyList LoadAuthoritativeEntriesFromFallback( } } } - catch (Exception ex) + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or CsvHelperException) { logger.LogWarning(ex, "Failed to load authoritative CSV from local disk for {GameType}", gameType); } From be24d50ae828720e9a0bc4994f89df34798c905b Mon Sep 17 00:00:00 2001 From: Undead <110314402+undead2146@users.noreply.github.com> Date: Sun, 30 Aug 2026 19:28:58 +0000 Subject: [PATCH 3/3] fix(manifest): use explicit typed catches for DeepSource analyzer compliance --- .../Manifest/ManifestGenerationService.cs | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/GenHub/GenHub/Features/Manifest/ManifestGenerationService.cs b/GenHub/GenHub/Features/Manifest/ManifestGenerationService.cs index 91c02c1ab..ff1e1cf2d 100644 --- a/GenHub/GenHub/Features/Manifest/ManifestGenerationService.cs +++ b/GenHub/GenHub/Features/Manifest/ManifestGenerationService.cs @@ -683,7 +683,11 @@ private static int CountExtraNonVanillaFiles(string installationPath, ISet> GetAuthoritativeEntriesAsync( }).ToList(); } } - catch (Exception ex) when (ex is HttpRequestException or IOException or InvalidOperationException) + catch (HttpRequestException ex) + { + logger.LogWarning(ex, "Failed to resolve CSV catalog via HTTP for {GameType} ({Language}), falling back to local/embedded registry", gameType, language); + } + catch (IOException ex) + { + logger.LogWarning(ex, "Failed to resolve CSV catalog via I/O for {GameType} ({Language}), falling back to local/embedded registry", gameType, language); + } + catch (InvalidOperationException ex) { logger.LogWarning(ex, "Failed to resolve CSV catalog via resolver for {GameType} ({Language}), falling back to local/embedded registry", gameType, language); } @@ -1152,9 +1164,17 @@ private IReadOnlyList LoadAuthoritativeEntriesFromFallback( } } } - catch (Exception ex) when (ex is IOException or FormatException or CsvHelperException) + catch (IOException ex) { - logger.LogWarning(ex, "Failed to load authoritative CSV from embedded resource for {GameType}", gameType); + logger.LogWarning(ex, "I/O failure loading authoritative CSV from embedded resource for {GameType}", gameType); + } + catch (FormatException ex) + { + logger.LogWarning(ex, "Format error loading authoritative CSV from embedded resource for {GameType}", gameType); + } + catch (CsvHelperException ex) + { + logger.LogWarning(ex, "CSV parse error loading authoritative CSV from embedded resource for {GameType}", gameType); } // Try local disk path if running from repo or development tree @@ -1183,9 +1203,17 @@ private IReadOnlyList LoadAuthoritativeEntriesFromFallback( } } } - catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or CsvHelperException) + catch (IOException ex) + { + logger.LogWarning(ex, "I/O failure loading authoritative CSV from local disk for {GameType}", gameType); + } + catch (UnauthorizedAccessException ex) + { + logger.LogWarning(ex, "Access denied loading authoritative CSV from local disk for {GameType}", gameType); + } + catch (CsvHelperException ex) { - logger.LogWarning(ex, "Failed to load authoritative CSV from local disk for {GameType}", gameType); + logger.LogWarning(ex, "CSV parse error loading authoritative CSV from local disk for {GameType}", gameType); } return [];