Skip to content

feat(ini): Implement optional INI lookup folders for all common game systems#1476

Merged
xezon merged 8 commits intoTheSuperHackers:mainfrom
xezon:xezon/add-ini-folders-2
Sep 4, 2025
Merged

feat(ini): Implement optional INI lookup folders for all common game systems#1476
xezon merged 8 commits intoTheSuperHackers:mainfrom
xezon:xezon/add-ini-folders-2

Conversation

@xezon
Copy link

@xezon xezon commented Aug 18, 2025

This change implements optional INI lookup folders for all common game systems. This allows to split System INI files into new INI files in the sub folders. It also allows Mods and Addons to selectively overwrite settings.

Loading Rules

Example 1: Data\INI\Armor.ini loads Data\INI\Armor.ini and all *.ini files in Data\INI\Armor

Example 2: Data\INI\Armor loads Data\INI\Armor.ini and all *.ini files in Data\INI\Armor

Example 3: Data\INI\Default\Armor loads Data\INI\Default\Armor.ini and all *.ini files in Data\INI\Default\Armor

In Generals a special rules applies: INI files will also be searched in a "Generals" directory (and its subdirectories) to hide them from Zero Hour.

Example 4: Data\INI\Armor.ini loads Data\INI\Armor.ini and all *.ini files in Data\INI\Armor and Data\INI\Generals\Armor

Simple use case

This for example allows the Control Bar Pro Addon to create a file Data\INI\GameData\ControlBarProGameData.ini with the following content:

GameData
  ViewportHeightScale = 1.0
End

Which would then set the full viewport when this Addon is loaded.

Complex use case

Mods and Patches can selectively write over all original INI files without the need to copy all base files. This allows for simpler and smaller INI distributions.

TODO

  • Fix tools
  • Replicate in Generals

@xezon xezon added this to the Important features milestone Aug 18, 2025
@xezon xezon added Enhancement Is new feature or request Major Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Mod Relates to Mods or modding labels Aug 18, 2025
@xezon
Copy link
Author

xezon commented Aug 18, 2025

Every target is failing because of Tools and Generals.

@ViTeXFTW
Copy link

What is the strategy if two files define the same thing (i.e. Armor)? Will the last loaded file overwrite previous defined fields? And if this is the case, how can we then ensure that when adding content (mods, extension, etc) that the changes are being applied in the correct order?

@ViTeXFTW
Copy link

Also what is the desired need for this?
Is it to be able to split fields (i.e. Armors, Weapons, etc) into directories where each file is a single field implementation? Or is there another more useful feature I am missing?

@xezon
Copy link
Author

xezon commented Aug 19, 2025

What is the strategy if two files define the same thing (i.e. Armor)? Will the last loaded file overwrite previous defined fields? And if this is the case, how can we then ensure that when adding content (mods, extension, etc) that the changes are being applied in the correct order?

If the files have the same name, then the higher file will be loaded only. If the files do not have the same name, then what happens depends on the implementation details - for how it treats collisions - of every individual system. It is reasonable to explore finer control over this behavior but this change does not go this far.

Also what is the desired need for this? Is it to be able to split fields (i.e. Armors, Weapons, etc) into directories where each file is a single field implementation? Or is there another more useful feature I am missing?

The purpose of this change is to be able to build Mods, Patches and Addons without the need to cramp all definitions into a single file for any particular system. I mentioned 2 use cases in the opening description.

@ViTeXFTW
Copy link

ViTeXFTW commented Aug 19, 2025

The purpose of this change is to be able to build Mods, Patches and Addons without the need to cramp all definitions into a single file for any particular system. I mentioned 2 use cases in the opening description.

I see the use cases, but a mod as an example could potentially break (or have side effects) if for some reason the original file is loaded last (or first based on the implementation).

@xezon
Copy link
Author

xezon commented Aug 19, 2025

Creators should certainly not put Mod over Mod if they are incompatible. If we want refined load orders on System levels then this will require rework of every single system which I am not willing to do for this change because of the complexity. It is something that would need to be built slowly over time.

The current implementation will work just fine with Single Product and simple Addons on top of Original Games. It is also entirely optional. Creators can use legacy INI files as is and not use folders. Note that Object INI files already used folders in the original game (see Data\INI\Object) so this entire concept is not new, but now can be used for all systems, and not just for Objects.

@xezon xezon force-pushed the xezon/add-ini-folders-2 branch from 2d3bcf4 to 06598b3 Compare August 19, 2025 21:23
@xezon
Copy link
Author

xezon commented Aug 19, 2025

Compile errors in Zero Hour tools fixed.

@xezon
Copy link
Author

xezon commented Aug 25, 2025

Replay Check is somehow failing here...

@xezon xezon requested a review from OmniBlade August 27, 2025 17:41
Copy link

@OmniBlade OmniBlade left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there are issues with the Core stuff depending on changes in just one of the games INI.h? Otherwise I'm much happier with the feature now and will approve for when the build is fixed.

@xezon
Copy link
Author

xezon commented Aug 27, 2025

The Generals compile error is expected because it was not replicated in Generals, but the Replay Check fail is unexpected and I do not know why it happens.

@xezon
Copy link
Author

xezon commented Aug 31, 2025

The CRC mismatch is caused by the new calls to TheFileSystem->doesFileExist, which changes the name key generator.

@xezon
Copy link
Author

xezon commented Aug 31, 2025

The NameKeyGenerator is decoupled from FileSystem with #1516

@xezon xezon force-pushed the xezon/add-ini-folders-2 branch from 561cd6c to 9efd3c7 Compare September 4, 2025 07:55
@xezon
Copy link
Author

xezon commented Sep 4, 2025

Replicated to Generals with conflicts

Tested and worked.

D:\Projects\TheSuperHackers\GeneralsGameCode>FOR /F "delims=" %b IN ('git merge-base --fork-point main') DO git diff %b  1>changes.patch

D:\Projects\TheSuperHackers\GeneralsGameCode>git diff 85fdc800168960b3ff06d9770a9515e542cf65a6  1>changes.patch

D:\Projects\TheSuperHackers\GeneralsGameCode>git apply -p2 --directory=Generals --reject --whitespace=fix changes.patch
Checking patch Generals/GameEngine/Source/Common/Audio/GameAudio.cpp...
error: Generals/GameEngine/Source/Common/Audio/GameAudio.cpp: No such file or directory
Checking patch Generals/GameEngine/Source/GameClient/VideoPlayer.cpp...
error: Generals/GameEngine/Source/GameClient/VideoPlayer.cpp: No such file or directory
Checking patch Generals/Tools/MapCacheBuilder/Source/WinMain.cpp...
error: Generals/Tools/MapCacheBuilder/Source/WinMain.cpp: No such file or directory
Checking patch Generals/Code/GameEngine/Include/Common/INI.h...
Hunk #1 succeeded at 173 (offset -2 lines).
Checking patch Generals/Code/GameEngine/Include/Common/SubsystemInterface.h...
Hunk #1 succeeded at 146 (offset -4 lines).
Checking patch Generals/Code/GameEngine/Source/Common/GameEngine.cpp...
Hunk #1 succeeded at 158 (offset 2 lines).
error: while searching for:


                DEBUG_ASSERTCRASH(TheWritableGlobalData,("TheWritableGlobalData expected to be created"));
                initSubsystem(TheWritableGlobalData, "TheWritableGlobalData", TheWritableGlobalData, &xferCRC, "Data\\INI\\Default\\GameData.ini", "Data\\INI\\GameData.ini");
                TheWritableGlobalData->parseCustomDefinition();



error: patch failed: Generals/Code/GameEngine/Source/Common/GameEngine.cpp:505
Hunk #3 succeeded at 452 (offset -73 lines).
error: while searching for:
                }

                // read the water settings from INI (must do prior to initing GameClient, apparently)
                ini.load( AsciiString( "Data\\INI\\Default\\Water.ini" ), INI_LOAD_OVERWRITE, &xferCRC );
                ini.load( AsciiString( "Data\\INI\\Water.ini" ), INI_LOAD_OVERWRITE, &xferCRC );
                ini.load( AsciiString( "Data\\INI\\Default\\Weather.ini" ), INI_LOAD_OVERWRITE, &xferCRC );
                ini.load( AsciiString( "Data\\INI\\Weather.ini" ), INI_LOAD_OVERWRITE, &xferCRC );




error: patch failed: Generals/Code/GameEngine/Source/Common/GameEngine.cpp:539
error: while searching for:


#ifdef DEBUG_CRC
                initSubsystem(TheDeepCRCSanityCheck, "TheDeepCRCSanityCheck", MSGNEW("GameEngineSubystem") DeepCRCSanityCheck, NULL, NULL, NULL, NULL);
#endif // DEBUG_CRC
                initSubsystem(TheGameText, "TheGameText", CreateGameTextInterface(), NULL);
                updateWindowTitle();

error: patch failed: Generals/Code/GameEngine/Source/Common/GameEngine.cpp:555
error: while searching for:
        #endif/////////////////////////////////////////////////////////////////////////////////////////////


                initSubsystem(TheScienceStore,"TheScienceStore", MSGNEW("GameEngineSubsystem") ScienceStore(), &xferCRC, "Data\\INI\\Default\\Science.ini", "Data\\INI\\Science.ini");
                initSubsystem(TheMultiplayerSettings,"TheMultiplayerSettings", MSGNEW("GameEngineSubsystem") MultiplayerSettings(), &xferCRC, "Data\\INI\\Default\\Multiplayer.ini", "Data\\INI\\Multiplayer.ini");
                initSubsystem(TheTerrainTypes,"TheTerrainTypes", MSGNEW("GameEngineSubsystem") TerrainTypeCollection(), &xferCRC, "Data\\INI\\Default\\Terrain.ini", "Data\\INI\\Terrain.ini");
                initSubsystem(TheTerrainRoads,"TheTerrainRoads", MSGNEW("GameEngineSubsystem") TerrainRoadCollection(), &xferCRC, "Data\\INI\\Default\\Roads.ini", "Data\\INI\\Roads.ini");
                initSubsystem(TheGlobalLanguageData,"TheGlobalLanguageData",MSGNEW("GameEngineSubsystem") GlobalLanguage, NULL); // must be before the game text
                initSubsystem(TheCDManager,"TheCDManager", CreateCDManager(), NULL);
        #ifdef DUMP_PERF_STATS///////////////////////////////////////////////////////////////////////////

error: patch failed: Generals/Code/GameEngine/Source/Common/GameEngine.cpp:568
error: while searching for:
                initSubsystem(TheMessageStream,"TheMessageStream", createMessageStream(), NULL);
                initSubsystem(TheSidesList,"TheSidesList", MSGNEW("GameEngineSubsystem") SidesList(), NULL);
                initSubsystem(TheCaveSystem,"TheCaveSystem", MSGNEW("GameEngineSubsystem") CaveSystem(), NULL);
                initSubsystem(TheRankInfoStore,"TheRankInfoStore", MSGNEW("GameEngineSubsystem") RankInfoStore(), &xferCRC, NULL, "Data\\INI\\Rank.ini");
                initSubsystem(ThePlayerTemplateStore,"ThePlayerTemplateStore", MSGNEW("GameEngineSubsystem") PlayerTemplateStore(), &xferCRC, "Data\\INI\\Default\\PlayerTemplate.ini", "Data\\INI\\PlayerTemplate.ini");
                initSubsystem(TheParticleSystemManager,"TheParticleSystemManager", createParticleSystemManager(), NULL);

        #ifdef DUMP_PERF_STATS///////////////////////////////////////////////////////////////////////////

error: patch failed: Generals/Code/GameEngine/Source/Common/GameEngine.cpp:597
error: while searching for:
        #endif/////////////////////////////////////////////////////////////////////////////////////////////


                initSubsystem(TheFXListStore,"TheFXListStore", MSGNEW("GameEngineSubsystem") FXListStore(), &xferCRC, "Data\\INI\\Default\\FXList.ini", "Data\\INI\\FXList.ini");
                initSubsystem(TheWeaponStore,"TheWeaponStore", MSGNEW("GameEngineSubsystem") WeaponStore(), &xferCRC, NULL, "Data\\INI\\Weapon.ini");
                initSubsystem(TheObjectCreationListStore,"TheObjectCreationListStore", MSGNEW("GameEngineSubsystem") ObjectCreationListStore(), &xferCRC, "Data\\INI\\Default\\ObjectCreationList.ini", "Data\\INI\\ObjectCreationList.ini");
                initSubsystem(TheLocomotorStore,"TheLocomotorStore", MSGNEW("GameEngineSubsystem") LocomotorStore(), &xferCRC, NULL, "Data\\INI\\Locomotor.ini");
                initSubsystem(TheSpecialPowerStore,"TheSpecialPowerStore", MSGNEW("GameEngineSubsystem") SpecialPowerStore(), &xferCRC, "Data\\INI\\Default\\SpecialPower.ini", "Data\\INI\\SpecialPower.ini");
                initSubsystem(TheDamageFXStore,"TheDamageFXStore", MSGNEW("GameEngineSubsystem") DamageFXStore(), &xferCRC, NULL, "Data\\INI\\DamageFX.ini");
                initSubsystem(TheArmorStore,"TheArmorStore", MSGNEW("GameEngineSubsystem") ArmorStore(), &xferCRC, NULL, "Data\\INI\\Armor.ini");
                initSubsystem(TheBuildAssistant,"TheBuildAssistant", MSGNEW("GameEngineSubsystem") BuildAssistant, NULL);



error: patch failed: Generals/Code/GameEngine/Source/Common/GameEngine.cpp:609
error: while searching for:



                initSubsystem(TheThingFactory,"TheThingFactory", createThingFactory(), &xferCRC, "Data\\INI\\Default\\Object.ini", NULL, "Data\\INI\\Object");

        #ifdef DUMP_PERF_STATS///////////////////////////////////////////////////////////////////////////
        GetPrecisionTimer(&endTime64);//////////////////////////////////////////////////////////////////

error: patch failed: Generals/Code/GameEngine/Source/Common/GameEngine.cpp:628
error: while searching for:
        #endif/////////////////////////////////////////////////////////////////////////////////////////////


                initSubsystem(TheUpgradeCenter,"TheUpgradeCenter", MSGNEW("GameEngineSubsystem") UpgradeCenter, &xferCRC, "Data\\INI\\Default\\Upgrade.ini", "Data\\INI\\Upgrade.ini");
                initSubsystem(TheGameClient,"TheGameClient", createGameClient(), NULL);



error: patch failed: Generals/Code/GameEngine/Source/Common/GameEngine.cpp:638
error: while searching for:
        #endif/////////////////////////////////////////////////////////////////////////////////////////////


                initSubsystem(TheAI,"TheAI", MSGNEW("GameEngineSubsystem") AI(), &xferCRC,  "Data\\INI\\Default\\AIData.ini", "Data\\INI\\AIData.ini");
                initSubsystem(TheGameLogic,"TheGameLogic", createGameLogic(), NULL);
                initSubsystem(TheTeamFactory,"TheTeamFactory", MSGNEW("GameEngineSubsystem") TeamFactory(), NULL);
                initSubsystem(TheCrateSystem,"TheCrateSystem", MSGNEW("GameEngineSubsystem") CrateSystem(), &xferCRC, "Data\\INI\\Default\\Crate.ini", "Data\\INI\\Crate.ini");
                initSubsystem(ThePlayerList,"ThePlayerList", MSGNEW("GameEngineSubsystem") PlayerList(), NULL);
                initSubsystem(TheRecorder,"TheRecorder", createRecorder(), NULL);
                initSubsystem(TheRadar,"TheRadar", TheGlobalData->m_headless ? NEW RadarDummy : createRadar(), NULL);

error: patch failed: Generals/Code/GameEngine/Source/Common/GameEngine.cpp:650
error: while searching for:


                AsciiString fname;
                fname.format("Data\\%s\\CommandMap.ini", GetRegistryLanguage().str());
                initSubsystem(TheMetaMap,"TheMetaMap", MSGNEW("GameEngineSubsystem") MetaMap(), NULL, fname.str(), "Data\\INI\\CommandMap.ini");

                TheMetaMap->generateMetaMap();

#if defined(RTS_DEBUG)
                ini.load("Data\\INI\\CommandMapDebug.ini", INI_LOAD_MULTIFILE, NULL);
#endif

#if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE)
                ini.load("Data\\INI\\CommandMapDemo.ini", INI_LOAD_MULTIFILE, NULL);
#endif


                initSubsystem(TheActionManager,"TheActionManager", MSGNEW("GameEngineSubsystem") ActionManager(), NULL);
                //initSubsystem((CComObject<WebBrowser> *)TheWebBrowser,"(CComObject<WebBrowser> *)TheWebBrowser", (CComObject<WebBrowser> *)createWebBrowser(), NULL);
                initSubsystem(TheGameStateMap,"TheGameStateMap", MSGNEW("GameEngineSubsystem") GameStateMap, NULL, NULL, NULL );
                initSubsystem(TheGameState,"TheGameState", MSGNEW("GameEngineSubsystem") GameState, NULL, NULL, NULL );

                // Create the interface for sending game results
                initSubsystem(TheGameResultsQueue,"TheGameResultsQueue", GameResultsInterface::createNewGameResultsInterface(), NULL, NULL, NULL, NULL);


        #ifdef DUMP_PERF_STATS///////////////////////////////////////////////////////////////////////////

error: patch failed: Generals/Code/GameEngine/Source/Common/GameEngine.cpp:670
Checking patch Generals/Code/GameEngine/Source/Common/GameLOD.cpp...
Hunk #1 succeeded at 266 (offset -2 lines).
Checking patch Generals/Code/GameEngine/Source/Common/INI/INI.cpp...
Hunk #1 succeeded at 198 (offset -6 lines).
Hunk #2 succeeded at 252 (offset -6 lines).
Hunk #3 succeeded at 263 (offset -6 lines).
Hunk #4 succeeded at 275 (offset -6 lines).
Hunk #5 succeeded at 286 (offset -6 lines).
Hunk #6 succeeded at 317 (offset -6 lines).
Hunk #7 succeeded at 377 (offset -7 lines).
Hunk #8 succeeded at 441 (offset -7 lines).
Checking patch Generals/Code/GameEngine/Source/Common/System/SubsystemInterface.cpp...
Hunk #1 succeeded at 142 (offset -9 lines).
Checking patch Generals/Code/GameEngine/Source/GameClient/Credits.cpp...
Checking patch Generals/Code/GameEngine/Source/GameClient/Eva.cpp...
Hunk #1 succeeded at 182 (offset -73 lines).
Checking patch Generals/Code/GameEngine/Source/GameClient/GUI/ChallengeGenerals.cpp...
error: Generals/Code/GameEngine/Source/GameClient/GUI/ChallengeGenerals.cpp: No such file or directory
Checking patch Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp...
Hunk #1 succeeded at 1042 (offset -23 lines).
Checking patch Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarResizer.cpp...
Checking patch Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp...
Hunk #1 succeeded at 1015 (offset -13 lines).
Checking patch Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp...
Checking patch Generals/Code/GameEngine/Source/GameClient/GUI/HeaderTemplate.cpp...
Checking patch Generals/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp...
Hunk #1 succeeded at 148 (offset -2 lines).
Checking patch Generals/Code/GameEngine/Source/GameClient/GUI/Shell/ShellMenuScheme.cpp...
Checking patch Generals/Code/GameEngine/Source/GameClient/GameClient.cpp...
Hunk #1 succeeded at 241 (offset -8 lines).
Checking patch Generals/Code/GameEngine/Source/GameClient/GlobalLanguage.cpp...
Hunk #1 succeeded at 136 (offset -2 lines).
error: while searching for:
        }


        ini.load( fname, INI_LOAD_OVERWRITE, NULL );
        StringListIt it = m_localFonts.begin();
        while( it != m_localFonts.end())
        {

error: patch failed: Generals/Code/GameEngine/Source/GameClient/GlobalLanguage.cpp:153
Checking patch Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp...
Hunk #1 succeeded at 1099 (offset -30 lines).
Checking patch Generals/Code/GameEngine/Source/GameClient/Input/Mouse.cpp...
Checking patch Generals/Code/GameEngine/Source/GameClient/System/Anim2D.cpp...
Checking patch Generals/Code/GameEngine/Source/GameClient/System/CampaignManager.cpp...
Hunk #1 succeeded at 238 (offset -8 lines).
Checking patch Generals/Code/GameEngine/Source/GameClient/System/Image.cpp...
Hunk #1 succeeded at 325 (offset 83 lines).
Hunk #2 succeeded at 334 (offset 83 lines).
Checking patch Generals/Code/GameEngine/Source/GameClient/System/ParticleSys.cpp...
Hunk #1 succeeded at 2963 (offset 98 lines).
Checking patch Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp...
Hunk #1 succeeded at 2160 (offset -324 lines).
Checking patch Generals/Code/GameEngine/Source/GameNetwork/WOLBrowser/WebBrowser.cpp...
Checking patch Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp...
Hunk #2 succeeded at 336 (offset -12 lines).
Hunk #3 succeeded at 364 (offset -13 lines).
error: while searching for:
        TheScriptEngine->turnBreezeOff(); // stop the tree sway.

        //  [2/11/2003]
        ini.load( AsciiString( "Data\\Scripts\\Scripts.ini" ), INI_LOAD_OVERWRITE, NULL );

        // need this before TheAudio in case we're running off of CD - TheAudio can try to open Music.big on the CD...
        initSubsystem(TheCDManager, CreateCDManager(), NULL);

error: patch failed: Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp:393
Hunk #5 succeeded at 387 (offset -18 lines).
Applied patch Generals/Code/GameEngine/Include/Common/INI.h cleanly.
Applied patch Generals/Code/GameEngine/Include/Common/SubsystemInterface.h cleanly.
Applying patch Generals/Code/GameEngine/Source/Common/GameEngine.cpp with 10 rejects...
Hunk #1 applied cleanly.
Rejected hunk #2.
Hunk #3 applied cleanly.
Rejected hunk #4.
Rejected hunk #5.
Rejected hunk #6.
Rejected hunk #7.
Rejected hunk #8.
Rejected hunk #9.
Rejected hunk #10.
Rejected hunk #11.
Rejected hunk #12.
Applied patch Generals/Code/GameEngine/Source/Common/GameLOD.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/Common/INI/INI.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/Common/System/SubsystemInterface.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameClient/Credits.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameClient/Eva.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarResizer.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarScheme.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameClient/GUI/HeaderTemplate.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameClient/GUI/Shell/ShellMenuScheme.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameClient/GameClient.cpp cleanly.
Applying patch Generals/Code/GameEngine/Source/GameClient/GlobalLanguage.cpp with 1 reject...
Hunk #1 applied cleanly.
Rejected hunk #2.
Applied patch Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameClient/Input/Mouse.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameClient/System/Anim2D.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameClient/System/CampaignManager.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameClient/System/Image.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameClient/System/ParticleSys.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameNetwork/WOLBrowser/WebBrowser.cpp cleanly.
Applying patch Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp with 1 reject...
Hunk #1 applied cleanly.
Hunk #2 applied cleanly.
Hunk #3 applied cleanly.
Rejected hunk #4.
Hunk #5 applied cleanly.

@xezon xezon merged commit bd2699a into TheSuperHackers:main Sep 4, 2025
18 checks passed
@xezon xezon deleted the xezon/add-ini-folders-2 branch September 4, 2025 09:12
fbraz3 pushed a commit to fbraz3/GeneralsX that referenced this pull request Nov 10, 2025
…systems (TheSuperHackers#1476)

For example the game will now not only load Data\INI\Armor.ini but also all *.ini files in Data\INI\Armor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement Is new feature or request Gen Relates to Generals Major Severity: Minor < Major < Critical < Blocker Mod Relates to Mods or modding ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants