Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/build-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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" } |
Expand Down
2 changes: 2 additions & 0 deletions Core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
195 changes: 195 additions & 0 deletions Core/Libraries/Source/Platform/PlatformPaths.h
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

// 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 <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#if defined(_WIN32)
#include <io.h>
#else
#include <unistd.h>
#include <limits.h>
#endif

#if defined(__APPLE__)
#include <mach-o/dyld.h>
#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
1 change: 1 addition & 0 deletions Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ target_link_libraries(corei_ww3d2 INTERFACE
core_browserengine
core_wwlib
core_wwmath
corei_platform
$<$<PLATFORM_ID:Linux>:Freetype::Freetype>
$<$<PLATFORM_ID:Darwin>:Freetype::Freetype>
$<$<PLATFORM_ID:Linux>:Fontconfig::Fontconfig>
Expand Down
Loading
Loading