diff --git a/.github/prompts/sync-thesuperhackers-upstream.prompt.md b/.github/prompts/sync-thesuperhackers-upstream.prompt.md index e5b8e192a0d..63f1b37a3dc 100644 --- a/.github/prompts/sync-thesuperhackers-upstream.prompt.md +++ b/.github/prompts/sync-thesuperhackers-upstream.prompt.md @@ -53,6 +53,7 @@ Expect many conflicts because the projects intentionally diverged. Every conflic - Preserve platform isolation. Do not allow platform-specific code to leak into gameplay logic. - Keep legacy compatibility paths only where they are still intentionally maintained by this repository, but do not let original-binary compatibility override the `GeneralsX` cross-platform objective. - Treat INI parser changes as high risk on macOS: upstream numeric parsing optimizations may require platform-specific compatibility handling for Apple deployment targets. +- **Never replace our CI/CD infrastructure with upstream versions.** Our `.github/workflows/`, `.github/ISSUE_TEMPLATE/`, `.github/copilot-instructions.md` and all CI configuration must be kept intact. Reject any upstream additions or modifications to these paths. - Review conflicts with extra care in these areas: - build system and presets - SDL3, DXVK, OpenAL, FFmpeg, and platform abstraction layers diff --git a/scripts/clang-tidy-plugin/CMakeLists.txt b/scripts/clang-tidy-plugin/CMakeLists.txt new file mode 100644 index 00000000000..e8fdbd2581a --- /dev/null +++ b/scripts/clang-tidy-plugin/CMakeLists.txt @@ -0,0 +1,63 @@ +# Custom clang-tidy plugin for GeneralsGameCode +# This plugin provides checks for custom types like AsciiString and UnicodeString + +cmake_minimum_required(VERSION 3.20) +project(GeneralsGameCodeClangTidyPlugin) + +# Find LLVM and Clang +find_package(LLVM REQUIRED CONFIG) +find_package(Clang REQUIRED CONFIG) + +message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") +message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") +message(STATUS "Using ClangConfig.cmake in: ${Clang_DIR}") + +# Set up include directories +include_directories(${LLVM_INCLUDE_DIRS}) +include_directories(${CLANG_INCLUDE_DIRS}) + +# Add definitions +add_definitions(${LLVM_DEFINITIONS}) +add_definitions(${CLANG_DEFINITIONS}) + +# Set C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Source files +set(SOURCES + GeneralsGameCodeTidyModule.cpp + readability/UseIsEmptyCheck.cpp + readability/UseThisInsteadOfSingletonCheck.cpp +) + +# Header files +set(HEADERS + GeneralsGameCodeTidyModule.h + readability/UseIsEmptyCheck.h + readability/UseThisInsteadOfSingletonCheck.h +) + +# Create the module library +add_library(GeneralsGameCodeClangTidyPlugin MODULE ${SOURCES} ${HEADERS}) + +# Link against required libraries +target_link_libraries(GeneralsGameCodeClangTidyPlugin + PRIVATE + clangTidy + clangTidyReadabilityModule + clangAST + clangASTMatchers + clangBasic + clangFrontend + clangLex + clangTooling + LLVMSupport +) + +# Set output directory +set_target_properties(GeneralsGameCodeClangTidyPlugin PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin +) + diff --git a/scripts/clang-tidy-plugin/GeneralsGameCodeTidyModule.cpp b/scripts/clang-tidy-plugin/GeneralsGameCodeTidyModule.cpp new file mode 100644 index 00000000000..ff4222ccebb --- /dev/null +++ b/scripts/clang-tidy-plugin/GeneralsGameCodeTidyModule.cpp @@ -0,0 +1,30 @@ +//===--- GeneralsGameCodeTidyModule.cpp - GeneralsGameCode Tidy Module ---===// +// +// Custom clang-tidy module for GeneralsGameCode +// Provides GeneralsGameCode-specific checks +// +//===----------------------------------------------------------------------===// + +#include "GeneralsGameCodeTidyModule.h" +#include "readability/UseIsEmptyCheck.h" +#include "readability/UseThisInsteadOfSingletonCheck.h" +#include "llvm/Support/Registry.h" + +namespace clang::tidy::generalsgamecode { + +void GeneralsGameCodeTidyModule::addCheckFactories( + ClangTidyCheckFactories &CheckFactories) { + CheckFactories.registerCheck( + "generals-use-is-empty"); + CheckFactories.registerCheck( + "generals-use-this-instead-of-singleton"); +} + +} // namespace clang::tidy::generalsgamecode + +static llvm::Registry<::clang::tidy::ClangTidyModule>::Add< + ::clang::tidy::generalsgamecode::GeneralsGameCodeTidyModule> + X("generalsgamecode", "GeneralsGameCode-specific checks"); + +volatile int GeneralsGameCodeTidyModuleAnchorSource = 0; + diff --git a/scripts/clang-tidy-plugin/GeneralsGameCodeTidyModule.h b/scripts/clang-tidy-plugin/GeneralsGameCodeTidyModule.h new file mode 100644 index 00000000000..f597578acb2 --- /dev/null +++ b/scripts/clang-tidy-plugin/GeneralsGameCodeTidyModule.h @@ -0,0 +1,24 @@ +//===--- GeneralsGameCodeTidyModule.h - GeneralsGameCode Tidy Module -----===// +// +// Custom clang-tidy module for GeneralsGameCode +// Provides checks for custom types like AsciiString and UnicodeString +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GENERALSGAMECODE_GENERALSGAMECODETIDYMODULE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GENERALSGAMECODE_GENERALSGAMECODETIDYMODULE_H + +#include "clang-tidy/ClangTidyModule.h" + +namespace clang::tidy::generalsgamecode { + +/// This module is for GeneralsGameCode-specific checks. +class GeneralsGameCodeTidyModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override; +}; + +} // namespace clang::tidy::generalsgamecode + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GENERALSGAMECODE_GENERALSGAMECODETIDYMODULE_H + diff --git a/scripts/clang-tidy-plugin/README.md b/scripts/clang-tidy-plugin/README.md new file mode 100644 index 00000000000..cbd9a2f83ef --- /dev/null +++ b/scripts/clang-tidy-plugin/README.md @@ -0,0 +1,250 @@ +# GeneralsGameCode Clang-Tidy Custom Checks + +Custom clang-tidy checks for the GeneralsGameCode codebase. **This is primarily designed for Windows**, where checks are built directly into clang-tidy. Mac/Linux users can optionally use the plugin version. + +## Quick Start + +### Windows + +**Option 1: Use Pre-built clang-tidy** +- Precompiled binaries can be found at: https://github.com/TheSuperHackers/GeneralsTools + +**Option 2: Build It Yourself** + +Follow the manual steps in the [Windows: Building Checks Into clang-tidy](#windows-building-checks-into-clang-tidy) section below. + +**Usage** +```powershell +llvm-project\build\bin\clang-tidy.exe -p build/clang-tidy ` + --checks='-*,generals-use-is-empty,generals-use-this-instead-of-singleton' ` + file.cpp +``` + +### macOS/Linux (Plugin Version) + +If you prefer the plugin approach on macOS/Linux: + +```bash +# Build the plugin +cd scripts/clang-tidy-plugin +mkdir build && cd build +cmake .. -DLLVM_DIR=/path/to/llvm/lib/cmake/llvm +cmake --build . --config Release + +# Use with --load flag +clang-tidy -p build/clang-tidy \ + --checks='-*,generals-use-is-empty' \ + -load scripts/clang-tidy-plugin/build/lib/libGeneralsGameCodeClangTidyPlugin.so \ + file.cpp +``` + +**Note:** On Windows, plugin loading via `--load` is **not supported** due to DLL static initialization limitations. Windows users must use the built-in version. + +## Checks + +### `generals-use-is-empty` + +Finds uses of `getLength() == 0`, `getLength() > 0`, `compare("") == 0`, or `compareNoCase("") == 0` on `AsciiString` and `UnicodeString`, and `Get_Length() == 0` on `StringClass` and `WideStringClass`, and suggests using `isEmpty()`/`Is_Empty()` or `!isEmpty()`/`!Is_Empty()` instead. + +**Examples:** + +```cpp +// Before (AsciiString/UnicodeString) +if (str.getLength() == 0) { ... } +if (str.getLength() > 0) { ... } +if (str.compare("") == 0) { ... } +if (str.compareNoCase("") == 0) { ... } +if (str.compare(AsciiString::TheEmptyString) == 0) { ... } + +// After (AsciiString/UnicodeString) +if (str.isEmpty()) { ... } +if (!str.isEmpty()) { ... } +if (str.isEmpty()) { ... } +if (str.isEmpty()) { ... } +if (str.isEmpty()) { ... } + +// Before (StringClass/WideStringClass) +if (str.Get_Length() == 0) { ... } +if (str.Get_Length() > 0) { ... } + +// After (StringClass/WideStringClass) +if (str.Is_Empty()) { ... } +if (!str.Is_Empty()) { ... } +``` + +### `generals-use-this-instead-of-singleton` + +Finds uses of singleton global variables (like `TheGameLogic->method()` or `TheGlobalData->member`) inside member functions of the same class type, and suggests using the member directly (e.g., `method()` or `member`) instead of the singleton reference. + +**Examples:** + +```cpp +// Before +void GameLogic::update() { + UnsignedInt now = TheGameLogic->getFrame(); + TheGameLogic->setFrame(now + 1); + TheGameLogic->m_frame = 10; +} + +// After +void GameLogic::update() { + UnsignedInt now = getFrame(); + setFrame(now + 1); + m_frame = 10; +} +``` + +## Prerequisites + +Before using clang-tidy, you need to generate a compile commands database: + +```bash +cmake -B build/clang-tidy -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON -G Ninja +``` + +This creates `build/clang-tidy/compile_commands.json` which tells clang-tidy how to compile each file. + +## Windows: Building Checks Into clang-tidy + +Since plugin loading via `--load` doesn't work on Windows, the checks are integrated directly into the clang-tidy source tree. This is the recommended approach for Windows. + +### Step 1: Clone and Build LLVM + +### Step 2: Copy Plugin Files to LLVM Source Tree + +```powershell +# Create the plugin directory in clang-tools-extra +mkdir llvm-project\clang-tools-extra\clang-tidy\plugins\generalsgamecode + +# Copy the plugin source files +cp -r scripts\clang-tidy-plugin\*.cpp llvm-project\clang-tools-extra\clang-tidy\plugins\generalsgamecode\ +cp -r scripts\clang-tidy-plugin\*.h llvm-project\clang-tools-extra\clang-tidy\plugins\generalsgamecode\ +cp -r scripts\clang-tidy-plugin\readability llvm-project\clang-tools-extra\clang-tidy\plugins\generalsgamecode\ +``` + +**Important:** After copying, you need to update the include paths in the headers: +- Change `#include "clang-tidy/ClangTidyCheck.h"` to `#include "../../../ClangTidyCheck.h"` +- Change `#include "clang-tidy/ClangTidyModule.h"` to `#include "../../ClangTidyModule.h"` + +### Step 3: Create CMakeLists.txt for the Module + +Create `llvm-project/clang-tools-extra/clang-tidy/plugins/generalsgamecode/CMakeLists.txt`: + +```cmake +add_clang_library(clangTidyGeneralsGameCodeModule STATIC + GeneralsGameCodeTidyModule.cpp + readability/UseIsEmptyCheck.cpp + readability/UseThisInsteadOfSingletonCheck.cpp + + LINK_LIBS + clangTidy + clangTidyUtils + ) + +clang_target_link_libraries(clangTidyGeneralsGameCodeModule + PRIVATE + clangAST + clangASTMatchers + clangBasic + clangLex + clangTooling + ) +``` + +### Step 4: Register the Module + +**Modify `llvm-project/clang-tools-extra/clang-tidy/CMakeLists.txt`:** + +Add the subdirectory: +```cmake +add_subdirectory(plugins/generalsgamecode) +``` + +Add to `ALL_CLANG_TIDY_CHECKS`: +```cmake +set(ALL_CLANG_TIDY_CHECKS + ... + clangTidyGeneralsGameCodeModule + ... +) +``` + +**Modify `llvm-project/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h`:** + +Add the anchor to force linker inclusion: +```cpp +// This anchor is used to force the linker to link the GeneralsGameCodeModule. +extern volatile int GeneralsGameCodeModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED GeneralsGameCodeModuleAnchorDestination = + GeneralsGameCodeModuleAnchorSource; +``` + +**Modify `llvm-project/clang-tools-extra/clang-tidy/plugins/generalsgamecode/GeneralsGameCodeTidyModule.cpp`:** + +Ensure the anchor is defined: +```cpp +namespace clang::tidy { +// ... module registration ... + +// Force linker to include this module +volatile int GeneralsGameCodeModuleAnchorSource = 0; +} +``` + +### Step 5: Rebuild clang-tidy + +```powershell +cd llvm-project\build +ninja clang-tidy +``` + +### Step 6: Use the Built-in Checks + +Once rebuilt, the checks are always available - no `--load` flag needed: + +```powershell +llvm-project\build\bin\clang-tidy.exe -p build/clang-tidy ` + --checks='-*,generals-use-is-empty,generals-use-this-instead-of-singleton' ` + file.cpp +``` + + +## macOS/Linux: Building the Plugin + +If you're on macOS or Linux and want to use the plugin version: + +### Prerequisites + +**macOS:** +```bash +brew install llvm@21 +``` + +**Linux (Ubuntu/Debian):** +```bash +sudo apt-get install llvm-21-dev clang-21 libclang-21-dev +``` + +### Building the Plugin + +```bash +cd scripts/clang-tidy-plugin +mkdir build && cd build +cmake .. -DLLVM_DIR=/path/to/llvm/lib/cmake/llvm -DClang_DIR=/path/to/clang/lib/cmake/clang +cmake --build . --config Release +``` + +The plugin will be built as a shared library (`.so` on Linux, `.dylib` on macOS) in the `build/lib/` directory. + +### Using the Plugin + +```bash +clang-tidy -p build/clang-tidy \ + --checks='-*,generals-use-is-empty,generals-use-this-instead-of-singleton' \ + -load scripts/clang-tidy-plugin/build/lib/libGeneralsGameCodeClangTidyPlugin.so \ + file.cpp +``` + +**Important:** The plugin must be built with the same LLVM version as the `clang-tidy` executable in your PATH. The CMake build will display which LLVM version it found (e.g., `Found LLVM 21.1.7`). Verify this matches your `clang-tidy --version` output. + +- Windows plugins not working is a known limitation - see [GitHub issue #159710](https://github.com/llvm/llvm-project/issues/159710) and [LLVM Discourse discussion](https://discourse.llvm.org/t/clang-tidy-is-clang-tidy-out-of-tree-check-plugin-load-mechanism-guaranteed-to-work-with-msvc/84111) diff --git a/scripts/clang-tidy-plugin/readability/UseIsEmptyCheck.cpp b/scripts/clang-tidy-plugin/readability/UseIsEmptyCheck.cpp new file mode 100644 index 00000000000..fe70a15b5ed --- /dev/null +++ b/scripts/clang-tidy-plugin/readability/UseIsEmptyCheck.cpp @@ -0,0 +1,254 @@ +//===--- UseIsEmptyCheck.cpp - Use isEmpty() instead of getLength() == 0 -===// +// +// This check finds patterns like: +// - AsciiString::getLength() == 0 -> AsciiString::isEmpty() +// - AsciiString::getLength() > 0 -> !AsciiString::isEmpty() +// - UnicodeString::getLength() == 0 -> UnicodeString::isEmpty() +// - UnicodeString::getLength() > 0 -> !UnicodeString::isEmpty() +// - StringClass::Get_Length() == 0 -> StringClass::Is_Empty() +// - WideStringClass::Get_Length() == 0 -> WideStringClass::Is_Empty() +// - AsciiString::compare("") == 0 -> AsciiString::isEmpty() +// - UnicodeString::compare(L"") == 0 -> UnicodeString::isEmpty() +// - AsciiString::compare(AsciiString::TheEmptyString) == 0 -> AsciiString::isEmpty() +// - UnicodeString::compare(UnicodeString::TheEmptyString) == 0 -> UnicodeString::isEmpty() +// +//===----------------------------------------------------------------------===// + +#include "UseIsEmptyCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::generalsgamecode::readability { + +void UseIsEmptyCheck::registerMatchers(MatchFinder *Finder) { + // Matcher for AsciiString/UnicodeString with getLength() + auto GetLengthCall = cxxMemberCallExpr( + callee(cxxMethodDecl(hasName("getLength"))), + on(hasType(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(cxxRecordDecl( + hasAnyName("AsciiString", "UnicodeString")))))))); + + // Matcher for StringClass/WideStringClass with Get_Length() + auto GetLengthCallWWVegas = cxxMemberCallExpr( + callee(cxxMethodDecl(hasName("Get_Length"))), + on(hasType(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(cxxRecordDecl( + hasAnyName("StringClass", "WideStringClass")))))))); + + // Helper function to add matchers for a given GetLength call matcher + auto addMatchersForGetLength = [&](const auto &GetLengthMatcher) { + Finder->addMatcher( + binaryOperator( + hasOperatorName("=="), + hasLHS(ignoringParenImpCasts(GetLengthMatcher.bind("getLengthCall"))), + hasRHS(integerLiteral(equals(0)).bind("zero"))) + .bind("comparison"), + this); + + Finder->addMatcher( + binaryOperator( + hasOperatorName("!="), + hasLHS(ignoringParenImpCasts(GetLengthMatcher.bind("getLengthCall"))), + hasRHS(integerLiteral(equals(0)).bind("zero"))) + .bind("comparison"), + this); + + Finder->addMatcher( + binaryOperator( + hasOperatorName(">"), + hasLHS(ignoringParenImpCasts(GetLengthMatcher.bind("getLengthCall"))), + hasRHS(integerLiteral(equals(0)).bind("zero"))) + .bind("comparison"), + this); + + Finder->addMatcher( + binaryOperator( + hasOperatorName("<="), + hasLHS(ignoringParenImpCasts(GetLengthMatcher.bind("getLengthCall"))), + hasRHS(integerLiteral(equals(0)).bind("zero"))) + .bind("comparison"), + this); + + Finder->addMatcher( + binaryOperator( + hasOperatorName("=="), + hasLHS(integerLiteral(equals(0)).bind("zero")), + hasRHS(ignoringParenImpCasts(GetLengthMatcher.bind("getLengthCall")))) + .bind("comparison"), + this); + + Finder->addMatcher( + binaryOperator( + hasOperatorName("!="), + hasLHS(integerLiteral(equals(0)).bind("zero")), + hasRHS(ignoringParenImpCasts(GetLengthMatcher.bind("getLengthCall")))) + .bind("comparison"), + this); + }; + + addMatchersForGetLength(GetLengthCall); + addMatchersForGetLength(GetLengthCallWWVegas); + + // Matcher for TheEmptyString static member access (AsciiString::TheEmptyString or UnicodeString::TheEmptyString) + auto TheEmptyStringRef = memberExpr( + member(hasName("TheEmptyString")), + hasObjectExpression(hasType(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(cxxRecordDecl( + hasAnyName("AsciiString", "UnicodeString")))))))); + + // Matcher for AsciiString/UnicodeString with compare() - we'll check if argument is empty in check() + auto CompareCall = cxxMemberCallExpr( + callee(cxxMethodDecl(hasName("compare"))), + on(hasType(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(cxxRecordDecl( + hasAnyName("AsciiString", "UnicodeString"))))))), + hasArgument(0, anyOf( + stringLiteral().bind("stringLiteralArg"), + TheEmptyStringRef.bind("theEmptyStringArg")))); + + // Matcher for AsciiString/UnicodeString with compareNoCase() - we'll check if argument is empty in check() + auto CompareNoCaseCall = cxxMemberCallExpr( + callee(cxxMethodDecl(hasName("compareNoCase"))), + on(hasType(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(cxxRecordDecl( + hasAnyName("AsciiString", "UnicodeString"))))))), + hasArgument(0, anyOf( + stringLiteral().bind("stringLiteralArg"), + TheEmptyStringRef.bind("theEmptyStringArg")))); + + // Helper function to add matchers for compare() and compareNoCase() calls + auto addMatchersForCompare = [&](const auto &CompareMatcher, const char *BindingName) { + Finder->addMatcher( + binaryOperator( + hasOperatorName("=="), + hasLHS(ignoringParenImpCasts(CompareMatcher.bind(BindingName))), + hasRHS(integerLiteral(equals(0)).bind("zero"))) + .bind("comparison"), + this); + + Finder->addMatcher( + binaryOperator( + hasOperatorName("!="), + hasLHS(ignoringParenImpCasts(CompareMatcher.bind(BindingName))), + hasRHS(integerLiteral(equals(0)).bind("zero"))) + .bind("comparison"), + this); + + Finder->addMatcher( + binaryOperator( + hasOperatorName("=="), + hasLHS(integerLiteral(equals(0)).bind("zero")), + hasRHS(ignoringParenImpCasts(CompareMatcher.bind(BindingName)))) + .bind("comparison"), + this); + + Finder->addMatcher( + binaryOperator( + hasOperatorName("!="), + hasLHS(integerLiteral(equals(0)).bind("zero")), + hasRHS(ignoringParenImpCasts(CompareMatcher.bind(BindingName)))) + .bind("comparison"), + this); + }; + + addMatchersForCompare(CompareCall, "compareCall"); + addMatchersForCompare(CompareNoCaseCall, "compareNoCaseCall"); +} + +void UseIsEmptyCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Comparison = Result.Nodes.getNodeAs("comparison"); + const auto *GetLengthCall = + Result.Nodes.getNodeAs("getLengthCall"); + const auto *CompareCall = + Result.Nodes.getNodeAs("compareCall"); + const auto *CompareNoCaseCall = + Result.Nodes.getNodeAs("compareNoCaseCall"); + + if (!Comparison) + return; + + const CXXMemberCallExpr *MethodCall = GetLengthCall ? GetLengthCall : + (CompareCall ? CompareCall : CompareNoCaseCall); + if (!MethodCall) + return; + + // For compare() and compareNoCase() calls, verify the argument is actually empty + if (CompareCall || CompareNoCaseCall) { + const auto *StringLit = Result.Nodes.getNodeAs("stringLiteralArg"); + const auto *TheEmptyString = Result.Nodes.getNodeAs("theEmptyStringArg"); + + if (StringLit) { + // Check if string literal is actually empty ("" or L"") + StringRef Str = StringLit->getString(); + if (!Str.empty()) { + return; // Not an empty string literal + } + } else if (!TheEmptyString) { + return; // Not matching empty string pattern + } + } + + const Expr *ObjectExpr = MethodCall->getImplicitObjectArgument(); + if (!ObjectExpr) + return; + + // Determine which method name to use based on the called method + StringRef MethodName = MethodCall->getMethodDecl()->getName(); + std::string IsEmptyMethodName; + std::string MethodNameStr; + + if (MethodName == "Get_Length") { + IsEmptyMethodName = "Is_Empty()"; + MethodNameStr = "Get_Length()"; + } else if (MethodName == "compare") { + IsEmptyMethodName = "isEmpty()"; + MethodNameStr = "compare()"; + } else if (MethodName == "compareNoCase") { + IsEmptyMethodName = "isEmpty()"; + MethodNameStr = "compareNoCase()"; + } else { + IsEmptyMethodName = "isEmpty()"; + MethodNameStr = "getLength()"; + } + + StringRef Operator = Comparison->getOpcodeStr(); + bool ShouldNegate = false; + + if (Operator == "==") { + ShouldNegate = false; + } else if (Operator == "!=") { + ShouldNegate = true; + } else if (Operator == ">") { + ShouldNegate = true; + } else if (Operator == "<=") { + ShouldNegate = false; + } else { + return; + } + + StringRef ObjectText = Lexer::getSourceText( + CharSourceRange::getTokenRange(ObjectExpr->getSourceRange()), + *Result.SourceManager, Result.Context->getLangOpts()); + + std::string Replacement; + if (ShouldNegate) { + Replacement = "!" + ObjectText.str() + "." + IsEmptyMethodName; + } else { + Replacement = ObjectText.str() + "." + IsEmptyMethodName; + } + + SourceLocation StartLoc = Comparison->getBeginLoc(); + SourceLocation EndLoc = Comparison->getEndLoc(); + + diag(Comparison->getBeginLoc(), + "use %0 instead of comparing %1 with 0") + << Replacement << MethodNameStr + << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(StartLoc, EndLoc), Replacement); +} + +} // namespace clang::tidy::generalsgamecode::readability diff --git a/scripts/clang-tidy-plugin/readability/UseIsEmptyCheck.h b/scripts/clang-tidy-plugin/readability/UseIsEmptyCheck.h new file mode 100644 index 00000000000..2d6dd84253d --- /dev/null +++ b/scripts/clang-tidy-plugin/readability/UseIsEmptyCheck.h @@ -0,0 +1,45 @@ +//===--- UseIsEmptyCheck.h - Use isEmpty() instead of getLength() == 0 ---===// +// +// This check finds patterns like: +// - AsciiString::getLength() == 0 -> AsciiString::isEmpty() +// - AsciiString::getLength() > 0 -> !AsciiString::isEmpty() +// - UnicodeString::getLength() == 0 -> UnicodeString::isEmpty() +// - UnicodeString::getLength() > 0 -> !UnicodeString::isEmpty() +// - StringClass::Get_Length() == 0 -> StringClass::Is_Empty() +// - WideStringClass::Get_Length() == 0 -> WideStringClass::Is_Empty() +// - AsciiString::compare("") == 0 -> AsciiString::isEmpty() +// - UnicodeString::compare(L"") == 0 -> UnicodeString::isEmpty() +// - AsciiString::compare(AsciiString::TheEmptyString) == 0 -> AsciiString::isEmpty() +// - UnicodeString::compare(UnicodeString::TheEmptyString) == 0 -> UnicodeString::isEmpty() +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GENERALSGAMECODE_READABILITY_USEISEMPTYCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GENERALSGAMECODE_READABILITY_USEISEMPTYCHECK_H + +#include "clang-tidy/ClangTidyCheck.h" + +namespace clang::tidy::generalsgamecode::readability { + +/// Finds uses of getLength() == 0 or getLength() > 0 on AsciiString and +/// UnicodeString, and Get_Length() == 0 on StringClass and WideStringClass, +/// and suggests using isEmpty()/Is_Empty() or !isEmpty()/!Is_Empty() instead. +/// Also finds uses of compare("") == 0 or compare(TheEmptyString) == 0 and +/// suggests using isEmpty() instead. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/generals-use-is-empty.html +class UseIsEmptyCheck : public ClangTidyCheck { +public: + UseIsEmptyCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } +}; + +} // namespace clang::tidy::generalsgamecode::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GENERALSGAMECODE_READABILITY_USEISEMPTYCHECK_H diff --git a/scripts/clang-tidy-plugin/readability/UseThisInsteadOfSingletonCheck.cpp b/scripts/clang-tidy-plugin/readability/UseThisInsteadOfSingletonCheck.cpp new file mode 100644 index 00000000000..51eff283ef6 --- /dev/null +++ b/scripts/clang-tidy-plugin/readability/UseThisInsteadOfSingletonCheck.cpp @@ -0,0 +1,225 @@ +#include "UseThisInsteadOfSingletonCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/Type.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/Lexer.h" + +using namespace clang; +using namespace clang::ast_matchers; + +namespace clang::tidy::generalsgamecode::readability { + + +static const CXXMethodDecl *getEnclosingMethod(ASTContext *Context, + const Stmt *S) { + const CXXMethodDecl *Method = nullptr; + + auto Parents = Context->getParents(*S); + while (!Parents.empty()) { + if (const auto *M = Parents[0].get()) { + if (!M->isStatic()) { + Method = M; + break; + } + } + Parents = Context->getParents(Parents[0]); + } + + return Method; +} + +static bool typesMatch(const QualType &SingletonType, + const CXXRecordDecl *EnclosingClass) { + if (!EnclosingClass) { + return false; + } + + const Type *TypePtr = SingletonType.getTypePtrOrNull(); + if (!TypePtr) { + return false; + } + + if (const PointerType *PtrType = TypePtr->getAs()) { + QualType PointeeType = PtrType->getPointeeType(); + if (const RecordType *RecordTy = PointeeType->getAs()) { + if (const CXXRecordDecl *RecordDecl = + dyn_cast(RecordTy->getDecl())) { + return RecordDecl->getCanonicalDecl() == + EnclosingClass->getCanonicalDecl(); + } + } + } + + return false; +} + +void UseThisInsteadOfSingletonCheck::registerMatchers(MatchFinder *Finder) { + auto MemberExprMatcher = memberExpr( + hasObjectExpression(ignoringParenImpCasts(declRefExpr(to(varDecl(anyOf(hasGlobalStorage(), hasExternalFormalLinkage())).bind("singletonVar"))))), + hasDeclaration(fieldDecl()), + unless(hasAncestor(cxxMemberCallExpr()))) + .bind("memberExpr"); + + auto MemberCallMatcher = cxxMemberCallExpr( + on(ignoringParenImpCasts(declRefExpr(to(varDecl(anyOf(hasGlobalStorage(), hasExternalFormalLinkage())).bind("singletonVarCall")))))) + .bind("memberCall"); + + Finder->addMatcher(MemberExprMatcher, this); + Finder->addMatcher(MemberCallMatcher, this); +} + +void UseThisInsteadOfSingletonCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *MemExpr = Result.Nodes.getNodeAs("memberExpr"); + const auto *MemberCall = Result.Nodes.getNodeAs("memberCall"); + + if (!MemExpr && !MemberCall) { + return; + } + + const Stmt *TargetStmt = nullptr; + const VarDecl *FoundSingletonVar = nullptr; + bool IsCall = false; + + if (MemberCall) { + IsCall = true; + TargetStmt = MemberCall; + FoundSingletonVar = Result.Nodes.getNodeAs("singletonVarCall"); + if (!FoundSingletonVar) { + const Expr *ImplicitObject = MemberCall->getImplicitObjectArgument(); + if (ImplicitObject) { + ImplicitObject = ImplicitObject->IgnoreParenImpCasts(); + if (const DeclRefExpr *DRE = dyn_cast(ImplicitObject)) { + FoundSingletonVar = dyn_cast(DRE->getDecl()); + } + } + } + } else if (MemExpr) { + TargetStmt = MemExpr; + FoundSingletonVar = Result.Nodes.getNodeAs("singletonVar"); + } + + if (!TargetStmt || !FoundSingletonVar) { + return; + } + + StringRef SingletonName = FoundSingletonVar->getName(); + if (!SingletonName.starts_with("The") || SingletonName.size() <= 3 || + (SingletonName[3] < 'A' || SingletonName[3] > 'Z')) { + return; + } + + const ASTContext *Context = Result.Context; + + const CXXMethodDecl *EnclosingMethod = getEnclosingMethod( + const_cast(Context), TargetStmt); + if (!EnclosingMethod) { + return; + } + + const CXXRecordDecl *EnclosingClass = EnclosingMethod->getParent(); + if (!EnclosingClass) { + return; + } + + QualType SingletonType = FoundSingletonVar->getType(); + if (!typesMatch(SingletonType, EnclosingClass)) { + return; + } + + const ValueDecl *Member = nullptr; + StringRef MemberName; + SourceLocation StartLoc, EndLoc; + + if (IsCall && MemberCall) { + const CXXMethodDecl *Method = MemberCall->getMethodDecl(); + if (!Method) { + return; + } + if (Method->isStatic()) { + return; + } + if (EnclosingMethod->isConst() && !Method->isConst()) { + return; + } + Member = Method; + MemberName = Method->getName(); + StartLoc = MemberCall->getBeginLoc(); + EndLoc = MemberCall->getEndLoc(); + } else if (MemExpr) { + Member = MemExpr->getMemberDecl(); + if (!Member) { + return; + } + MemberName = Member->getName(); + StartLoc = MemExpr->getBeginLoc(); + EndLoc = MemExpr->getEndLoc(); + } else { + return; + } + + SourceManager &SM = *Result.SourceManager; + + std::string Replacement = std::string(MemberName); + + if (IsCall && MemberCall) { + SourceLocation RParenLoc = MemberCall->getRParenLoc(); + const Expr *Callee = MemberCall->getCallee(); + + if (RParenLoc.isValid() && Callee) { + SourceLocation CalleeEnd = Lexer::getLocForEndOfToken( + Callee->getEndLoc(), 0, SM, Result.Context->getLangOpts()); + + if (CalleeEnd.isValid() && CalleeEnd < RParenLoc) { + SourceLocation ArgsStart = CalleeEnd; + SourceLocation ArgsEnd = Lexer::getLocForEndOfToken( + RParenLoc, 0, SM, Result.Context->getLangOpts()); + + if (ArgsStart.isValid() && ArgsEnd.isValid() && ArgsStart < ArgsEnd) { + StringRef ArgsText = Lexer::getSourceText( + CharSourceRange::getCharRange(ArgsStart, ArgsEnd), SM, + Result.Context->getLangOpts()); + ArgsText = ArgsText.ltrim(); + if (!ArgsText.empty()) { + Replacement += ArgsText.str(); + } else { + Replacement += "()"; + } + } else { + Replacement += "()"; + } + } else { + SourceLocation CallEnd = Lexer::getLocForEndOfToken( + MemberCall->getEndLoc(), 0, SM, Result.Context->getLangOpts()); + if (CalleeEnd.isValid() && CallEnd.isValid() && CalleeEnd < CallEnd) { + StringRef ArgsText = Lexer::getSourceText( + CharSourceRange::getCharRange(CalleeEnd, CallEnd), SM, + Result.Context->getLangOpts()); + ArgsText = ArgsText.ltrim(); + if (!ArgsText.empty()) { + Replacement += ArgsText.str(); + } else { + Replacement += "()"; + } + } else { + Replacement += "()"; + } + } + } else { + Replacement += "()"; + } + } + + diag(StartLoc, "use '%0' instead of '%1->%2' when inside a member function") + << Replacement << FoundSingletonVar->getName() << MemberName + << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(StartLoc, EndLoc), Replacement); +} + +} // namespace clang::tidy::generalsgamecode::readability + diff --git a/scripts/clang-tidy-plugin/readability/UseThisInsteadOfSingletonCheck.h b/scripts/clang-tidy-plugin/readability/UseThisInsteadOfSingletonCheck.h new file mode 100644 index 00000000000..1005d5d84b5 --- /dev/null +++ b/scripts/clang-tidy-plugin/readability/UseThisInsteadOfSingletonCheck.h @@ -0,0 +1,22 @@ +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GENERALSGAMECODE_READABILITY_USETHISINSTEADOFSINGLETONCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GENERALSGAMECODE_READABILITY_USETHISINSTEADOFSINGLETONCHECK_H + +#include "clang-tidy/ClangTidyCheck.h" + +namespace clang::tidy::generalsgamecode::readability { + +class UseThisInsteadOfSingletonCheck : public ClangTidyCheck { +public: + UseThisInsteadOfSingletonCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } +}; + +} // namespace clang::tidy::generalsgamecode::readability + +#endif + diff --git a/scripts/cpp/apply_code_formatting.py b/scripts/cpp/apply_code_formatting.py new file mode 100644 index 00000000000..aa223652f81 --- /dev/null +++ b/scripts/cpp/apply_code_formatting.py @@ -0,0 +1,66 @@ +# Created with python 3.11.4 + +# This script applies basic formatting and cleanups to the various CPP files. +# Just run it. + +import glob +import os + + +def apply_formatting(line: str) -> str: + # Strip useless comments behind scope end + scopeEndIndex = line.find('}') + if scopeEndIndex >= 0: + if scopeEndIndex == 0 or line[:scopeEndIndex].isspace(): + afterScopeIndex = scopeEndIndex + 1 + while afterScopeIndex < len(line) and line[afterScopeIndex] == ';': + afterScopeIndex += 1 + commentBeginIndex = line.find("//") + namespaceCommentBeginIndex = line.find("// namespace") + if commentBeginIndex >= 0 and namespaceCommentBeginIndex < 0: + if afterScopeIndex == commentBeginIndex or line[afterScopeIndex:commentBeginIndex].isspace(): + line = line[:commentBeginIndex] + + # remove trailing whitespace + line = line.rstrip() + line += "\n" + + return line + + +def main(): + current_dir = os.path.dirname(os.path.abspath(__file__)) + root_dir = os.path.join(current_dir, "..", "..") + root_dir = os.path.normpath(root_dir) + core_dir = os.path.join(root_dir, "Core") + generals_dir = os.path.join(root_dir, "Generals") + generalsmd_dir = os.path.join(root_dir, "GeneralsMD") + utility_dir = os.path.join(root_dir, "Dependencies", "Utility") + fileNames = [] + for ext in ["*.cpp", "*.h", "*.inl"]: + fileNames.extend(glob.glob(os.path.join(core_dir, '**', ext), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generals_dir, '**', ext), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generalsmd_dir, '**', ext), recursive=True)) + fileNames.extend(glob.glob(os.path.join(utility_dir, '**', ext), recursive=True)) + + for fileName in fileNames: + with open(fileName, 'r', encoding="cp1252") as file: + try: + lines = file.readlines() + except UnicodeDecodeError: + continue # Not good. + + with open(fileName, 'w', encoding="cp1252") as file: + for line in lines: + line = apply_formatting(line) + file.write(line) + if lines: + lastLine = lines[-1] + if lastLine and lastLine[-1] != '\n': + file.write("\n") # write new line to end of file + + return + + +if __name__ == "__main__": + main() diff --git a/scripts/cpp/fixInludesCase.sh b/scripts/cpp/fixInludesCase.sh new file mode 100755 index 00000000000..59cfa043608 --- /dev/null +++ b/scripts/cpp/fixInludesCase.sh @@ -0,0 +1,162 @@ +#!/bin/sh + +# BSD Zero Clause License +# +# Copyright (c) 2025 zzambers +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +set -eu + +scriptName="$0" + +printHelp() { +cat << EOF +USAGE: +${scriptName} [OPTION]... SRC_DIR + +Fixes filename case include directives of source files found in SRC_DIR, +as necessary. It also fixes slashes ( \\ -> / ). + +Details: +Script tries to find included file in include paths. (first relative to source +file's directory, then in specified include paths). First it tries +case-sensitive search in all paths. If that fails, it tries case-insensitive +search in all include paths (excpet for nofix paths). If it succseeds, it fixes +filename in include directory as necessary. If it fails no change is done. +Slashes are fixed either way, if necessary ( \\ -> / ). + +Limitations: +Script does not support include paths with spaces. +Script cannot fix case in include directive starting with ../ (parent dir), +except for fixing slashes. + +OPTIONS: +-I [PATH] +--include-path [PATH] + include path, where to search included files. Also used to fix filename + case as necessary, if possible. This argument can be specified + more than once. + +-N [PATH] +--include-paths-nofix [PATH] + similar include-path, but files here are not considered when trying to fix + case (only in search, if file exists) This argument can be specified + more than once. +EOF +exit 0 +} + +includePaths="" +includePathsNofix="" +ignoredIncludes="" + +fixFile() { + file="$1" + regex='^[[:space:]]*#[[:space:]]*include[[:space:]]*["<](.*[.][hH])[">].*$' + includeLines="$( cat "$file" | grep -E "$regex" )" || : + if [ -z "${includeLines}" ] ; then + return + fi + dir="$(dirname "${file}" )" + + printf "%s\n" "${includeLines}" | while IFS='' read -r includeL ; do + includeOrig="$( echo "$includeL" | sed -E "s;${regex};\\1;g" )" + include="$( echo "$includeOrig" | sed -E 's;\\+;/;g' )" + + if echo "${ignoredIncludes:-}" | grep -qi "^${include}\$" ; then + continue + fi + found=0 + for path in "$( dirname "${file}" )" ${includePaths:-} ${includePathsNofix:-} ; do + if [ -e "${path}/${include}" ] ; then + found=1 + break + fi + done + includeOrigEscaped="$( echo "${includeOrig}" | sed 's;\\;[\\];g' )" + updated=0 + if [ $found -eq 0 ]; then + echo "Srcfile: ${file}" + echo "missing: ${include}" + + includeLower="$( echo "${include}" | tr '[:upper:]' '[:lower:]' )" + for path in "$( dirname "${file}" )" ${includePaths:-} ; do + for testedFile in $( find "${path}" -iname "$( basename "${include}" )" ) ; do + testedLower="$( echo "${testedFile}" | tr '[:upper:]' '[:lower:]' )" + if echo "${testedLower}" | grep -q "${includeLower}\$" ; then + fixed="${testedFile#${path}/}" + fixedLower="$( echo "$fixed" | tr '[:upper:]' '[:lower:]' )" + if [ "${includeLower}" = "${fixedLower}" ] ; then + echo "fix: ${testedFile#${path}/}" + updated=1 + sed -E -i "s;([\"<])${includeOrigEscaped}([\">]);\\1${fixed}\\2;g" "${file}" + break + fi + fi + done + [ $updated -eq 1 ] && break + done + echo + fi + [ $updated -eq 1 ] && continue + if ! [ "${include}" = "${includeOrig}" ] ; then + echo "Srcfile: ${file}" + echo "orig: ${includeOrig}" + echo "fix: ${include}" + sed -E -i "s;([\"<])${includeOrigEscaped}([\">]);\\1${include}\\2;g" "${file}" + echo + fi + done +} + +fixTree() { + find "$@" | grep -iE '[.](c|cpp|h)$' | while IFS='' read -r file ; do + fixFile "${file}" + done +} + +main() { + while [ "$#" -gt 0 ] ; do + case "$1" in + -h|--help) + printHelp + ;; + -I|--include-path) + includePaths="$( printf '%s\nx' "${includePaths:-}$2" )" + includePaths="${includePaths%x}" # to keep newline + shift; + shift; + ;; + -N|--include-paths-nofix) + includePathsNofix="$( printf '%s\nx' "${includePathsNofix:-}$2" )" + includePathsNofix="${includePathsNofix%x}" # to keep newline + shift; + shift; + ;; + *) + printf "SRC_DIR:\n%s\n" "$@" + if [ "x${includePaths:-}" != "x" ] ; then + printf 'INCLUDE PATHS:\n%s' "$includePaths" + fi + if [ "x${includePathsNofix:-}" != "x" ] ; then + printf 'INCLUDE PATHS NOFIX:\n%s' "$includePathsNofix" + fi + echo + fixTree "$@" + break; + ;; + esac + done +} + +main "$@" diff --git a/scripts/cpp/harmonize_linebreaks_pragmaonce.py b/scripts/cpp/harmonize_linebreaks_pragmaonce.py new file mode 100644 index 00000000000..cb53e0d0d3e --- /dev/null +++ b/scripts/cpp/harmonize_linebreaks_pragmaonce.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +""" +format_pragma_once_spacing.py + +Ensure consistent spacing around '#pragma once' in .h files: + +- Exactly one blank line AFTER '#pragma once'. +- Exactly one blank line BEFORE '#pragma once' IF there is any non-blank content above it. + (If '#pragma once' is already the first non-blank content in the file, we do NOT insert + a leading blank line.) + +We leave everything else untouched. + +Outputs: +- total .h files found +- files changed +- files without '#pragma once' +""" + +from __future__ import annotations +import argparse +import re +from pathlib import Path + +RE_PRAGMA_ONCE = re.compile(r'^\s*#\s*pragma\s+once\s*$', re.IGNORECASE) + +def is_blank(s: str) -> bool: + return s.strip() == "" + +def normalize_pragma_once_spacing(text: str) -> tuple[str, bool]: + """ + For each '#pragma once' line in the file, enforce spacing rules. + Returns (new_text, changed). + """ + # Preserve final newline behaviour + had_trailing_nl = text.endswith("\n") + lines = text.splitlines() + + # Find all pragma indices + pragma_idxs = [i for i, ln in enumerate(lines) if RE_PRAGMA_ONCE.match(ln)] + if not pragma_idxs: + return text, False + + changed = False + # We'll operate on a mutable list; keep track of index shifts when inserting/removing + i = 0 + while i < len(lines): + if i in pragma_idxs: + # ----- BEFORE: ensure exactly one blank line IF there is any non-blank above ----- + # Count run of blank lines immediately above + j = i - 1 + blanks_before = 0 + while j >= 0 and is_blank(lines[j]): + blanks_before += 1 + j -= 1 + + has_nonblank_above = j >= 0 # there's some non-blank content above + + # If there is content above, enforce exactly one blank line before + if has_nonblank_above: + # Remove all blank lines immediately above + if blanks_before > 1: + # delete the extra blanks, keep none for now + del lines[i - blanks_before: i - 1] # keep one slot for later insert + i -= (blanks_before - 1) + changed = True + elif blanks_before == 0: + # none present now; we'll insert one below + pass + # After removals, ensure exactly one blank line + if i - 1 < 0 or not is_blank(lines[i - 1]): + lines.insert(i, "") + i += 1 # pragma shifts down by one + # Update pragma index list too + pragma_idxs = [p + 1 if p >= i - 1 else p for p in pragma_idxs] + changed = True + else: + # No nonblank content above; ensure there are zero blank lines above + if blanks_before > 0: + del lines[i - blanks_before:i] + i -= blanks_before + # Adjust pragma indices after deletion + removed = blanks_before + pragma_idxs = [p - removed if p >= i else p for p in pragma_idxs] + changed = True + + # Recompute blanks_after starting from (possibly) updated i + # ----- AFTER: ensure exactly one blank line after ----- + k = i + 1 + blanks_after = 0 + while k < len(lines) and is_blank(lines[k]): + blanks_after += 1 + k += 1 + + # Remove all blank lines after, then insert exactly one + if blanks_after != 1: + # remove all current blanks after + if blanks_after > 0: + del lines[i + 1 : i + 1 + blanks_after] + # adjust pragma indices after deletion (those after k shift left) + # Not strictly needed for correctness here + changed = True + # insert exactly one blank line + lines.insert(i + 1, "") + changed = True + + # Advance past the pragma and the single enforced blank after it + i = i + 2 + continue + + i += 1 + + new_text = "\n".join(lines) + ("\n" if had_trailing_nl else "") + return new_text, changed + +def main(): + ap = argparse.ArgumentParser(description="Ensure exactly one empty line before/after '#pragma once' in .h files.") + ap.add_argument("directory", type=Path, help="Root directory to scan recursively") + args = ap.parse_args() + + root: Path = args.directory + if not root.exists() or not root.is_dir(): + print(f"Error: {root} is not a directory") + raise SystemExit(2) + + headers = [p for p in root.rglob("*.h") if p.is_file()] + changed = 0 + no_pragma: list[str] = [] + + for hdr in headers: + try: + text = hdr.read_text(encoding="utf-8", errors="replace") + except Exception as e: + print(f"Read error: {hdr} ({e})") + continue + + new_text, did_change = normalize_pragma_once_spacing(text) + if did_change: + try: + hdr.write_text(new_text, encoding="utf-8") + changed += 1 + except Exception as e: + print(f"Write error: {hdr} ({e})") + else: + # Determine if it lacked pragma + if not RE_PRAGMA_ONCE.search(text): + no_pragma.append(str(hdr)) + + print(f"Total .h files found: {len(headers)}") + print(f"Files changed: {changed}") + if no_pragma: + print("Files without '#pragma once':") + for p in no_pragma: + print(f" - {p}") + +if __name__ == "__main__": + main() diff --git a/scripts/cpp/refactor_asciistring_unicodestring_instantiation.py b/scripts/cpp/refactor_asciistring_unicodestring_instantiation.py new file mode 100644 index 00000000000..9d72bc150b5 --- /dev/null +++ b/scripts/cpp/refactor_asciistring_unicodestring_instantiation.py @@ -0,0 +1,50 @@ +# Created with python 3.11.4 + +# This script applies basic formatting and cleanups to the various CPP files. +# Just run it. + +import glob +import os +import re + + +def fix_string(line: str, typename: str) -> str: + # Build a regex that allows arbitrary whitespace + pattern = rf"""{typename}\s*\(\s*(L?)\s*"([^"]*)"\s*\)""" + # Replace typename( "value" ) -> "value" + return re.sub(pattern, r'\1"\2"', line) + + +def main(): + current_dir = os.path.dirname(os.path.abspath(__file__)) + root_dir = os.path.join(current_dir, "..", "..") + root_dir = os.path.normpath(root_dir) + core_dir = os.path.join(root_dir, "Core") + generals_dir = os.path.join(root_dir, "Generals") + generalsmd_dir = os.path.join(root_dir, "GeneralsMD") + utility_dir = os.path.join(root_dir, "Dependencies", "Utility") + fileNames = [] + for ext in ["*.cpp", "*.h", "*.inl"]: + fileNames.extend(glob.glob(os.path.join(core_dir, '**', ext), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generals_dir, '**', ext), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generalsmd_dir, '**', ext), recursive=True)) + fileNames.extend(glob.glob(os.path.join(utility_dir, '**', ext), recursive=True)) + + for fileName in fileNames: + with open(fileName, 'r', encoding="cp1252") as file: + try: + lines = file.readlines() + except UnicodeDecodeError: + continue # Not good. + + with open(fileName, 'w', encoding="cp1252") as file: + for line in lines: + line = fix_string(line, 'AsciiString') + line = fix_string(line, 'UnicodeString') + file.write(line) + + return + + +if __name__ == "__main__": + main() diff --git a/scripts/cpp/refactor_debug_log_newline.py b/scripts/cpp/refactor_debug_log_newline.py new file mode 100644 index 00000000000..177a6275f1f --- /dev/null +++ b/scripts/cpp/refactor_debug_log_newline.py @@ -0,0 +1,98 @@ +# Created with python 3.11.4 + +# This script helps removing trailing CR LF characters from game debug log messages in the various CPP files. +# Just run it. + +import glob +import os + + +def modifyLine(line: str) -> str: + searchWords = [ + "DEBUG_LOG", + "DEBUG_LOG_LEVEL", + "DEBUG_CRASH", + "DEBUG_ASSERTLOG", + "DEBUG_ASSERTCRASH", + "RELEASE_CRASH", + "RELEASE_CRASHLOCALIZED", + "WWDEBUG_SAY", + "WWDEBUG_WARNING", + "WWRELEASE_SAY", + "WWRELEASE_WARNING", + "WWRELEASE_ERROR", + "WWASSERT_PRINT", + "WWDEBUG_ERROR", + "SNAPSHOT_SAY", + "SHATTER_DEBUG_SAY", + "DBGMSG", + ### "REALLY_VERBOSE_LOG", + "DOUBLE_DEBUG", + "PERF_LOG", + "CRCGEN_LOG", + "STATECHANGED_LOG", + "PING_LOG", + "BONEPOS_LOG", + ] + + SEARCH_PATTERNS = [ + r'\r\n"', + r'\n"', + ] + + for searchWord in searchWords: + wordBegin = line.find(searchWord) + wordEnd = wordBegin + len(searchWord) + + if wordBegin >= 0: + for searchPattern in SEARCH_PATTERNS: + searchPatternLen = len(searchPattern) + i = wordEnd + lookEnd = len(line) - searchPatternLen + + while i < lookEnd: + pattern = line[i:i+searchPatternLen] + if pattern == searchPattern: + lineCopy = line[:i] + '"' + line[i+searchPatternLen:] + return lineCopy + i += 1 + + break + + return line + + +def main(): + current_dir = os.path.dirname(os.path.abspath(__file__)) + root_dir = os.path.join(current_dir, "..", "..") + root_dir = os.path.normpath(root_dir) + core_dir = os.path.join(root_dir, "Core") + generals_dir = os.path.join(root_dir, "Generals") + generalsmd_dir = os.path.join(root_dir, "GeneralsMD") + fileNames = [] + fileNames.extend(glob.glob(os.path.join(core_dir, '**', '*.h'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(core_dir, '**', '*.cpp'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(core_dir, '**', '*.inl'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generals_dir, '**', '*.h'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generals_dir, '**', '*.cpp'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generals_dir, '**', '*.inl'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generalsmd_dir, '**', '*.h'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generalsmd_dir, '**', '*.cpp'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generalsmd_dir, '**', '*.inl'), recursive=True)) + + for fileName in fileNames: + with open(fileName, 'r', encoding="cp1252") as file: + try: + lines = file.readlines() + except UnicodeDecodeError: + continue # Not good. + with open(fileName, 'w', encoding="cp1252") as file: + for line in lines: + line = modifyLine(line) + file.write(line) + + return + + +if __name__ == "__main__": + main() diff --git a/scripts/cpp/refactor_delete_instance.py b/scripts/cpp/refactor_delete_instance.py new file mode 100644 index 00000000000..72e279890a2 --- /dev/null +++ b/scripts/cpp/refactor_delete_instance.py @@ -0,0 +1,93 @@ +# Created with python 3.11.4 + +import glob +import os + + +def modifyLine(line: str) -> str: + if 'friend_deleteInstance()' in line: + return line + + deleteInstanceBegin = line.find('deleteInstance()') + deleteInstanceEnd = deleteInstanceBegin + len('deleteInstance()') + if deleteInstanceBegin >= 0: + i = deleteInstanceBegin + + # Skip MemoryPoolObject::deleteInstance() + if i >= 2 and line[i-2:i] == '::': + return line + + # Skip void deleteInstance() + if i >= 5 and line[i-5:i] == 'void ': + return line + + # Skip void friend_deleteInstance() + if i >= 5 and line[i-5:i] == 'void ': + return line + + # Walk back to object end + i -= 1 + while i >= 0: + ch = line[i] + if ch != '>' and ch != '-' and not ch.isspace(): + break + i -= 1 + objectEnd = i + 1 + + # Walk back to object begin + while i >= 0: + ch = line[i] + if ch.isspace() or ch == '{' or ch == '}': + break + i -= 1 + objectBegin = i + 1 + objectName = line[objectBegin:objectEnd] + + if objectName: + lineCopy = line[:objectBegin] + lineCopy += f'MemoryPoolObject::deleteInstance({objectName})' + lineCopy += line[deleteInstanceEnd:] + return lineCopy + else: + lineCopy = line[:deleteInstanceBegin] + lineCopy += 'MemoryPoolObject::deleteInstance(this)' + lineCopy += line[deleteInstanceEnd:] + return lineCopy + + return line + + +def main(): + current_dir = os.path.dirname(os.path.abspath(__file__)) + root_dir = os.path.join(current_dir, "..", "..") + root_dir = os.path.normpath(root_dir) + core_dir = os.path.join(root_dir, "Core") + generals_dir = os.path.join(root_dir, "Generals") + generalsmd_dir = os.path.join(root_dir, "GeneralsMD") + fileNames = [] + fileNames.extend(glob.glob(os.path.join(core_dir, '**', '*.h'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(core_dir, '**', '*.cpp'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(core_dir, '**', '*.inl'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generals_dir, '**', '*.h'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generals_dir, '**', '*.cpp'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generals_dir, '**', '*.inl'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generalsmd_dir, '**', '*.h'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generalsmd_dir, '**', '*.cpp'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generalsmd_dir, '**', '*.inl'), recursive=True)) + + for fileName in fileNames: + with open(fileName, 'r', encoding="cp1252") as file: + try: + lines = file.readlines() + except UnicodeDecodeError: + continue # Not good. + with open(fileName, 'w', encoding="cp1252") as file: + for line in lines: + line = modifyLine(line) + file.write(line) + + return + + +if __name__ == "__main__": + main() diff --git a/scripts/cpp/remove_duplicate_include_directives.py b/scripts/cpp/remove_duplicate_include_directives.py new file mode 100644 index 00000000000..230bd874410 --- /dev/null +++ b/scripts/cpp/remove_duplicate_include_directives.py @@ -0,0 +1,41 @@ +# Created with python 3.13.2 + +# This script removes duplicate include directives from the codebase of Generals and GeneralsMD. + +import os +import re + +current_dir = os.path.dirname(os.path.abspath(__file__)) +root_dir = os.path.join(current_dir, "..", "..") +root_dir = os.path.normpath(root_dir) +core_dir = os.path.join(root_dir, "Core") +generals_dir = os.path.join(root_dir, "Generals", "Code") +generalsmd_dir = os.path.join(root_dir, "GeneralsMD", "Code") + +def remove_duplicate_includes_from_file(filepath): + include_pattern = re.compile(r'^\s*#\s*include\s+[<"].+[>"].*$', re.MULTILINE) + seen = set() + output_lines = [] + + with open(filepath, 'r', encoding='utf-8') as f: + for line in f: + match = include_pattern.match(line) + if match: + normalized = line.strip() + if normalized in seen: + continue # Skip duplicate + seen.add(normalized) + output_lines.append(line) + + with open(filepath, 'w', encoding='utf-8') as f: + f.writelines(output_lines) + +def process_directory(root_dir): + for subdir, _, files in os.walk(root_dir): + for file in files: + if file.endswith(('.cpp', '.h', '.hpp', '.c', '.inl')): + filepath = os.path.join(subdir, file) + remove_duplicate_includes_from_file(filepath) + +process_directory(generals_dir) +process_directory(generalsmd_dir) diff --git a/scripts/cpp/remove_include_guards_pragma.py b/scripts/cpp/remove_include_guards_pragma.py new file mode 100644 index 00000000000..d2554d7733a --- /dev/null +++ b/scripts/cpp/remove_include_guards_pragma.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +""" +remove_include_guards.py + +Recursively scan a directory for .h files and remove classic include guards +ONLY if: + 1) the file contains `#pragma once`, AND + 2) there is substantive content between the guard's #define and its matching #endif + (i.e., not just blank/comment lines). + +Safely handles both: +- #ifndef MACRO ... #define MACRO ... #endif +- #if !defined(MACRO) ... #define MACRO ... #endif + +Leaves #pragma once intact. Prints totals and files skipped (with reasons). +""" + +from __future__ import annotations +import argparse +import re +from pathlib import Path + +RE_PRAGMA_ONCE = re.compile(r'^\s*#\s*pragma\s+once\b', re.IGNORECASE) + +RE_IFNDEF = re.compile(r'^\s*#\s*ifndef\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:\/\/.*|/\*.*\*/\s*)?$', re.ASCII) +RE_IF_NOT_DEFINED = re.compile( + r'^\s*#\s*if\s*!+\s*defined\s*\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)\s*(?:\/\/.*|/\*.*\*/\s*)?$', + re.ASCII, +) +RE_DEFINE = re.compile(r'^\s*#\s*define\s+([A-Za-z_][A-Za-z0-9_]*)\b.*$', re.ASCII) +RE_IF = re.compile(r'^\s*#\s*if(n?def)?\b', re.ASCII) # #if/#ifdef/#ifndef +RE_ENDIF = re.compile(r'^\s*#\s*endif\b', re.ASCII) + +def _strip_bom(text: str) -> str: + return text.lstrip('\ufeff') + +def has_pragma_once(lines: list[str]) -> bool: + return any(RE_PRAGMA_ONCE.match(l) for l in lines) + +def is_comment_or_blank(s: str) -> bool: + t = s.strip() + if not t: + return True + # Treat single-line comments and block-comment-only lines as non-substantive. + return t.startswith('//') or t.startswith('/*') or t.endswith('*/') + +def has_substantive_content(lines: list[str], start_idx: int, end_idx: int) -> bool: + """ + Return True if there's at least one 'substantive' line between start_idx (inclusive) + and end_idx (exclusive). Substantive means not just blank/comment-only. + Any preprocessor directive like #include, #if, #define (for other macros), + code, etc. counts as content. (#endif is outside this range by design.) + """ + for k in range(start_idx, end_idx): + s = lines[k] + if not is_comment_or_blank(s): + return True + return False + +def find_guard(lines: list[str], search_window: int = 150): + """ + Locate a classic include guard near the file start. + + Returns (i_if, i_define, macro, i_endif) or None. + """ + n = len(lines) + lim = min(search_window, n) + + for i in range(lim): + line = lines[i] + m1 = RE_IFNDEF.match(line) + m2 = RE_IF_NOT_DEFINED.match(line) + macro = None + if m1: + macro = m1.group(1) + elif m2: + macro = m2.group(1) + else: + continue + + # Find #define with the same macro within a few lines, skipping blanks/comments + j = i + 1 + max_lookahead = min(i + 10, n - 1) + found_define_index = None + while j <= max_lookahead: + l2 = lines[j] + if is_comment_or_blank(l2): + j += 1 + continue + mdef = RE_DEFINE.match(l2) + if mdef and mdef.group(1) == macro: + found_define_index = j + break + if found_define_index is None: + continue + + # Find the matching #endif for the opening #if/#ifndef at i + i_endif = match_endif(lines, start_index=found_define_index + 1, initial_depth=1) + if i_endif is None: + continue + + return (i, found_define_index, macro, i_endif) + + return None + +def match_endif(lines: list[str], start_index: int, initial_depth: int = 1): + depth = initial_depth + for k in range(start_index, len(lines)): + s = lines[k] + if not s.lstrip().startswith('#'): + continue + if RE_IF.match(s): + depth += 1 + elif RE_ENDIF.match(s): + depth -= 1 + if depth == 0: + return k + return None + +def remove_guard_from_text(text: str) -> tuple[str, bool, str | None]: + """ + Remove include guard if detected AND: + - file has #pragma once + - there is substantive content between #define and #endif + Returns (new_text, changed_bool, reason_if_not_changed). + """ + text = _strip_bom(text) + lines = text.splitlines() + + if not has_pragma_once(lines): + return text, False, "no #pragma once" + + found = find_guard(lines) + if not found: + return text, False, "no classic guard detected" + + i_if, i_define, macro, i_endif = found + + # NEW RULE: ensure the guarded region contains more than just the macro define. + if not has_substantive_content(lines, i_define + 1, i_endif): + # Likely a utility macro wrapper like: + # #ifndef X + # #define X ... + # #endif + return text, False, "guard region has no content (macro wrapper)" + + # Proceed to remove the guard + to_remove = {i_if, i_define} + if i_define + 1 < len(lines) and lines[i_define + 1].strip() == '': + to_remove.add(i_define + 1) + if i_if - 1 >= 0 and lines[i_if - 1].strip() == '': + to_remove.add(i_if - 1) + + to_remove.add(i_endif) + if i_endif - 1 >= 0 and lines[i_endif - 1].strip() == '': + to_remove.add(i_endif - 1) + + new_lines = [ln for idx, ln in enumerate(lines) if idx not in to_remove] + + # Tidy leading/trailing blanks + while new_lines and new_lines[0].strip() == '': + new_lines.pop(0) + while new_lines and new_lines[-1].strip() == '': + new_lines.pop() + + new_text = "\n".join(new_lines) + ("\n" if text.endswith("\n") else "") + return new_text, True, None + +def main(): + ap = argparse.ArgumentParser(description="Remove classic include guards from .h files that already use #pragma once, skipping macro-only wrappers.") + ap.add_argument("directory", type=Path, help="Root directory to scan recursively") + args = ap.parse_args() + + root: Path = args.directory + if not root.exists() or not root.is_dir(): + print(f"Error: {root} is not a directory") + raise SystemExit(2) + + all_headers = [p for p in root.rglob("*.h") if p.is_file()] + changed = 0 + failed: list[str] = [] + + for hdr in all_headers: + try: + text = hdr.read_text(encoding="utf-8", errors="replace") + except Exception as e: + failed.append(f"{hdr} (read-error: {e})") + continue + + new_text, did_change, reason = remove_guard_from_text(text) + if did_change: + try: + hdr.write_text(new_text, encoding="utf-8") + changed += 1 + except Exception as e: + failed.append(f"{hdr} (write-error: {e})") + else: + failed.append(f"{hdr} ({reason or 'unknown reason'})") + + print(f"Total .h files found: {len(all_headers)}") + print(f"Files changed: {changed}") + if failed: + print("Guards not removed from:") + for path in failed: + print(f" - {path}") + +if __name__ == "__main__": + main() diff --git a/scripts/cpp/remove_mscver_from_pragma.py b/scripts/cpp/remove_mscver_from_pragma.py new file mode 100644 index 00000000000..8870789d0a2 --- /dev/null +++ b/scripts/cpp/remove_mscver_from_pragma.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +""" +Remove MSVC-only guards around '#pragma once' in .h files, preserving surrounding blank lines. + +Recognised guard forms: + #if defined(_MSC_VER) + #ifdef _MSC_VER + #if _MSC_VER >= 1000 + +Removes only: + - the '#if...' line, + - any blank lines immediately after it, + - any blank lines immediately after '#pragma once', + - the matching '#endif' line (optionally with trailing comment). + +Usage: + python unguard_pragma_once_universal.py /path/to/dir +""" + +import sys +import re +from pathlib import Path + +# Match any of the IF guard forms. No VERBOSE flag; escape literal '#'. +RE_IF = re.compile( + r'^\s*\#\s*' + r'(?:' + r'if\s+defined\s*\(\s*_MSC_VER\s*\)' # #if defined(_MSC_VER) + r'|ifdef\s+_MSC_VER' # #ifdef _MSC_VER + r'|if\s+_MSC_VER(?:\s*[<>!=]=?\s*\d+)?' # #if _MSC_VER [op num] + r')' + r'\s*(?://.*)?\r?\n?$', # optional end-of-line comment + re.IGNORECASE +) + +RE_PRAGMA = re.compile(r'^\s*\#\s*pragma\s+once\s*\r?\n?$', re.IGNORECASE) +RE_ENDIF = re.compile(r'^\s*\#\s*endif\b.*\r?\n?$', re.IGNORECASE) +RE_BLANK = re.compile(r'^[ \t]*\r?\n?$') + +def read_text_with_fallback(p: Path) -> str: + for enc in ("utf-8", "utf-8-sig", "cp1252", "latin-1"): + try: + return p.read_text(encoding=enc) + except UnicodeDecodeError: + continue + return p.read_bytes().decode("latin-1", errors="replace") + +def unguard_msc_pragma_once(text: str) -> tuple[str, bool]: + lines = text.splitlines(keepends=True) + i = 0 + changed = False + + while i < len(lines): + if not RE_IF.match(lines[i]): + i += 1 + continue + + if_idx = i + j = i + 1 + + # remove blanks immediately after #if + while j < len(lines) and RE_BLANK.match(lines[j]): + j += 1 + + # require #pragma once next + if j >= len(lines) or not RE_PRAGMA.match(lines[j]): + i += 1 + continue + + pragma_idx = j + j += 1 + + # remove blanks immediately after pragma + while j < len(lines) and RE_BLANK.match(lines[j]): + j += 1 + + # require #endif next + if j >= len(lines) or not RE_ENDIF.match(lines[j]): + i += 1 + continue + + endif_idx = j + + # Keep: everything before IF, the pragma line itself, everything after ENDIF + new_lines = [] + new_lines.extend(lines[:if_idx]) # preserves any blank lines before the block + new_lines.append(lines[pragma_idx]) # keep '#pragma once' + new_lines.extend(lines[endif_idx + 1:]) # preserves any blank lines after the block + + lines = new_lines + changed = True + + # Resume scanning near the pragma location in the new list + i = max(0, if_idx - 1) + + return "".join(lines), changed + +def main(): + if len(sys.argv) != 2: + print("Usage: python unguard_pragma_once_universal.py /path/to/dir", file=sys.stderr) + sys.exit(2) + + root = Path(sys.argv[1]) + if not root.is_dir(): + print(f"Error: {root} is not a directory", file=sys.stderr) + sys.exit(2) + + total = 0 + changed_count = 0 + + for p in root.rglob("*.h"): + total += 1 + try: + original = read_text_with_fallback(p) + updated, changed = unguard_msc_pragma_once(original) + if changed: + p.write_text(updated, encoding="utf-8", newline="") + changed_count += 1 + except Exception as ex: + print(f"[ERROR] Failed to process {p}: {ex}", file=sys.stderr) + + print(f"Scanned {total} .h file(s); changed {changed_count} file(s).") + +if __name__ == "__main__": + main() diff --git a/scripts/cpp/remove_return.py b/scripts/cpp/remove_return.py new file mode 100644 index 00000000000..ee5e84eac58 --- /dev/null +++ b/scripts/cpp/remove_return.py @@ -0,0 +1,66 @@ +# Created with python 3.11.4 + +# This script aims to find and remove superfluous trailing return words in functions. +# Just run it. + +import glob +import os + + +def apply_fix(line: str, nextLine: str) -> str: + lineStripped = line.strip() + if (lineStripped.find("return;") == 0 or + lineStripped.find("return ;") == 0): + if nextLine.find("}") == 0: + return "" + + return line + + +def main(): + current_dir = os.path.dirname(os.path.abspath(__file__)) + root_dir = os.path.join(current_dir, "..", "..") + root_dir = os.path.normpath(root_dir) + core_dir = os.path.join(root_dir, "Core") + generals_dir = os.path.join(root_dir, "Generals") + generalsmd_dir = os.path.join(root_dir, "GeneralsMD") + utility_dir = os.path.join(root_dir, "Dependencies", "Utility") + fileNames = [] + for ext in ["*.cpp", "*.h", "*.inl"]: + fileNames.extend(glob.glob(os.path.join(core_dir, '**', ext), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generals_dir, '**', ext), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generalsmd_dir, '**', ext), recursive=True)) + fileNames.extend(glob.glob(os.path.join(utility_dir, '**', ext), recursive=True)) + + for fileName in fileNames: + with open(fileName, 'r', encoding="cp1252") as file: + try: + lines = file.readlines() + except UnicodeDecodeError: + continue # Not good. + + with open(fileName, 'w', encoding="cp1252") as file: + newLines = [] + for index,line in enumerate(lines): + if index+1 < len(lines): + nextLineIndex = index + 1 + nextLine = lines[nextLineIndex] + while (nextLine.isspace() or nextLine == "") and nextLineIndex+1 < len(lines): + nextLineIndex += 1 + nextLine = lines[nextLineIndex] + + line = apply_fix(line, nextLine) + + if line == "": + while (newLines and newLines[-1].isspace()) or (newLines and newLines[-1] == ""): + newLines.pop() + + newLines.append(line) + + file.writelines(newLines) + + return + + +if __name__ == "__main__": + main() diff --git a/scripts/cpp/remove_rts_internal.py b/scripts/cpp/remove_rts_internal.py new file mode 100644 index 00000000000..5c287772c76 --- /dev/null +++ b/scripts/cpp/remove_rts_internal.py @@ -0,0 +1,89 @@ +# Created with python 3.11.4 + +# This script helps removing RTS_INTERNAL words from the various CPP files. +# Just run it. + +import glob +import os + + +def modifyLine(line: str) -> str: + searchWords = [ + "!defined(RTS_DEBUG) && !defined(RTS_INTERNAL)", + "!defined(RTS_INTERNAL) && !defined(RTS_DEBUG)", + "defined(RTS_DEBUG) || defined(RTS_INTERNAL)", + "defined(RTS_INTERNAL) || defined(RTS_DEBUG)", + "defined( RTS_INTERNAL ) || defined( RTS_DEBUG )", + "defined RTS_DEBUG || defined RTS_INTERNAL", + "RTS_DEBUG || RTS_INTERNAL", + ] + + replaceWords = [ + "!defined(RTS_DEBUG)", + "!defined(RTS_DEBUG)", + "defined(RTS_DEBUG)", + "defined(RTS_DEBUG)", + "defined(RTS_DEBUG)", + "defined(RTS_DEBUG)", + "RTS_DEBUG", + ] + + for searchIdx, searchWord in enumerate(searchWords): + wordBegin = line.find(searchWord) + wordEnd = wordBegin + len(searchWord) + if wordBegin >= 0: + replaceWord = replaceWords[searchIdx] + lineCopy = line[:wordBegin] + replaceWord + line[wordEnd:] + return lineCopy + + return line + + +def main(): + current_dir = os.path.dirname(os.path.abspath(__file__)) + root_dir = os.path.join(current_dir, "..", "..") + root_dir = os.path.normpath(root_dir) + core_dir = os.path.join(root_dir, "Core") + generals_dir = os.path.join(root_dir, "Generals") + generalsmd_dir = os.path.join(root_dir, "GeneralsMD") + fileNames = [] + fileNames.extend(glob.glob(os.path.join(core_dir, '**', '*.h'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(core_dir, '**', '*.cpp'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(core_dir, '**', '*.inl'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generals_dir, '**', '*.h'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generals_dir, '**', '*.cpp'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generals_dir, '**', '*.inl'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generalsmd_dir, '**', '*.h'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generalsmd_dir, '**', '*.cpp'), recursive=True)) + fileNames.extend(glob.glob(os.path.join(generalsmd_dir, '**', '*.inl'), recursive=True)) + + for fileName in fileNames: + with open(fileName, 'r', encoding="cp1252") as file: + try: + lines = file.readlines() + except UnicodeDecodeError: + continue # Not good. + with open(fileName, 'w', encoding="cp1252") as file: + skipLine = 0 + for line in lines: + # Skip RTS_INTERNAL ifdef blocks + if skipLine > 0: + if "#if" in line: + skipLine += 1 + elif "#endif" in line: + skipLine -= 1 + continue + if skipLine > 0: + continue + if line == "#ifdef RTS_INTERNAL\n" or line == "#if defined(RTS_INTERNAL)\n": + skipLine += 1 + continue + + line = modifyLine(line) + file.write(line) + + return + + +if __name__ == "__main__": + main() diff --git a/scripts/cpp/replace_include_guards_with_pragma.py b/scripts/cpp/replace_include_guards_with_pragma.py new file mode 100644 index 00000000000..fc187ac52c9 --- /dev/null +++ b/scripts/cpp/replace_include_guards_with_pragma.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python3 +""" +replace_guards_with_pragma_once.py + +Recursively scan a directory for .h files that DO NOT contain `#pragma once`. +For those files, detect classic include guards and replace the guard with a +single `#pragma once`. + +We replace only when: + - A classic guard is found near the start: + #ifndef MACRO OR #if !defined(MACRO) + #define MACRO #define MACRO + ... (substantive content) ... + #endif + - There is substantive content between the #define and its matching #endif + (i.e., more than just blank lines or comment-only lines), to avoid replacing + macro wrapper patterns like: + #ifndef ARRAY_SIZE + #define ARRAY_SIZE(x) ... + #endif + +Output: +- total .h files found +- files changed +- list of files not changed (with reason) +""" + +from __future__ import annotations +import argparse +import re +from pathlib import Path + +RE_PRAGMA_ONCE = re.compile(r'^\s*#\s*pragma\s+once\b', re.IGNORECASE) + +RE_IFNDEF = re.compile(r'^\s*#\s*ifndef\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:\/\/.*|/\*.*\*/\s*)?$', re.ASCII) +RE_IF_NOT_DEFINED = re.compile( + r'^\s*#\s*if\s*!+\s*defined\s*\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)\s*(?:\/\/.*|/\*.*\*/\s*)?$', + re.ASCII, +) +RE_DEFINE = re.compile(r'^\s*#\s*define\s+([A-Za-z_][A-Za-z0-9_]*)\b.*$', re.ASCII) +RE_IF = re.compile(r'^\s*#\s*if(n?def)?\b', re.ASCII) # #if/#ifdef/#ifndef +RE_ENDIF = re.compile(r'^\s*#\s*endif\b', re.ASCII) + +def _strip_bom(text: str) -> str: + return text.lstrip('\ufeff') + +def has_pragma_once(lines: list[str]) -> bool: + return any(RE_PRAGMA_ONCE.match(l) for l in lines) + +def is_comment_or_blank(s: str) -> bool: + t = s.strip() + if not t: + return True + # treat single-line/comment-only lines as non-substantive + return t.startswith('//') or t.startswith('/*') or t.endswith('*/') + +def has_substantive_content(lines: list[str], start_idx: int, end_idx: int) -> bool: + """ + Return True if there's at least one non-blank, non-comment-only line + between start_idx (inclusive) and end_idx (exclusive). + """ + for k in range(start_idx, end_idx): + if not is_comment_or_blank(lines[k]): + return True + return False + +def match_endif(lines: list[str], start_index: int, initial_depth: int = 1): + """ + Find the index of the #endif that closes the #if/#ifndef starting before start_index. + Handles nested preprocessor blocks. + """ + depth = initial_depth + for k in range(start_index, len(lines)): + s = lines[k] + if not s.lstrip().startswith('#'): + continue + if RE_IF.match(s): + depth += 1 + elif RE_ENDIF.match(s): + depth -= 1 + if depth == 0: + return k + return None + +def find_guard(lines: list[str], search_window: int = 200): + """ + Locate classic include guard near the beginning (after possible license header). + Returns (i_if, i_define, macro, i_endif) or None. + """ + n = len(lines) + lim = min(search_window, n) + + for i in range(lim): + line = lines[i] + m1 = RE_IFNDEF.match(line) + m2 = RE_IF_NOT_DEFINED.match(line) + macro = None + if m1: + macro = m1.group(1) + elif m2: + macro = m2.group(1) + else: + continue + + # Find #define with the same macro in the next few lines, skipping blanks/comments + j = i + 1 + max_lookahead = min(i + 12, n - 1) + found_define_index = None + while j <= max_lookahead: + l2 = lines[j] + if is_comment_or_blank(l2): + j += 1 + continue + mdef = RE_DEFINE.match(l2) + if mdef and mdef.group(1) == macro: + found_define_index = j + break + if found_define_index is None: + continue + + # Find the closing #endif for this guard block + i_endif = match_endif(lines, start_index=found_define_index + 1, initial_depth=1) + if i_endif is None: + continue + + return (i, found_define_index, macro, i_endif) + + return None + +def replace_guard_with_pragma_once(text: str): + """ + If file lacks #pragma once and has a classic include guard with substantive content, + replace the guard with a single '#pragma once'. + Returns (new_text, changed_bool, reason_if_not_changed). + """ + text = _strip_bom(text) + lines = text.splitlines() + + if has_pragma_once(lines): + return text, False, "already has #pragma once" + + found = find_guard(lines) + if not found: + return text, False, "no classic guard detected" + + i_if, i_define, macro, i_endif = found + + # Ensure the region contains more than just the macro define + if not has_substantive_content(lines, i_define + 1, i_endif): + return text, False, "guard region has no content (macro wrapper)" + + # Build new content: + # - Replace the opening guard pair with a single '#pragma once' + # - Remove the closing '#endif' + new_lines = list(lines) + + # Prepare a tidy insertion: remove surrounding blank lines near opening/closing + remove_idxs = set() + + # Remove opening lines + remove_idxs.add(i_if) + remove_idxs.add(i_define) + + # Optional tidy: blank line immediately after the #define + if i_define + 1 < len(new_lines) and new_lines[i_define + 1].strip() == '': + remove_idxs.add(i_define + 1) + + # Optional tidy: blank line immediately before the #if + if i_if - 1 >= 0 and new_lines[i_if - 1].strip() == '': + remove_idxs.add(i_if - 1) + + # Remove closing '#endif' and an optional blank before it + remove_idxs.add(i_endif) + if i_endif - 1 >= 0 and new_lines[i_endif - 1].strip() == '': + remove_idxs.add(i_endif - 1) + + # Apply removals + new_lines = [ln for idx, ln in enumerate(new_lines) if idx not in remove_idxs] + + # Insert '#pragma once' at the original guard start position (adjusted for removals) + # Compute the new insertion index as the smallest kept index >= i_if + insertion_index = 0 + count = 0 + for idx in range(len(lines)): + if idx in remove_idxs: + continue + if idx >= i_if: + insertion_index = count + break + count += 1 + else: + insertion_index = 0 # fallback (shouldn't happen) + + new_lines.insert(insertion_index, "#pragma once") + + # Tidy leading/trailing blank lines + while new_lines and new_lines[0].strip() == '': + new_lines.pop(0) + while new_lines and new_lines[-1].strip() == '': + new_lines.pop() + + new_text = "\n".join(new_lines) + ("\n" if text.endswith("\n") else "") + return new_text, True, None + +def main(): + ap = argparse.ArgumentParser( + description="Replace classic include guards with #pragma once in .h files that don't already have it." + ) + ap.add_argument("directory", type=Path, help="Root directory to scan recursively") + args = ap.parse_args() + + root: Path = args.directory + if not root.exists() or not root.is_dir(): + print(f"Error: {root} is not a directory") + raise SystemExit(2) + + all_headers = [p for p in root.rglob("*.h") if p.is_file()] + changed = 0 + skipped: list[str] = [] + + for hdr in all_headers: + try: + text = hdr.read_text(encoding="utf-8", errors="replace") + except Exception as e: + skipped.append(f"{hdr} (read-error: {e})") + continue + + new_text, did_change, reason = replace_guard_with_pragma_once(text) + if did_change: + try: + hdr.write_text(new_text, encoding="utf-8") + changed += 1 + except Exception as e: + skipped.append(f"{hdr} (write-error: {e})") + else: + skipped.append(f"{hdr} ({reason or 'unknown reason'})") + + print(f"Total .h files found: {len(all_headers)}") + print(f"Files changed: {changed}") + if skipped: + print("Not changed:") + for path in skipped: + print(f" - {path}") + +if __name__ == "__main__": + main() diff --git a/scripts/cpp/unify_move_files.py b/scripts/cpp/unify_move_files.py new file mode 100644 index 00000000000..936b08844e6 --- /dev/null +++ b/scripts/cpp/unify_move_files.py @@ -0,0 +1,553 @@ +# Created with python 3.11.4 + +# This script helps with moving cpp files from Generals or GeneralsMD to Core + +import os +import shutil +from enum import Enum + + +class Game(Enum): + GENERALS = 0 + ZEROHOUR = 1 + CORE = 2 + + +class CmakeModifyType(Enum): + ADD_COMMENT = 0 + REMOVE_COMMENT = 1 + + +current_dir = os.path.dirname(os.path.abspath(__file__)) +root_dir = os.path.join(current_dir, "..", "..") +root_dir = os.path.normpath(root_dir) +core_dir = os.path.join(root_dir, "Core") +generals_dir = os.path.join(root_dir, "Generals", "Code") +generalsmd_dir = os.path.join(root_dir, "GeneralsMD", "Code") + + +def get_game_path(game: Game): + if game == Game.GENERALS: + return generals_dir + elif game == Game.ZEROHOUR: + return generalsmd_dir + elif game == Game.CORE: + return core_dir + assert(0) + + +def get_opposite_game(game: Game): + if game == Game.GENERALS: + return Game.ZEROHOUR + elif game == Game.ZEROHOUR: + return Game.GENERALS + assert(0) + + +def move_file(fromGame: Game, fromFile: str, toGame: Game, toFile: str): + fromPath = os.path.join(get_game_path(fromGame), os.path.normpath(fromFile)) + toPath = os.path.join(get_game_path(toGame), os.path.normpath(toFile)) + os.makedirs(os.path.dirname(toPath), exist_ok=True) + shutil.move(fromPath, toPath) + + +def delete_file(game: Game, path: str): + os.remove(os.path.join(get_game_path(game), os.path.normpath(path))) + + +def modify_cmakelists(cmakeFile: str, searchString: str, type: CmakeModifyType): + lines: list[str] + with open(cmakeFile, 'r', encoding="ascii") as file: + lines = file.readlines() + + with open(cmakeFile, 'w', encoding="ascii") as file: + for index, line in enumerate(lines): + if searchString in line: + if type == CmakeModifyType.ADD_COMMENT: + lines[index] = "#" + line + else: + lines[index] = line.replace("#", "", 1) + + file.writelines(lines) + + +def unify_file(fromGame: Game, fromFile: str, toGame: Game, toFile: str): + assert(toGame == Game.CORE) + + fromOppositeGame = get_opposite_game(fromGame) + fromOppositeGamePath = get_game_path(fromOppositeGame) + fromGamePath = get_game_path(fromGame) + toGamePath = get_game_path(toGame) + + fromFirstFolderIndex = fromFile.find("/") + toFirstFolderIndex = toFile.find("/") + assert(fromFirstFolderIndex > 0) + assert(toFirstFolderIndex > 0) + + fromFirstFolderName = fromFile[:fromFirstFolderIndex] + toFirstFolderName = toFile[:toFirstFolderIndex] + fromFileInCmake = fromFile[fromFirstFolderIndex+1:] + toFileInCmake = toFile[toFirstFolderIndex+1:] + + fromOppositeCmakeFile = os.path.join(fromOppositeGamePath, fromFirstFolderName, "CMakeLists.txt") + fromCmakeFile = os.path.join(fromGamePath, fromFirstFolderName, "CMakeLists.txt") + toCmakeFile = os.path.join(toGamePath, toFirstFolderName, "CMakeLists.txt") + + modify_cmakelists(fromOppositeCmakeFile, fromFileInCmake, CmakeModifyType.ADD_COMMENT) + modify_cmakelists(fromCmakeFile, fromFileInCmake, CmakeModifyType.ADD_COMMENT) + modify_cmakelists(toCmakeFile, toFileInCmake, CmakeModifyType.REMOVE_COMMENT) + + delete_file(fromOppositeGame, fromFile) + move_file(fromGame, fromFile, toGame, toFile) + + +def unify_move_file(fromGame: Game, fromFile: str, toGame: Game, toFile: str): + assert(toGame == Game.CORE) + + fromGamePath = get_game_path(fromGame) + toGamePath = get_game_path(toGame) + + fromFirstFolderIndex = fromFile.find("/") + toFirstFolderIndex = toFile.find("/") + assert(fromFirstFolderIndex > 0) + assert(toFirstFolderIndex > 0) + + fromFirstFolderName = fromFile[:fromFirstFolderIndex] + toFirstFolderName = toFile[:toFirstFolderIndex] + fromFileInCmake = fromFile[fromFirstFolderIndex+1:] + toFileInCmake = toFile[toFirstFolderIndex+1:] + + fromCmakeFile = os.path.join(fromGamePath, fromFirstFolderName, "CMakeLists.txt") + toCmakeFile = os.path.join(toGamePath, toFirstFolderName, "CMakeLists.txt") + + modify_cmakelists(fromCmakeFile, fromFileInCmake, CmakeModifyType.ADD_COMMENT) + modify_cmakelists(toCmakeFile, toFileInCmake, CmakeModifyType.REMOVE_COMMENT) + + move_file(fromGame, fromFile, toGame, toFile) + + +def unify_file_lib(fromGame: Game, fromFile: str, toGame: Game, toFile: str): + assert(toGame == Game.CORE) + + fromOppositeGame = get_opposite_game(fromGame) + fromOppositeGamePath = get_game_path(fromOppositeGame) + fromGamePath = get_game_path(fromGame) + toGamePath = get_game_path(toGame) + + fromFirstFolderIndex = fromFile.rfind("/") + toFirstFolderIndex = toFile.rfind("/") + assert(fromFirstFolderIndex > 0) + assert(toFirstFolderIndex > 0) + + fromFirstFolderName = fromFile[:fromFirstFolderIndex] + toFirstFolderName = toFile[:toFirstFolderIndex] + fromFileInCmake = fromFile[fromFirstFolderIndex+1:] + toFileInCmake = toFile[toFirstFolderIndex+1:] + + fromOppositeCmakeFile = os.path.join(fromOppositeGamePath, fromFirstFolderName, "CMakeLists.txt") + fromCmakeFile = os.path.join(fromGamePath, fromFirstFolderName, "CMakeLists.txt") + toCmakeFile = os.path.join(toGamePath, toFirstFolderName, "CMakeLists.txt") + + modify_cmakelists(fromOppositeCmakeFile, fromFileInCmake, CmakeModifyType.ADD_COMMENT) + modify_cmakelists(fromCmakeFile, fromFileInCmake, CmakeModifyType.ADD_COMMENT) + modify_cmakelists(toCmakeFile, toFileInCmake, CmakeModifyType.REMOVE_COMMENT) + + delete_file(fromOppositeGame, fromFile) + move_file(fromGame, fromFile, toGame, toFile) + + +def unify_move_file_lib(fromGame: Game, fromFile: str, toGame: Game, toFile: str): + assert(toGame == Game.CORE) + + fromGamePath = get_game_path(fromGame) + toGamePath = get_game_path(toGame) + + fromFirstFolderIndex = fromFile.rfind("/") + toFirstFolderIndex = toFile.rfind("/") + assert(fromFirstFolderIndex > 0) + assert(toFirstFolderIndex > 0) + + fromFirstFolderName = fromFile[:fromFirstFolderIndex] + toFirstFolderName = toFile[:toFirstFolderIndex] + fromFileInCmake = fromFile[fromFirstFolderIndex+1:] + toFileInCmake = toFile[toFirstFolderIndex+1:] + + fromCmakeFile = os.path.join(fromGamePath, fromFirstFolderName, "CMakeLists.txt") + toCmakeFile = os.path.join(toGamePath, toFirstFolderName, "CMakeLists.txt") + + modify_cmakelists(fromCmakeFile, fromFileInCmake, CmakeModifyType.ADD_COMMENT) + modify_cmakelists(toCmakeFile, toFileInCmake, CmakeModifyType.REMOVE_COMMENT) + + move_file(fromGame, fromFile, toGame, toFile) + + +def main(): + + #unify_file(Game.ZEROHOUR, "GameEngine/Include/Common/crc.h", Game.CORE, "GameEngine/Include/Common/crc.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/Common/CRCDebug.h", Game.CORE, "GameEngine/Include/Common/CRCDebug.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/Common/crc.cpp", Game.CORE, "GameEngine/Source/Common/crc.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/Common/CRCDebug.cpp", Game.CORE, "GameEngine/Source/Common/CRCDebug.cpp") + + #unify_file(Game.ZEROHOUR, "GameEngine/Include/Common/RandomValue.h", Game.CORE, "GameEngine/Include/Common/RandomValue.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/ClientRandomValue.h", Game.CORE, "GameEngine/Include/GameClient/ClientRandomValue.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameLogic/LogicRandomValue.h", Game.CORE, "GameEngine/Include/GameLogic/LogicRandomValue.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/Common/RandomValue.cpp", Game.CORE, "GameEngine/Source/Common/RandomValue.cpp") + + #unify_file(Game.ZEROHOUR, "GameEngine/Include/Common/Debug.h", Game.CORE, "GameEngine/Include/Common/Debug.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/Common/System/Debug.cpp", Game.CORE, "GameEngine/Source/Common/System/Debug.cpp") + + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/VideoPlayer.h", Game.CORE, "GameEngine/Include/GameClient/VideoPlayer.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/VideoPlayer.cpp", Game.CORE, "GameEngine/Source/GameClient/VideoPlayer.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/VideoStream.cpp", Game.CORE, "GameEngine/Source/GameClient/VideoStream.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/WindowVideoManager.h", Game.CORE, "GameEngine/Include/GameClient/WindowVideoManager.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/WindowVideoManager.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/WindowVideoManager.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/Common/INI/INIVideo.cpp", Game.CORE, "GameEngine/Source/Common/INI/INIVideo.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/VideoDevice/Bink/BinkVideoPlayer.h", Game.CORE, "GameEngineDevice/Include/VideoDevice/Bink/BinkVideoPlayer.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/VideoDevice/Bink/BinkVideoPlayer.cpp", Game.CORE, "GameEngineDevice/Source/VideoDevice/Bink/BinkVideoPlayer.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/W3DVideoBuffer.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/W3DVideoBuffer.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/W3DVideoBuffer.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/W3DVideoBuffer.cpp") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Include/VideoDevice/FFmpeg/FFmpegFile.h", Game.CORE, "GameEngineDevice/Include/VideoDevice/FFmpeg/FFmpegFile.h") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Include/VideoDevice/FFmpeg/FFmpegVideoPlayer.h", Game.CORE, "GameEngineDevice/Include/VideoDevice/FFmpeg/FFmpegVideoPlayer.h") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegFile.cpp", Game.CORE, "GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegFile.cpp") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegVideoPlayer.cpp", Game.CORE, "GameEngineDevice/Source/VideoDevice/FFmpeg/FFmpegVideoPlayer.cpp") + + #unify_file(Game.ZEROHOUR, "GameEngine/Include/Common/GameMemory.h", Game.CORE, "GameEngine/Include/Common/GameMemory.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/Common/GameMemoryNull.h", Game.CORE, "GameEngine/Include/Common/GameMemoryNull.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/Common/System/GameMemory.cpp", Game.CORE, "GameEngine/Source/Common/System/GameMemory.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/Common/System/GameMemoryNull.cpp", Game.CORE, "GameEngine/Source/Common/System/GameMemoryNull.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/Common/System/MemoryInit.cpp", Game.CORE, "GameEngine/Source/Common/System/GameMemoryInit.cpp") + #unify_move_file(Game.GENERALS, "GameEngine/Source/Common/System/GameMemoryInitDMA_Generals.inl", Game.CORE, "GameEngine/Source/Common/System/GameMemoryInitDMA_Generals.inl") + #unify_move_file(Game.ZEROHOUR, "GameEngine/Source/Common/System/GameMemoryInitDMA_GeneralsMD.inl", Game.CORE, "GameEngine/Source/Common/System/GameMemoryInitDMA_GeneralsMD.inl") + #unify_move_file(Game.GENERALS, "GameEngine/Source/Common/System/GameMemoryInitPools_Generals.inl", Game.CORE, "GameEngine/Source/Common/System/GameMemoryInitPools_Generals.inl") + #unify_move_file(Game.ZEROHOUR, "GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl", Game.CORE, "GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl") + + #unify_file(Game.ZEROHOUR, "GameEngine/Include/Common/ObjectStatusTypes.h", Game.CORE, "GameEngine/Include/Common/ObjectStatusTypes.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/Common/Radar.h", Game.CORE, "GameEngine/Include/Common/Radar.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/Common/System/ObjectStatusTypes.cpp", Game.CORE, "GameEngine/Source/Common/System/ObjectStatusTypes.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/Common/System/Radar.cpp", Game.CORE, "GameEngine/Source/Common/System/Radar.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/Common/W3DRadar.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/Common/W3DRadar.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp") + + #unify_move_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/Smudge.h", Game.CORE, "GameEngine/Include/GameClient/Smudge.h") + #unify_move_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/System/Smudge.cpp", Game.CORE, "GameEngine/Source/GameClient/System/Smudge.cpp") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/W3DSmudge.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/W3DSmudge.h") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/W3DShaderManager.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/W3DShaderManager.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/W3DShaderManager.cpp") + + #unify_move_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/ParabolicEase.h", Game.CORE, "GameEngine/Include/GameClient/ParabolicEase.h") + #unify_move_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/ParabolicEase.cpp", Game.CORE, "GameEngine/Source/GameClient/ParabolicEase.cpp") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/camerashakesystem.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/CameraShakeSystem.h") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/camerashakesystem.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/CameraShakeSystem.cpp") + + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/View.h", Game.CORE, "GameEngine/Include/GameClient/View.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/View.cpp", Game.CORE, "GameEngine/Source/GameClient/View.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/W3DView.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp") + + #unify_file(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/bmp2d.cpp", Game.CORE, "Libraries/Source/WWVegas/WW3D2/bmp2d.cpp") + #unify_file(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/bmp2d.h", Game.CORE, "Libraries/Source/WWVegas/WW3D2/bmp2d.h") + #unify_file(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8texman.cpp", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8texman.cpp") + #unify_file(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8texman.h", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8texman.h") + #unify_file(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/matpass.cpp", Game.CORE, "Libraries/Source/WWVegas/WW3D2/matpass.cpp") + #unify_file(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/matpass.h", Game.CORE, "Libraries/Source/WWVegas/WW3D2/matpass.h") + #unify_file(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/texproject.cpp", Game.CORE, "Libraries/Source/WWVegas/WW3D2/texproject.cpp") + #unify_file(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/texproject.h", Game.CORE, "Libraries/Source/WWVegas/WW3D2/texproject.h") + #unify_file(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/texture.cpp", Game.CORE, "Libraries/Source/WWVegas/WW3D2/texture.cpp") + #unify_file(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/texture.h", Game.CORE, "Libraries/Source/WWVegas/WW3D2/texture.h") + #unify_file(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/texturefilter.cpp", Game.CORE, "Libraries/Source/WWVegas/WW3D2/texturefilter.cpp") + #unify_file(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/texturefilter.h", Game.CORE, "Libraries/Source/WWVegas/WW3D2/texturefilter.h") + #unify_file(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/textureloader.cpp", Game.CORE, "Libraries/Source/WWVegas/WW3D2/textureloader.cpp") + #unify_file(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/textureloader.h", Game.CORE, "Libraries/Source/WWVegas/WW3D2/textureloader.h") + #unify_file(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/texturethumbnail.cpp", Game.CORE, "Libraries/Source/WWVegas/WW3D2/texturethumbnail.cpp") + #unify_file(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/texturethumbnail.h", Game.CORE, "Libraries/Source/WWVegas/WW3D2/texturethumbnail.h") + + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/Water.h", Game.CORE, "GameEngine/Include/GameClient/Water.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/Water.cpp", Game.CORE, "GameEngine/Source/GameClient/Water.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DLaserDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DLaserDraw.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/W3DWater.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/W3DWater.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/W3DWaterTracks.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/W3DWaterTracks.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DLaserDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DLaserDraw.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWaterTracks.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Water/wave.nvp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Water/wave.nvp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Water/wave.nvv", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Water/wave.nvv") + + #unify_move_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/Snow.h", Game.CORE, "GameEngine/Include/GameClient/Snow.h") + #unify_move_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/Snow.cpp", Game.CORE, "GameEngine/Source/GameClient/Snow.cpp") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/BaseHeightMap.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/BaseHeightMap.h") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/FlatHeightMap.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/FlatHeightMap.h") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/W3DPropBuffer.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/W3DPropBuffer.h") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/W3DSnow.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/W3DSnow.h") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/W3DTerrainBackground.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/W3DTerrainBackground.h") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DPropDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DPropDraw.h") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DTreeDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DTreeDraw.h") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/BaseHeightMap.cpp") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/FlatHeightMap.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/FlatHeightMap.cpp") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/W3DPropBuffer.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/W3DPropBuffer.cpp") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/W3DSnow.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/W3DSnow.cpp") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainBackground.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainBackground.cpp") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DPropDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DPropDraw.cpp") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTreeDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTreeDraw.cpp") + + #unify_file(Game.ZEROHOUR, "GameEngine/Include/Common/MapObject.h", Game.CORE, "GameEngine/Include/Common/MapObject.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/MapUtil.h", Game.CORE, "GameEngine/Include/GameClient/MapUtil.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/TerrainRoads.h", Game.CORE, "GameEngine/Include/GameClient/TerrainRoads.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/TerrainVisual.h", Game.CORE, "GameEngine/Include/GameClient/TerrainVisual.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/MapUtil.cpp", Game.CORE, "GameEngine/Source/GameClient/MapUtil.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/Terrain/TerrainRoads.cpp", Game.CORE, "GameEngine/Source/GameClient/Terrain/TerrainRoads.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/Terrain/TerrainVisual.cpp", Game.CORE, "GameEngine/Source/GameClient/Terrain/TerrainVisual.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/HeightMap.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/HeightMap.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/TerrainTex.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/TerrainTex.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/TileData.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/TileData.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/W3DTerrainTracks.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/W3DTerrainTracks.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/W3DTerrainVisual.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/W3DTerrainVisual.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/W3DTreeBuffer.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/W3DTreeBuffer.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/WorldHeightMap.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/WorldHeightMap.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/TerrainTex.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/TerrainTex.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/TileData.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/TileData.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainTracks.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainTracks.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainVisual.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainVisual.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/W3DTreeBuffer.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/W3DTreeBuffer.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/WorldHeightMap.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/WorldHeightMap.cpp") + + #unify_file(Game.ZEROHOUR, "GameEngine/Include/Common/UserPreferences.h", Game.CORE, "GameEngine/Include/Common/UserPreferences.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/Common/UserPreferences.cpp", Game.CORE, "GameEngine/Source/Common/UserPreferences.cpp") + + #unify_file(Game.ZEROHOUR, "Libraries/Include/Lib/BaseType.h", Game.CORE, "Libraries/Include/Lib/BaseType.h") + #unify_file(Game.ZEROHOUR, "Libraries/Include/Lib/trig.h", Game.CORE, "Libraries/Include/Lib/trig.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/Common/Errors.h", Game.CORE, "GameEngine/Include/Common/Errors.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/Common/GameCommon.h", Game.CORE, "GameEngine/Include/Common/GameCommon.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/Common/GameType.h", Game.CORE, "GameEngine/Include/Common/GameType.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/Common/INI.h", Game.CORE, "GameEngine/Include/Common/INI.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/Common/Snapshot.h", Game.CORE, "GameEngine/Include/Common/Snapshot.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/Common/STLTypedefs.h", Game.CORE, "GameEngine/Include/Common/STLTypedefs.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/Common/SubsystemInterface.h", Game.CORE, "GameEngine/Include/Common/SubsystemInterface.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/ChallengeGenerals.h", Game.CORE, "GameEngine/Include/GameClient/ChallengeGenerals.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/Common/INI/INI.cpp", Game.CORE, "GameEngine/Source/Common/INI/INI.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/Common/System/GameCommon.cpp", Game.CORE, "GameEngine/Source/Common/System/GameCommon.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/Common/System/GameType.cpp", Game.CORE, "GameEngine/Source/Common/System/GameType.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/Common/System/Snapshot.cpp", Game.CORE, "GameEngine/Source/Common/System/Snapshot.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/Common/System/SubsystemInterface.cpp", Game.CORE, "GameEngine/Source/Common/System/SubsystemInterface.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/ChallengeGenerals.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/ChallengeGenerals.cpp") + + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/ParticleSys.h", Game.CORE, "GameEngine/Include/GameClient/ParticleSys.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/System/ParticleSys.cpp", Game.CORE, "GameEngine/Source/GameClient/System/ParticleSys.cpp") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/res/ParticleEditor.rc2", Game.CORE, "Tools/ParticleEditor/res/ParticleEditor.rc2") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/CButtonShowColor.cpp", Game.CORE, "Tools/ParticleEditor/CButtonShowColor.cpp") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/CButtonShowColor.h", Game.CORE, "Tools/ParticleEditor/CButtonShowColor.h") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/CColorAlphaDialog.cpp", Game.CORE, "Tools/ParticleEditor/CColorAlphaDialog.cpp") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/CColorAlphaDialog.h", Game.CORE, "Tools/ParticleEditor/CColorAlphaDialog.h") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/CParticleEditorPage.h", Game.CORE, "Tools/ParticleEditor/CParticleEditorPage.h") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/CSwitchesDialog.cpp", Game.CORE, "Tools/ParticleEditor/CSwitchesDialog.cpp") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/CSwitchesDialog.h", Game.CORE, "Tools/ParticleEditor/CSwitchesDialog.h") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/EmissionTypePanels.cpp", Game.CORE, "Tools/ParticleEditor/EmissionTypePanels.cpp") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/EmissionTypePanels.h", Game.CORE, "Tools/ParticleEditor/EmissionTypePanels.h") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/ISwapablePanel.h", Game.CORE, "Tools/ParticleEditor/ISwapablePanel.h") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/MoreParmsDialog.cpp", Game.CORE, "Tools/ParticleEditor/MoreParmsDialog.cpp") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/MoreParmsDialog.h", Game.CORE, "Tools/ParticleEditor/MoreParmsDialog.h") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/ParticleEditor.cpp", Game.CORE, "Tools/ParticleEditor/ParticleEditor.cpp") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/ParticleEditor.def", Game.CORE, "Tools/ParticleEditor/ParticleEditor.def") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/ParticleEditor.h", Game.CORE, "Tools/ParticleEditor/ParticleEditor.h") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/ParticleEditor.rc", Game.CORE, "Tools/ParticleEditor/ParticleEditor.rc") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/ParticleEditorDialog.cpp", Game.CORE, "Tools/ParticleEditor/ParticleEditorDialog.cpp") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/ParticleEditorDialog.h", Game.CORE, "Tools/ParticleEditor/ParticleEditorDialog.h") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/ParticleEditorExport.h", Game.CORE, "Tools/ParticleEditor/ParticleEditorExport.h") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/ParticleTypePanels.cpp", Game.CORE, "Tools/ParticleEditor/ParticleTypePanels.cpp") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/ParticleTypePanels.h", Game.CORE, "Tools/ParticleEditor/ParticleTypePanels.h") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/post-build-Release.bat", Game.CORE, "Tools/ParticleEditor/post-build-Release.bat") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/post-build.bat", Game.CORE, "Tools/ParticleEditor/post-build.bat") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/Resource.h", Game.CORE, "Tools/ParticleEditor/Resource.h") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/ShaderTypePanels.cpp", Game.CORE, "Tools/ParticleEditor/ShaderTypePanels.cpp") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/ShaderTypePanels.h", Game.CORE, "Tools/ParticleEditor/ShaderTypePanels.h") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/StdAfx.cpp", Game.CORE, "Tools/ParticleEditor/StdAfx.cpp") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/StdAfx.h", Game.CORE, "Tools/ParticleEditor/StdAfx.h") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/VelocityTypePanels.cpp", Game.CORE, "Tools/ParticleEditor/VelocityTypePanels.cpp") + #unify_file(Game.ZEROHOUR, "Tools/ParticleEditor/VelocityTypePanels.h", Game.CORE, "Tools/ParticleEditor/VelocityTypePanels.h") + + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDebrisDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDebrisDraw.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDefaultDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDefaultDraw.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDependencyModelDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DDependencyModelDraw.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DOverlordTankDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DOverlordTankDraw.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DPoliceCarDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DPoliceCarDraw.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DProjectileStreamDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DProjectileStreamDraw.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DRopeDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DRopeDraw.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DScienceModelDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DScienceModelDraw.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DSupplyDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DSupplyDraw.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DTankDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DTankDraw.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DTankTruckDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DTankTruckDraw.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DTracerDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DTracerDraw.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DTruckDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DTruckDraw.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDebrisDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDebrisDraw.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDefaultDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDefaultDraw.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDependencyModelDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDependencyModelDraw.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTankDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTankDraw.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DPoliceCarDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DPoliceCarDraw.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DProjectileStreamDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DProjectileStreamDraw.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DRopeDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DRopeDraw.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DScienceModelDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DScienceModelDraw.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DSupplyDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DSupplyDraw.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTracerDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTracerDraw.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTruckDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTruckDraw.cpp") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DOverlordAircraftDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DOverlordAircraftDraw.h") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DOverlordTruckDraw.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DOverlordTruckDraw.h") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordAircraftDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordAircraftDraw.cpp") + #unify_move_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTruckDraw.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTruckDraw.cpp") + + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/Mouse.h", Game.CORE, "GameEngine/Include/GameClient/Mouse.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/Input/Mouse.cpp", Game.CORE, "GameEngine/Source/GameClient/Input/Mouse.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/Keyboard.h", Game.CORE, "GameEngine/Include/GameClient/Keyboard.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/Input/Keyboard.cpp", Game.CORE, "GameEngine/Source/GameClient/Input/Keyboard.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/W3DMouse.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/W3DMouse.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/W3DMouse.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/W3DMouse.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/Win32Device/GameClient/Win32DIKeyboard.h", Game.CORE, "GameEngineDevice/Include/Win32Device/GameClient/Win32DIKeyboard.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/Win32Device/GameClient/Win32DIMouse.h", Game.CORE, "GameEngineDevice/Include/Win32Device/GameClient/Win32DIMouse.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/Win32Device/GameClient/Win32Mouse.h", Game.CORE, "GameEngineDevice/Include/Win32Device/GameClient/Win32Mouse.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/Win32Device/GameClient/Win32DIKeyboard.cpp", Game.CORE, "GameEngineDevice/Source/Win32Device/GameClient/Win32DIKeyboard.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/Win32Device/GameClient/Win32DIMouse.cpp", Game.CORE, "GameEngineDevice/Source/Win32Device/GameClient/Win32DIMouse.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/Win32Device/GameClient/Win32Mouse.cpp", Game.CORE, "GameEngineDevice/Source/Win32Device/GameClient/Win32Mouse.cpp") + + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/GameFont.h", Game.CORE, "GameEngine/Include/GameClient/GameFont.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/GameWindow.h", Game.CORE, "GameEngine/Include/GameClient/GameWindow.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/GameWindowGlobal.h", Game.CORE, "GameEngine/Include/GameClient/GameWindowGlobal.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/GameWindowTransitions.h", Game.CORE, "GameEngine/Include/GameClient/GameWindowTransitions.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/HeaderTemplate.h", Game.CORE, "GameEngine/Include/GameClient/HeaderTemplate.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/IMEManager.h", Game.CORE, "GameEngine/Include/GameClient/IMEManager.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/LoadScreen.h", Game.CORE, "GameEngine/Include/GameClient/LoadScreen.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/ProcessAnimateWindow.h", Game.CORE, "GameEngine/Include/GameClient/ProcessAnimateWindow.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/WindowLayout.h", Game.CORE, "GameEngine/Include/GameClient/WindowLayout.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/WinInstanceData.h", Game.CORE, "GameEngine/Include/GameClient/WinInstanceData.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/GameFont.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/GameFont.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/GameWindow.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/GameWindow.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/GameWindowGlobal.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/GameWindowGlobal.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/HeaderTemplate.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/HeaderTemplate.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/IMEManager.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/IMEManager.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/LoadScreen.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/LoadScreen.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/ProcessAnimateWindow.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/ProcessAnimateWindow.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/WindowLayout.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/WindowLayout.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/WinInstanceData.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/WinInstanceData.cpp") + + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/ClientInstance.h", Game.CORE, "GameEngine/Include/GameClient/ClientInstance.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/Color.h", Game.CORE, "GameEngine/Include/GameClient/Color.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/Credits.h", Game.CORE, "GameEngine/Include/GameClient/Credits.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/DisplayString.h", Game.CORE, "GameEngine/Include/GameClient/DisplayString.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/DisplayStringManager.h", Game.CORE, "GameEngine/Include/GameClient/DisplayStringManager.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/DrawGroupInfo.h", Game.CORE, "GameEngine/Include/GameClient/DrawGroupInfo.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/FXList.h", Game.CORE, "GameEngine/Include/GameClient/FXList.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/GameText.h", Game.CORE, "GameEngine/Include/GameClient/GameText.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/GlobalLanguage.h", Game.CORE, "GameEngine/Include/GameClient/GlobalLanguage.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/GraphDraw.h", Game.CORE, "GameEngine/Include/GameClient/GraphDraw.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/LanguageFilter.h", Game.CORE, "GameEngine/Include/GameClient/LanguageFilter.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/Line2D.h", Game.CORE, "GameEngine/Include/GameClient/Line2D.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/RadiusDecal.h", Game.CORE, "GameEngine/Include/GameClient/RadiusDecal.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/SelectionInfo.h", Game.CORE, "GameEngine/Include/GameClient/SelectionInfo.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/Statistics.h", Game.CORE, "GameEngine/Include/GameClient/Statistics.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/ClientInstance.cpp", Game.CORE, "GameEngine/Source/GameClient/ClientInstance.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/Color.cpp", Game.CORE, "GameEngine/Source/GameClient/Color.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/Credits.cpp", Game.CORE, "GameEngine/Source/GameClient/Credits.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/DisplayString.cpp", Game.CORE, "GameEngine/Source/GameClient/DisplayString.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/DisplayStringManager.cpp", Game.CORE, "GameEngine/Source/GameClient/DisplayStringManager.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/DrawGroupInfo.cpp", Game.CORE, "GameEngine/Source/GameClient/DrawGroupInfo.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/FXList.cpp", Game.CORE, "GameEngine/Source/GameClient/FXList.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GameText.cpp", Game.CORE, "GameEngine/Source/GameClient/GameText.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GlobalLanguage.cpp", Game.CORE, "GameEngine/Source/GameClient/GlobalLanguage.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GraphDraw.cpp", Game.CORE, "GameEngine/Source/GameClient/GraphDraw.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/LanguageFilter.cpp", Game.CORE, "GameEngine/Source/GameClient/LanguageFilter.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/Line2D.cpp", Game.CORE, "GameEngine/Source/GameClient/Line2D.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/RadiusDecal.cpp", Game.CORE, "GameEngine/Source/GameClient/RadiusDecal.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/SelectionInfo.cpp", Game.CORE, "GameEngine/Source/GameClient/SelectionInfo.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/Statistics.cpp", Game.CORE, "GameEngine/Source/GameClient/Statistics.cpp") + + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameLogic/AI/AIPathfind.cpp", Game.CORE, "GameEngine/Source/GameLogic/AI/AIPathfind.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameLogic/AIPathfind.h", Game.CORE, "GameEngine/Include/GameLogic/AIPathfind.h") + + #unify_move_file_lib(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8rendererdebugger.h", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8rendererdebugger.h") + #unify_move_file_lib(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8rendererdebugger.cpp", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8rendererdebugger.cpp") + #unify_move_file_lib(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/shdlib.h", Game.CORE, "Libraries/Source/WWVegas/WW3D2/shdlib.h") + #unify_file_lib(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8caps.h", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8caps.h") + #unify_file_lib(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8wrapper.h", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8wrapper.h") + #unify_file_lib(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8caps.cpp", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8caps.cpp") + #unify_file_lib(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp") + + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/Display.cpp", Game.CORE, "GameEngine/Source/GameClient/Display.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/Display.h", Game.CORE, "GameEngine/Include/GameClient/Display.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/System/Anim2D.cpp", Game.CORE, "GameEngine/Source/GameClient/System/Anim2D.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/Anim2D.h", Game.CORE, "GameEngine/Include/GameClient/Anim2D.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/System/Image.cpp", Game.CORE, "GameEngine/Source/GameClient/System/Image.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/Image.h", Game.CORE, "GameEngine/Include/GameClient/Image.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/System/DebugDisplay.cpp", Game.CORE, "GameEngine/Source/GameClient/System/DebugDisplay.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/DebugDisplay.h", Game.CORE, "GameEngine/Include/GameClient/DebugDisplay.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/System/RayEffect.cpp", Game.CORE, "GameEngine/Source/GameClient/System/RayEffect.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/RayEffect.h", Game.CORE, "GameEngine/Include/GameClient/RayEffect.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/Drawable/Update/AnimatedParticleSysBoneClientUpdate.cpp", Game.CORE, "GameEngine/Source/GameClient/Drawable/Update/AnimatedParticleSysBoneClientUpdate.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/Module/AnimatedParticleSysBoneClientUpdate.h", Game.CORE, "GameEngine/Include/GameClient/Module/AnimatedParticleSysBoneClientUpdate.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/Drawable/Update/BeaconClientUpdate.cpp", Game.CORE, "GameEngine/Source/GameClient/Drawable/Update/BeaconClientUpdate.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/Module/BeaconClientUpdate.h", Game.CORE, "GameEngine/Include/GameClient/Module/BeaconClientUpdate.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/Drawable/Update/SwayClientUpdate.cpp", Game.CORE, "GameEngine/Source/GameClient/Drawable/Update/SwayClientUpdate.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/Module/SwayClientUpdate.h", Game.CORE, "GameEngine/Include/GameClient/Module/SwayClientUpdate.h") + + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/Gadget.h", Game.CORE, "GameEngine/Include/GameClient/Gadget.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/Gadget/GadgetCheckBox.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/Gadget/GadgetCheckBox.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/GadgetCheckBox.h", Game.CORE, "GameEngine/Include/GameClient/GadgetCheckBox.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/Gadget/GadgetComboBox.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/Gadget/GadgetComboBox.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/GadgetComboBox.h", Game.CORE, "GameEngine/Include/GameClient/GadgetComboBox.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/Gadget/GadgetListBox.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/Gadget/GadgetListBox.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/GadgetListBox.h", Game.CORE, "GameEngine/Include/GameClient/GadgetListBox.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/Gadget/GadgetProgressBar.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/Gadget/GadgetProgressBar.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/GadgetProgressBar.h", Game.CORE, "GameEngine/Include/GameClient/GadgetProgressBar.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/Gadget/GadgetPushButton.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/Gadget/GadgetPushButton.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/GadgetPushButton.h", Game.CORE, "GameEngine/Include/GameClient/GadgetPushButton.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/Gadget/GadgetRadioButton.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/Gadget/GadgetRadioButton.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/GadgetRadioButton.h", Game.CORE, "GameEngine/Include/GameClient/GadgetRadioButton.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/Gadget/GadgetStaticText.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/Gadget/GadgetStaticText.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/GadgetStaticText.h", Game.CORE, "GameEngine/Include/GameClient/GadgetStaticText.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/Gadget/GadgetTabControl.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/Gadget/GadgetTabControl.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/GadgetTabControl.h", Game.CORE, "GameEngine/Include/GameClient/GadgetTabControl.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/Gadget/GadgetTextEntry.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/Gadget/GadgetTextEntry.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/GadgetTextEntry.h", Game.CORE, "GameEngine/Include/GameClient/GadgetTextEntry.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/Gadget/GadgetHorizontalSlider.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/Gadget/GadgetHorizontalSlider.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameClient/GUI/Gadget/GadgetVerticalSlider.cpp", Game.CORE, "GameEngine/Source/GameClient/GUI/Gadget/GadgetVerticalSlider.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameClient/GadgetSlider.h", Game.CORE, "GameEngine/Include/GameClient/GadgetSlider.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/W3DGadget.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/W3DGadget.h") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DCheckBox.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DCheckBox.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DComboBox.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DComboBox.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DHorizontalSlider.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DHorizontalSlider.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DListBox.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DListBox.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DProgressBar.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DProgressBar.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DPushButton.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DPushButton.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DRadioButton.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DRadioButton.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DStaticText.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DStaticText.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DTabControl.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DTabControl.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DTextEntry.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DTextEntry.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DVerticalSlider.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/GUI/Gadget/W3DVerticalSlider.cpp") + + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameLogic/CaveSystem.h", Game.CORE, "GameEngine/Include/GameLogic/CaveSystem.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameLogic/CrateSystem.h", Game.CORE, "GameEngine/Include/GameLogic/CrateSystem.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameLogic/Damage.h", Game.CORE, "GameEngine/Include/GameLogic/Damage.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Include/GameLogic/RankInfo.h", Game.CORE, "GameEngine/Include/GameLogic/RankInfo.h") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameLogic/System/CaveSystem.cpp", Game.CORE, "GameEngine/Source/GameLogic/System/CaveSystem.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameLogic/System/CrateSystem.cpp", Game.CORE, "GameEngine/Source/GameLogic/System/CrateSystem.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameLogic/System/Damage.cpp", Game.CORE, "GameEngine/Source/GameLogic/System/Damage.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp", Game.CORE, "GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp") + #unify_file(Game.ZEROHOUR, "GameEngine/Source/GameLogic/System/RankInfo.cpp", Game.CORE, "GameEngine/Source/GameLogic/System/RankInfo.cpp") + + #unify_file_lib(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8fvf.h", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8fvf.h") + #unify_file_lib(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.h", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.h") + #unify_file_lib(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8renderer.h", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8renderer.h") + #unify_file_lib(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.h", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.h") + #unify_file_lib(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8fvf.cpp", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8fvf.cpp") + #unify_file_lib(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.cpp", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.cpp") + #unify_file_lib(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp") + #unify_file_lib(Game.ZEROHOUR, "Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp", Game.CORE, "Libraries/Source/WWVegas/WW3D2/dx8vertexbuffer.cpp") + + return + + +if __name__ == "__main__": + main() diff --git a/scripts/docker-build.sh b/scripts/docker-build.sh new file mode 100755 index 00000000000..c47c8b6aa9a --- /dev/null +++ b/scripts/docker-build.sh @@ -0,0 +1,286 @@ +#!/usr/bin/env bash +# +# Build script for compiling Generals/Zero Hour on Linux using Docker +# +# This script builds Windows executables using a Docker container with Wine and VC6. +# The resulting binaries can be run on Linux using Wine or on Windows natively. +# +# Usage: +# ./scripts/docker-build.sh # Full build (both games) +# ./scripts/docker-build.sh --game zh # Build Zero Hour only +# ./scripts/docker-build.sh --game generals # Build Generals only +# ./scripts/docker-build.sh --target z_generals # Build specific target +# ./scripts/docker-build.sh --clean # Clean build directory +# ./scripts/docker-build.sh --interactive # Enter container shell +# + +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" +BUILD_DIR="$PROJECT_DIR/build/docker" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +print_header() { + echo -e "${BLUE}======================================${NC}" + echo -e "${BLUE} Generals Game Code - Linux Builder${NC}" + echo -e "${BLUE}======================================${NC}" + echo "" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +usage() { + cat </dev/null; then + print_error "Docker is not installed. Please install Docker first." + echo " See: https://docs.docker.com/engine/install/" + exit 1 + fi + + if ! docker info &>/dev/null; then + print_error "Docker daemon is not running or you don't have permission." + echo " Try: sudo systemctl start docker" + echo " Or add your user to the docker group: sudo usermod -aG docker \$USER" + exit 1 + fi + + print_success "All dependencies satisfied" +} + +build_docker_image() { + print_info "Building Docker image (this may take a while on first run)..." + + docker build \ + --build-arg UID="$(id -u)" \ + --build-arg GID="$(id -g)" \ + "$PROJECT_DIR/resources/dockerbuild" \ + -t zerohour-build \ + || { + print_error "Failed to build Docker image" + exit 1 + } + + print_success "Docker image ready" +} + +fix_compile_commands() { + local fix_script="$SCRIPT_DIR/fix_compile_commands.py" + if [[ -f "$fix_script" ]] && command -v python3 &>/dev/null; then + # Only run if compile_commands.json exists in the build dir + if [[ -f "$BUILD_DIR/compile_commands.json" ]]; then + print_info "Fixing compile_commands.json for host environment..." + python3 "$fix_script" || print_warning "Failed to fix compile_commands.json" + fi + fi +} + +run_build() { + local force_cmake="$1" + local target="$2" + local interactive="$3" + + local docker_flags="" + if [[ "$interactive" == "true" ]]; then + docker_flags="-it --entrypoint bash" + fi + + print_info "Starting build..." + if [[ -n "$target" ]]; then + print_info "Target: $target" + fi + + # shellcheck disable=SC2086 + docker run \ + -u "$(id -u):$(id -g)" \ + -e MAKE_TARGET="$target" \ + -e FORCE_CMAKE="$force_cmake" \ + -v "$PROJECT_DIR:/build/cnc" \ + --rm \ + $docker_flags \ + zerohour-build \ + || { + print_error "Build failed" + exit 1 + } + + if [[ "$interactive" != "true" ]]; then + print_success "Build completed" + fi +} + +clean_build() { + print_info "Cleaning build directory..." + rm -rf "$BUILD_DIR" + print_success "Build directory cleaned" +} + +list_outputs() { + echo "" + print_info "Build outputs:" + echo "" + + if [[ -d "$BUILD_DIR/GeneralsMD" ]]; then + echo "Zero Hour (GeneralsMD):" + find "$BUILD_DIR/GeneralsMD" -maxdepth 1 -name "*.exe" -printf " %f (%s bytes)\n" 2>/dev/null || true + fi + + if [[ -d "$BUILD_DIR/Generals" ]]; then + echo "" + echo "Generals:" + find "$BUILD_DIR/Generals" -maxdepth 1 -name "*.exe" -printf " %f (%s bytes)\n" 2>/dev/null || true + fi +} + +# Parse arguments +GAME="all" +TARGET="" +CLEAN=false +INTERACTIVE=false +FORCE_CMAKE=false + +while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) + usage + exit 0 + ;; + -g|--game) + GAME="$2" + shift 2 + ;; + -t|--target) + TARGET="$2" + shift 2 + ;; + -c|--clean) + CLEAN=true + shift + ;; + -i|--interactive) + INTERACTIVE=true + shift + ;; + --cmake) + FORCE_CMAKE=true + shift + ;; + -j|--jobs) + # Jobs are handled by ninja in the container + shift 2 + ;; + -*) + print_error "Unknown option: $1" + usage + exit 1 + ;; + *) + # Positional argument treated as target + TARGET="$TARGET $1" + shift + ;; + esac +done + +# Set target based on game selection +if [[ -z "$TARGET" ]]; then + case "$GAME" in + zh|zerohour) + TARGET="z_generals z_worldbuilder core_particleeditor core_debugwindow z_guiedit z_imagepacker z_mapcachebuilder z_w3dview z_wdump" + ;; + generals) + TARGET="g_generals g_worldbuilder core_particleeditor core_debugwindow g_guiedit g_imagepacker g_mapcachebuilder g_w3dview" + ;; + all) + TARGET="" # Build all + ;; + *) + print_error "Unknown game: $GAME (use 'zh', 'generals', or 'all')" + exit 1 + ;; + esac +fi + +# Main execution +print_header +check_dependencies + +if [[ "$CLEAN" == "true" ]]; then + clean_build +fi + +build_docker_image +run_build "$FORCE_CMAKE" "$TARGET" "$INTERACTIVE" + +if [[ "$INTERACTIVE" != "true" ]]; then + fix_compile_commands + list_outputs + + echo "" + print_info "To run the game with Wine:" + echo " wine $BUILD_DIR/GeneralsMD/generalszh.exe" +fi diff --git a/scripts/docker-install.sh b/scripts/docker-install.sh new file mode 100755 index 00000000000..12135de56c8 --- /dev/null +++ b/scripts/docker-install.sh @@ -0,0 +1,405 @@ +#!/usr/bin/env bash +# +# Install built executables to an existing Generals/Zero Hour installation +# +# This script copies the built executables and tools to a game installation, +# allowing you to test your compiled version with the full game data. +# +# Usage: +# ./scripts/docker-install.sh /path/to/game/installation +# ./scripts/docker-install.sh --detect # Auto-detect game location +# ./scripts/docker-install.sh --restore # Restore original files +# + +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" +BUILD_DIR="$PROJECT_DIR/build/docker" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } +print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } +print_error() { echo -e "${RED}[ERROR]${NC} $1"; } +print_info() { echo -e "${BLUE}[INFO]${NC} $1"; } + +usage() { + cat </dev/null; then + for key in \ + "HKLM\\SOFTWARE\\Electronic Arts\\EA Games\\Command and Conquer Generals Zero Hour" \ + "HKLM\\SOFTWARE\\WOW6432Node\\Electronic Arts\\EA Games\\Command and Conquer Generals Zero Hour"; do + local install_path + install_path=$(reg.exe query "$key" //v "InstallPath" 2>/dev/null | grep -oP 'REG_SZ\s+\K.*' | tr -d '\r') + if [ -n "$install_path" ] && [ -d "$install_path/Data" ]; then + candidates+=("$install_path") + fi + done + fi + + # Check Wine registry (for Linux with Wine) + for reg_file in ~/.wine/system.reg ~/.wine32/system.reg; do + if [ -f "$reg_file" ]; then + local install_path + install_path=$(grep -A1 '\[Software\\\\Electronic Arts\\\\EA Games\\\\Command and Conquer Generals Zero Hour\]' "$reg_file" 2>/dev/null | grep -oP '"InstallPath"="\K[^"]+' | sed 's/\\\\x0//g; s/\\\\/\//g; s/^C:/~\/.wine\/drive_c/') + if [ -n "$install_path" ] && [ -d "$install_path/Data" ]; then + candidates+=("$install_path") + fi + fi + done + + # Fallback: Common Linux Wine locations + for prefix in ~/.wine ~/.wine32 ~/.wine-*; do + if [ -d "$prefix" ]; then + for path in \ + "$prefix/drive_c/Program Files/EA Games/Command and Conquer Generals Zero Hour" \ + "$prefix/drive_c/Program Files (x86)/EA Games/Command and Conquer Generals Zero Hour" \ + "$prefix/drive_c/Program Files/DODI-Repacks/Generals Zero Hour" \ + "$prefix/drive_c/GOG Games/Command Conquer Generals Zero Hour"; do + if [ -d "$path/Data" ]; then + candidates+=("$path") + fi + done + fi + done + + # Steam Proton prefixes (for games run through Proton) + for prefix in ~/.local/share/Steam/steamapps/compatdata/*/pfx; do + if [ -d "$prefix" ]; then + for path in \ + "$prefix/drive_c/Program Files/EA Games/Command and Conquer Generals Zero Hour" \ + "$prefix/drive_c/Program Files (x86)/EA Games/Command and Conquer Generals Zero Hour"; do + if [ -d "$path/Data" ]; then + candidates+=("$path") + fi + done + fi + done + + # Steam native game installations (steamapps/common) + for path in \ + ~/.local/share/Steam/steamapps/common/"Command and Conquer Generals Zero Hour" \ + ~/.local/share/Steam/steamapps/common/"Command & Conquer Generals - Zero Hour" \ + ~/.steam/steam/steamapps/common/"Command and Conquer Generals Zero Hour" \ + ~/.steam/steam/steamapps/common/"Command & Conquer Generals - Zero Hour"; do + if [ -d "$path/Data" ]; then + candidates+=("$path") + fi + done + + # Fallback: Windows paths (for Git Bash/WSL) + for path in \ + "/c/Program Files/EA Games/Command and Conquer Generals Zero Hour" \ + "/c/Program Files (x86)/EA Games/Command and Conquer Generals Zero Hour" \ + "C:/Program Files/EA Games/Command and Conquer Generals Zero Hour" \ + "C:/Program Files (x86)/EA Games/Command and Conquer Generals Zero Hour"; do + if [ -d "$path/Data" ]; then + candidates+=("$path") + fi + done + + if [ ${#candidates[@]} -eq 0 ]; then + print_error "No game installation found" >&2 + echo "" >&2 + echo "Please specify the game directory manually:" >&2 + echo " $(basename "$0") /path/to/game/installation" >&2 + exit 1 + fi + + if [ ${#candidates[@]} -eq 1 ]; then + echo "${candidates[0]}" + return + fi + + echo "Found multiple installations:" >&2 + for i in "${!candidates[@]}"; do + echo " $((i+1)). ${candidates[$i]}" >&2 + done + echo "" >&2 + read -p "Select installation (1-${#candidates[@]}): " selection >&2 + if [[ ! "$selection" =~ ^[0-9]+$ ]] || [ "$selection" -lt 1 ] || [ "$selection" -gt "${#candidates[@]}" ]; then + print_error "Invalid selection" >&2 + exit 1 + fi + echo "${candidates[$((selection-1))]}" +} + +check_build() { + if [ ! -d "$BUILD_DIR" ]; then + print_error "Build directory not found: $BUILD_DIR" + echo "" + echo "Please build the project first:" + echo " ./scripts/docker-build.sh" + exit 1 + fi + + if [ ! -f "$BUILD_DIR/GeneralsMD/generalszh.exe" ]; then + print_error "Built executables not found" + echo "" + echo "Please build the project first:" + echo " ./scripts/docker-build.sh" + exit 1 + fi + + print_success "Build directory found" +} + +backup_file() { + local file="$1" + local backup="${file}.backup" + + if [ -f "$file" ] && [ ! -f "$backup" ]; then + cp "$file" "$backup" + print_info "Backed up: $(basename "$file")" + fi +} + +install_file() { + local src="$1" + local dest="$2" + local dry_run="${3:-false}" + + if [ "$dry_run" == "true" ]; then + echo " Would copy: $(basename "$src") -> $dest" + else + backup_file "$dest" + cp "$src" "$dest" + print_success "Installed: $(basename "$src")" + fi +} + +install_game() { + local game_dir="$1" + local game_type="$2" # "zh" or "generals" + local dry_run="${3:-false}" + + local data_dir="$game_dir/Data" + local build_game_dir + local exe_name + local tools_prefix + + if [ "$game_type" == "zh" ]; then + build_game_dir="$BUILD_DIR/GeneralsMD" + exe_name="generalszh.exe" + tools_prefix="ZH" + else + build_game_dir="$BUILD_DIR/Generals" + exe_name="generalsv.exe" + tools_prefix="V" + fi + + if [ ! -d "$build_game_dir" ]; then + print_warning "Build for $game_type not found, skipping" + return + fi + + print_info "Installing $game_type executables..." + + # Main game executable + if [ -f "$build_game_dir/$exe_name" ]; then + install_file "$build_game_dir/$exe_name" "$data_dir/$exe_name" "$dry_run" + fi + + # Tools + for tool in WorldBuilder$tools_prefix W3DView$tools_prefix guiedit imagepacker mapcachebuilder wdump; do + local tool_exe="${tool}.exe" + if [ -f "$build_game_dir/$tool_exe" ]; then + install_file "$build_game_dir/$tool_exe" "$data_dir/$tool_exe" "$dry_run" + fi + done + + # DLLs (but NOT mss32.dll or binkw32.dll - keep originals) + for dll in DebugWindow.dll ParticleEditor.dll; do + if [ -f "$build_game_dir/$dll" ]; then + install_file "$build_game_dir/$dll" "$data_dir/$dll" "$dry_run" + fi + done + + # Also check Core directory for DLLs + if [ -f "$BUILD_DIR/Core/DebugWindow.dll" ]; then + install_file "$BUILD_DIR/Core/DebugWindow.dll" "$data_dir/DebugWindow.dll" "$dry_run" + fi +} + +restore_files() { + local game_dir="$1" + local data_dir="$game_dir/Data" + + print_info "Restoring original files..." + + local restored=0 + for backup in "$data_dir"/*.backup; do + if [ -f "$backup" ]; then + local original="${backup%.backup}" + cp "$backup" "$original" + print_success "Restored: $(basename "$original")" + ((restored++)) + fi + done + + if [ $restored -eq 0 ]; then + print_warning "No backup files found to restore" + else + print_success "Restored $restored files" + fi +} + +# Parse arguments +GAME_DIR="" +DETECT=false +RESTORE=false +GAME_TYPE="zh" +DRY_RUN=false + +while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) + usage + exit 0 + ;; + -d|--detect) + DETECT=true + shift + ;; + -r|--restore) + RESTORE=true + shift + ;; + -g|--game) + GAME_TYPE="$2" + shift 2 + ;; + --dry-run) + DRY_RUN=true + shift + ;; + -*) + print_error "Unknown option: $1" + usage + exit 1 + ;; + *) + GAME_DIR="$1" + shift + ;; + esac +done + +# Main execution +echo "" +echo "===================================" +echo " Generals Game Code - Installer" +echo "===================================" +echo "" + +# Detect or validate game directory +if [ "$DETECT" == "true" ] || [ -z "$GAME_DIR" ]; then + print_info "Searching for game installations..." + GAME_DIR=$(detect_game) +fi + +print_info "Game directory: $GAME_DIR" + +# Validate game directory +if [ ! -d "$GAME_DIR/Data" ]; then + print_error "Invalid game directory (Data/ subdirectory not found)" + echo "" + echo "Expected directory structure:" + echo " $GAME_DIR/" + echo " Data/" + echo " generalszh.exe" + echo " *.big files" + exit 1 +fi + +# Restore mode +if [ "$RESTORE" == "true" ]; then + restore_files "$GAME_DIR" + exit 0 +fi + +# Check build exists +check_build + +# Install +if [ "$DRY_RUN" == "true" ]; then + print_info "Dry run - showing what would be installed:" +fi + +case "$GAME_TYPE" in + zh|zerohour) + install_game "$GAME_DIR" "zh" "$DRY_RUN" + ;; + generals) + install_game "$GAME_DIR" "generals" "$DRY_RUN" + ;; + both|all) + install_game "$GAME_DIR" "zh" "$DRY_RUN" + install_game "$GAME_DIR" "generals" "$DRY_RUN" + ;; + *) + print_error "Unknown game type: $GAME_TYPE" + exit 1 + ;; +esac + +if [ "$DRY_RUN" != "true" ]; then + echo "" + print_success "Installation complete!" + echo "" + echo "To run the game:" + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + echo " wine \"$GAME_DIR/Data/generalszh.exe\"" + else + echo " cd \"$GAME_DIR/Data\" && generalszh.exe" + fi + echo "" + echo "To restore original files:" + echo " $(basename "$0") --restore \"$GAME_DIR\"" +fi diff --git a/scripts/dockerbuild.sh b/scripts/dockerbuild.sh new file mode 100755 index 00000000000..73e876acfb9 --- /dev/null +++ b/scripts/dockerbuild.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# +# Legacy Docker build script - consider using docker-build.sh instead +# +# docker-build.sh provides a more user-friendly interface with: +# - Game selection (--game zh/generals/all) +# - Colored output and progress messages +# - Target selection (--target) +# - Clean build option (--clean) +# +# This script is kept for backwards compatibility. +# + +set -euo pipefail + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +RESOURCES_DIR=$SCRIPT_DIR/../resources + + +usage() { + cat <&2 + usage + exit 1 + ;; + *) + # Positional TARGET (accept only one) + MAKE_TARGET="$MAKE_TARGET $1" + shift + ;; + esac +done + + +docker build --build-arg UID=$(id -u) --build-arg GID=$(id -g) $RESOURCES_DIR/dockerbuild -t zerohour-build +if [[ "$INTERACTIVE" == "true" ]]; then + FLAGS=" -it --entrypoint bash" +else + FLAGS="" +fi +docker run -u $(id -u):$(id -g) -e MAKE_TARGET="$MAKE_TARGET" -e FORCE_CMAKE=$FORCE_CMAKE -v "`pwd`":/build/cnc --rm $FLAGS zerohour-build + + + + + diff --git a/scripts/run-clang-tidy.py b/scripts/run-clang-tidy.py new file mode 100755 index 00000000000..71b41ab70ce --- /dev/null +++ b/scripts/run-clang-tidy.py @@ -0,0 +1,547 @@ +#!/usr/bin/env python3 +# TheSuperHackers @build JohnsterID 15/09/2025 Add clang-tidy runner script for code quality analysis +# TheSuperHackers @build bobtista 04/12/2025 Simplify script for PCH-free analysis builds + +""" +Clang-tidy runner script for GeneralsGameCode project. + +This is a convenience wrapper that: +- Auto-detects the clang-tidy analysis build (build/clang-tidy) +- Filters source files by include/exclude patterns +- Processes files in batches to handle Windows command-line limits +- Provides quiet progress reporting (only shows warnings/errors by default) + +For the analysis build to work correctly, it must be built WITHOUT precompiled headers. +Run this first: + cmake -B build/clang-tidy -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -G Ninja +""" + +import argparse +import json +import multiprocessing +import subprocess +import sys +from collections import defaultdict +from pathlib import Path +from typing import List, Optional, Tuple, Dict + + +def find_clang_tidy() -> str: + """Find clang-tidy executable in PATH or common locations.""" + try: + result = subprocess.run( + ['clang-tidy', '--version'], + capture_output=True, + text=True, + timeout=5 + ) + if result.returncode == 0: + return 'clang-tidy' + except (FileNotFoundError, subprocess.TimeoutExpired): + pass + + import platform + if platform.system() == 'Darwin': + import glob + import re + possible_paths = glob.glob('/opt/homebrew/Cellar/llvm*/*/bin/clang-tidy') + possible_paths.extend(glob.glob('/usr/local/Cellar/llvm*/*/bin/clang-tidy')) + + def extract_version(path): + match = re.search(r'llvm@?(\d+)', path) + if match: + return int(match.group(1)) + match = re.search(r'/(\d+)\.(\d+)\.(\d+)', path) + if match: + return int(match.group(1)) * 10000 + int(match.group(2)) * 100 + int(match.group(3)) + return 0 + + for path in sorted(possible_paths, key=extract_version, reverse=True): + try: + result = subprocess.run( + [path, '--version'], + capture_output=True, + text=True, + timeout=5 + ) + if result.returncode == 0: + return path + except (FileNotFoundError, subprocess.TimeoutExpired): + continue + + raise RuntimeError( + "clang-tidy not found in PATH. Please install clang-tidy:\n" + " macOS: brew install llvm\n" + " Windows: Install LLVM from https://llvm.org/builds/" + ) + + +def find_project_root() -> Path: + """Find the project root directory.""" + current = Path(__file__).resolve().parent + while current != current.parent: + if (current / 'CMakeLists.txt').exists(): + return current + current = current.parent + raise RuntimeError("Could not find project root (no CMakeLists.txt found)") + + +def get_clang_tidy_version(clang_tidy_exe: str) -> Optional[str]: + """Get the version string from clang-tidy.""" + try: + result = subprocess.run( + [clang_tidy_exe, '--version'], + capture_output=True, + text=True, + timeout=5 + ) + if result.returncode == 0: + return result.stdout.strip() + except (FileNotFoundError, subprocess.TimeoutExpired): + pass + return None + + +def extract_llvm_version(version_string: str) -> Optional[str]: + """Extract LLVM version number from clang-tidy version string.""" + import re + patterns = [ + r'LLVM version (\d+\.\d+\.\d+)', + r'llvm version (\d+\.\d+\.\d+)', + r'Homebrew LLVM version (\d+\.\d+\.\d+)', + r'version (\d+\.\d+\.\d+)', + ] + for pattern in patterns: + match = re.search(pattern, version_string, re.IGNORECASE) + if match: + return match.group(1) + return None + + +def find_clang_tidy_plugin(project_root: Path) -> Optional[str]: + """Find the GeneralsGameCode clang-tidy plugin.""" + possible_paths = [ + project_root / "scripts" / "clang-tidy-plugin" / "build" / "lib" / "libGeneralsGameCodeClangTidyPlugin.so", + project_root / "scripts" / "clang-tidy-plugin" / "build" / "lib" / "libGeneralsGameCodeClangTidyPlugin.dylib", + project_root / "scripts" / "clang-tidy-plugin" / "build" / "lib" / "libGeneralsGameCodeClangTidyPlugin.dll", + project_root / "scripts" / "clang-tidy-plugin" / "build" / "bin" / "libGeneralsGameCodeClangTidyPlugin.dll", + ] + + for path in possible_paths: + if path.exists(): + return str(path) + + return None + + +def find_compile_commands(build_dir: Optional[Path] = None) -> Path: + """Find compile_commands.json from the clang-tidy analysis build.""" + project_root = find_project_root() + + if build_dir: + if not build_dir.is_absolute(): + build_dir = project_root / build_dir + compile_commands = build_dir / "compile_commands.json" + if compile_commands.exists(): + return compile_commands + raise FileNotFoundError( + f"compile_commands.json not found in {build_dir}" + ) + + clang_tidy_build = project_root / "build" / "clang-tidy" + compile_commands = clang_tidy_build / "compile_commands.json" + + if not compile_commands.exists(): + raise RuntimeError( + "compile_commands.json not found!\n\n" + "Create the analysis build first:\n" + " cmake -B build/clang-tidy -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -G Ninja\n\n" + "Or specify a different build with --build-dir" + ) + + return compile_commands + + +def load_compile_commands(compile_commands_path: Path) -> List[dict]: + """Load and parse compile_commands.json.""" + try: + with open(compile_commands_path, 'r') as f: + return json.load(f) + except (json.JSONDecodeError, IOError) as e: + raise RuntimeError(f"Failed to load compile_commands.json: {e}") + + +def filter_source_files(compile_commands: List[dict], + include_patterns: List[str], + exclude_patterns: List[str]) -> List[str]: + """Filter source files based on include/exclude patterns.""" + project_root = find_project_root() + source_files = set() + + for entry in compile_commands: + file_path = Path(entry['file']) + + try: + rel_path = file_path.relative_to(project_root) + except ValueError: + continue + + rel_path_str = str(rel_path) + + if include_patterns: + if not any(pattern in rel_path_str for pattern in include_patterns): + continue + + if any(pattern in rel_path_str for pattern in exclude_patterns): + continue + + if file_path.suffix in {'.cpp', '.cxx', '.cc', '.c'}: + source_files.add(str(file_path)) + + return sorted(source_files) + + +def _run_batch(args: Tuple) -> Tuple[int, Dict[str, List[str]]]: + """Helper function to run clang-tidy on a batch of files (for multiprocessing).""" + batch_num, batch, compile_commands_dir, fix, extra_args, project_root, clang_tidy_exe, verbose = args + + cmd = [ + clang_tidy_exe, + f'-p={compile_commands_dir}', + ] + + if fix: + cmd.append('--fix') + + if extra_args: + cmd.extend(extra_args) + + cmd.extend(batch) + + issues_by_file = defaultdict(list) + + try: + result = subprocess.run( + cmd, + cwd=project_root, + capture_output=True, + text=True + ) + + if result.stdout or result.stderr: + output = result.stdout + result.stderr + + for line in output.splitlines(): + line = line.strip() + if not line: + continue + + line_lower = line.lower() + is_warning_or_error = ('warning' in line_lower or 'error' in line_lower) + + if ':' in line and (is_warning_or_error or verbose): + parts = line.split(':', 1) + if parts: + file_path = parts[0].strip() + if any(file_path.endswith(ext) for ext in ['.cpp', '.cxx', '.cc', '.c', '.h', '.hpp', '.hxx']): + try: + rel_path = Path(file_path).relative_to(project_root) + file_key = str(rel_path) + except (ValueError, OSError): + file_key = file_path + + if is_warning_or_error or verbose: + issues_by_file[file_key].append(line) + + return result.returncode, dict(issues_by_file) + except FileNotFoundError: + error_msg = "Error: clang-tidy not found. Please install LLVM/Clang." + if verbose: + print(error_msg, file=sys.stderr) + return 1, {} + + +def run_clang_tidy(source_files: List[str], + compile_commands_path: Path, + extra_args: List[str], + fix: bool = False, + jobs: int = 1, + verbose: bool = False, + load_plugin: bool = True) -> int: + """Run clang-tidy on source files in batches, optionally in parallel.""" + if not source_files: + print("No source files to analyze.") + return 0 + + clang_tidy_exe = find_clang_tidy() + + project_root = find_project_root() + plugin_path = None + if load_plugin: + plugin_path = find_clang_tidy_plugin(project_root) + if plugin_path: + clang_tidy_version_str = get_clang_tidy_version(clang_tidy_exe) + if clang_tidy_version_str: + detected_version = extract_llvm_version(clang_tidy_version_str) + if detected_version: + if verbose: + print(f"Found clang-tidy plugin: {plugin_path}") + print(f"Using clang-tidy LLVM version: {detected_version}") + print("Note: Ensure the plugin was built with the same LLVM version (check CMake build output).\n") + else: + print(f"Note: Verify plugin was built with LLVM {detected_version} (check CMake build output)") + else: + if verbose: + print(f"Found clang-tidy plugin: {plugin_path}\n") + else: + if verbose: + print(f"Found clang-tidy plugin: {plugin_path}\n") + + BATCH_SIZE = 50 + total_files = len(source_files) + batches = [source_files[i:i + BATCH_SIZE] for i in range(0, total_files, BATCH_SIZE)] + + compile_commands_dir = compile_commands_path.parent + + all_issues = defaultdict(list) + files_with_issues = set() + total_issues = 0 + + if plugin_path and '-load' not in ' '.join(extra_args): + extra_args = ['-load', plugin_path] + extra_args + + if jobs > 1: + if verbose: + print(f"Running clang-tidy on {total_files} file(s) in {len(batches)} batch(es) with {jobs} workers...\n") + else: + print(f"Analyzing {total_files} file(s) with {jobs} workers...", end='', flush=True) + + try: + with multiprocessing.Pool(processes=jobs) as pool: + batch_results = pool.map( + _run_batch, + [ + (idx + 1, batch, compile_commands_dir, fix, extra_args, project_root, clang_tidy_exe, verbose) + for idx, batch in enumerate(batches) + ] + ) + + overall_returncode = 0 + for returncode, issues in batch_results: + if returncode != 0: + overall_returncode = returncode + for file_path, file_issues in issues.items(): + if file_issues: + all_issues[file_path].extend(file_issues) + files_with_issues.add(file_path) + total_issues += len(file_issues) + + if not verbose: + print(" done.") + except KeyboardInterrupt: + print("\nInterrupted by user.") + return 130 + else: + if verbose: + print(f"Running clang-tidy on {total_files} file(s) in {len(batches)} batch(es)...\n") + else: + print(f"Analyzing {total_files} file(s)...", end='', flush=True) + + overall_returncode = 0 + for batch_num, batch in enumerate(batches, 1): + try: + if verbose: + print(f"Batch {batch_num}/{len(batches)}: {len(batch)} file(s)...") + + returncode, issues = _run_batch((batch_num, batch, compile_commands_dir, fix, extra_args, project_root, clang_tidy_exe, verbose)) + if returncode != 0: + overall_returncode = returncode + + for file_path, file_issues in issues.items(): + if file_issues: + all_issues[file_path].extend(file_issues) + files_with_issues.add(file_path) + total_issues += len(file_issues) + + if not verbose and batch_num < len(batches): + print('.', end='', flush=True) + except KeyboardInterrupt: + print("\nInterrupted by user.") + return 130 + + if not verbose: + print(" done.") + + print(f"\nSummary: {len(files_with_issues)} file(s) with issues, {total_issues} total issue(s)") + + if all_issues: + print("\nIssues found:") + for file_path in sorted(all_issues.keys()): + print(f"\n{file_path}:") + for issue in all_issues[file_path]: + print(f" {issue}") + + return overall_returncode + + +def main(): + parser = argparse.ArgumentParser( + description="Run clang-tidy on GeneralsGameCode project", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + # First-time setup: Create PCH-free analysis build + cmake -B build/clang-tidy -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -G Ninja + + # Analyze all source files + python scripts/run-clang-tidy.py + + # Analyze specific directory + python scripts/run-clang-tidy.py --include Core/Libraries/ + + # Analyze with specific checks + python scripts/run-clang-tidy.py --include GameClient/ -- -checks="-*,modernize-use-nullptr" + + # Apply fixes (use with caution!) + python scripts/run-clang-tidy.py --fix --include Keyboard.cpp -- -checks="-*,modernize-use-nullptr" + + # Use parallel processing (recommended: --jobs 4 for 6-core CPUs) + python scripts/run-clang-tidy.py --jobs 4 -- -checks="-*,modernize-use-nullptr" + + # Show verbose output (default: only warnings/errors) + python scripts/run-clang-tidy.py --verbose --include Core/Libraries/ + + # Use different build directory + python scripts/run-clang-tidy.py --build-dir build/win32-debug + +Note: Requires a PCH-free build. Create with: + cmake -B build/clang-tidy -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -G Ninja + """ + ) + + parser.add_argument( + '--build-dir', '-b', + type=Path, + help='Build directory with compile_commands.json (auto-detected if omitted)' + ) + + parser.add_argument( + '--include', '-i', + action='append', + default=[], + help='Include files matching this pattern (can be used multiple times)' + ) + + parser.add_argument( + '--exclude', '-e', + action='append', + default=[], + help='Exclude files matching this pattern (can be used multiple times)' + ) + + parser.add_argument( + '--fix', + action='store_true', + help='Apply suggested fixes automatically (use with caution!)' + ) + + parser.add_argument( + '--jobs', '-j', + type=int, + default=multiprocessing.cpu_count(), + help=f'Number of parallel workers (default: {multiprocessing.cpu_count()} - auto-detected). Use 1 for serial processing' + ) + + parser.add_argument( + '--verbose', '-v', + action='store_true', + help='Show detailed output for each file (default: only show warnings/errors)' + ) + + parser.add_argument( + '--no-plugin', + action='store_true', + help='Do not automatically load the GeneralsGameCode clang-tidy plugin' + ) + + parser.add_argument( + 'clang_tidy_args', + nargs='*', + help='Additional arguments to pass to clang-tidy, or specific files to analyze (if files are provided, --include/--exclude are ignored)' + ) + + args = parser.parse_args() + + try: + compile_commands_path = find_compile_commands(args.build_dir) + print(f"Using compile commands: {compile_commands_path}\n") + + project_root = find_project_root() + specified_files = [] + clang_tidy_args = [] + + for arg in args.clang_tidy_args: + file_path = Path(arg) + if not file_path.is_absolute(): + file_path = project_root / file_path + + if file_path.exists() and file_path.suffix in {'.cpp', '.cxx', '.cc', '.c', '.h', '.hpp'}: + specified_files.append(str(file_path.resolve())) + else: + clang_tidy_args.append(arg) + + if specified_files: + if args.verbose: + print(f"Analyzing {len(specified_files)} specified file(s)\n") + return run_clang_tidy( + specified_files, + compile_commands_path, + clang_tidy_args, + args.fix, + args.jobs, + args.verbose, + load_plugin=not args.no_plugin + ) + + compile_commands = load_compile_commands(compile_commands_path) + + default_excludes = [ + 'Dependencies/MaxSDK', # External SDK + '_deps/', # CMake dependencies + 'build/', # Build artifacts + '.git/', # Git directory + ] + + exclude_patterns = default_excludes + args.exclude + + source_files = filter_source_files( + compile_commands, + args.include, + exclude_patterns + ) + + if not source_files: + print("No source files found matching the criteria.") + return 1 + + if args.verbose: + print(f"Found {len(source_files)} source file(s) to analyze\n") + + return run_clang_tidy( + source_files, + compile_commands_path, + clang_tidy_args, + args.fix, + args.jobs, + args.verbose, + load_plugin=not args.no_plugin + ) + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + return 1 + + +if __name__ == '__main__': + sys.exit(main()) +