Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
67c03cc
feat: integrate deterministic fdlibm math library and update game eng…
Okladnoj Apr 16, 2026
654fa3e
feat(launcher): add SteamCMD BYOG download and tabbed UI
Okladnoj Apr 17, 2026
15a3173
feat: add Keychain support, asset reorganization, and purchase valida…
Okladnoj Apr 17, 2026
3333d9f
feat: Add downloadingPatch and unpackingPatch states to MainView and …
Okladnoj Apr 17, 2026
145191c
feat: Integrate deterministic fdlibm math library and update game eng…
Okladnoj Apr 16, 2026
e71c718
fix(macos): Resolve SIGSEGV 10/11 in UI string formatting and Particl…
Okladnoj Apr 17, 2026
8966c9d
Merge branch 'okji/feat/macos-port' into okji/feat/launcher-steamcmd
Okladnoj Apr 17, 2026
ad3b1c2
feat(launcher): allow engine and UI to recognize unified Steam folder…
Okladnoj Apr 17, 2026
6c75ed9
feat(launcher): Finalize SteamCMD integration UI, resolve race condit…
Okladnoj Apr 17, 2026
b5457ea
chore: Сomment out local install path configuration and DMG creation …
Okladnoj Apr 17, 2026
a5f002a
Refactor: Unified MacOS Metal hardware device capabilities & cleaned …
Okladnoj Apr 18, 2026
bb17c81
feat: Add DEBUG_RENDER_CORE_MAC logging and update device capabilitie…
Okladnoj Apr 18, 2026
98ff1af
fix(macos): resolve missing buddy names and intermittent from_utf8 crash
Okladnoj Apr 19, 2026
1ec8819
fix(macos): Force windowed mode startup to prevent initialization des…
Okladnoj Apr 19, 2026
2c0d4dd
feat(macos-port): Replace markdown installation guide with interactiv…
Okladnoj Apr 19, 2026
02ad1da
fix(macos-port): update launcher scenario in step 5 of instructions
Okladnoj Apr 19, 2026
b70cac0
Revert "fix(macos-port): update launcher scenario in step 5 of instru…
Okladnoj Apr 19, 2026
c27b4d7
docs: update setup instructions in MacOS launcher documentation
Okladnoj Apr 19, 2026
4d9239e
fix(gameclient): Fix particle effects not freezing on game pause unde…
Apr 24, 2026
8503c3b
Merge pull request #478 from MrS-ibra/fix/pause-particle-effects
x64-dev Apr 24, 2026
93a4f2d
fix: switch display resolution and drawable sizing to logical points …
Okladnoj Apr 25, 2026
f648ea8
feat: implement auto-update infrastructure and versioning for macOS l…
Okladnoj Apr 25, 2026
f1d1a5b
chore: add Firebase Hosting directory to gitignore
Okladnoj Apr 25, 2026
cce3bd3
chore: ignore local Firebase Hosting deployment directory
Okladnoj Apr 25, 2026
8f798b5
feat: implement UI repositioning for resolution changes and add gener…
Okladnoj Apr 26, 2026
15b736c
Fix MacOS AVAudioEngine pausing on mission restart and refactor Audio…
Okladnoj Apr 26, 2026
44fab7e
chore: comment out DEBUG_AUDIO_MAC_FLAG in MacDebug.h
Okladnoj Apr 26, 2026
bf6c69b
Merge remote-tracking branch 'god-team/main' into okji/feat/macos-port
Okladnoj Apr 26, 2026
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,5 @@ Data/*.big
Platform/MacOS/Build/Logs/
*.gif

# Firebase Hosting (maintainer-local deployment)
/Dependencies/general_online_zh/
20 changes: 17 additions & 3 deletions Core/GameEngine/Include/Common/Debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,11 @@ class SimpleProfiler
#define DEBUG_CRASH_MAC(m) ((void)0)
#endif

#define DEBUG_BUILDMAPCACHE_FLAG
#define DEBUG_INFO_MAC_FLAG
#define DEBUG_FILESYSTEM_MAC_FLAG
// #define DEBUG_BUILDMAPCACHE_FLAG
// #define DEBUG_INFO_MAC_FLAG
// #define DEBUG_FILESYSTEM_MAC_FLAG
// #define DEBUG_RENDER_CORE_MAC_FLAG
// #define DEBUG_NETWORK_MAC_FLAG

#ifdef DEBUG_BUILDMAPCACHE_FLAG
#define DEBUG_BUILDMAPCACHE(m) MAC_LOG_TAG("DEBUG_BUILDMAPCACHE", m)
Expand All @@ -303,3 +305,15 @@ class SimpleProfiler
#define DEBUG_FILESYSTEM_MAC(m) ((void)0)
#endif

#ifdef DEBUG_RENDER_CORE_MAC_FLAG
#define DEBUG_RENDER_CORE_MAC(m) MAC_LOG_TAG("DEBUG_RENDER_CORE_MAC", m)
#else
#define DEBUG_RENDER_CORE_MAC(m) ((void)0)
#endif

#ifdef DEBUG_NETWORK_MAC_FLAG
#define DEBUG_NETWORK_MAC(m) MAC_LOG_TAG("DEBUG_NETWORK_MAC", m)
#else
#define DEBUG_NETWORK_MAC(m) ((void)0)
#endif

45 changes: 45 additions & 0 deletions Core/GameEngine/Source/Common/System/UnicodeString.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,52 @@ void UnicodeString::format_va(const WideChar* format, va_list args)
{
validate();
WideChar buf[MAX_FORMAT_BUF_LEN];

#ifdef __APPLE__
WideChar fixedFmt[MAX_FORMAT_BUF_LEN];
size_t di = 0;
for (size_t si = 0; format[si] != 0 && di < MAX_FORMAT_BUF_LEN - 2; ) {
if (format[si] == L'%') {
if (format[si+1] == L'%') {
fixedFmt[di++] = format[si++];
fixedFmt[di++] = format[si++];
continue;
}
fixedFmt[di++] = format[si++];
while (format[si] == L'-' || format[si] == L'+' || format[si] == L'0' ||
format[si] == L' ' || format[si] == L'#') {
fixedFmt[di++] = format[si++];
}
while ((format[si] >= L'0' && format[si] <= L'9') || format[si] == L'*') {
fixedFmt[di++] = format[si++];
}
if (format[si] == L'.') {
fixedFmt[di++] = format[si++];
while ((format[si] >= L'0' && format[si] <= L'9') || format[si] == L'*') {
fixedFmt[di++] = format[si++];
}
}
if (format[si] == L'h' || format[si] == L'l') {
fixedFmt[di++] = format[si++];
if (format[si] == L'h' || format[si] == L'l') fixedFmt[di++] = format[si++];
fixedFmt[di++] = format[si++];
continue;
}
if (format[si] == L's') {
fixedFmt[di++] = L'l';
fixedFmt[di++] = format[si++];
} else {
fixedFmt[di++] = format[si++];
}
} else {
fixedFmt[di++] = format[si++];
}
}
fixedFmt[di] = 0;
const int result = vswprintf(buf, sizeof(buf)/sizeof(WideChar), fixedFmt, args);
#else
const int result = vswprintf(buf, sizeof(buf)/sizeof(WideChar), format, args);
#endif
if (result >= 0)
{
set(buf);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2606,12 +2606,15 @@ void W3DShaderManager::init()
if ((res=W3DShaderManager::getChipset()) != 0)
{
m_currentChipset = res; //cache the current chipset.
DEBUG_RENDER_CORE_MAC(("W3DShaderManager::init chipset=%d", (int)res));

//Some of our effects require an offscreen render target, so try creating it here.
HRESULT hr=DX8Wrapper::_Get_D3D_Device8()->GetRenderTarget(&m_oldRenderSurface);

if (hr != S_OK || !m_oldRenderSurface)
if (hr != S_OK || !m_oldRenderSurface) {
DEBUG_RENDER_CORE_MAC(("W3DShaderManager::init EARLY EXIT: GetRenderTarget hr=0x%x surf=%p", (unsigned)hr, m_oldRenderSurface));
return;
}

m_oldRenderSurface->GetDesc(&desc);

Expand All @@ -2620,9 +2623,11 @@ void W3DShaderManager::init()
if (desc.MultiSampleType == D3DMULTISAMPLE_NONE)
{
hr=DX8Wrapper::_Get_D3D_Device8()->CreateTexture(desc.Width,desc.Height,1,D3DUSAGE_RENDERTARGET,desc.Format,D3DPOOL_DEFAULT,&m_renderTexture);
DEBUG_RENDER_CORE_MAC(("RTT CreateTexture (no MSAA) hr=0x%x tex=%p", (unsigned)hr, m_renderTexture));
}
else
{
DEBUG_RENDER_CORE_MAC(("RTT SKIPPED: MSAA type=%u", (unsigned)desc.MultiSampleType));
// Force failure path to avoid MSAA mismatch
hr = E_FAIL;
}
Expand Down Expand Up @@ -2935,6 +2940,8 @@ ChipsetType W3DShaderManager::getChipset()
ChipsetType chip=DC_UNKNOWN;
IDirect3D8* d3d8Interface=DX8Wrapper::_Get_D3D8();

DEBUG_RENDER_CORE_MAC(("getChipset: d3d8=%p device=%p globalOverride=%d", d3d8Interface, DX8Wrapper::_Get_D3D_Device8(), TheGlobalData ? TheGlobalData->m_chipSetType : -1));

if (d3d8Interface && DX8Wrapper::_Get_D3D_Device8())
{

Expand All @@ -2943,6 +2950,8 @@ ChipsetType W3DShaderManager::getChipset()
/* HRESULT res = */ d3d8Interface->GetAdapterIdentifier(0,D3DENUM_NO_WHQL_LEVEL,&did);
*((LARGE_INTEGER*)&m_driverVersion) = did.DriverVersion;

DEBUG_RENDER_CORE_MAC(("getChipset: VendorId=0x%x DeviceId=0x%x", did.VendorId, did.DeviceId));

if(did.VendorId == DC_NVIDIA_VENDOR_ID)
{
m_currentVendor = DC_NVIDIA_VENDOR_ID;
Expand Down Expand Up @@ -3001,6 +3010,8 @@ ChipsetType W3DShaderManager::getChipset()
sprintf(buf,"%d.%d",DX8Wrapper::Get_Current_Caps()->Get_Pixel_Shader_Major_Version(),DX8Wrapper::Get_Current_Caps()->Get_Pixel_Shader_Minor_Version());
sscanf(buf,"%f",&pixelShaderVersion);

DEBUG_RENDER_CORE_MAC(("getChipset: maxTex=%d psVer=%f psMajor=%d psMinor=%d", maxTextures, pixelShaderVersion, DX8Wrapper::Get_Current_Caps()->Get_Pixel_Shader_Major_Version(), DX8Wrapper::Get_Current_Caps()->Get_Pixel_Shader_Minor_Version()));

if (maxTextures >= 4)
{ if (pixelShaderVersion >= 1.1f)
chip=DC_GENERIC_PIXEL_SHADER_1_1;
Expand All @@ -3009,6 +3020,8 @@ ChipsetType W3DShaderManager::getChipset()
if (maxTextures >= 8 && pixelShaderVersion >= 2.0f)
chip=DC_GENERIC_PIXEL_SHADER_2_0;
}

DEBUG_RENDER_CORE_MAC(("getChipset: result=%d", (int)chip));
}

return chip;
Expand All @@ -3021,6 +3034,7 @@ ChipsetType W3DShaderManager::getChipset()
//=============================================================================
HRESULT W3DShaderManager::LoadAndCreateD3DShader(const char* strFilePath, const DWORD* pDeclaration, DWORD Usage, Bool ShaderType, DWORD* pHandle)
{
DEBUG_RENDER_CORE_MAC(("LoadAndCreateD3DShader: '%s' type=%s chipset=%d", strFilePath, ShaderType ? "VS" : "PS", (int)getChipset()));
if (getChipset() < DC_GENERIC_PIXEL_SHADER_1_1)
return E_FAIL; //don't allow loading any shaders if hardware can't handle it.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,7 @@ Int WaterRenderObjClass::init(Real waterLevel, Real dx, Real dy, SceneClass *par

m_parentScene=parentScene;
m_waterType = type;
DEBUG_RENDER_CORE_MAC(("W3DWater::init waterType=%d (0=TRANSLUCENT,1=FB_REFL,2=PVSHADER,3=GRIDMESH)", (int)type));

/// Hack for now
//m_waterType = WATER_TYPE_0_TRANSLUCENT;
Expand Down
10 changes: 10 additions & 0 deletions Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,11 @@ Render2DSentenceClass::Record_Sentence_Chunk ()
void
Render2DSentenceClass::Allocate_New_Surface (const WCHAR *text, bool justCalcExtents)
{
#ifdef __APPLE__
if (text == nullptr) {
return;
}
#endif
if (!justCalcExtents)
{
//
Expand Down Expand Up @@ -991,6 +996,11 @@ void Render2DSentenceClass::Build_Sentence_Centered (const WCHAR *text, int *hkX
////////////////////////////////////////////////////////////////////////////////////
Vector2 Render2DSentenceClass::Build_Sentence_Not_Centered (const WCHAR *text, int *hkX, int *hkY, bool justCalcExtents)
{
#ifdef __APPLE__
if (text == nullptr) {
return Vector2(0.0f, 0.0f);
}
#endif
Vector2 cursor = Cursor;
int textureStartX = TextureStartX;
float maxX = 0;
Expand Down
1 change: 1 addition & 0 deletions Dependencies/fdlibm-deterministic
Submodule fdlibm-deterministic added at 02d7d2
4 changes: 4 additions & 0 deletions Generals/Code/Libraries/Source/WWVegas/WW3D2/part_emt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,11 @@ ParticleEmitterClass::ParticleEmitterClass(const ParticleEmitterClass & src) :
FirstTime = true;
IsComplete = false;

#ifdef __APPLE__
NameString = src.NameString ? ::_strdup (src.NameString) : nullptr;
#else
NameString = ::_strdup (src.NameString);
#endif
}


Expand Down
5 changes: 5 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/GameClient/ControlBar.h
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,11 @@ class ControlBar : public SubsystemInterface

void initSpecialPowershortcutBar( Player *player);

#ifdef __APPLE__
// TheSuperHackers @feature okji 26/04/2026 Reposition right-edge-anchored UI during gameplay resize
void repositionForResolution(Int oldW, Int newW);
#endif

void triggerRadarAttackGlow();

void drawSpecialPowerShortcutMultiplierText();
Expand Down
4 changes: 4 additions & 0 deletions GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,10 @@ void GlobalData::parseGameDataDefinition( INI* ini )
}
}
}
// TheSuperHackers @tweak macOS: Force the game to launch in windowed mode
// to prevent UI layout desynchronization issues. The user can toggle
// to fullscreen in-game via the Options menu or Cmd+Enter.
TheWritableGlobalData->m_windowed = true;
#endif

TheWritableGlobalData->m_xResolution = xres;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3772,6 +3772,42 @@ void ControlBar::hideSpecialPowerShortcut()

}

#ifdef __APPLE__
// TheSuperHackers @feature okji 26/04/2026 Reposition right-edge-anchored UI
// elements (special power shortcut bar, right HUD) when the display resolution
// changes during gameplay. Full recreateControlBar() crashes mid-game, so this
// lightweight approach only adjusts X coordinates by the width delta.
void ControlBar::repositionForResolution(Int oldW, Int newW)
{
if (oldW == newW || oldW == 0) return;

if (m_specialPowerShortcutParent) {
if (m_animateWindowManagerForGenShortcuts
&& !m_animateWindowManagerForGenShortcuts->isEmpty()) {
m_animateWindowManagerForGenShortcuts->reset();
}

Int x, y, w, h;
m_specialPowerShortcutParent->winGetPosition(&x, &y);
m_specialPowerShortcutParent->winGetSize(&w, &h);
Int newW_panel = static_cast<Int>(static_cast<Real>(w) * newW / oldW);
Int newX = newW - newW_panel;
m_specialPowerShortcutParent->winSetPosition(newX, y);
m_specialPowerShortcutParent->winSetSize(newW_panel, h);
}

if (m_rightHUDWindow) {
Int x, y, w, h;
m_rightHUDWindow->winGetPosition(&x, &y);
m_rightHUDWindow->winGetSize(&w, &h);
Int newX = static_cast<Int>(static_cast<Real>(x) * newW / oldW);
Int newW_hud = static_cast<Int>(static_cast<Real>(w) * newW / oldW);
m_rightHUDWindow->winSetPosition(newX, y);
m_rightHUDWindow->winSetSize(newW_hud, h);
}
}
#endif

void ControlBar::setFullViewportHeight()
{
TheTacticalView->setHeight(TheDisplay->getHeight());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1970,7 +1970,13 @@ WindowMsgHandledType WOLBuddyOverlayRCMenuSystem( GameWindow *window, UnsignedIn
DEBUG_LOG(("buttonStatsID was pushed"));

#if defined(GENERALS_ONLINE)
#ifdef __APPLE__
UnicodeString uniNick;
uniNick.translate(nick);
SetLookAtPlayer(profileID, uniNick);
#else
SetLookAtPlayer(profileID, UnicodeString(from_utf8(nick.str()).c_str()));
#endif
GameSpyOpenOverlay(GSOVERLAY_PLAYERINFO);
#else
SetLookAtPlayer(profileID, nick);
Expand Down
10 changes: 9 additions & 1 deletion GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -795,8 +795,16 @@ void GameClient::update()
}

#if defined(GENERALS_ONLINE_HIGH_FPS_RENDER)
#ifdef __APPLE__
int64_t currTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
m_legacyFrameMSAccured += currTime - m_LegacyFrameEndLastFrame;
#else
int64_t currTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::utc_clock::now().time_since_epoch()).count();
#endif

if (!freezeTime)
{
m_legacyFrameMSAccured += currTime - m_LegacyFrameEndLastFrame;
}
m_LegacyFrameEndLastFrame = currTime;

// TODO_NGMP: This should really use partial frame intervals instead of a fixed 60hz update
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,15 +389,18 @@ void NGMP_OnlineServicesManager::FetchMacParityCRC(std::function<void(long)> fnC
VersionManifestResponse manifestResp = jsonObject.get<VersionManifestResponse>();
rawExeCRC = manifestResp.execrc_60;
NetworkLog(ELogVerbosity::LOG_RELEASE, "VERSION MANIFEST: Dynamic CRC obtained: %llu", (unsigned long long)rawExeCRC);
DEBUG_NETWORK_MAC(("VERSION MANIFEST: Dynamic CRC obtained: %llu", (unsigned long long)rawExeCRC));
}
catch (...)
{
NetworkLog(ELogVerbosity::LOG_RELEASE, "VERSION MANIFEST: Failed to parse response. Body: %s", strBody.c_str());
DEBUG_NETWORK_MAC(("VERSION MANIFEST: Failed to parse response. Body: %s", strBody.c_str()));
}
}
else
{
NetworkLog(ELogVerbosity::LOG_RELEASE, "VERSION MANIFEST: Failed to get manifest (status %d)", statusCode);
DEBUG_NETWORK_MAC(("VERSION MANIFEST: Failed to get manifest (status %d)", statusCode));
}

fnCallback(rawExeCRC);
Expand Down Expand Up @@ -460,20 +463,24 @@ void NGMP_OnlineServicesManager::StartVersionCheck(std::function<void(bool bSucc
NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()->SendPOSTRequest(strURI.c_str(), EIPProtocolVersion::DONT_CARE, mapHeaders, strPostData.c_str(), [=](bool bSuccess, int statusCode, std::string strBody, HTTPRequest* pReq)
{
NetworkLog(ELogVerbosity::LOG_RELEASE, "Version Check: Response code was %d and body was %s", statusCode, strBody.c_str());
DEBUG_NETWORK_MAC(("Version Check: Response code was %d and body was %s", statusCode, strBody.c_str()));
try
{
NetworkLog(ELogVerbosity::LOG_RELEASE, "VERSION CHECK: Up To Date");
DEBUG_NETWORK_MAC(("VERSION CHECK: Up To Date"));
nlohmann::json jsonObject = nlohmann::json::parse(strBody);
VersionCheckResponse authResp = jsonObject.get<VersionCheckResponse>();

if (authResp.result == EVersionCheckResponseResult::OK)
{
NetworkLog(ELogVerbosity::LOG_RELEASE, "VERSION CHECK: Up To Date");
DEBUG_NETWORK_MAC(("VERSION CHECK: Up To Date"));
fnCallback(true, false);
}
else if (authResp.result == EVersionCheckResponseResult::NEEDS_UPDATE)
{
NetworkLog(ELogVerbosity::LOG_RELEASE, "VERSION CHECK: Needs Update");
DEBUG_NETWORK_MAC(("VERSION CHECK: Needs Update"));

// cache the data
m_patcher_name = authResp.patcher_name;
Expand All @@ -485,12 +492,14 @@ void NGMP_OnlineServicesManager::StartVersionCheck(std::function<void(bool bSucc
else
{
NetworkLog(ELogVerbosity::LOG_RELEASE, "VERSION CHECK: Failed");
DEBUG_NETWORK_MAC(("VERSION CHECK: Failed"));
fnCallback(false, false);
}
}
catch (...)
{
NetworkLog(ELogVerbosity::LOG_RELEASE, "VERSION CHECK: Failed to parse response");
DEBUG_NETWORK_MAC(("VERSION CHECK: Failed to parse response"));
fnCallback(false, false);
}
}, nullptr, -1);
Expand Down
Loading
Loading