diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index c53ec082eb9..2d63becc8de 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -124,6 +124,16 @@ jobs: Copy-Item $_.FullName ci-artifacts/ } + if (Test-Path "assets/fonts") { + if (-not (Test-Path "assets/fonts/LICENSE.liberation") -or -not (Test-Path "assets/fonts/LICENSE.fontawesome")) { + throw "Missing required font license files in assets/fonts" + } + New-Item -ItemType Directory -Force artifacts/fonts | Out-Null + Copy-Item assets/fonts/* artifacts/fonts/ + New-Item -ItemType Directory -Force ci-artifacts/fonts | Out-Null + Copy-Item assets/fonts/* ci-artifacts/fonts/ + } + # Collect Desktop Release CRT DLLs (msvcp140.dll, vcruntime140.dll, etc.) $vcReleaseDir = Get-ChildItem "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Redist\MSVC" -Directory -Recurse | Where-Object { $_.FullName -match "\\x86\\" -and $_.FullName -match "Microsoft\.VC143\.CRT$" -and $_.FullName -notmatch "onecore" } | diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index 96dc3361509..1d00cd86408 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -3,6 +3,7 @@ add_library(corei_gameengine_include INTERFACE) add_library(corei_libraries_include INTERFACE) add_library(corei_libraries_source_wwvegas INTERFACE) add_library(corei_main INTERFACE) +add_library(corei_platform INTERFACE) add_library(corei_always INTERFACE) add_library(corei_always_no_pch INTERFACE) # Use this for Shared Libs with MFC AFX @@ -16,6 +17,7 @@ endif() target_include_directories(corei_gameengine_include INTERFACE "GameEngine/Include") target_include_directories(corei_libraries_include INTERFACE "Libraries/Include") target_include_directories(corei_libraries_source_wwvegas INTERFACE "Libraries/Source/WWVegas") +target_include_directories(corei_platform INTERFACE "Libraries/Source/Platform") target_include_directories(corei_main INTERFACE "Main") target_sources(corei_libraries_include PRIVATE diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp index 9af356cc3a8..6551e88892b 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp @@ -72,9 +72,12 @@ FontCharsClass *LoadUnicodeFallbackFont(Int size, Bool bold, const char *base_na // then known-good Unicode fonts. We iterate all candidates and pick the first that loads AND has a // different family from the base font. This avoids returning a font that lacks Cyrillic coverage // (e.g., Arial on macOS) when a better universal font like "Arial Unicode MS" is available. + // GeneralsX @feature felipebraz 26/09/2026 Include Liberation Sans in Unicode fallback candidate list static const char *kFallbackUnicodeFonts[] = { "Arial Unicode MS", "Arial Unicode", + "Liberation Sans", + "Liberation Serif", "Arial", "Helvetica Neue", "Helvetica", diff --git a/Core/Libraries/Source/Platform/PlatformPaths.h b/Core/Libraries/Source/Platform/PlatformPaths.h new file mode 100644 index 00000000000..8af0ede142d --- /dev/null +++ b/Core/Libraries/Source/Platform/PlatformPaths.h @@ -0,0 +1,195 @@ +/* +** Command & Conquer(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +// PlatformPaths.h - Cross-platform executable and bundle path resolution +// GeneralsX @feature felipebraz 26/09/2026 Platform abstraction for bundle paths +// GeneralsX @refactor felipebraz 26/09/2026 Encapsulate font probing and file accessibility in platform layer + +#pragma once + +#include +#include +#include +#include + +#if defined(_WIN32) +#include +#else +#include +#include +#endif + +#if defined(__APPLE__) +#include +#endif + +namespace Platform { + +inline bool IsFileReadable(const char *path) +{ + if ( !path || path[0] == '\0' ) { + return false; + } +#if defined(_WIN32) + return ( _access( path, 4 ) == 0 ); +#else + return ( access( path, R_OK ) == 0 ); +#endif +} + +inline bool GetMacOSBundleFontDirectories(char *resFonts, size_t resFontsSize, + char *binFonts, size_t binFontsSize) +{ +#if defined(__APPLE__) + char rawExecPath[1024] = {0}; + uint32_t bufSize = sizeof(rawExecPath); + if ( _NSGetExecutablePath( rawExecPath, &bufSize ) != 0 ) { + return false; + } + + char realExecPath[1024] = {0}; + if ( realpath( rawExecPath, realExecPath ) == nullptr ) { + return false; + } + + char *lastSlash = strrchr( realExecPath, '/' ); + if ( lastSlash == nullptr ) { + return false; + } + *lastSlash = '\0'; + + if ( resFonts && resFontsSize > 0 ) { + snprintf( resFonts, resFontsSize, "%s/../Resources/fonts", realExecPath ); + } + if ( binFonts && binFontsSize > 0 ) { + snprintf( binFonts, binFontsSize, "%s/../fonts", realExecPath ); + } + return true; +#else + (void)resFonts; + (void)resFontsSize; + (void)binFonts; + (void)binFontsSize; + return false; +#endif +} + +inline bool FindLocalFontFile(const char *const *candidates, int candidateCount, + char *outPath, size_t outPathSize) +{ + if ( !candidates || candidateCount <= 0 || !outPath || outPathSize == 0 ) { + return false; + } + + const char *searchDirs[24]; + int searchDirCount = 0; + + char envBundleFonts[512] = {0}; + const char *gxBundleFonts = getenv( "GX_BUNDLE_FONTS" ); + if ( gxBundleFonts && gxBundleFonts[0] != '\0' ) { + snprintf( envBundleFonts, sizeof(envBundleFonts), "%s", gxBundleFonts ); + searchDirs[searchDirCount++] = envBundleFonts; + } + +#if defined(__APPLE__) + char macosResFonts[512] = {0}; + char macosBinFonts[512] = {0}; + if ( GetMacOSBundleFontDirectories( macosResFonts, sizeof(macosResFonts), + macosBinFonts, sizeof(macosBinFonts) ) ) { + if ( IsFileReadable( macosResFonts ) && searchDirCount < 24 ) { + searchDirs[searchDirCount++] = macosResFonts; + } + if ( IsFileReadable( macosBinFonts ) && searchDirCount < 24 ) { + searchDirs[searchDirCount++] = macosBinFonts; + } + } +#endif + + if ( searchDirCount < 24 ) searchDirs[searchDirCount++] = "fonts"; + if ( searchDirCount < 24 ) searchDirs[searchDirCount++] = "./fonts"; + if ( searchDirCount < 24 ) searchDirs[searchDirCount++] = "../Resources/fonts"; + if ( searchDirCount < 24 ) searchDirs[searchDirCount++] = "Resources/fonts"; + if ( searchDirCount < 24 ) searchDirs[searchDirCount++] = "assets/fonts"; + + char envZhFonts[512] = {0}; + const char *zhPath = getenv( "CNC_GENERALS_ZH_PATH" ); + if ( zhPath && zhPath[0] != '\0' && searchDirCount < 24 ) { + snprintf( envZhFonts, sizeof(envZhFonts), "%s/fonts", zhPath ); + searchDirs[searchDirCount++] = envZhFonts; + } + + char envGenFonts[512] = {0}; + const char *genPath = getenv( "CNC_GENERALS_PATH" ); + if ( genPath && genPath[0] != '\0' && searchDirCount < 24 ) { + snprintf( envGenFonts, sizeof(envGenFonts), "%s/fonts", genPath ); + searchDirs[searchDirCount++] = envGenFonts; + } + + char homeZhFonts[512] = {0}; + char homeGenFonts[512] = {0}; + const char *homePath = getenv( "HOME" ); + if ( homePath && homePath[0] != '\0' ) { + if ( searchDirCount < 24 ) { + snprintf( homeZhFonts, sizeof(homeZhFonts), "%s/GeneralsX/GeneralsZH/fonts", homePath ); + searchDirs[searchDirCount++] = homeZhFonts; + } + if ( searchDirCount < 24 ) { + snprintf( homeGenFonts, sizeof(homeGenFonts), "%s/GeneralsX/Generals/fonts", homePath ); + searchDirs[searchDirCount++] = homeGenFonts; + } + } + + if ( searchDirCount < 24 ) { + searchDirs[searchDirCount++] = "/app/share/fonts"; + } + if ( searchDirCount < 24 ) { + searchDirs[searchDirCount++] = "/usr/share/fonts/truetype/liberation"; + } + + static const char *extensions[] = { ".ttf", ".otf", ".ttc" }; + char candidatePath[1024]; + + for ( int d = 0; d < searchDirCount; ++d ) { + for ( int c = 0; c < candidateCount; ++c ) { + const char *cand = candidates[c]; + if ( !cand || cand[0] == '\0' ) { + continue; + } + if ( strrchr( cand, '.' ) != nullptr ) { + snprintf( candidatePath, sizeof(candidatePath), "%s/%s", searchDirs[d], cand ); + if ( IsFileReadable( candidatePath ) ) { + snprintf( outPath, outPathSize, "%s", candidatePath ); + return true; + } + } else { + for ( size_t e = 0; e < sizeof(extensions) / sizeof(extensions[0]); ++e ) { + snprintf( candidatePath, sizeof(candidatePath), "%s/%s%s", + searchDirs[d], cand, extensions[e] ); + if ( IsFileReadable( candidatePath ) ) { + snprintf( outPath, outPathSize, "%s", candidatePath ); + return true; + } + } + } + } + } + + return false; +} + +} // namespace Platform diff --git a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt index 21f2b1be15e..44fc313e754 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt @@ -277,6 +277,7 @@ target_link_libraries(corei_ww3d2 INTERFACE core_browserengine core_wwlib core_wwmath + corei_platform $<$:Freetype::Freetype> $<$:Freetype::Freetype> $<$:Fontconfig::Fontconfig> diff --git a/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp b/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp index 3f4d802b850..c396aa1ce44 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp @@ -1636,62 +1636,185 @@ FontCharsClass::Free_GDI_Font () #if defined(SAGE_USE_FREETYPE) && !defined(_WIN32) +#include +#include +#include +#include +#if defined(__APPLE__) +#include +#endif +#include "PlatformPaths.h" + //////////////////////////////////////////////////////////////////////////////////// // // Locate_Font_FontConfig // -// TheSuperHackers @feature FreeType port 10/02/2026 Locate system font using Fontconfig +// GeneralsX @feature felipebraz 26/09/2026 Universal multi-tiered font resolution: +// Tier 1: Probe local/bundled fonts/ directory (app bundle, game root, cwd) +// Tier 2: System Fontconfig match (desktop Linux/macOS) +// Tier 3: Guaranteed fallback to bundled Arial (Liberation Sans) //////////////////////////////////////////////////////////////////////////////////// const char * FontCharsClass::Locate_Font_FontConfig (const char *font_name) { - // - // Initialize Fontconfig library - // - FcConfig *config = FcInitLoadConfigAndFonts(); - if ( config == nullptr ) { + if ( font_name == nullptr || font_name[0] == '\0' ) { return nullptr; } // - // Create a pattern for the requested font + // Normalize font name (lowercase, strip spaces, hyphens, and underscores) // - FcPattern *pattern = FcNameParse( (const FcChar8*)font_name ); - if ( pattern == nullptr ) { - FcConfigDestroy( config ); - return nullptr; + char normalized[128]; + int n = 0; + for ( const char *p = font_name; *p != '\0' && n < (int)sizeof(normalized) - 1; ++p ) { + if ( *p == ' ' || *p == '-' || *p == '_' ) { + continue; + } + normalized[n++] = (char)tolower( (unsigned char)*p ); + } + normalized[n] = '\0'; + + // Map "generals" to "arial" + if ( strcmp( normalized, "generals" ) == 0 ) { + strncpy( normalized, "arial", sizeof(normalized) - 1 ); + normalized[sizeof(normalized) - 1] = '\0'; } // - // Configure the pattern + // Prepare candidate names based on requested font and bold status // - FcConfigSubstitute( config, pattern, FcMatchPattern ); - FcDefaultSubstitute( pattern ); + bool is_bold = this->IsBold || (strstr( font_name, "Bold" ) != nullptr) || (strstr( font_name, "bold" ) != nullptr); + + const char *candidates[12]; + int candidate_count = 0; + + char bold_candidate[140]; + if ( is_bold && !strstr( normalized, "bold" ) ) { + snprintf( bold_candidate, sizeof(bold_candidate), "%sbold", normalized ); + candidates[candidate_count++] = bold_candidate; + } + + candidates[candidate_count++] = normalized; + + // Add common Liberation and standard aliases (exact normalized matches) + // GeneralsX @bugfix felipebraz 26/09/2026 Use exact alias checks to prevent matching wide-coverage fonts like Arial Unicode MS + bool is_arial = (strcmp( normalized, "arial" ) == 0 || + strcmp( normalized, "arialbold" ) == 0 || + strcmp( normalized, "generals" ) == 0 || + strcmp( normalized, "generalsbold" ) == 0 || + strcmp( normalized, "liberationsans" ) == 0 || + strcmp( normalized, "liberationsansbold" ) == 0); + + bool is_times = (strcmp( normalized, "times" ) == 0 || + strcmp( normalized, "timesbold" ) == 0 || + strcmp( normalized, "timesnewroman" ) == 0 || + strcmp( normalized, "timesnewromanbold" ) == 0 || + strcmp( normalized, "liberationserif" ) == 0 || + strcmp( normalized, "liberationserifbold" ) == 0); + + bool is_courier = (strcmp( normalized, "courier" ) == 0 || + strcmp( normalized, "courierbold" ) == 0 || + strcmp( normalized, "couriernew" ) == 0 || + strcmp( normalized, "couriernewbold" ) == 0 || + strcmp( normalized, "liberationmono" ) == 0 || + strcmp( normalized, "liberationmonobold" ) == 0); + + if ( is_arial ) { + if ( is_bold ) { + candidates[candidate_count++] = "LiberationSans-Bold"; + candidates[candidate_count++] = "liberationsans-bold"; + } + candidates[candidate_count++] = "LiberationSans-Regular"; + candidates[candidate_count++] = "liberationsans-regular"; + candidates[candidate_count++] = "LiberationSans"; + } else if ( is_times ) { + if ( is_bold ) { + candidates[candidate_count++] = "LiberationSerif-Bold"; + candidates[candidate_count++] = "liberationserif-bold"; + } + candidates[candidate_count++] = "LiberationSerif-Regular"; + candidates[candidate_count++] = "liberationserif-regular"; + candidates[candidate_count++] = "LiberationSerif"; + } else if ( is_courier ) { + if ( is_bold ) { + candidates[candidate_count++] = "LiberationMono-Bold"; + candidates[candidate_count++] = "liberationmono-bold"; + } + candidates[candidate_count++] = "LiberationMono-Regular"; + candidates[candidate_count++] = "liberationmono-regular"; + candidates[candidate_count++] = "LiberationMono"; + } // - // Find the best match + // Tier 1: Probe local candidate font files via platform abstraction + // GeneralsX @refactor felipebraz 26/09/2026 Encapsulate font probing in platform layer // - FcResult result = FcResultNoMatch; - FcPattern *font = FcFontMatch( config, pattern, &result ); + char candidate_path[1024]; + if ( Platform::FindLocalFontFile( candidates, candidate_count, candidate_path, sizeof(candidate_path) ) ) { + FreetypeFontPath = candidate_path; + return FreetypeFontPath; + } - const char *font_path = nullptr; - if ( font != nullptr && result == FcResultMatch ) { - // - // Extract the font file path - // - FcChar8 *file_path = nullptr; - if ( FcPatternGetString( font, FC_FILE, 0, &file_path ) == FcResultMatch ) { - FreetypeFontPath = (const char*)file_path; - font_path = FreetypeFontPath; + // + // Tier 2: System Fontconfig match (desktop Linux/macOS) + // +#if !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) + FcConfig *config = FcInitLoadConfigAndFonts(); + if ( config != nullptr ) { + FcPattern *pattern = FcNameParse( (const FcChar8*)font_name ); + if ( pattern != nullptr ) { + FcConfigSubstitute( config, pattern, FcMatchPattern ); + FcDefaultSubstitute( pattern ); + + FcResult result = FcResultNoMatch; + FcPattern *font = FcFontMatch( config, pattern, &result ); + if ( font != nullptr && result == FcResultMatch ) { + FcChar8 *file_path = nullptr; + if ( FcPatternGetString( font, FC_FILE, 0, &file_path ) == FcResultMatch && file_path != nullptr ) { + if ( Platform::IsFileReadable( (const char*)file_path ) ) { + FreetypeFontPath = (const char*)file_path; + FcPatternDestroy( font ); + FcPatternDestroy( pattern ); + FcConfigDestroy( config ); + return FreetypeFontPath; + } + } + FcPatternDestroy( font ); + } + FcPatternDestroy( pattern ); } - - FcPatternDestroy( font ); + FcConfigDestroy( config ); } +#endif - FcPatternDestroy( pattern ); - FcConfigDestroy( config ); + // + // Tier 3: Universal fallback to bundled Arial/Liberation font for recognized core UI fonts + // GeneralsX @bugfix felipebraz 26/09/2026 Guard Tier 3 so unresolved Unicode/CJK fonts return nullptr and allow caller fallback delegation + // + if ( is_arial || is_times || is_courier ) { + static const char *fallback_names_bold[] = { + "arialbold.ttf", + "arial.ttf", + "LiberationSans-Bold.ttf", + "LiberationSans-Regular.ttf" + }; + static const char *fallback_names_reg[] = { + "arial.ttf", + "LiberationSans-Regular.ttf" + }; + const char *const *fnames = is_bold ? fallback_names_bold : fallback_names_reg; + int fcount = is_bold ? 4 : 2; + if ( Platform::FindLocalFontFile( fnames, fcount, candidate_path, sizeof(candidate_path) ) ) { + fprintf( stderr, "[Font] Note: Font '%s' not found; falling back to bundled '%s'\n", font_name, candidate_path ); + fflush( stderr ); + FreetypeFontPath = candidate_path; + return FreetypeFontPath; + } + } - return font_path; + fprintf( stderr, "[Font] WARNING: Unable to resolve font '%s' (all tiers exhausted)\n", font_name ); + fflush( stderr ); + return nullptr; } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.h b/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.h index 6f5af9f45fe..a122acd5704 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.h @@ -44,10 +44,16 @@ #include "WWLib/win.h" // GeneralsX @build fbraz 11/02/2026 BenderAI - FreeType2 for Linux text rendering (Phase 1.5) +// GeneralsX @feature felipebraz 26/09/2026 Guard fontconfig header for platforms without fontconfig (e.g. iOS) #if defined(SAGE_USE_FREETYPE) && !defined(_WIN32) #include #include FT_FREETYPE_H - #include + #if defined(__APPLE__) + #include + #endif + #if !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) + #include + #endif #endif /* diff --git a/assets/fonts/LICENSE.fontawesome b/assets/fonts/LICENSE.fontawesome new file mode 100644 index 00000000000..e69c5e39a30 --- /dev/null +++ b/assets/fonts/LICENSE.fontawesome @@ -0,0 +1,165 @@ +Fonticons, Inc. (https://fontawesome.com) + +-------------------------------------------------------------------------------- + +Font Awesome Free License + +Font Awesome Free is free, open source, and GPL friendly. You can use it for +commercial projects, open source projects, or really almost whatever you want. +Full Font Awesome Free license: https://fontawesome.com/license/free. + +-------------------------------------------------------------------------------- + +# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/) + +The Font Awesome Free download is licensed under a Creative Commons +Attribution 4.0 International License and applies to all icons packaged +as SVG and JS file types. + +-------------------------------------------------------------------------------- + +# Fonts: SIL OFL 1.1 License + +In the Font Awesome Free download, the SIL OFL license applies to all icons +packaged as web and desktop font files. + +Copyright (c) 2024 Fonticons, Inc. (https://fontawesome.com) +with Reserved Font Name: "Font Awesome". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +SIL OPEN FONT LICENSE +Version 1.1 - 26 February 2007 + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting — in part or in whole — any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +-------------------------------------------------------------------------------- + +# Code: MIT License (https://opensource.org/licenses/MIT) + +In the Font Awesome Free download, the MIT license applies to all non-font and +non-icon files. + +Copyright 2024 Fonticons, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in the +Software without restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +-------------------------------------------------------------------------------- + +# Attribution + +Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font +Awesome Free files already contain embedded comments with sufficient +attribution, so you shouldn't need to do anything additional when using these +files normally. + +We've kept attribution comments terse, so we ask that you do not actively work +to remove them from files, especially code. They're a great way for folks to +learn about Font Awesome. + +-------------------------------------------------------------------------------- + +# Brand Icons + +All brand icons are trademarks of their respective owners. The use of these +trademarks does not indicate endorsement of the trademark holder by Font +Awesome, nor vice versa. **Please do not use brand logos for any purpose except +to represent the company, product, or service to which they refer.** diff --git a/assets/fonts/LICENSE.liberation b/assets/fonts/LICENSE.liberation new file mode 100644 index 00000000000..aba73e8a403 --- /dev/null +++ b/assets/fonts/LICENSE.liberation @@ -0,0 +1,102 @@ +Digitized data copyright (c) 2010 Google Corporation + with Reserved Font Arimo, Tinos and Cousine. +Copyright (c) 2012 Red Hat, Inc. + with Reserved Font Name Liberation. + +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 + +PREAMBLE The goals of the Open Font License (OFL) are to stimulate +worldwide development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to provide +a free and open framework in which fonts may be shared and improved in +partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. +The fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + + + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. +This may include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components +as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting ? in part or in whole ? +any of the components of the Original Version, by changing formats or +by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer +or other person who contributed to the Font Software. + + +PERMISSION & CONDITIONS + +Permission is hereby granted, free of charge, to any person obtaining a +copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components,in + Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, + redistributed and/or sold with any software, provided that each copy + contains the above copyright notice and this license. These can be + included either as stand-alone text files, human-readable headers or + in the appropriate machine-readable metadata fields within text or + binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font + Name(s) unless explicit written permission is granted by the + corresponding Copyright Holder. This restriction only applies to the + primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font + Software shall not be used to promote, endorse or advertise any + Modified Version, except to acknowledge the contribution(s) of the + Copyright Holder(s) and the Author(s) or with their explicit written + permission. + +5) The Font Software, modified or unmodified, in part or in whole, must + be distributed entirely under this license, and must not be distributed + under any other license. The requirement for fonts to remain under + this license does not apply to any document created using the Font + Software. + + + +TERMINATION +This license becomes null and void if any of the above conditions are not met. + + + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER +DEALINGS IN THE FONT SOFTWARE. + diff --git a/assets/fonts/arial.ttf b/assets/fonts/arial.ttf new file mode 100644 index 00000000000..e6339859d0b Binary files /dev/null and b/assets/fonts/arial.ttf differ diff --git a/assets/fonts/arialbold.ttf b/assets/fonts/arialbold.ttf new file mode 100644 index 00000000000..dc5d57f15f0 Binary files /dev/null and b/assets/fonts/arialbold.ttf differ diff --git a/assets/fonts/couriernew.ttf b/assets/fonts/couriernew.ttf new file mode 100644 index 00000000000..e774859cba4 Binary files /dev/null and b/assets/fonts/couriernew.ttf differ diff --git a/assets/fonts/fa-brands-400.ttf b/assets/fonts/fa-brands-400.ttf index d05c969ab38..1fbb1f7c32d 100644 Binary files a/assets/fonts/fa-brands-400.ttf and b/assets/fonts/fa-brands-400.ttf differ diff --git a/assets/fonts/timesnewroman.ttf b/assets/fonts/timesnewroman.ttf new file mode 100644 index 00000000000..5e5550c0afc Binary files /dev/null and b/assets/fonts/timesnewroman.ttf differ diff --git a/docs/WORKLOG/2026-09-DIARY.md b/docs/WORKLOG/2026-09-DIARY.md index a052820b054..7a72c962fbf 100644 --- a/docs/WORKLOG/2026-09-DIARY.md +++ b/docs/WORKLOG/2026-09-DIARY.md @@ -3,6 +3,54 @@ > [!NOTE] > **AI-Generated Content Disclosure**: This worklog is automatically generated and maintained by AI coding agents to document daily progress, debugging sessions, and technical decisions. +## 26/09/2026 +### Universal Font Resolution & Bundled Liberation Fonts +- **Context**: The game engine previously relied exclusively on system `Fontconfig` on non-Win32 platforms (Linux and macOS) to locate fonts. When system fonts (such as Microsoft's proprietary `Arial`) were missing or Fontconfig configuration was absent (e.g. clean macOS installations, stripped Linux/Flatpak environments, or iOS), font resolution failed and resulted in missing in-game text or startup crashes. +- **Root Cause & Licensing**: + - Proprietary Microsoft Windows fonts (`Arial.ttf`, `Times New Roman`, etc.) cannot be legally redistributed with GeneralsX releases. + - The iOS port (`ammaarreshi/Generals-Mac-iOS-iPad`) solved this by bundling **Liberation Fonts v2.1.5** (SIL Open Font License 1.1), which are metric-compatible replacements for Arial, Times New Roman, and Courier New with wide multilingual support (Latin, Cyrillic, Greek). +- **Changes**: + - **Asset Staging**: + - Added bundled Liberation Fonts into `assets/fonts/`: `arial.ttf` (LiberationSans-Regular), `arialbold.ttf` (LiberationSans-Bold), `couriernew.ttf` (LiberationMono-Regular), `timesnewroman.ttf` (LiberationSerif-Regular), and `LICENSE.liberation` (OFL 1.1). + - Created `scripts/env/setup-fonts.sh` to download, verify (SHA-256), and stage Liberation fonts if needed. + - **Multi-Tiered Font Resolution (`Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp`)**: + - **Tier 1 (Bundled/Local)**: Probes local `fonts/` directory across runtime paths (`fonts/`, `./fonts/`, `Resources/fonts/`, `../Resources/fonts/`, `assets/fonts/`, `$CNC_GENERALS_ZH_PATH/fonts/`, `$CNC_GENERALS_PATH/fonts/`, `$HOME/GeneralsX/...`, `/app/share/fonts/`). Normalizes font names (lowercase, strips spaces/hyphens) and checks bold variants (`arialbold.ttf`) and Liberation aliases. + - **Tier 2 (System Fontconfig)**: If local search yields no match and Fontconfig is supported on the target platform, queries Fontconfig for system-installed fonts. + - **Tier 3 (Universal Fallback)**: If Fontconfig fails or is disabled/unavailable, falls back to bundled `arial.ttf` (or `arialbold.ttf` if bold) to guarantee text rendering under all circumstances. + - **Fontconfig Isolation (`render2dsentence.h`)**: + - Guarded `` with `TargetConditionals.h` to skip Fontconfig on iOS/iPadOS while keeping desktop macOS and Linux intact. + - **Unicode Fallback (`Core/GameEngineDevice/Source/W3DDevice/GameClient/GUI/W3DGameFont.cpp`)**: + - Added `Liberation Sans` and `Liberation Serif` to `kFallbackUnicodeFonts`. Excluded `Liberation Sans` from `kFullCoverageFonts` so CJK text can cleanly delegate to an alternate Unicode font. + - **Packaging & Bundling Updates**: + - Updated `scripts/build/macos/bundle-macos-zh.sh`, `scripts/build/macos/bundle-macos-generals.sh`, and `deploy-*.sh` scripts to copy `assets/fonts/*.ttf` and `LICENSE.liberation` into runtime/resources `fonts/`. + - Updated `flatpak/com.fbraz3.GeneralsXZH.yml` and `flatpak/com.fbraz3.GeneralsX.yml` to install `assets/fonts/*.ttf` and `LICENSE.liberation` into `/app/share/fonts/`. + - **CodeRabbit Review Findings Resolution**: + - Replaced substring matching (`strstr(normalized, "arial")`) with exact normalized alias checks for Arial, Times, and Courier families in `Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp`. This prevents wide-coverage font requests such as `Arial Unicode MS` from being hijacked in Tier 1. + - Guarded Tier 3 universal fallback to recognized core UI aliases (`is_arial || is_times || is_courier`), so unresolved Unicode/CJK requests return `nullptr` and allow caller fallback delegation (such as `LoadUnicodeFallbackFont()`). + - Resolved macOS application bundle's `Contents/Resources/fonts` dynamically at runtime via `_NSGetExecutablePath` and exported `GX_BUNDLE_FONTS` in `bundle-macos-zh.sh` and `bundle-macos-generals.sh` launchers, ensuring fonts are found even when the launcher changes CWD to external game data. + - Isolated macOS executable and bundle path resolution into a dedicated platform layer helper (`Core/Libraries/Source/Platform/PlatformPaths.h` and `corei_platform` CMake interface target), removing raw `_NSGetExecutablePath` and `` calls from the renderer. + - Enforced platform isolation by encapsulating local font search directories, candidate probing, environment variable queries (`GX_BUNDLE_FONTS`, `CNC_GENERALS_ZH_PATH`, `CNC_GENERALS_PATH`, `HOME`), and file readability checks (`Platform::IsFileReadable()`) in `PlatformPaths.h` under `namespace Platform`. Removed direct ``, `getenv()`, and `access()` calls from `render2dsentence.cpp`. + - Enforced strict error handling in macOS bundling and Linux/macOS deployment scripts (`deploy-linux.sh`, `deploy-linux-zh.sh`, `deploy-macos-generals.sh`, `deploy-macos-zh.sh`, `bundle-macos-zh.sh`, `bundle-macos-generals.sh`), removing output suppression and `|| true` on font and license copying so missing files fail fast. + - **Font Awesome Brands Staging & Licensing**: + - Verified and staged clean binary `assets/fonts/fa-brands-400.ttf` (Font Awesome 6 Free Brands, format 4 cmap covering Apple `0xF179`, Windows `0xF17A`, Linux `0xF17C`, Discord `0xF392`, Steam `0xF1B6`, GitHub `0xF09B`). + - Staged `assets/fonts/LICENSE.fontawesome` (SIL OFL 1.1). + - Hardened `scripts/env/setup-fonts.sh` with atomic staging via temporary directory and fixed SHA-256 integrity verification (`sha256_verify`) for both Liberation fonts/license and Font Awesome assets (pinned to official Font Awesome v6.5.2 release archive fallback). Replaced superficial existence checks with full content hash verification (`is_valid_liberation` and `is_valid_fa`) so truncated, modified, or corrupt files trigger automated recovery and restaging. + - **Packaging & Deploy Scripts Parity**: + - Updated `scripts/build/macos/deploy-macos-zh.sh`, `scripts/build/macos/deploy-macos-generals.sh`, `scripts/build/linux/deploy-linux-zh.sh`, `scripts/build/linux/deploy-linux.sh`, `scripts/build/macos/bundle-macos-zh.sh`, and `scripts/build/macos/bundle-macos-generals.sh` to explicitly require and copy both `LICENSE.liberation` and `LICENSE.fontawesome` alongside all `*.ttf` fonts. + - Updated Flatpak manifests (`flatpak/com.fbraz3.GeneralsX.yml`, `flatpak/com.fbraz3.GeneralsXZH.yml`) to copy both `LICENSE.liberation` and `LICENSE.fontawesome` into `/app/share/fonts/`. + - Updated Windows CI workflow (`.github/workflows/build-windows.yml`) to validate presence of both license files and package `assets/fonts/` into Windows release and CI artifacts. + - Updated `scripts/env/setup-fonts.sh` to require both license files in completion checks and restore each license independently even if fonts are present. + - **Flatpak GCC 15 Build Fix & CI Run 36269858774 Investigation**: + - Investigated GitHub Actions run 36269858774: verified Windows builds passed completely (both Zero Hour and base game compiled cleanly and passed replay tests). The CI failure was isolated to Linux Flatpak (`org.freedesktop.Sdk 25.08` with GCC 15.2.0). + - Diagnosed GCC 15 compilation failure in `` (`__msg_inc_istreambuf is not a member of __gnu_debug`): identified that `target_include_directories(corei_platform INTERFACE "Libraries/Source")` linked into `corei_always` added `Core/Libraries/Source` to the include path for all targets. When libstdc++ included ``, GCC resolved `Core/Libraries/Source/debug/debug.h` instead of the standard library header, shadowing `__gnu_debug`. + - Isolated `corei_platform` include directory to `Libraries/Source/Platform`, removed `corei_platform` from global `corei_always`, and included `"PlatformPaths.h"` in `render2dsentence.cpp`. +- **Validation**: + - Compiled both `z_generals` (GeneralsMD/Zero Hour) and `g_generals` (Generals base game) cleanly with 0 errors. + - Deployed to `~/GeneralsX/GeneralsZH/` and `~/GeneralsX/Generals/` and verified all font assets are copied. + - Verified `./scripts/env/setup-fonts.sh` with a temporary directory, confirming SHA-256 verification and atomic staging for all fonts and licenses. + - Tested headless replay execution with `FONTCONFIG_FILE=""` to simulate a system without Fontconfig; verified clean initialization and asset loading. + - Replied to all CodeRabbit review comments on PR #336 via GitHub API. + ## 25/09/2026 ### Saturate Out-of-Range INI Integers to Match Retail Mod Compatibility (#297) - **Context**: In issue #297, Shockwave mod 1.201 failed to load with `[INI] Cannot parse numeric token '9999999999999999999': out of range` on `SpawnReplaceDelay` in `MinigunnerSquad.ini` and `RecenterTime` in `HellStorm.ini`. diff --git a/flatpak/com.fbraz3.GeneralsX.yml b/flatpak/com.fbraz3.GeneralsX.yml index c32d4346e57..cb89576bf0e 100644 --- a/flatpak/com.fbraz3.GeneralsX.yml +++ b/flatpak/com.fbraz3.GeneralsX.yml @@ -106,6 +106,8 @@ modules: - install -Dm644 flatpak/com.fbraz3.GeneralsX.metainfo.xml /app/share/metainfo/com.fbraz3.GeneralsX.metainfo.xml - install -Dm644 flatpak/generalsx_icon_512.png /app/share/icons/hicolor/512x512/apps/com.fbraz3.GeneralsX.png - install -Dm644 resources/dxvk/dxvk.conf /app/share/generalsx/dxvk.conf + - install -d /app/share/fonts + - if [ -d assets/fonts ]; then cp assets/fonts/*.ttf assets/fonts/LICENSE.liberation assets/fonts/LICENSE.fontawesome /app/share/fonts/; fi - install -d /app/lib - cp -a /run/build/generalsx/dxvk-src/lib/libdxvk_d3d8.so* /app/lib/ - cp -a /run/build/generalsx/dxvk-src/lib/libdxvk_d3d9.so* /app/lib/ diff --git a/flatpak/com.fbraz3.GeneralsXZH.yml b/flatpak/com.fbraz3.GeneralsXZH.yml index 136e6eaf1db..475d03e89c5 100644 --- a/flatpak/com.fbraz3.GeneralsXZH.yml +++ b/flatpak/com.fbraz3.GeneralsXZH.yml @@ -106,6 +106,8 @@ modules: - install -Dm644 flatpak/com.fbraz3.GeneralsXZH.metainfo.xml /app/share/metainfo/com.fbraz3.GeneralsXZH.metainfo.xml - install -Dm644 flatpak/generalsx-zh_icon_512.png /app/share/icons/hicolor/512x512/apps/com.fbraz3.GeneralsXZH.png - install -Dm644 resources/dxvk/dxvk.conf /app/share/generalsxzh/dxvk.conf + - install -d /app/share/fonts + - if [ -d assets/fonts ]; then cp assets/fonts/*.ttf assets/fonts/LICENSE.liberation assets/fonts/LICENSE.fontawesome /app/share/fonts/; fi - install -d /app/lib - cp -a /run/build/generalsxzh/dxvk-src/lib/libdxvk_d3d8.so* /app/lib/ - cp -a /run/build/generalsxzh/dxvk-src/lib/libdxvk_d3d9.so* /app/lib/ diff --git a/scripts/build/linux/deploy-linux-zh.sh b/scripts/build/linux/deploy-linux-zh.sh index a2fc36b39fe..22f1e97b46a 100755 --- a/scripts/build/linux/deploy-linux-zh.sh +++ b/scripts/build/linux/deploy-linux-zh.sh @@ -183,7 +183,9 @@ fi echo " Deploying fonts..." mkdir -p "${RUNTIME_DIR}/fonts" if [[ -d "${PROJECT_ROOT}/assets/fonts" ]]; then - cp -v "${PROJECT_ROOT}/assets/fonts"/*.ttf "${RUNTIME_DIR}/fonts/" 2>/dev/null || true + cp -v "${PROJECT_ROOT}/assets/fonts"/*.ttf "${RUNTIME_DIR}/fonts/" + cp -v "${PROJECT_ROOT}/assets/fonts/LICENSE.liberation" "${RUNTIME_DIR}/fonts/" + cp -v "${PROJECT_ROOT}/assets/fonts/LICENSE.fontawesome" "${RUNTIME_DIR}/fonts/" fi # Copy run wrapper script diff --git a/scripts/build/linux/deploy-linux.sh b/scripts/build/linux/deploy-linux.sh index 97ad874c9bb..7f616907d54 100755 --- a/scripts/build/linux/deploy-linux.sh +++ b/scripts/build/linux/deploy-linux.sh @@ -152,7 +152,9 @@ patchelf --set-rpath '$ORIGIN' "${RUNTIME_DIR}/GeneralsX" 2>/dev/null || { echo " Deploying fonts..." mkdir -p "${RUNTIME_DIR}/fonts" if [[ -d "${PROJECT_ROOT}/assets/fonts" ]]; then - cp -v "${PROJECT_ROOT}/assets/fonts"/*.ttf "${RUNTIME_DIR}/fonts/" 2>/dev/null || true + cp -v "${PROJECT_ROOT}/assets/fonts"/*.ttf "${RUNTIME_DIR}/fonts/" + cp -v "${PROJECT_ROOT}/assets/fonts/LICENSE.liberation" "${RUNTIME_DIR}/fonts/" + cp -v "${PROJECT_ROOT}/assets/fonts/LICENSE.fontawesome" "${RUNTIME_DIR}/fonts/" fi # Copy run wrapper script diff --git a/scripts/build/macos/bundle-macos-generals.sh b/scripts/build/macos/bundle-macos-generals.sh index 0489abf492b..6cc988902f7 100755 --- a/scripts/build/macos/bundle-macos-generals.sh +++ b/scripts/build/macos/bundle-macos-generals.sh @@ -343,6 +343,18 @@ else echo "WARNING: ${DXVK_CONF_SRC} not found - terrain shaders may fail on macOS" fi +# GeneralsX @feature felipebraz 26/09/2026 Bundle universal fonts into application resources. +mkdir -p "${RESOURCES_DIR}/fonts" +if [[ -d "${PROJECT_ROOT}/assets/fonts" ]]; then + echo " + Bundled fonts" + cp "${PROJECT_ROOT}/assets/fonts"/*.ttf "${RESOURCES_DIR}/fonts/" + cp "${PROJECT_ROOT}/assets/fonts/LICENSE.liberation" "${RESOURCES_DIR}/fonts/" + cp "${PROJECT_ROOT}/assets/fonts/LICENSE.fontawesome" "${RESOURCES_DIR}/fonts/" +else + echo "ERROR: ${PROJECT_ROOT}/assets/fonts directory not found - cannot bundle fonts" >&2 + exit 1 +fi + # App launcher wrapper echo " + App launcher" cat > "${MACOS_DIR}/run.sh" << 'WRAPPER' @@ -389,6 +401,11 @@ if [[ -f "${RESOURCES_DIR}/dxvk.conf" ]]; then export DXVK_CONFIG_FILE="${RESOURCES_DIR}/dxvk.conf" fi +# GeneralsX @bugfix felipebraz 26/09/2026 Export GX_BUNDLE_FONTS so engine resolves staged fonts when CWD changes to asset root. +if [[ -d "${RESOURCES_DIR}/fonts" ]]; then + export GX_BUNDLE_FONTS="${RESOURCES_DIR}/fonts" +fi + # Run from the detected Generals asset root when available. if [[ -d "${CNC_GENERALS_PATH}" ]]; then cd "${CNC_GENERALS_PATH}" diff --git a/scripts/build/macos/bundle-macos-zh.sh b/scripts/build/macos/bundle-macos-zh.sh index ee7ac608ad0..9849608bbd2 100755 --- a/scripts/build/macos/bundle-macos-zh.sh +++ b/scripts/build/macos/bundle-macos-zh.sh @@ -361,6 +361,18 @@ else echo "WARNING: ${FONTCONFIG_ETC_DIR}/fonts.conf not found - in-game font lookup may fail on macOS" fi +# GeneralsX @feature felipebraz 26/09/2026 Bundle universal fonts into application resources. +mkdir -p "${RESOURCES_DIR}/fonts" +if [[ -d "${PROJECT_ROOT}/assets/fonts" ]]; then + echo " + Bundled fonts" + cp "${PROJECT_ROOT}/assets/fonts"/*.ttf "${RESOURCES_DIR}/fonts/" + cp "${PROJECT_ROOT}/assets/fonts/LICENSE.liberation" "${RESOURCES_DIR}/fonts/" + cp "${PROJECT_ROOT}/assets/fonts/LICENSE.fontawesome" "${RESOURCES_DIR}/fonts/" +else + echo "ERROR: ${PROJECT_ROOT}/assets/fonts directory not found - cannot bundle fonts" >&2 + exit 1 +fi + # App launcher wrapper echo " + App launcher" cat > "${MACOS_DIR}/run.sh" << 'WRAPPER' @@ -434,6 +446,11 @@ if [[ -f "${RESOURCES_DIR}/fontconfig/fonts.conf" ]]; then export FONTCONFIG_PATH="${RESOURCES_DIR}/fontconfig" fi +# GeneralsX @bugfix felipebraz 26/09/2026 Export GX_BUNDLE_FONTS so engine resolves staged fonts when CWD changes to asset root. +if [[ -d "${RESOURCES_DIR}/fonts" ]]; then + export GX_BUNDLE_FONTS="${RESOURCES_DIR}/fonts" +fi + # Run from the detected Zero Hour asset root when available. if [[ -d "${CNC_GENERALS_ZH_PATH}" ]]; then cd "${CNC_GENERALS_ZH_PATH}" diff --git a/scripts/build/macos/deploy-macos-generals.sh b/scripts/build/macos/deploy-macos-generals.sh index 649b5dcc9c9..b3d59a3218e 100755 --- a/scripts/build/macos/deploy-macos-generals.sh +++ b/scripts/build/macos/deploy-macos-generals.sh @@ -114,7 +114,9 @@ fi echo " Deploying Fontconfig config & fonts..." mkdir -p "${RUNTIME_DIR}/fonts" if [[ -d "${PROJECT_ROOT}/assets/fonts" ]]; then - cp -v "${PROJECT_ROOT}/assets/fonts"/*.ttf "${RUNTIME_DIR}/fonts/" 2>/dev/null || true + cp -v "${PROJECT_ROOT}/assets/fonts"/*.ttf "${RUNTIME_DIR}/fonts/" + cp -v "${PROJECT_ROOT}/assets/fonts/LICENSE.liberation" "${RUNTIME_DIR}/fonts/" + cp -v "${PROJECT_ROOT}/assets/fonts/LICENSE.fontawesome" "${RUNTIME_DIR}/fonts/" fi FONTCONFIG_ETC_DIR="${BUILD_DIR}/vcpkg_installed/arm64-osx/etc/fonts" diff --git a/scripts/build/macos/deploy-macos-zh.sh b/scripts/build/macos/deploy-macos-zh.sh index bc782770172..9eecdc27d4a 100755 --- a/scripts/build/macos/deploy-macos-zh.sh +++ b/scripts/build/macos/deploy-macos-zh.sh @@ -166,7 +166,9 @@ fi echo " Deploying Fontconfig config & fonts..." mkdir -p "${RUNTIME_DIR}/fonts" if [[ -d "${PROJECT_ROOT}/assets/fonts" ]]; then - cp -v "${PROJECT_ROOT}/assets/fonts"/*.ttf "${RUNTIME_DIR}/fonts/" 2>/dev/null || true + cp -v "${PROJECT_ROOT}/assets/fonts"/*.ttf "${RUNTIME_DIR}/fonts/" + cp -v "${PROJECT_ROOT}/assets/fonts/LICENSE.liberation" "${RUNTIME_DIR}/fonts/" + cp -v "${PROJECT_ROOT}/assets/fonts/LICENSE.fontawesome" "${RUNTIME_DIR}/fonts/" fi if [[ -f "${FONTCONFIG_ETC_DIR}/fonts.conf" ]]; then diff --git a/scripts/env/setup-fonts.sh b/scripts/env/setup-fonts.sh new file mode 100755 index 00000000000..1c65d52968c --- /dev/null +++ b/scripts/env/setup-fonts.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash +# Stage and verify universal Liberation and Font Awesome fonts for GeneralsX +# +# Usage: +# ./scripts/env/setup-fonts.sh [DEST_DIR] +# +# Environment: +# GX_FONTS Destination directory for fonts (default: assets/fonts) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +LIB_VERSION="2.1.5" +LIB_SHA256="7191c669bf38899f73a2094ed00f7b800553364f90e2637010a69c0e268f25d0" +LIB_ARIAL_SHA256="76d04c18ea243f426b7de1f3ad208e927008f961dc5945e5aad352d0dfde8ee8" +LIB_ARIALBOLD_SHA256="788abee4c806d660e8aee46689dd8540cd4bb98da03dcc9d171ce3efd99a9173" +LIB_COURIER_SHA256="f2b83c763e8afd21709333370bed4774337fae82267937e2b5aea7e2fbd922c1" +LIB_TIMES_SHA256="058ea80864aef09a23f45cbec2bb5400bc3dfbdea01c3f10538a21fcb497fb74" +LIB_LIC_SHA256="93fed46019c38bbe566b479d22148e2e8a1e85ada614accb0211c37b2c61c19b" + +FA_VERSION="6.5.2" +FA_SHA256="e28096fa75a96ac77020155ea3a6dd7312983e84115366d4cf49a0c312ec6d51" +FA_LIC_SHA256="9b914eae88817d63b576eab5aafde7068c7a1abae125d7cdfb034f1da43a9afc" + +DEST="${1:-${GX_FONTS:-${PROJECT_ROOT}/assets/fonts}}" +TMP="$(mktemp -d)" +trap 'rm -rf "${TMP}"' EXIT + +sha256_verify() { + local checksum="$1" + local file="$2" + if command -v sha256sum >/dev/null 2>&1; then + echo "${checksum} ${file}" | sha256sum -c - + else + echo "${checksum} ${file}" | shasum -a 256 -c - + fi +} + +mkdir -p "${DEST}" + +is_valid_fa() { + [[ -f "${DEST}/fa-brands-400.ttf" ]] && \ + [[ -f "${DEST}/LICENSE.fontawesome" ]] && \ + sha256_verify "${FA_SHA256}" "${DEST}/fa-brands-400.ttf" >/dev/null 2>&1 && \ + sha256_verify "${FA_LIC_SHA256}" "${DEST}/LICENSE.fontawesome" >/dev/null 2>&1 +} + +is_valid_liberation() { + [[ -f "${DEST}/arial.ttf" ]] && \ + [[ -f "${DEST}/arialbold.ttf" ]] && \ + [[ -f "${DEST}/couriernew.ttf" ]] && \ + [[ -f "${DEST}/timesnewroman.ttf" ]] && \ + [[ -f "${DEST}/LICENSE.liberation" ]] && \ + sha256_verify "${LIB_ARIAL_SHA256}" "${DEST}/arial.ttf" >/dev/null 2>&1 && \ + sha256_verify "${LIB_ARIALBOLD_SHA256}" "${DEST}/arialbold.ttf" >/dev/null 2>&1 && \ + sha256_verify "${LIB_COURIER_SHA256}" "${DEST}/couriernew.ttf" >/dev/null 2>&1 && \ + sha256_verify "${LIB_TIMES_SHA256}" "${DEST}/timesnewroman.ttf" >/dev/null 2>&1 && \ + sha256_verify "${LIB_LIC_SHA256}" "${DEST}/LICENSE.liberation" >/dev/null 2>&1 +} + +if is_valid_liberation && is_valid_fa; then + echo "==> Fonts and licenses already present and verified at ${DEST}" + exit 0 +fi + +if ! is_valid_liberation; then + if [[ -f "${PROJECT_ROOT}/assets/fonts/arial.ttf" ]] && \ + sha256_verify "${LIB_ARIAL_SHA256}" "${PROJECT_ROOT}/assets/fonts/arial.ttf" >/dev/null 2>&1 && \ + sha256_verify "${LIB_ARIALBOLD_SHA256}" "${PROJECT_ROOT}/assets/fonts/arialbold.ttf" >/dev/null 2>&1 && \ + sha256_verify "${LIB_COURIER_SHA256}" "${PROJECT_ROOT}/assets/fonts/couriernew.ttf" >/dev/null 2>&1 && \ + sha256_verify "${LIB_TIMES_SHA256}" "${PROJECT_ROOT}/assets/fonts/timesnewroman.ttf" >/dev/null 2>&1 && \ + sha256_verify "${LIB_LIC_SHA256}" "${PROJECT_ROOT}/assets/fonts/LICENSE.liberation" >/dev/null 2>&1; then + echo "==> Staging Liberation fonts from repository assets/" + cp "${PROJECT_ROOT}/assets/fonts/arial.ttf" "${TMP}/arial.ttf" + cp "${PROJECT_ROOT}/assets/fonts/arialbold.ttf" "${TMP}/arialbold.ttf" + cp "${PROJECT_ROOT}/assets/fonts/couriernew.ttf" "${TMP}/couriernew.ttf" + cp "${PROJECT_ROOT}/assets/fonts/timesnewroman.ttf" "${TMP}/timesnewroman.ttf" + cp "${PROJECT_ROOT}/assets/fonts/LICENSE.liberation" "${TMP}/LICENSE.liberation" + else + echo "==> Downloading Liberation fonts ${LIB_VERSION}" + if ! curl -fL -o "${TMP}/liberation.tar.gz" \ + "https://github.com/liberationfonts/liberation-fonts/files/7261482/liberation-fonts-ttf-${LIB_VERSION}.tar.gz" && + ! curl -fL -o "${TMP}/liberation.tar.gz" \ + "https://github.com/liberationfonts/liberation-fonts/releases/download/${LIB_VERSION}/liberation-fonts-ttf-${LIB_VERSION}.tar.gz"; then + echo "ERROR: Could not download Liberation fonts ${LIB_VERSION} (check network / URLs)." >&2 + exit 1 + fi + + sha256_verify "${LIB_SHA256}" "${TMP}/liberation.tar.gz" + tar -xzf "${TMP}/liberation.tar.gz" -C "${TMP}" + SRC="$(find "${TMP}" -name "LiberationSans-Regular.ttf" -exec dirname {} \; | head -1)" + [[ -n "${SRC}" ]] || { echo "ERROR: Liberation fonts not found in extracted archive" >&2; exit 1; } + + cp "${SRC}/LiberationSans-Regular.ttf" "${TMP}/arial.ttf" + cp "${SRC}/LiberationSans-Bold.ttf" "${TMP}/arialbold.ttf" + cp "${SRC}/LiberationMono-Regular.ttf" "${TMP}/couriernew.ttf" + cp "${SRC}/LiberationSerif-Regular.ttf" "${TMP}/timesnewroman.ttf" + cp "${SRC}/LICENSE" "${TMP}/LICENSE.liberation" + fi + + sha256_verify "${LIB_ARIAL_SHA256}" "${TMP}/arial.ttf" + sha256_verify "${LIB_ARIALBOLD_SHA256}" "${TMP}/arialbold.ttf" + sha256_verify "${LIB_COURIER_SHA256}" "${TMP}/couriernew.ttf" + sha256_verify "${LIB_TIMES_SHA256}" "${TMP}/timesnewroman.ttf" + sha256_verify "${LIB_LIC_SHA256}" "${TMP}/LICENSE.liberation" + + cp "${TMP}/arial.ttf" "${DEST}/arial.ttf" + cp "${TMP}/arialbold.ttf" "${DEST}/arialbold.ttf" + cp "${TMP}/couriernew.ttf" "${DEST}/couriernew.ttf" + cp "${TMP}/timesnewroman.ttf" "${DEST}/timesnewroman.ttf" + cp "${TMP}/LICENSE.liberation" "${DEST}/LICENSE.liberation" +fi + +if ! is_valid_fa; then + if [[ -f "${PROJECT_ROOT}/assets/fonts/fa-brands-400.ttf" && -f "${PROJECT_ROOT}/assets/fonts/LICENSE.fontawesome" ]] && \ + sha256_verify "${FA_SHA256}" "${PROJECT_ROOT}/assets/fonts/fa-brands-400.ttf" >/dev/null 2>&1 && \ + sha256_verify "${FA_LIC_SHA256}" "${PROJECT_ROOT}/assets/fonts/LICENSE.fontawesome" >/dev/null 2>&1; then + echo "==> Staging Font Awesome assets from repository assets/" + cp "${PROJECT_ROOT}/assets/fonts/fa-brands-400.ttf" "${TMP}/fa-brands-400.ttf" + cp "${PROJECT_ROOT}/assets/fonts/LICENSE.fontawesome" "${TMP}/LICENSE.fontawesome" + else + echo "==> Downloading Font Awesome ${FA_VERSION} web archive" + if ! curl -fL -o "${TMP}/fa-web.zip" \ + "https://github.com/FortAwesome/Font-Awesome/releases/download/${FA_VERSION}/fontawesome-free-${FA_VERSION}-web.zip"; then + echo "ERROR: Could not download Font Awesome ${FA_VERSION}." >&2 + exit 1 + fi + unzip -q -j "${TMP}/fa-web.zip" "fontawesome-free-${FA_VERSION}-web/webfonts/fa-brands-400.ttf" -d "${TMP}" + unzip -q -j "${TMP}/fa-web.zip" "fontawesome-free-${FA_VERSION}-web/LICENSE.txt" -d "${TMP}" + mv "${TMP}/LICENSE.txt" "${TMP}/LICENSE.fontawesome" + fi + + sha256_verify "${FA_SHA256}" "${TMP}/fa-brands-400.ttf" + sha256_verify "${FA_LIC_SHA256}" "${TMP}/LICENSE.fontawesome" + cp "${TMP}/fa-brands-400.ttf" "${DEST}/fa-brands-400.ttf" + cp "${TMP}/LICENSE.fontawesome" "${DEST}/LICENSE.fontawesome" +fi + +echo "==> Staged universal fonts to ${DEST}"