From 18230b296f3e8544772143242281db291edbe453 Mon Sep 17 00:00:00 2001 From: Marcel Bierling Date: Wed, 2 Sep 2026 16:33:07 +0200 Subject: [PATCH 001/179] Read the coarse clock from std::chrono instead of winmm --- code/dropship.cpp | 33 +++++++++++++++++---------------- code/gametime.cpp | 5 +++-- code/hostclock.h | 26 ++++++++++++++++++++++++++ code/init.cpp | 3 ++- code/mainloop.cpp | 5 +++-- code/milsectmr.cpp | 11 ++++++----- code/milsectmr.h | 2 +- code/mstimer.cpp | 16 +++++++++------- code/session.cpp | 3 ++- code/stimer.cpp | 9 +++++---- code/video.cpp | 5 +++-- 11 files changed, 77 insertions(+), 41 deletions(-) create mode 100644 code/hostclock.h diff --git a/code/dropship.cpp b/code/dropship.cpp index 7325650db..9088a45c4 100644 --- a/code/dropship.cpp +++ b/code/dropship.cpp @@ -30,6 +30,7 @@ #include "dsurface.h" #include "font.h" #include "globals.h" +#include "hostclock.h" #include "house.h" #include "infatype.h" #include "keyboard.h" @@ -247,7 +248,7 @@ struct CrossDissolveEffect FromSurface(from_surface), ToSurface(to_surface), Alpha(0), - StartTime(::timeGetTime()), + StartTime(Host_Milliseconds()), FromBlitter(from_blitter), ToBlitter(to_blitter), WasDrawn(false) @@ -557,7 +558,7 @@ void Dropship_Screen(void) DynamicVectorClass button_fades; - unsigned int money_display_time = ::timeGetTime(); + unsigned int money_display_time = Host_Milliseconds(); unsigned int screen_start_time = money_display_time; int loadout_anim_frame = 0; unsigned int last_input_time = 0; @@ -613,7 +614,7 @@ void Dropship_Screen(void) KeyNumType input = button_list->Input(); if (!recent_click && (input & KN_BUTTON) != 0) { input = (KeyNumType)(input & ~KN_BUTTON); - last_input_time = ::timeGetTime(); + last_input_time = Host_Milliseconds(); if (input <= _cameo_count && selected_count < dropship_count * SLOT_PER_DROPSHIP) { int candidate_index = cameo_top + input - 1; @@ -650,13 +651,13 @@ void Dropship_Screen(void) int light_row = (input - 1) / 2; if (light_frame[light_row] == (unsigned int)-1) { light_frame[light_row] = 0; - light_start[light_row] = ::timeGetTime(); + light_start[light_row] = Host_Milliseconds(); } force_light_redraw = true; for (j = 0; ; ++j) { if (j >= button_fades.Count()) { - ButtonFadeEffect *fade = new ButtonFadeEffect(::timeGetTime(), candidate_index, 127.0f, -1.0f, false); + ButtonFadeEffect *fade = new ButtonFadeEffect(Host_Milliseconds(), candidate_index, 127.0f, -1.0f, false); if (usage_index != -1 && Scen->AllowableUnitCounts[usage_index] >= Scen->AllowableUnitMaximums[usage_index]) { force_info_redraw = true; fade->StopAtLow = true; @@ -709,7 +710,7 @@ void Dropship_Screen(void) if (Scen->AllowableUnitCounts[usage_index] >= Scen->AllowableUnitMaximums[usage_index]) { for (j = 0; j < candidates.Count(); ++j) { if (candidates[j] == selections[remove_index]) { - ButtonFadeEffect *fade = new ButtonFadeEffect(::timeGetTime(), j, 127.0f, -1.0f, false); + ButtonFadeEffect *fade = new ButtonFadeEffect(Host_Milliseconds(), j, 127.0f, -1.0f, false); fade->Direction = 1.0f; fade->Alpha = 63.0f; button_fades.Add(fade); @@ -780,7 +781,7 @@ void Dropship_Screen(void) for (i = 0; i < ARRAY_SIZE(_green_light_ys); ++i) { if (light_frame[i] != (unsigned int)-1) { int frame = light_frame[i]; - int next_frame = (timeGetTime() - light_start[i]) / _light_rate; + int next_frame = (Host_Milliseconds() - light_start[i]) / _light_rate; if (next_frame != frame || force_light_redraw) { int light_y = y + _green_light_ys[i]; int light_x = x + _light_x; @@ -816,14 +817,14 @@ void Dropship_Screen(void) ButtonFadeEffect *effect = button_fades[i]; int alpha; if (effect->Direction < 0.0f) { - alpha = 127 - (_fade_rate * timeGetTime() - _fade_rate * effect->StartTime) / _fade_scale; + alpha = 127 - (_fade_rate * Host_Milliseconds() - _fade_rate * effect->StartTime) / _fade_scale; if (alpha < _fade_low) { alpha = _fade_low; effect->Direction = 1.0f; - effect->StartTime = timeGetTime(); + effect->StartTime = Host_Milliseconds(); } } else { - alpha = (_fade_rate * timeGetTime() - _fade_rate * effect->StartTime) / _fade_scale + _fade_low; + alpha = (_fade_rate * Host_Milliseconds() - _fade_rate * effect->StartTime) / _fade_scale + _fade_low; if (alpha > 127) { alpha = 127; } @@ -867,7 +868,7 @@ void Dropship_Screen(void) --j; } - int alpha = std::min(255ul, (_dissolve_rate * timeGetTime() - _dissolve_rate * effect->StartTime) / _dissolve_scale); + int alpha = std::min(255ul, (_dissolve_rate * Host_Milliseconds() - _dissolve_rate * effect->StartTime) / _dissolve_scale); if (alpha != effect->Alpha || overlap_drawn) { effect->Alpha = alpha; @@ -899,17 +900,17 @@ void Dropship_Screen(void) } } - recent_click = (::timeGetTime() - last_input_time) < _click_delay; + recent_click = (Host_Milliseconds() - last_input_time) < _click_delay; int loadout_count = loadout_shape->Get_Count(); - int next_loadout_frame = ((::timeGetTime() - screen_start_time) / _loadout_rate) % loadout_count; + int next_loadout_frame = ((Host_Milliseconds() - screen_start_time) / _loadout_rate) % loadout_count; if (next_loadout_frame != loadout_anim_frame) { loadout_anim_frame = next_loadout_frame; Draw_Shape(*HiddenSurface, *drawer_dropship, loadout_shape, loadout_anim_frame, Point2D(0, 0), Rect(Point2D(x, y) + Point2D(_loadout_x, _loadout_y), loadout_shape->Get_Width(), loadout_shape->Get_Height()), SHAPE_NORMAL); redraw = true; } - unsigned int now = ::timeGetTime(); + unsigned int now = Host_Milliseconds(); if ((unsigned int)money != money_display) { unsigned int elapsed = now - money_display_time; if (elapsed >= _money_rate) { @@ -933,7 +934,7 @@ void Dropship_Screen(void) } if (pilot_frame >= 0) { - int next_pilot_frame = (::timeGetTime() - pilot_start_time) / _pilot_rate; + int next_pilot_frame = (Host_Milliseconds() - pilot_start_time) / _pilot_rate; if (next_pilot_frame != pilot_frame) { if (next_pilot_frame < pilotlight_shape->Get_Count()) { pilot_frame = next_pilot_frame; @@ -950,7 +951,7 @@ void Dropship_Screen(void) if (pilot_timer == 0) { if (Scen->RandomNumber(0, INT_MAX - 1) / (double)(INT_MAX - 1) < _pilot_chance) { pilot_frame = 0; - pilot_start_time = ::timeGetTime(); + pilot_start_time = Host_Milliseconds(); Draw_Shape(*HiddenSurface, *drawer_dropship, pilotlight_shape, 0, Point2D(0, 0), Rect(x + _pilot_x, y + _pilot_y, pilotlight_shape->Get_Width(), pilotlight_shape->Get_Height()), SHAPE_NORMAL); redraw = true; } diff --git a/code/gametime.cpp b/code/gametime.cpp index a4ff06d15..37298e69c 100644 --- a/code/gametime.cpp +++ b/code/gametime.cpp @@ -37,6 +37,7 @@ #include "gametime.h" +#include "hostclock.h" #include "win.h" //========================================================================== @@ -59,7 +60,7 @@ GameTimeClass Game_Time; *=========================================================================*/ GameTimeClass::GameTimeClass( void ) { - game_start_time = timeGetTime(); + game_start_time = Host_Milliseconds(); } @@ -81,7 +82,7 @@ unsigned int GameTimeClass::Get_Time( void ) unsigned int curr_windows_time; unsigned int game_time; - curr_windows_time = timeGetTime(); + curr_windows_time = Host_Milliseconds(); if ( curr_windows_time <= game_start_time ) { // Handles the case if the windows time wraps while playing the game. game_time = MAX_ULONG - game_start_time + curr_windows_time; diff --git a/code/hostclock.h b/code/hostclock.h new file mode 100644 index 000000000..28589f03d --- /dev/null +++ b/code/hostclock.h @@ -0,0 +1,26 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#pragma once + +#include +#include + +/* + * The engine's coarse clock, in milliseconds from a clock that only ever moves forward. + * Every caller measures an interval with it and none depends on where it starts. The + * reading wraps roughly every forty nine days, so compare differences and not the + * readings themselves. + */ +inline uint32_t Host_Milliseconds(void) +{ + return((uint32_t)std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count()); +} + diff --git a/code/init.cpp b/code/init.cpp index 1e67a2a09..d841c81df 100644 --- a/code/init.cpp +++ b/code/init.cpp @@ -113,6 +113,7 @@ #include "gamedlg.h" #include "getcpu.h" #include "globals.h" +#include "hostclock.h" #include "houstype.h" #include "incdec.h" #include "infatype.h" @@ -1888,7 +1889,7 @@ void Init_Random(void) Seed = CustomSeed; } else { CryptRandom.Get(&Seed, sizeof(Seed)); - Seed = GetTickCount(); + Seed = Host_Milliseconds(); //srand(time(NULL)); //Seed = rand(); } diff --git a/code/mainloop.cpp b/code/mainloop.cpp index d0383c2ed..7447bd665 100644 --- a/code/mainloop.cpp +++ b/code/mainloop.cpp @@ -36,6 +36,7 @@ #include "fog.h" #include "globals.h" #include "goptions.h" +#include "hostclock.h" #include "ipxmgr.h" #include "language\language.h" #include "logic.h" @@ -227,7 +228,7 @@ bool Main_Loop(void) // // Initialize our AI processing timer // - Session.ProcessTimer = timeGetTime();/// TickCount; + Session.ProcessTimer = Host_Milliseconds();/// TickCount; if (Session.TrapCheckHeap) { Debug_Trap_Check_Heap = true; @@ -346,7 +347,7 @@ bool Main_Loop(void) // // Measure how long it took to process the AI // - Session.ProcessTicks += std::min(1000, (timeGetTime() - Session.ProcessTimer)); // (TickCount - Session.ProcessTimer) + Session.ProcessTicks += std::min(1000, (Host_Milliseconds() - Session.ProcessTimer)); // (TickCount - Session.ProcessTimer) Session.ProcessFrames++; /* diff --git a/code/milsectmr.cpp b/code/milsectmr.cpp index ccc29ab15..f9779cb49 100644 --- a/code/milsectmr.cpp +++ b/code/milsectmr.cpp @@ -13,6 +13,7 @@ #include "dbgprint.h" #include "getcpu.h" +#include "hostclock.h" #include "mpu.h" #include "win.h" @@ -29,8 +30,9 @@ /// /// Creates the millisecond timer and works out how to drive it. /// This routine will ask the processor for its clock rate so that the cycle counter can be -/// scaled into milliseconds. Machines that will not report a rate fall back to the Windows -/// multimedia timer, whose resolution is raised to one millisecond for the life of the timer. +/// scaled into milliseconds. Machines that will not report a rate fall back to the host +/// clock instead. The resolution raised here no longer sharpens that reading, but the +/// request is process wide and the game's waits are rounded up to whatever is in force. /// MillisecondTimerClass::MillisecondTimerClass(void) { @@ -70,8 +72,7 @@ MillisecondTimerClass::~MillisecondTimerClass(void) /// Fetches the current time, expressed in milliseconds. /// This routine is used every time the timer is read. The processor's own cycle counter /// supplies the value when the machine is new enough to have one, since it is both cheaper -/// and finer grained than the system timer. Otherwise the Windows multimedia timer is -/// consulted instead. +/// and finer grained than the host clock. Otherwise the host clock is read instead. /// /// Returns with the current time in milliseconds. MillisecondTimerClass::operator double () const @@ -94,5 +95,5 @@ MillisecondTimerClass::operator double () const return(LI_TO_DBL(dh, dl) / Frequency); } - return(timeGetTime()); + return(Host_Milliseconds()); } diff --git a/code/milsectmr.h b/code/milsectmr.h index 3228f85d2..45dc6b0bc 100644 --- a/code/milsectmr.h +++ b/code/milsectmr.h @@ -22,7 +22,7 @@ class MillisecondTimerClass /* * This is the number of processor clock cycles that pass in one millisecond, and * the raw cycle count is divided by it to yield a time. If it is 1.0, then the - * processor would not report its rate and the multimedia timer is read instead. + * processor would not report its rate and the host clock is read instead. */ double Frequency; }; diff --git a/code/mstimer.cpp b/code/mstimer.cpp index fceb0fc3a..505ee52f6 100644 --- a/code/mstimer.cpp +++ b/code/mstimer.cpp @@ -11,13 +11,15 @@ #include "mstimer.h" +#include "hostclock.h" #include "win.h" /// -/// Asks Windows for one millisecond timer resolution. -/// This routine is called when the timer is created so that the readings it hands out -/// are fine grained enough for the game to pace itself by. +/// Requests one millisecond timer resolution for as long as this object exists. +/// The reading itself no longer needs it, since hostclock.h answers that from a clock of +/// its own. The request is process wide, though, and every wait the game paces itself with +/// is rounded up to whatever resolution is in force. /// MillisecondSystemTimerClass::MillisecondSystemTimerClass(void) { @@ -41,10 +43,10 @@ MillisecondSystemTimerClass::~MillisecondSystemTimerClass(void) /// This is the sampling routine that the timer templates call whenever they need to /// know how much time has passed. /// -/// Returns with the number of milliseconds elapsed since Windows started. +/// Returns with the host clock's millisecond reading. int MillisecondSystemTimerClass::operator () (void) const { - return(timeGetTime()); + return(Host_Milliseconds()); } @@ -52,8 +54,8 @@ int MillisecondSystemTimerClass::operator () (void) const /// Converts the timer into its current millisecond reading. /// This routine lets the timer object be used wherever a plain time value is expected. /// -/// Returns with the number of milliseconds elapsed since Windows started. +/// Returns with the host clock's millisecond reading. MillisecondSystemTimerClass::operator int (void) const { - return(timeGetTime()); + return(Host_Milliseconds()); } diff --git a/code/session.cpp b/code/session.cpp index da274270b..30afc28ff 100644 --- a/code/session.cpp +++ b/code/session.cpp @@ -58,6 +58,7 @@ #include "dbgprint.h" #include "gamedirs.h" // for Search_Files. #include "globals.h" +#include "hostclock.h" #include "ipxmgr.h" #include "language\language.h" #include "msgloop.h" @@ -937,7 +938,7 @@ unsigned int SessionClass::Compute_Unique_ID(void) //------------------------------------------------------------------------ // time(&tm); // id = (unsigned long)tm; - id = timeGetTime(); + id = Host_Milliseconds(); //------------------------------------------------------------------------ // Now add in the free space on the hard drive diff --git a/code/stimer.cpp b/code/stimer.cpp index 9574ee3db..1d4dd13dc 100644 --- a/code/stimer.cpp +++ b/code/stimer.cpp @@ -33,6 +33,7 @@ #include "stimer.h" +#include "hostclock.h" #include "win.h" #ifdef _MSC_VER @@ -48,13 +49,13 @@ /// /// Fetches the current system timer value. /// This routine is the clock source that the timer templates are built upon. It scales -/// the Windows multimedia clock down so that timers tick in game sized units rather -/// than in milliseconds. +/// the host clock down so that timers tick in game sized units rather than in +/// milliseconds. /// /// Returns with the current system time, expressed in timer ticks. int SystemTimerClass::operator () (void) const { - return(timeGetTime()/16); + return(Host_Milliseconds()/16); } @@ -66,5 +67,5 @@ int SystemTimerClass::operator () (void) const /// Returns with the current system time, expressed in timer ticks. SystemTimerClass::operator int (void) const { - return(timeGetTime()/16); + return(Host_Milliseconds()/16); } diff --git a/code/video.cpp b/code/video.cpp index e25f6fa86..5330e4977 100644 --- a/code/video.cpp +++ b/code/video.cpp @@ -21,6 +21,7 @@ #include "dsurface.h" #include "globals.h" #include "goptions.h" +#include "hostclock.h" #include "misc.h" #include "surface.h" #include "wincursor.h" @@ -299,7 +300,7 @@ void Video_Present(void) _Presenting = false; _FrameIsDirty = false; - _LastPresentTime = timeGetTime(); + _LastPresentTime = Host_Milliseconds(); } @@ -315,7 +316,7 @@ void Video_Present_If_Dirty(void) return; } - unsigned int now = timeGetTime(); + unsigned int now = Host_Milliseconds(); if ((now - _LastPresentTime) < _PresentInterval) { return; } From e7e1600f1f0f6b70f4cf050b662520c603fbc63e Mon Sep 17 00:00:00 2001 From: Marcel Bierling Date: Wed, 2 Sep 2026 16:33:07 +0200 Subject: [PATCH 002/179] Mark the timer resolution calls as the Windows ones they are --- code/hostclock.h | 10 ++++------ code/milsectmr.cpp | 6 ++++++ code/mstimer.cpp | 6 ++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/code/hostclock.h b/code/hostclock.h index 28589f03d..07ddae781 100644 --- a/code/hostclock.h +++ b/code/hostclock.h @@ -12,12 +12,10 @@ #include #include -/* - * The engine's coarse clock, in milliseconds from a clock that only ever moves forward. - * Every caller measures an interval with it and none depends on where it starts. The - * reading wraps roughly every forty nine days, so compare differences and not the - * readings themselves. - */ +// The engine's coarse clock, in milliseconds from a clock that only ever moves forward. +// Every caller measures an interval with it and none depends on where it starts. The +// reading wraps roughly every forty nine days, so compare differences and not the +// readings themselves. inline uint32_t Host_Milliseconds(void) { return((uint32_t)std::chrono::duration_cast( diff --git a/code/milsectmr.cpp b/code/milsectmr.cpp index f9779cb49..b437a9ac3 100644 --- a/code/milsectmr.cpp +++ b/code/milsectmr.cpp @@ -41,7 +41,10 @@ MillisecondTimerClass::MillisecondTimerClass(void) unsigned int low = Get_CPU_Rate(high); if (low == 0 && high == 0) { + // Windows only; no other host has a resolution to bid for. +#ifdef _WIN32 timeBeginPeriod(PERIOD_RESOLUTION); +#endif } else { double dl = low; @@ -63,7 +66,10 @@ MillisecondTimerClass::MillisecondTimerClass(void) MillisecondTimerClass::~MillisecondTimerClass(void) { if (Frequency != 1.0) { + // Windows only; no other host has a resolution to bid for. +#ifdef _WIN32 timeEndPeriod(PERIOD_RESOLUTION); +#endif } } diff --git a/code/mstimer.cpp b/code/mstimer.cpp index 505ee52f6..02e68c307 100644 --- a/code/mstimer.cpp +++ b/code/mstimer.cpp @@ -23,7 +23,10 @@ /// MillisecondSystemTimerClass::MillisecondSystemTimerClass(void) { + // Windows only; no other host has a resolution to bid for. +#ifdef _WIN32 timeBeginPeriod(1); +#endif } @@ -34,7 +37,10 @@ MillisecondSystemTimerClass::MillisecondSystemTimerClass(void) /// MillisecondSystemTimerClass::~MillisecondSystemTimerClass(void) { + // Windows only; no other host has a resolution to bid for. +#ifdef _WIN32 timeEndPeriod(1); +#endif } From bea22b61984ff004f0999e0d51e2d753c1985f85 Mon Sep 17 00:00:00 2001 From: Marcel Bierling Date: Wed, 2 Sep 2026 17:37:54 +0200 Subject: [PATCH 003/179] Include where size_t is used --- code/lcw.cpp | 1 + code/vqalib/cmp.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/code/lcw.cpp b/code/lcw.cpp index 82f5b5c53..7587a95e1 100644 --- a/code/lcw.cpp +++ b/code/lcw.cpp @@ -31,6 +31,7 @@ * LCW_Uncomp -- Decompress an LCW encoded data block. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#include #include "always.h" #include "lcw.h" diff --git a/code/vqalib/cmp.h b/code/vqalib/cmp.h index e00d166d8..9681b7456 100644 --- a/code/vqalib/cmp.h +++ b/code/vqalib/cmp.h @@ -16,6 +16,8 @@ #pragma once +#include + #include #if defined(__WATCOMC__) || defined(_MSC_VER) From 1d3d40817e0bfa82263b0ff8a1c3dbd5d210e762 Mon Sep 17 00:00:00 2001 From: Marcel Bierling Date: Wed, 2 Sep 2026 17:39:43 +0200 Subject: [PATCH 004/179] Define the angle macros outside the MSVC block --- code/visualc.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/visualc.h b/code/visualc.h index 9fa9a9126..ca9c57a9e 100644 --- a/code/visualc.h +++ b/code/visualc.h @@ -116,6 +116,9 @@ // Single precision pi, for the float paths that would otherwise round M_PI at every use. #define M_FPI 3.141592654f +#endif + + /* ** Macros to convert between degrees and radians */ @@ -134,6 +137,3 @@ #ifndef DEG_TO_RADF #define DEG_TO_RADF(x) (((float)x)*M_PI/180.0f) #endif - - -#endif From db83320722211e515b5bfa9f554e20ad4892fba6 Mon Sep 17 00:00:00 2001 From: Marcel Bierling Date: Wed, 2 Sep 2026 17:39:43 +0200 Subject: [PATCH 005/179] Guard MSVC's low-level I/O header --- code/conquer.cpp | 2 ++ code/startup.cpp | 2 ++ code/vqalib/dstream.cpp | 2 ++ code/wwfile.h | 2 ++ 4 files changed, 8 insertions(+) diff --git a/code/conquer.cpp b/code/conquer.cpp index fbfc034cf..5a0677e50 100644 --- a/code/conquer.cpp +++ b/code/conquer.cpp @@ -124,7 +124,9 @@ #include #include #include +#ifdef _WIN32 #include +#endif #include #include diff --git a/code/startup.cpp b/code/startup.cpp index 22199f90d..279acc904 100644 --- a/code/startup.cpp +++ b/code/startup.cpp @@ -160,7 +160,9 @@ #include #include +#ifdef _WIN32 #include +#endif #include #include #include diff --git a/code/vqalib/dstream.cpp b/code/vqalib/dstream.cpp index ec4c3c034..ae76ce1bd 100644 --- a/code/vqalib/dstream.cpp +++ b/code/vqalib/dstream.cpp @@ -45,7 +45,9 @@ #include "vqaplayp.h" #include #include +#ifdef _WIN32 #include +#endif #include diff --git a/code/wwfile.h b/code/wwfile.h index 9ac186f90..426e1a595 100644 --- a/code/wwfile.h +++ b/code/wwfile.h @@ -40,7 +40,9 @@ #include #include +#ifdef _WIN32 #include +#endif #ifndef SEEK_SET #define SEEK_SET 0 // Seek from start of file. From 9be425bc5605513aae98704381f661e58de62801 Mon Sep 17 00:00:00 2001 From: Marcel Bierling Date: Wed, 2 Sep 2026 17:39:43 +0200 Subject: [PATCH 006/179] Include rather than MSVC's --- code/arraylist.h | 2 +- code/data.cpp | 2 +- code/newdel.cpp | 2 +- code/sha.h | 2 +- code/techtype.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/code/arraylist.h b/code/arraylist.h index 5cf6ecfb8..057887723 100644 --- a/code/arraylist.h +++ b/code/arraylist.h @@ -20,7 +20,7 @@ #include #include #include -#include +#include template diff --git a/code/data.cpp b/code/data.cpp index 2c510a0de..7c9a1107e 100644 --- a/code/data.cpp +++ b/code/data.cpp @@ -37,7 +37,7 @@ #include "data.h" -#include +#include HINSTANCE LanguageResources; diff --git a/code/newdel.cpp b/code/newdel.cpp index 6f49bd1fd..7f129e8e1 100644 --- a/code/newdel.cpp +++ b/code/newdel.cpp @@ -17,7 +17,7 @@ #ifdef STEVES_NEW_CATCHER #include -#include +#include /// diff --git a/code/sha.h b/code/sha.h index 2a2887cef..e74f2160d 100644 --- a/code/sha.h +++ b/code/sha.h @@ -35,7 +35,7 @@ #include #include #include -#include +#include /* diff --git a/code/techtype.cpp b/code/techtype.cpp index 5ea075a14..80cbf7410 100644 --- a/code/techtype.cpp +++ b/code/techtype.cpp @@ -43,7 +43,7 @@ #include "voc.hh" #include -#include +#include /*************************************************************************** From df917383273eec9c26d2e78b03374ce7ed8182f6 Mon Sep 17 00:00:00 2001 From: Marcel Bierling Date: Wed, 2 Sep 2026 18:08:35 +0200 Subject: [PATCH 007/179] Guard MSVC's source-annotation header --- code/dbgprint.h | 2 ++ code/except.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/code/dbgprint.h b/code/dbgprint.h index 49f70fed0..288ce61be 100644 --- a/code/dbgprint.h +++ b/code/dbgprint.h @@ -15,7 +15,9 @@ #include "sun.h" +#ifdef _WIN32 #include +#endif void Debug_Init(void); void Debug_Init_Console(void); diff --git a/code/except.h b/code/except.h index 401a11421..9c536fded 100644 --- a/code/except.h +++ b/code/except.h @@ -33,7 +33,9 @@ #include "win.h" +#ifdef _WIN32 #include +#endif // Posted to the main window so that a requested test fault happens inside window procedure // dispatch, which the operating system unwinds differently from an ordinary call. From d5ddb531e000c94a890a40ab67b168a9e9c7a392 Mon Sep 17 00:00:00 2001 From: Marcel Bierling Date: Wed, 2 Sep 2026 18:08:35 +0200 Subject: [PATCH 008/179] Define O_BINARY where the platform has no text mode --- code/vqalib/dstream.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/code/vqalib/dstream.cpp b/code/vqalib/dstream.cpp index ae76ce1bd..a21de7642 100644 --- a/code/vqalib/dstream.cpp +++ b/code/vqalib/dstream.cpp @@ -48,6 +48,10 @@ #ifdef _WIN32 #include #endif + +#ifndef O_BINARY +#define O_BINARY 0 +#endif #include From af9b3e4ad9540373ddae525b5761e51302bd78a7 Mon Sep 17 00:00:00 2001 From: Marcel Bierling Date: Wed, 2 Sep 2026 18:08:35 +0200 Subject: [PATCH 009/179] Name the comparison type where the enum is not an int --- code/sha.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/sha.cpp b/code/sha.cpp index df04f76e7..85bdbfc4a 100644 --- a/code/sha.cpp +++ b/code/sha.cpp @@ -78,7 +78,7 @@ void SHAEngine::Process_Partial(void const * & data, int & length) ** Attach as many bytes as possible from the source data into ** the staging buffer. */ - int add_count = std::min((int)length, SRC_BLOCK_SIZE - PartialCount); + int add_count = std::min((int)length, SRC_BLOCK_SIZE - PartialCount); memcpy(&Partial[PartialCount], data, add_count); data = ((char const *&)data) + add_count; PartialCount += add_count; From 477d903e0406b1f99c1860d32c84016e329276b1 Mon Sep 17 00:00:00 2001 From: Marcel Bierling Date: Wed, 2 Sep 2026 18:10:16 +0200 Subject: [PATCH 010/179] Answer the SAL annotation where sal.h is absent --- code/dbgprint.h | 2 ++ code/except.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/code/dbgprint.h b/code/dbgprint.h index 288ce61be..176743d5e 100644 --- a/code/dbgprint.h +++ b/code/dbgprint.h @@ -17,6 +17,8 @@ #ifdef _WIN32 #include +#else +#define _Printf_format_string_ #endif void Debug_Init(void); diff --git a/code/except.h b/code/except.h index 9c536fded..abfde6d6c 100644 --- a/code/except.h +++ b/code/except.h @@ -35,6 +35,8 @@ #ifdef _WIN32 #include +#else +#define _Printf_format_string_ #endif // Posted to the main window so that a requested test fault happens inside window procedure From dd5823a4727c57b155bc72fc13dd1e1ffeda96ab Mon Sep 17 00:00:00 2001 From: Marcel Bierling Date: Wed, 2 Sep 2026 18:10:16 +0200 Subject: [PATCH 011/179] Include for the descriptor calls off Windows --- code/vqalib/dstream.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/vqalib/dstream.cpp b/code/vqalib/dstream.cpp index a21de7642..f2b60fedf 100644 --- a/code/vqalib/dstream.cpp +++ b/code/vqalib/dstream.cpp @@ -47,6 +47,8 @@ #include #ifdef _WIN32 #include +#else +#include #endif #ifndef O_BINARY From 2159e679660910c8ecd733445a299fed9deb7837 Mon Sep 17 00:00:00 2001 From: Marcel Bierling Date: Wed, 2 Sep 2026 20:52:22 +0200 Subject: [PATCH 012/179] Let a native toolchain configure --- CMakeLists.txt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 27386aa80..1be27031f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ set(OPENTS_VERSION_PRERELEASE "") option(OPENTS_OFFICIAL_BUILD "Build as an official release of the declared version" OFF) option(OPENTS_EXPERIMENTAL_CLANG_CL "Build with clang-cl using the MSVC ABI" OFF) +option(OPENTS_EXPERIMENTAL_NATIVE "Configure a native build for the host platform" OFF) if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") @@ -24,14 +25,20 @@ elseif(MSVC) if(MSVC_VERSION LESS 1930) message(FATAL_ERROR "OpenTS requires MSVC 19.30 or newer.") endif() +elseif(OPENTS_EXPERIMENTAL_NATIVE) + message(STATUS "OpenTS: configuring an unsupported native build for the host platform.") else() message(FATAL_ERROR "OpenTS requires the Visual Studio 2022 MSVC toolchain. " "For the unsupported clang-cl experiment, configure with " - "-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/clang-cl-msvc.cmake.") + "-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/clang-cl-msvc.cmake. " + "For the unsupported native build, configure with " + "-DOPENTS_EXPERIMENTAL_NATIVE=ON.") endif() -enable_language(RC) +if(WIN32) + enable_language(RC) +endif() set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) From 82ed29c4d87a88edd7b4f7aca60b0efe3c8062b7 Mon Sep 17 00:00:00 2001 From: Marcel Bierling Date: Wed, 2 Sep 2026 20:52:22 +0200 Subject: [PATCH 013/179] Scope the Windows-only build steps to Windows --- code/CMakeLists.txt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 94876c529..b350a8fb9 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -2,7 +2,7 @@ set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE) # OpenTS currently supports 32-bit (x86) builds only. -if(NOT CMAKE_SIZEOF_VOID_P EQUAL 4) +if(WIN32 AND NOT CMAKE_SIZEOF_VOID_P EQUAL 4) message(FATAL_ERROR "OpenTS must be built as 32-bit x86. Reconfigure with -A Win32.") endif() @@ -165,7 +165,7 @@ set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/bgfxbackend.cpp" PROPER "${BGFX_ROOT}/include;${CMAKE_SOURCE_DIR}/thirdparty/bgfx.cmake/bx/include;${BGFX_ROOT}/examples/common/imgui" COMPILE_DEFINITIONS "BX_CONFIG_DEBUG=$,1,$>" - COMPILE_OPTIONS "/Zc:preprocessor" + COMPILE_OPTIONS "$<$:/Zc:preprocessor>" ) # bx rewrites __stdcall while its headers are being parsed by clang-cl. Force the @@ -358,12 +358,14 @@ add_custom_command(TARGET OpenTS POST_BUILD "${TS_RUN_DIR}" ) -# Copy the linker-generated .pdb alongside the exe -add_custom_command(TARGET OpenTS POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "$" - "${TS_RUN_DIR}" -) +# Copy the linker-generated .pdb alongside the exe. Only the MSVC linker writes one. +if(MSVC) + add_custom_command(TARGET OpenTS POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "$" + "${TS_RUN_DIR}" + ) +endif() # Copy the linker-generated .map (sits next to the exe, no GenEx for it) add_custom_command(TARGET OpenTS POST_BUILD From 0ac74acf92f6a55e151a4597c8d60c52e982cb03 Mon Sep 17 00:00:00 2001 From: Marcel Bierling Date: Wed, 2 Sep 2026 20:52:22 +0200 Subject: [PATCH 014/179] Separate the language include with a forward slash --- code/addon.cpp | 2 +- code/chat.cpp | 2 +- code/conquer.cpp | 2 +- code/credits.cpp | 2 +- code/display.cpp | 2 +- code/dropship.cpp | 2 +- code/dsaudio.cpp | 2 +- code/egos.cpp | 2 +- code/event.cpp | 2 +- code/gamedlg.cpp | 2 +- code/goptions.cpp | 2 +- code/house.cpp | 2 +- code/infantry.cpp | 2 +- code/init.cpp | 2 +- code/ion.cpp | 2 +- code/list.cpp | 2 +- code/loaddlg.cpp | 2 +- code/mainloop.cpp | 2 +- code/mainopt.cpp | 2 +- code/mapgen.cpp | 2 +- code/mpscore.cpp | 2 +- code/msgbox.h | 2 +- code/netdlg.cpp | 2 +- code/netdlg2.cpp | 2 +- code/netshare.cpp | 2 +- code/options.cpp | 2 +- code/ownrdraw.cpp | 2 +- code/power.cpp | 2 +- code/preview.cpp | 2 +- code/progress.cpp | 2 +- code/queue.cpp | 2 +- code/radar.cpp | 2 +- code/restate.cpp | 2 +- code/saveload.cpp | 2 +- code/scenario.cpp | 2 +- code/score.cpp | 2 +- code/session.cpp | 2 +- code/sidebar.cpp | 2 +- code/skirmish.cpp | 2 +- code/sounddlg.cpp | 2 +- code/spawner.cpp | 2 +- code/startup.cpp | 2 +- code/super.cpp | 2 +- code/tab.cpp | 2 +- code/techno.cpp | 2 +- code/textbtn.cpp | 2 +- code/wdtconflict.cpp | 2 +- code/wdtgameoptions.cpp | 2 +- code/wdtprops.cpp | 2 +- code/wdtsel.cpp | 2 +- code/worlddom.cpp | 2 +- 51 files changed, 51 insertions(+), 51 deletions(-) diff --git a/code/addon.cpp b/code/addon.cpp index fdeb7ea96..772f55278 100644 --- a/code/addon.cpp +++ b/code/addon.cpp @@ -14,7 +14,7 @@ #include "ccfile.h" #include "data.h" #include "init.h" -#include "language\language.h" +#include "language/language.h" #include "ownrdraw.h" BOOL CALLBACK Select_Game_Type_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); diff --git a/code/chat.cpp b/code/chat.cpp index 659baf4c5..10cec49be 100644 --- a/code/chat.cpp +++ b/code/chat.cpp @@ -19,7 +19,7 @@ #include "globals.h" #include "house.h" #include "ipxmgr.h" -#include "language\language.h" +#include "language/language.h" #include "rules.h" #include "session.h" #include "stimer.h" diff --git a/code/conquer.cpp b/code/conquer.cpp index fbfc034cf..3227f44e1 100644 --- a/code/conquer.cpp +++ b/code/conquer.cpp @@ -89,7 +89,7 @@ #include "init.h" #include "ipxmgr.h" #include "keyboard.h" -#include "language\language.h" +#include "language/language.h" #include "logic.h" #include "mainloop.h" #include "movie.h" diff --git a/code/credits.cpp b/code/credits.cpp index 5449b17dc..5c709b1a3 100644 --- a/code/credits.cpp +++ b/code/credits.cpp @@ -46,7 +46,7 @@ #include "dsurface.h" #include "globals.h" #include "house.h" -#include "language\language.h" +#include "language/language.h" #include "rules.h" #include "scenario.h" #include "scheme.h" diff --git a/code/display.cpp b/code/display.cpp index be765e85f..f856fad33 100644 --- a/code/display.cpp +++ b/code/display.cpp @@ -124,7 +124,7 @@ #include "inline.h" #include "isotype.h" #include "keyboard.h" -#include "language\language.h" +#include "language/language.h" #include "logic.h" #include "mixfile.h" #include "overtype.h" diff --git a/code/dropship.cpp b/code/dropship.cpp index 7325650db..960b13d19 100644 --- a/code/dropship.cpp +++ b/code/dropship.cpp @@ -33,7 +33,7 @@ #include "house.h" #include "infatype.h" #include "keyboard.h" -#include "language\language.h" +#include "language/language.h" #include "misc.h" #include "mixfile.h" #include "mouse.h" diff --git a/code/dsaudio.cpp b/code/dsaudio.cpp index a4f60c7b3..fb0391967 100644 --- a/code/dsaudio.cpp +++ b/code/dsaudio.cpp @@ -19,7 +19,7 @@ #include "data.h" #include "dbgprint.h" #include "globals.h" // for GameInFocus -#include "language\language.h" +#include "language/language.h" #include "soscomp.h" #include "winfix.h" diff --git a/code/egos.cpp b/code/egos.cpp index 64d3cfc63..dab242569 100644 --- a/code/egos.cpp +++ b/code/egos.cpp @@ -54,7 +54,7 @@ #include "globals.h" #include "goptions.h" #include "gscreen.h" -#include "language\language.h" +#include "language/language.h" #include "misc.h" #include "ownrdraw.h" #include "scheme.h" diff --git a/code/event.cpp b/code/event.cpp index 19679101a..866ab0c4d 100644 --- a/code/event.cpp +++ b/code/event.cpp @@ -58,7 +58,7 @@ #include "foot.h" #include "goptions.h" #include "house.h" -#include "language\language.h" +#include "language/language.h" #include "mouse.h" #include "netsemantic.h" #include "rules.h" diff --git a/code/gamedlg.cpp b/code/gamedlg.cpp index 2440d99f5..ba3c3999d 100644 --- a/code/gamedlg.cpp +++ b/code/gamedlg.cpp @@ -43,7 +43,7 @@ #include "dsaudio.h" #include "globals.h" #include "init.h" -#include "language\language.h" +#include "language/language.h" #include "ownrdraw.h" #include "queue.h" #include "session.h" diff --git a/code/goptions.cpp b/code/goptions.cpp index c22beb600..4f2dfa678 100644 --- a/code/goptions.cpp +++ b/code/goptions.cpp @@ -40,7 +40,7 @@ #include "data.h" #include "dbgprint.h" #include "gamedlg.h" -#include "language\language.h" +#include "language/language.h" #include "loaddlg.h" #include "ownrdraw.h" #include "queue.h" diff --git a/code/house.cpp b/code/house.cpp index 931d66a12..e8e5b88cb 100644 --- a/code/house.cpp +++ b/code/house.cpp @@ -159,7 +159,7 @@ #include "infatype.h" #include "inline.h" #include "ion.h" -#include "language\language.h" +#include "language/language.h" #include "lightcon.h" #include "logic.h" #include "mono.h" diff --git a/code/infantry.cpp b/code/infantry.cpp index 86b08d7f7..5498ce305 100644 --- a/code/infantry.cpp +++ b/code/infantry.cpp @@ -113,7 +113,7 @@ #include "inline.h" #include "ion.h" #include "isotype.h" -#include "language\language.h" +#include "language/language.h" #include "lightcon.h" #include "mixfile.h" #include "mono.h" diff --git a/code/init.cpp b/code/init.cpp index 7bc8abe2e..ba33dcc32 100644 --- a/code/init.cpp +++ b/code/init.cpp @@ -121,7 +121,7 @@ #include "ionblast.h" #include "ipxmgr.h" #include "keyboard.h" -#include "language\language.h" +#include "language/language.h" #include "laser.h" #include "light.h" #include "lightcon.h" diff --git a/code/ion.cpp b/code/ion.cpp index 43e50167e..c4ce335a1 100644 --- a/code/ion.cpp +++ b/code/ion.cpp @@ -30,7 +30,7 @@ #include "globals.h" #include "gscreen.h" #include "house.h" -#include "language\language.h" +#include "language/language.h" #include "laser.h" #include "lightcon.h" #include "mixfile.h" diff --git a/code/list.cpp b/code/list.cpp index bbe11fc05..0059e6d56 100644 --- a/code/list.cpp +++ b/code/list.cpp @@ -63,7 +63,7 @@ #include "dialog.h" #include "dsurface.h" #include "font.h" -#include "language\language.h" +#include "language/language.h" #include "scheme.h" #include "vector.h" #include "wwmouse.h" diff --git a/code/loaddlg.cpp b/code/loaddlg.cpp index a3306cc3b..be5853109 100644 --- a/code/loaddlg.cpp +++ b/code/loaddlg.cpp @@ -48,7 +48,7 @@ #include "globals.h" #include "houstype.h" #include "init.h" -#include "language\language.h" +#include "language/language.h" #include "msgbox.h" #include "ownrdraw.h" #include "saveload.h" diff --git a/code/mainloop.cpp b/code/mainloop.cpp index 8a2ed93e5..d4d0276e1 100644 --- a/code/mainloop.cpp +++ b/code/mainloop.cpp @@ -38,7 +38,7 @@ #include "globals.h" #include "goptions.h" #include "ipxmgr.h" -#include "language\language.h" +#include "language/language.h" #include "logic.h" #include "misc.h" #include "mpscore.h" diff --git a/code/mainopt.cpp b/code/mainopt.cpp index 891cdcd95..2f078ab63 100644 --- a/code/mainopt.cpp +++ b/code/mainopt.cpp @@ -23,7 +23,7 @@ #include "gamedlg.h" #include "globals.h" #include "init.h" -#include "language\language.h" +#include "language/language.h" #include "misc.h" #include "video.h" #include "mixfile.h" diff --git a/code/mapgen.cpp b/code/mapgen.cpp index d5ed56108..978492589 100644 --- a/code/mapgen.cpp +++ b/code/mapgen.cpp @@ -38,7 +38,7 @@ #include "init.h" #include "inline.h" #include "isotype.h" -#include "language\language.h" +#include "language/language.h" #include "netshare.h" #include "nodes.h" #include "overtype.h" diff --git a/code/mpscore.cpp b/code/mpscore.cpp index bf2561ba3..ca2e03c76 100644 --- a/code/mpscore.cpp +++ b/code/mpscore.cpp @@ -27,7 +27,7 @@ #include "houstype.h" #include "incdec.h" #include "keyboard.h" -#include "language\language.h" +#include "language/language.h" #include "msanim.h" #include "msengine.h" #include "msfont.h" diff --git a/code/msgbox.h b/code/msgbox.h index 95933cb2d..77532ad87 100644 --- a/code/msgbox.h +++ b/code/msgbox.h @@ -32,7 +32,7 @@ #pragma once -#include "language\language.h" +#include "language/language.h" #include "win.h" class WWMessageBox diff --git a/code/netdlg.cpp b/code/netdlg.cpp index 8997e3906..1262564e7 100644 --- a/code/netdlg.cpp +++ b/code/netdlg.cpp @@ -112,7 +112,7 @@ #include "globals.h" #include "houstype.h" #include "ipxmgr.h" -#include "language\language.h" +#include "language/language.h" #include "list.h" #include "queue.h" #include "rules.h" diff --git a/code/netdlg2.cpp b/code/netdlg2.cpp index 7e679fcbb..d21717b17 100644 --- a/code/netdlg2.cpp +++ b/code/netdlg2.cpp @@ -28,7 +28,7 @@ #include "houstype.h" #include "init.h" #include "ipxmgr.h" -#include "language\language.h" +#include "language/language.h" #include "mapgen.h" #include "mplayer.h" #include "msgbox.h" diff --git a/code/netshare.cpp b/code/netshare.cpp index 1bee7959f..46f4c278f 100644 --- a/code/netshare.cpp +++ b/code/netshare.cpp @@ -18,7 +18,7 @@ #include "globals.h" #include "goptions.h" #include "ipxmgr.h" -#include "language\language.h" +#include "language/language.h" #include "lzopipe.h" #include "lzostraw.h" #include "mapgen.h" diff --git a/code/options.cpp b/code/options.cpp index d5cbb84f4..ca7576104 100644 --- a/code/options.cpp +++ b/code/options.cpp @@ -72,7 +72,7 @@ #include "init.h" #include "ipxmgr.h" #include "keyboard.h" -#include "language\language.h" +#include "language/language.h" #include "mouse.h" #include "msgbox.h" #include "ownrdraw.h" diff --git a/code/ownrdraw.cpp b/code/ownrdraw.cpp index 148f1ef8d..4ae0a92bd 100644 --- a/code/ownrdraw.cpp +++ b/code/ownrdraw.cpp @@ -28,7 +28,7 @@ #include "goptions.h" #include "hsv.h" #include "keyboard.h" -#include "language\language.h" +#include "language/language.h" #include "mainloop.h" #include "misc.h" #include "msgroute.h" diff --git a/code/power.cpp b/code/power.cpp index 4dd473ffc..8bb08a1a3 100644 --- a/code/power.cpp +++ b/code/power.cpp @@ -56,7 +56,7 @@ #include "draw.h" #include "globals.h" #include "house.h" -#include "language\language.h" +#include "language/language.h" #include "mixfile.h" #include "savestream.h" #include "surface.h" diff --git a/code/preview.cpp b/code/preview.cpp index 3635d6099..a7ba40107 100644 --- a/code/preview.cpp +++ b/code/preview.cpp @@ -19,7 +19,7 @@ #include "cell.h" #include "dbgprint.h" #include "dsurface.h" -#include "language\language.h" +#include "language/language.h" #include "lzopipe.h" #include "lzostraw.h" #include "overtype.h" diff --git a/code/progress.cpp b/code/progress.cpp index 552fc04e5..68fd2e9e2 100644 --- a/code/progress.cpp +++ b/code/progress.cpp @@ -20,7 +20,7 @@ #include "draw.h" #include "gscreen.h" #include "init.h" -#include "language\language.h" +#include "language/language.h" #include "lightcon.h" #include "mixfile.h" #include "ownrdraw.h" diff --git a/code/queue.cpp b/code/queue.cpp index 831e11409..66258239d 100644 --- a/code/queue.cpp +++ b/code/queue.cpp @@ -118,7 +118,7 @@ #include "infatype.h" #include "ipxmgr.h" #include "keyboard.h" -#include "language\language.h" +#include "language/language.h" #include "light.h" #include "mainloop.h" #include "mono.h" diff --git a/code/radar.cpp b/code/radar.cpp index 388f938b1..5b8e89da4 100644 --- a/code/radar.cpp +++ b/code/radar.cpp @@ -92,7 +92,7 @@ #include "infantry.h" #include "infatype.h" #include "init.h" -#include "language\language.h" +#include "language/language.h" #include "lightcon.h" #include "mixfile.h" #include "movies.h" diff --git a/code/restate.cpp b/code/restate.cpp index 0da391d8d..673fe4e6b 100644 --- a/code/restate.cpp +++ b/code/restate.cpp @@ -24,7 +24,7 @@ #include "dbgprint.h" #include "globals.h" #include "keyboard.h" -#include "language\language.h" +#include "language/language.h" #include "movie.h" #include "msanim.h" #include "msengine.h" diff --git a/code/saveload.cpp b/code/saveload.cpp index bcaa5dcc1..90bd891b6 100644 --- a/code/saveload.cpp +++ b/code/saveload.cpp @@ -82,7 +82,7 @@ #include "infatype.h" #include "init.h" #include "ion.h" -#include "language\language.h" +#include "language/language.h" #include "loaddlg.h" #include "light.h" #include "logic.h" diff --git a/code/scenario.cpp b/code/scenario.cpp index e19fd7e0b..4b893dd03 100644 --- a/code/scenario.cpp +++ b/code/scenario.cpp @@ -107,7 +107,7 @@ #include "ion.h" #include "ipxmgr.h" #include "isotype.h" -#include "language\language.h" +#include "language/language.h" #include "light.h" #include "logic.h" #include "mainopt.h" diff --git a/code/score.cpp b/code/score.cpp index 701bb4ef3..7fd1885b6 100644 --- a/code/score.cpp +++ b/code/score.cpp @@ -61,7 +61,7 @@ #include "goptions.h" #include "houstype.h" #include "keyboard.h" -#include "language\language.h" +#include "language/language.h" #include "misc.h" #include "mixfile.h" #include "movie.h" diff --git a/code/session.cpp b/code/session.cpp index 4782befb8..20d67b4fe 100644 --- a/code/session.cpp +++ b/code/session.cpp @@ -59,7 +59,7 @@ #include "gamedirs.h" // for Search_Files. #include "globals.h" #include "ipxmgr.h" -#include "language\language.h" +#include "language/language.h" #include "msgloop.h" #include "progress.h" #include "queue.h" diff --git a/code/sidebar.cpp b/code/sidebar.cpp index 4ca141177..4caf915b9 100644 --- a/code/sidebar.cpp +++ b/code/sidebar.cpp @@ -99,7 +99,7 @@ #include "goptions.h" #include "house.h" #include "incdec.h" -#include "language\language.h" +#include "language/language.h" #include "map.h" #include "mixfile.h" #include "movie.h" diff --git a/code/skirmish.cpp b/code/skirmish.cpp index 830738977..81e48432d 100644 --- a/code/skirmish.cpp +++ b/code/skirmish.cpp @@ -17,7 +17,7 @@ #include "goptions.h" #include "houstype.h" #include "init.h" -#include "language\language.h" +#include "language/language.h" #include "mapgen.h" #include "mplayer.h" #include "msgbox.h" diff --git a/code/sounddlg.cpp b/code/sounddlg.cpp index 3fa7c19b8..1f6af60c9 100644 --- a/code/sounddlg.cpp +++ b/code/sounddlg.cpp @@ -42,7 +42,7 @@ #include "goptions.h" #include "incdec.h" #include "init.h" -#include "language\language.h" +#include "language/language.h" #include "ownrdraw.h" #include "theme.h" #include "winfix.h" diff --git a/code/spawner.cpp b/code/spawner.cpp index 5cf1cb245..6d29d356b 100644 --- a/code/spawner.cpp +++ b/code/spawner.cpp @@ -25,7 +25,7 @@ #include "houstype.h" #include "init.h" #include "ipxmgr.h" -#include "language\language.h" +#include "language/language.h" #include "loaddlg.h" #include "mplayer.h" #include "netshare.h" diff --git a/code/startup.cpp b/code/startup.cpp index 22199f90d..3dffa6298 100644 --- a/code/startup.cpp +++ b/code/startup.cpp @@ -90,7 +90,7 @@ #include "ipxmgr.h" #include "isotype.h" #include "jumpjet.h" -#include "language\language.h" +#include "language/language.h" #include "levitate.h" #include "light.h" #include "lightcon.h" diff --git a/code/super.cpp b/code/super.cpp index 2c204708b..bdf380148 100644 --- a/code/super.cpp +++ b/code/super.cpp @@ -62,7 +62,7 @@ #include "infatype.h" #include "inline.h" #include "ionblast.h" -#include "language\language.h" +#include "language/language.h" #include "mouse.h" #include "rules.h" #include "savestream.h" diff --git a/code/tab.cpp b/code/tab.cpp index c6dac143f..3312c9613 100644 --- a/code/tab.cpp +++ b/code/tab.cpp @@ -47,7 +47,7 @@ #include "dialog.h" #include "draw.h" #include "goptions.h" -#include "language\language.h" +#include "language/language.h" #include "mixfile.h" #include "queue.h" #include "rules.h" diff --git a/code/techno.cpp b/code/techno.cpp index 2cfded419..3531ed065 100644 --- a/code/techno.cpp +++ b/code/techno.cpp @@ -169,7 +169,7 @@ #include "ion.h" #include "isotile.h" #include "isotype.h" -#include "language\language.h" +#include "language/language.h" #include "laser.h" #include "lightcon.h" #include "mono.h" diff --git a/code/textbtn.cpp b/code/textbtn.cpp index 747d30a82..7fd743799 100644 --- a/code/textbtn.cpp +++ b/code/textbtn.cpp @@ -45,7 +45,7 @@ #include "data.h" #include "dialog.h" #include "font.h" -#include "language\language.h" +#include "language/language.h" #include "lightcon.h" #include "scheme.h" #include "surface.h" diff --git a/code/wdtconflict.cpp b/code/wdtconflict.cpp index 322e5a0e7..9ff4cc88c 100644 --- a/code/wdtconflict.cpp +++ b/code/wdtconflict.cpp @@ -10,7 +10,7 @@ #include "always.h" #include "data.h" -#include "language\language.h" +#include "language/language.h" #include "wdtnet.h" using namespace WorldDominationTour; diff --git a/code/wdtgameoptions.cpp b/code/wdtgameoptions.cpp index b88b256ab..51a3bfc0a 100644 --- a/code/wdtgameoptions.cpp +++ b/code/wdtgameoptions.cpp @@ -10,7 +10,7 @@ #include "always.h" #include "data.h" -#include "language\language.h" +#include "language/language.h" #include "wdtnet.h" using namespace WorldDominationTour; diff --git a/code/wdtprops.cpp b/code/wdtprops.cpp index f5bb1d199..d4d5c0c9f 100644 --- a/code/wdtprops.cpp +++ b/code/wdtprops.cpp @@ -10,7 +10,7 @@ #include "always.h" #include "data.h" -#include "language\language.h" +#include "language/language.h" #include "ownrdraw.h" #include "wdtnet.h" diff --git a/code/wdtsel.cpp b/code/wdtsel.cpp index 82c78a6af..b6f362eac 100644 --- a/code/wdtsel.cpp +++ b/code/wdtsel.cpp @@ -8,7 +8,7 @@ ******************************************************************************/ #include "always.h" -#include "language\language.h" +#include "language/language.h" #include "_keyboar.h" #include "_surface.h" diff --git a/code/worlddom.cpp b/code/worlddom.cpp index 6bdbe41fe..a6d972747 100644 --- a/code/worlddom.cpp +++ b/code/worlddom.cpp @@ -15,7 +15,7 @@ #include "_pk.h" #include "addon.h" #include "grphmenu.h" -#include "language\language.h" +#include "language/language.h" #include "mapgen.h" #include "mixfile.h" #include "ownrdraw.h" From 1ffc9d3548787817bc20e0874d77d84078391f34 Mon Sep 17 00:00:00 2001 From: Marcel Bierling Date: Wed, 2 Sep 2026 20:52:22 +0200 Subject: [PATCH 015/179] Enable the Microsoft extensions the tree relies on --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1be27031f..6907d09d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,10 @@ elseif(MSVC) endif() elseif(OPENTS_EXPERIMENTAL_NATIVE) message(STATUS "OpenTS: configuring an unsupported native build for the host platform.") + + # The tree declares imports and exports with __declspec throughout, which clang only + # accepts with the Microsoft extensions enabled. + add_compile_options(-fms-extensions) else() message(FATAL_ERROR "OpenTS requires the Visual Studio 2022 MSVC toolchain. " From 75cf54cf5d44ce54294744e17b0e3989a34d7892 Mon Sep 17 00:00:00 2001 From: Marcel Bierling Date: Wed, 2 Sep 2026 20:52:22 +0200 Subject: [PATCH 016/179] Document the native build --- docs/BUILDING.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 01980ddc7..0e0116fff 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -93,6 +93,19 @@ The toolchain requires `clang-cl`, `lld-link`, `llvm-lib`, `llvm-mt`, `llvm-rc`, and `uasm` on `PATH`. It exports `compile_commands.json`; one configuration in `.vscode/c_cpp_properties.clang.example.json` reads that file for IntelliSense. +## Experimental native build + +An unsupported native build for the host platform is available for portability +work. It does not expand the supported build matrix or establish runtime +behavior. + +```bash +cmake -S . -B build/native -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DOPENTS_EXPERIMENTAL_NATIVE=ON +cmake --build build/native +``` + ## Build from Visual Studio Code With the recommended extensions installed, the repository provides: From 1506c52d6ae3fb8d443fc4d6c71b31706f1976fd Mon Sep 17 00:00:00 2001 From: Marcel Bierling Date: Thu, 3 Sep 2026 17:40:37 +0200 Subject: [PATCH 017/179] fixup! Include where size_t is used --- code/lcw.cpp | 3 ++- code/vqalib/cmp.h | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/lcw.cpp b/code/lcw.cpp index 7587a95e1..7a1a4d92a 100644 --- a/code/lcw.cpp +++ b/code/lcw.cpp @@ -31,10 +31,11 @@ * LCW_Uncomp -- Decompress an LCW encoded data block. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#include #include "always.h" #include "lcw.h" +#include + /// /// Decompresses an LCW encoded data block. diff --git a/code/vqalib/cmp.h b/code/vqalib/cmp.h index 9681b7456..740fcbdae 100644 --- a/code/vqalib/cmp.h +++ b/code/vqalib/cmp.h @@ -17,7 +17,6 @@ #pragma once #include - #include #if defined(__WATCOMC__) || defined(_MSC_VER) From 9ac30714af0748f2cb6a0b455a6ff1764dea8510 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Mon, 7 Sep 2026 11:18:04 +0200 Subject: [PATCH 018/179] Vendor the LZO 2.10 library and build it from thirdparty --- docs/BUILDING.md | 7 + thirdparty/CMakeLists.txt | 24 + thirdparty/licenses/lzo.txt | 349 +++ thirdparty/lzo/AUTHORS | 3 + thirdparty/lzo/COPYING | 339 +++ thirdparty/lzo/ChangeLog | 3 + thirdparty/lzo/NEWS | 294 ++ thirdparty/lzo/THANKS | 14 + thirdparty/lzo/include/lzo/lzo1x.h | 165 ++ thirdparty/lzo/include/lzo/lzoconf.h | 453 ++++ thirdparty/lzo/include/lzo/lzodefs.h | 3268 +++++++++++++++++++++++ thirdparty/lzo/src/config1x.h | 106 + thirdparty/lzo/src/lzo1_d.ch | 156 ++ thirdparty/lzo/src/lzo1x_1.c | 57 + thirdparty/lzo/src/lzo1x_c.ch | 403 +++ thirdparty/lzo/src/lzo1x_d.ch | 475 ++++ thirdparty/lzo/src/lzo1x_d2.c | 61 + thirdparty/lzo/src/lzo_conf.h | 436 +++ thirdparty/lzo/src/lzo_dict.h | 307 +++ thirdparty/lzo/src/lzo_dll.ch | 50 + thirdparty/lzo/src/lzo_func.h | 491 ++++ thirdparty/lzo/src/lzo_init.c | 239 ++ thirdparty/lzo/src/lzo_ptr.h | 123 + thirdparty/lzo/src/lzo_supp.h | 3678 ++++++++++++++++++++++++++ 24 files changed, 11501 insertions(+) create mode 100644 thirdparty/licenses/lzo.txt create mode 100644 thirdparty/lzo/AUTHORS create mode 100644 thirdparty/lzo/COPYING create mode 100644 thirdparty/lzo/ChangeLog create mode 100644 thirdparty/lzo/NEWS create mode 100644 thirdparty/lzo/THANKS create mode 100644 thirdparty/lzo/include/lzo/lzo1x.h create mode 100644 thirdparty/lzo/include/lzo/lzoconf.h create mode 100644 thirdparty/lzo/include/lzo/lzodefs.h create mode 100644 thirdparty/lzo/src/config1x.h create mode 100644 thirdparty/lzo/src/lzo1_d.ch create mode 100644 thirdparty/lzo/src/lzo1x_1.c create mode 100644 thirdparty/lzo/src/lzo1x_c.ch create mode 100644 thirdparty/lzo/src/lzo1x_d.ch create mode 100644 thirdparty/lzo/src/lzo1x_d2.c create mode 100644 thirdparty/lzo/src/lzo_conf.h create mode 100644 thirdparty/lzo/src/lzo_dict.h create mode 100644 thirdparty/lzo/src/lzo_dll.ch create mode 100644 thirdparty/lzo/src/lzo_func.h create mode 100644 thirdparty/lzo/src/lzo_init.c create mode 100644 thirdparty/lzo/src/lzo_ptr.h create mode 100644 thirdparty/lzo/src/lzo_supp.h diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 25c61988c..dc1517650 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -43,6 +43,13 @@ For a fresh clone, use `git clone --recurse-submodules`. Configuration stops with instructions if a submodule is missing. Update a pinned tag in a separate change. +Compression uses [LZO](https://www.oberhumer.com/opensource/lzo/) 2.10, +vendored under `thirdparty/lzo` and built by `thirdparty/CMakeLists.txt`. +Upstream publishes releases as a tarball rather than through a repository, so +this copy is checked in instead of pinned as a submodule. It holds only the +LZO1X-1 sources the engine calls; take a later release by extracting it over +the files already there, in a separate change. + ## Configure and build Run these commands from the repository root in PowerShell: diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index aab8357fd..a47cd2a3e 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -79,3 +79,27 @@ if(MSVC) # Match the engine's floating-point model so the resampler behaves the same in both. target_compile_options(miniaudio PRIVATE /arch:SSE2 /fp:precise) endif() + +# +# --------------------------------------------------------- +# LZO (LZO1X compression for saves, maps, and network blocks) +# --------------------------------------------------------- +# +# A vendored copy rather than a submodule. LZO is published only as a source tarball from +# oberhumer.com; the git copies that exist are unofficial mirrors, so there is no upstream +# repository a submodule could pin. Only the LZO1X-1 sources the engine calls are kept, +# which is every file the three it links can reach. +# +# The library is built here rather than through its own CMakeLists.txt, which asks for +# compatibility with CMake 3.0 and is refused outright by CMake 4, and which also builds +# the test drivers and install rules that the engine does not ship. +file(GLOB LZO_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/lzo/src/*.c") +add_library(lzo STATIC ${LZO_SOURCES}) + +# LZO reads a generated config.h only when LZO_HAVE_CONFIG_H is defined, and probes the +# target with its own preprocessor tests otherwise. Leaving it undefined keeps the +# vendored tree free of a configure step. +target_include_directories(lzo + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/lzo/include" + PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/lzo/src" +) diff --git a/thirdparty/licenses/lzo.txt b/thirdparty/licenses/lzo.txt new file mode 100644 index 000000000..b24f0d212 --- /dev/null +++ b/thirdparty/licenses/lzo.txt @@ -0,0 +1,349 @@ +LZO (thirdparty/lzo) + +Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer +All Rights Reserved. + +The LZO library 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 2 of the License, or (at your option) any +later version. The full text of that license follows. +------------------------------------------------------------------------------ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 2 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/thirdparty/lzo/AUTHORS b/thirdparty/lzo/AUTHORS new file mode 100644 index 000000000..d53d32cdf --- /dev/null +++ b/thirdparty/lzo/AUTHORS @@ -0,0 +1,3 @@ +Authors of the LZO data compression library: + +Markus F.X.J. Oberhumer. Invented, designed and implemented LZO. diff --git a/thirdparty/lzo/COPYING b/thirdparty/lzo/COPYING new file mode 100644 index 000000000..d159169d1 --- /dev/null +++ b/thirdparty/lzo/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 2 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/thirdparty/lzo/ChangeLog b/thirdparty/lzo/ChangeLog new file mode 100644 index 000000000..755db71e0 --- /dev/null +++ b/thirdparty/lzo/ChangeLog @@ -0,0 +1,3 @@ +Sorry, no detailed ChangeLog available yet. + +Please see the file NEWS for a list of user visible changes. diff --git a/thirdparty/lzo/NEWS b/thirdparty/lzo/NEWS new file mode 100644 index 000000000..4c2bbda09 --- /dev/null +++ b/thirdparty/lzo/NEWS @@ -0,0 +1,294 @@ +============================================================================ +User visible changes for LZO -- a real-time data compression library +============================================================================ + +Changes in 2.10 (01 Mar 2017) + * Improve CMake build support. + * Add support for pkg-config. + * Do not redefine "snprintf" so that the examples build with MSVC 2015. + * Assorted cleanups. + +Changes in 2.09 (04 Feb 2015) + * Work around gcc bug #64516 that could affect architectures like + armv4, armv5 and sparc. + +Changes in 2.08 (29 Jun 2014) + * Updated the Autoconf scripts to fix some reported build problems. + * Added CMake build support. + * Fixed lzo_init() on big-endian architectures like Sparc. + +Changes in 2.07 (25 Jun 2014) + * Fixed a potential integer overflow condition in the "safe" decompressor + variants which could result in a possible buffer overrun when + processing maliciously crafted compressed input data. + + Note that this issue only affects 32-bit systems and also can only happen + if you use uncommonly huge buffer sizes where you have to decompress more + than 16 MiB (> 2^24 bytes) untrusted compressed bytes within a + *single* function call, so the practical implications are limited. + + POTENTIAL SECURITY ISSUE. CVE-2014-4607. + + * Removed support for ancient configurations like 16-bit "huge" pointers - + LZO now requires a flat 32-bit or 64-bit memory model. + * Assorted cleanups. + +Changes in 2.06 (12 Aug 2011) + * Some minor optimizations for big-endian architectures. + * Fixed overly strict malloc() misalignment check in examples. + +Changes in 2.05 (23 Apr 2011) + * Converted the configure system to non-recursive Automake. + * Applied some overdue speed optimizations for modern x86/x64 architectures + and current compilers like gcc 4.6 and MSVC 2010. + +Changes in 2.04 (31 Oct 2010) + * Fixed a gcc-4.5 aliasing issue in lzo_init(). + * Updated the configure system. + * Assorted cleanups. + +Changes in 2.03 (30 Apr 2008) + * Updated the ELF assembler sources to mark the stack as non-executable. + * Fixed a HP-UX 11 build issue with Itanium in ILP32 mode. + * Updated the configure system. + +Changes in 2.02 (17 Oct 2005) + * Updated the build and Autoconf scripts to fix some reported + compilation problems. + +Changes in 2.01 (27 Jun 2005) + * Changed the configure system to install the LZO library under the + name "liblzo2" so that parallel installation with LZO v1 is possible. + * Improved auto-configuration in miniLZO for some embedded targets like + Blackfin and H8/300 processors. + +Changes in 2.00 (30 May 2005) + + [Library interface changes] + * The 'lzo_uint' typedef has been changed to match 'size_t', which means + it now is 64 bits on most 64-bit architectures. 32-bit machines + are not affected by this change. + * The formula for maximum expansion of incompressible data has changed. + See doc/LZO.FAQ. This is needed for some upcoming speed improvements, + and also for compatibility with our commercial LZO Professional product. + * The progress indicator callback interface has been revamped. + * All public header files now get installed into a "lzo" subdirectory, so + your applications should use #include . + * A number of (internal) macros have been renamed. See LZO_CFG_COMPAT + in if your code depends on these. + + [Speed] + * Small overall speedup by exploiting features like branch prediction + hints and explicit inline control present in modern C/C++ compilers. + * Significant speedup for 64-bit architectures like AMD64. + + [Portability] + * LZO now fully supports the LLP64 programming model. + * LZO now fully supports the ILP64 and SILP64 programming models which + are used on some supercomputing architectures. + * Full Win64 support for AMD64 (aka x64) and IA64 (Itanium). + * Full 16-bit support for ancient DOS 286 protected mode, OS/2 1.x + and Windows 3.x. + * The LZO library now compiles and works on completely freestanding or + embedded systems as long as you have and header + files. See the B/generic/build_freestanding.sh build script. + + [Misc] + * The i386 assembler versions of the decompressors are now automatically + built and installed. See also asm/i386/00README.TXT. + * Added include file that provides prototypes for all + assembler functions. + * Under MacOS X, the configure script now will use the '-no-cpp-precomp' + compiler option in order to work around bugs in some versions of + Apple's native "smart" preprocessor. + * Worked around a preprocessor bug that is present in all compilers which + are based on the lcc compiler kit. + * Added simple B/generic/build.sh build script family. + * Added lots of new build scripts for various DOS/Windows compilers. + + [Upgrade instructions from LZO v1 to LZO v2] + * On 64-bit architectures, revisit all uses of lzo_uint. + * Adapt for the maximum expansion change of incompressible data. + * If you use the progress callback then adapt for the new interface. + * Use #include or adjust your include path. + * Check your code for use of deprecated macros. Add a + #define LZO_CFG_COMPAT if necessary. + * Other than that LZO v2 should be fully source-compatible with LZO v1. + Of course, the compressed data is fully compatible as well. + * Re-compile and re-link your application. + * Enjoy the improvements! + +---------------------------------------------------------------------------- + +Changes in 1.08 (12 Jul 2002) + * Much better support for cross compiling. + * Straighten out ANSI-conforming compiler checks. + * Avoid harmless compiler warnings reported by -Wcast-align. + * Fixed some sign extension problems on rather exotic machines where + sizeof(size_t) < sizeof(ptrdiff_t) and sizeof(lzo_uint) == sizeof(size_t) + * Updated the configure system to use the latest Autoconf, Automake + and Libtool versions. + +Changes in 1.07 (18 Oct 2000) + * Default to '--disable-shared' (I'm getting tired of Libtool's + shared library build problems, this time AIX was the culprit). + * Avoid some harmless compiler warnings. + +Changes in 1.06 (29 Nov 1999) + * Updated the configure system to use Autoconf 2.13, Automake 1.4 and + Libtool 1.3.3. This should hopefully fix the shared-library build + problems that were reported on some machines. + * Enhanced example programs a little bit. + +Changes in 1.05 (14 Apr 1998) + * Just a one-line change in the configure script to workaround + a HPUX and IRIX build problem. + +Changes in 1.04 (15 Mar 1998) + * Worked around a bug in the cpp preprocessor under HPUX 10.20. + * Adapted for Automake 1.2f and Libtool 1.1. + +Changes in 1.03 (18 Jan 1998) + * minor compression ratio improvement + * extended example program to show how to do overlapping compression + * assembler changes, added support for the nasm assembler + * better support for cross compiling + * some cleanups + +Changes in 1.02 (07 Dec 1997) + * improved LZO1X-999 and LZO1Y-999 compression ratio a little bit again + * introduced compression levels for LZO1X-999 and LZO1Y-999 + * added support for preset dictionaries + * implemented LZO1X-1(12): needs 16 KiB for compression + * new algorithm LZO1Z: this is another variant of LZO1X + * added example program: how to use preset dictionaries + * added example program: how to do in-place decompression + * added a little file-packer example program + * LZO now works cleanly under checkergcc + * strict 16-bit memory model is working (but not officially supported) + * shared libraries are supported on many platforms + * adapted for Automake 1.2d and Libtool 1.0h + +Changes in 1.01 (10 Aug 1997) + * improved LZO1X-999 and LZO1Y-999 compression ratio a little bit + * i386+gcc: significant speedup of the C version of the LZO1, LZO1A, + LZO1B and LZO1C decompressors + * added example programs that show how to generate pre-compressed data + * added Makefiles for DOS, Windows and OS/2 targets + +Changes in 1.00 (13 Jul 1997) + * added miniLZO - can be easily included in your project + * improved documentation, added LZO.FAQ + * added build scripts for many systems where Autoconf is not available: + Windows 3.1 (LIB+DLL), Windows 95/NT (LIB+DLL), DOS (16+32 bit), OS/2 + * adapted for Automake 1.2 and Libtool 1.0 + +Changes in 0.90 (27 Jun 1997): never released + * LZO now uses GNU Automake 1.0 - lots of configuration changes + * added LZO1X-1(11): this version needs only 8 KiB for compression + * implemented LZO1Y-1 + * added i386 assembler decompressors for MASM/TASM/WASM + * the name of some assembler functions changed + * the numeric value of some error codes changed + * portability fixes + +Changes in 0.29 (04 May 1997) + * Linux ELF shared library support + * workaround for gcc 2.7.2 optimizer bug under AIX + * added lzo_crc32() checksum + +Changes in 0.28 (22 Feb 1997) + * new algorithm LZO1Y: LZO1Y-999 and LZO1Y decompressor + * added lzo1x_optimize() and lzo1y_optimize() + * minor speedup in assembler decompressors (i386+gcc) + * ltest.c rewritten + +Changes in 0.27 (19 Jan 1997) + * fixed a bug in LZO1B-999 and LZO1C-999 that could produce + invalid compressed data in very rare cases + +Changes in 0.26 (18 Jan 1997): never released + * implemented LZO1B-999 + * renamed LZO1D to LZO2A (also updated all docs) + * some cleanups + +Changes in 0.25 (28 Dec 1996): never released + * some portability fixes (LZO now works on my old Atari ST :-) + * adapted for Autoconf 2.12 + +Changes in 0.24 (30 Nov 1996): never released + * improved performance of LZO1X assembler decompressor on a Pentium (i386+gcc) + +Changes in 0.23 (23 Nov 1996) + * added LZO1C, LZO1F and LZO1X decompressors in assembler (i386+gcc) + * added corresponding LZO_PUBLIC to all LZO_EXTERN functions + * added support for Microsoft C 7.0 (16-bit DOS) + * introduced lzo_uint32. This could prove useful for a strict 16-bit + version that doesn't use 'huge' pointers. + * all algorithms use incremental hashing now + * some cleanups and portability fixes + +Changes in 0.22 (19 Sep 1996) + * LZO1X: minor decompressor speedup, added some checks in safe decompressor + * Autoconf: added detection of gcc strength-reduction bug + * Makefile changes + +Changes in 0.21 (08 Sep 1996) + * LZO now uses GNU Autoconf 2.10 - lots of configuration changes + * a few cosmetical changes + +Changes in 0.20 (11 Aug 1996) + * new algorithm LZO1X: LZO1X-1, LZO1X-999 and LZO1X decompressor + * significantly speeded up LZO1B, LZO1C and LZO1F decompressors + on CPUs which allow unaligned memory access (e.g. Intel i386) + * greatly speeded up LZO2A-999 compressor at the cost of some memory + * some cleanups, portability fixes and minor speedups + +Changes in 0.16 (22 Jul 1996) + * speeded up LZO1F decompressor a little bit + * improved LZO1F-999 compression ratio + +Changes in 0.15 (10 Jul 1996) + * new algorithm LZO1F: LZO1F-1, LZO1F-999 and LZO1F decompressor + * improved LZO2A-999 compression ratio + * removed LZO1E as it is dominated by LZO1F + +Changes in 0.14 (06 Jul 1996): never released + * experimental algorithms: LZO1E and LZO1F + * added LZO_EXTERN to all prototypes. Useful when building a DLL. + * improved LZO1C-999 and LZO2A-999 compression ratio a little bit + * fixed progress indicator callback (it was called only once) + +Changes in 0.13 (20 Jun 1996) + * some speed improvements in LZO1C-999 and LZO2A-999 + +Changes in 0.12 (18 Jun 1996): never released + * added LZO1C-999, a slow but nearly optimal compressor + intended for generating pre-compressed data + * added tests for lookbehind-overrun in all safe decompressors + * source tree completely rearranged, some filenames changed + * extensions changed: a .ch file is a C source code that is included + for reasons of code sharing + * new algorithm LZO2A: LZO2A-999 and LZO2A decompressor. There is + no fast compressor yet. + * some cleanups + +Changes in 0.11 (29 May 1996) + * source tree rearranged + * LZO now compiles fine as a C++ library (interface still has C linkage) + * improved overall compression ratio a little bit + * LZO1B-99/LZO1C-99 now search for longer matches + * incremental hash is working, it's a little bit faster + * Makefile changed + * added lzo_uint and lzo_sizeof in some places + * split LZO1B compressor into even more include-files + +Changes in 0.10 (20 May 1996): first public release of the LZO library + * includes LZO1, LZO1A, LZO1B and LZO1C algorithms + (compression levels 1-9 and 99) + +14 Mar 1996: + * public release of the LZO1A algorithm + +04 Mar 1996: + * public release of the LZO1 algorithm diff --git a/thirdparty/lzo/THANKS b/thirdparty/lzo/THANKS new file mode 100644 index 000000000..cf09bdf6b --- /dev/null +++ b/thirdparty/lzo/THANKS @@ -0,0 +1,14 @@ +I want to thank the following people for giving feedback, doing +beta-testing or helping me some other way: + +Charles W. Sandmann +Frank Donahoe +Holger Berger +Jean-loup Gailly +Laszlo Molnar +Mark Adler +Paul D. Eccles +Rodolphe Ortalo +William Magro +Wolfgang Lugmayr +Natascha diff --git a/thirdparty/lzo/include/lzo/lzo1x.h b/thirdparty/lzo/include/lzo/lzo1x.h new file mode 100644 index 000000000..a11151407 --- /dev/null +++ b/thirdparty/lzo/include/lzo/lzo1x.h @@ -0,0 +1,165 @@ +/* lzo1x.h -- public interface of the LZO1X compression algorithm + + This file is part of the LZO real-time data compression library. + + Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer + All Rights Reserved. + + The LZO library 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 2 of + the License, or (at your option) any later version. + + The LZO library 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 the LZO library; see the file COPYING. + If not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Markus F.X.J. Oberhumer + + http://www.oberhumer.com/opensource/lzo/ + */ + + +#ifndef __LZO1X_H_INCLUDED +#define __LZO1X_H_INCLUDED 1 + +#ifndef __LZOCONF_H_INCLUDED +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +/*********************************************************************** +// +************************************************************************/ + +/* Memory required for the wrkmem parameter. + * When the required size is 0, you can also pass a NULL pointer. + */ + +#define LZO1X_MEM_COMPRESS LZO1X_1_MEM_COMPRESS +#define LZO1X_MEM_DECOMPRESS (0) +#define LZO1X_MEM_OPTIMIZE (0) + + +/* decompression */ +LZO_EXTERN(int) +lzo1x_decompress ( const lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem /* NOT USED */ ); + +/* safe decompression with overrun testing */ +LZO_EXTERN(int) +lzo1x_decompress_safe ( const lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem /* NOT USED */ ); + + +/*********************************************************************** +// +************************************************************************/ + +#define LZO1X_1_MEM_COMPRESS ((lzo_uint32_t) (16384L * lzo_sizeof_dict_t)) + +LZO_EXTERN(int) +lzo1x_1_compress ( const lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem ); + + +/*********************************************************************** +// special compressor versions +************************************************************************/ + +/* this version needs only 8 KiB work memory */ +#define LZO1X_1_11_MEM_COMPRESS ((lzo_uint32_t) (2048L * lzo_sizeof_dict_t)) + +LZO_EXTERN(int) +lzo1x_1_11_compress ( const lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem ); + + +/* this version needs 16 KiB work memory */ +#define LZO1X_1_12_MEM_COMPRESS ((lzo_uint32_t) (4096L * lzo_sizeof_dict_t)) + +LZO_EXTERN(int) +lzo1x_1_12_compress ( const lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem ); + + +/* use this version if you need a little more compression speed */ +#define LZO1X_1_15_MEM_COMPRESS ((lzo_uint32_t) (32768L * lzo_sizeof_dict_t)) + +LZO_EXTERN(int) +lzo1x_1_15_compress ( const lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem ); + + +/*********************************************************************** +// better compression ratio at the cost of more memory and time +************************************************************************/ + +#define LZO1X_999_MEM_COMPRESS ((lzo_uint32_t) (14 * 16384L * sizeof(short))) + +LZO_EXTERN(int) +lzo1x_999_compress ( const lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem ); + + +/*********************************************************************** +// +************************************************************************/ + +LZO_EXTERN(int) +lzo1x_999_compress_dict ( const lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem, + const lzo_bytep dict, lzo_uint dict_len ); + +LZO_EXTERN(int) +lzo1x_999_compress_level ( const lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem, + const lzo_bytep dict, lzo_uint dict_len, + lzo_callback_p cb, + int compression_level ); + +LZO_EXTERN(int) +lzo1x_decompress_dict_safe ( const lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem /* NOT USED */, + const lzo_bytep dict, lzo_uint dict_len ); + + +/*********************************************************************** +// optimize a compressed data block +************************************************************************/ + +LZO_EXTERN(int) +lzo1x_optimize ( lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem /* NOT USED */ ); + + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* already included */ + + +/* vim:set ts=4 sw=4 et: */ diff --git a/thirdparty/lzo/include/lzo/lzoconf.h b/thirdparty/lzo/include/lzo/lzoconf.h new file mode 100644 index 000000000..f9a8bdbee --- /dev/null +++ b/thirdparty/lzo/include/lzo/lzoconf.h @@ -0,0 +1,453 @@ +/* lzoconf.h -- configuration of the LZO data compression library + + This file is part of the LZO real-time data compression library. + + Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer + All Rights Reserved. + + The LZO library 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 2 of + the License, or (at your option) any later version. + + The LZO library 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 the LZO library; see the file COPYING. + If not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Markus F.X.J. Oberhumer + + http://www.oberhumer.com/opensource/lzo/ + */ + + +#ifndef __LZOCONF_H_INCLUDED +#define __LZOCONF_H_INCLUDED 1 + +#define LZO_VERSION 0x20a0 /* 2.10 */ +#define LZO_VERSION_STRING "2.10" +#define LZO_VERSION_DATE "Mar 01 2017" + +/* internal Autoconf configuration file - only used when building LZO */ +#if defined(LZO_HAVE_CONFIG_H) +# include +#endif +#include +#include + + +/*********************************************************************** +// LZO requires a conforming +************************************************************************/ + +#if !defined(CHAR_BIT) || (CHAR_BIT != 8) +# error "invalid CHAR_BIT" +#endif +#if !defined(UCHAR_MAX) || !defined(USHRT_MAX) || !defined(UINT_MAX) || !defined(ULONG_MAX) +# error "check your compiler installation" +#endif +#if (USHRT_MAX < 1) || (UINT_MAX < 1) || (ULONG_MAX < 1) +# error "your limits.h macros are broken" +#endif + +/* get OS and architecture defines */ +#ifndef __LZODEFS_H_INCLUDED +#include +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + + +/*********************************************************************** +// some core defines +************************************************************************/ + +/* memory checkers */ +#if !defined(__LZO_CHECKER) +# if defined(__BOUNDS_CHECKING_ON) +# define __LZO_CHECKER 1 +# elif defined(__CHECKER__) +# define __LZO_CHECKER 1 +# elif defined(__INSURE__) +# define __LZO_CHECKER 1 +# elif defined(__PURIFY__) +# define __LZO_CHECKER 1 +# endif +#endif + + +/*********************************************************************** +// integral and pointer types +************************************************************************/ + +/* lzo_uint must match size_t */ +#if !defined(LZO_UINT_MAX) +# if (LZO_ABI_LLP64) +# if (LZO_OS_WIN64) + typedef unsigned __int64 lzo_uint; + typedef __int64 lzo_int; +# define LZO_TYPEOF_LZO_INT LZO_TYPEOF___INT64 +# else + typedef lzo_ullong_t lzo_uint; + typedef lzo_llong_t lzo_int; +# define LZO_TYPEOF_LZO_INT LZO_TYPEOF_LONG_LONG +# endif +# define LZO_SIZEOF_LZO_INT 8 +# define LZO_UINT_MAX 0xffffffffffffffffull +# define LZO_INT_MAX 9223372036854775807LL +# define LZO_INT_MIN (-1LL - LZO_INT_MAX) +# elif (LZO_ABI_IP32L64) /* MIPS R5900 */ + typedef unsigned int lzo_uint; + typedef int lzo_int; +# define LZO_SIZEOF_LZO_INT LZO_SIZEOF_INT +# define LZO_TYPEOF_LZO_INT LZO_TYPEOF_INT +# define LZO_UINT_MAX UINT_MAX +# define LZO_INT_MAX INT_MAX +# define LZO_INT_MIN INT_MIN +# elif (ULONG_MAX >= LZO_0xffffffffL) + typedef unsigned long lzo_uint; + typedef long lzo_int; +# define LZO_SIZEOF_LZO_INT LZO_SIZEOF_LONG +# define LZO_TYPEOF_LZO_INT LZO_TYPEOF_LONG +# define LZO_UINT_MAX ULONG_MAX +# define LZO_INT_MAX LONG_MAX +# define LZO_INT_MIN LONG_MIN +# else +# error "lzo_uint" +# endif +#endif + +/* The larger type of lzo_uint and lzo_uint32_t. */ +#if (LZO_SIZEOF_LZO_INT >= 4) +# define lzo_xint lzo_uint +#else +# define lzo_xint lzo_uint32_t +#endif + +typedef int lzo_bool; + +/* sanity checks */ +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int) == LZO_SIZEOF_LZO_INT) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_uint) == LZO_SIZEOF_LZO_INT) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_xint) >= sizeof(lzo_uint)) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_xint) >= sizeof(lzo_uint32_t)) + +#ifndef __LZO_MMODEL +#define __LZO_MMODEL /*empty*/ +#endif + +/* no typedef here because of const-pointer issues */ +#define lzo_bytep unsigned char __LZO_MMODEL * +#define lzo_charp char __LZO_MMODEL * +#define lzo_voidp void __LZO_MMODEL * +#define lzo_shortp short __LZO_MMODEL * +#define lzo_ushortp unsigned short __LZO_MMODEL * +#define lzo_intp lzo_int __LZO_MMODEL * +#define lzo_uintp lzo_uint __LZO_MMODEL * +#define lzo_xintp lzo_xint __LZO_MMODEL * +#define lzo_voidpp lzo_voidp __LZO_MMODEL * +#define lzo_bytepp lzo_bytep __LZO_MMODEL * + +#define lzo_int8_tp lzo_int8_t __LZO_MMODEL * +#define lzo_uint8_tp lzo_uint8_t __LZO_MMODEL * +#define lzo_int16_tp lzo_int16_t __LZO_MMODEL * +#define lzo_uint16_tp lzo_uint16_t __LZO_MMODEL * +#define lzo_int32_tp lzo_int32_t __LZO_MMODEL * +#define lzo_uint32_tp lzo_uint32_t __LZO_MMODEL * +#if defined(lzo_int64_t) +#define lzo_int64_tp lzo_int64_t __LZO_MMODEL * +#define lzo_uint64_tp lzo_uint64_t __LZO_MMODEL * +#endif + +/* Older LZO versions used to support ancient systems and memory models + * such as 16-bit MSDOS with __huge pointers or Cray PVP, but these + * obsolete configurations are not supported any longer. + */ +#if defined(__LZO_MMODEL_HUGE) +#error "__LZO_MMODEL_HUGE memory model is unsupported" +#endif +#if (LZO_MM_PVP) +#error "LZO_MM_PVP memory model is unsupported" +#endif +#if (LZO_SIZEOF_INT < 4) +#error "LZO_SIZEOF_INT < 4 is unsupported" +#endif +#if (__LZO_UINTPTR_T_IS_POINTER) +#error "__LZO_UINTPTR_T_IS_POINTER is unsupported" +#endif +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(int) >= 4) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_uint) >= 4) +/* Strange configurations where sizeof(lzo_uint) != sizeof(size_t) should + * work but have not received much testing lately, so be strict here. + */ +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_uint) == sizeof(size_t)) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_uint) == sizeof(ptrdiff_t)) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_uint) == sizeof(lzo_uintptr_t)) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(void *) == sizeof(lzo_uintptr_t)) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(char *) == sizeof(lzo_uintptr_t)) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(long *) == sizeof(lzo_uintptr_t)) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(void *) == sizeof(lzo_voidp)) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(char *) == sizeof(lzo_bytep)) + + +/*********************************************************************** +// function types +************************************************************************/ + +/* name mangling */ +#if !defined(__LZO_EXTERN_C) +# ifdef __cplusplus +# define __LZO_EXTERN_C extern "C" +# else +# define __LZO_EXTERN_C extern +# endif +#endif + +/* calling convention */ +#if !defined(__LZO_CDECL) +# define __LZO_CDECL __lzo_cdecl +#endif + +/* DLL export information */ +#if !defined(__LZO_EXPORT1) +# define __LZO_EXPORT1 /*empty*/ +#endif +#if !defined(__LZO_EXPORT2) +# define __LZO_EXPORT2 /*empty*/ +#endif + +/* __cdecl calling convention for public C and assembly functions */ +#if !defined(LZO_PUBLIC) +# define LZO_PUBLIC(r) __LZO_EXPORT1 r __LZO_EXPORT2 __LZO_CDECL +#endif +#if !defined(LZO_EXTERN) +# define LZO_EXTERN(r) __LZO_EXTERN_C LZO_PUBLIC(r) +#endif +#if !defined(LZO_PRIVATE) +# define LZO_PRIVATE(r) static r __LZO_CDECL +#endif + +/* function types */ +typedef int +(__LZO_CDECL *lzo_compress_t) ( const lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem ); + +typedef int +(__LZO_CDECL *lzo_decompress_t) ( const lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem ); + +typedef int +(__LZO_CDECL *lzo_optimize_t) ( lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem ); + +typedef int +(__LZO_CDECL *lzo_compress_dict_t)(const lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem, + const lzo_bytep dict, lzo_uint dict_len ); + +typedef int +(__LZO_CDECL *lzo_decompress_dict_t)(const lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem, + const lzo_bytep dict, lzo_uint dict_len ); + + +/* Callback interface. Currently only the progress indicator ("nprogress") + * is used, but this may change in a future release. */ + +struct lzo_callback_t; +typedef struct lzo_callback_t lzo_callback_t; +#define lzo_callback_p lzo_callback_t __LZO_MMODEL * + +/* malloc & free function types */ +typedef lzo_voidp (__LZO_CDECL *lzo_alloc_func_t) + (lzo_callback_p self, lzo_uint items, lzo_uint size); +typedef void (__LZO_CDECL *lzo_free_func_t) + (lzo_callback_p self, lzo_voidp ptr); + +/* a progress indicator callback function */ +typedef void (__LZO_CDECL *lzo_progress_func_t) + (lzo_callback_p, lzo_uint, lzo_uint, int); + +struct lzo_callback_t +{ + /* custom allocators (set to 0 to disable) */ + lzo_alloc_func_t nalloc; /* [not used right now] */ + lzo_free_func_t nfree; /* [not used right now] */ + + /* a progress indicator callback function (set to 0 to disable) */ + lzo_progress_func_t nprogress; + + /* INFO: the first parameter "self" of the nalloc/nfree/nprogress + * callbacks points back to this struct, so you are free to store + * some extra info in the following variables. */ + lzo_voidp user1; + lzo_xint user2; + lzo_xint user3; +}; + + +/*********************************************************************** +// error codes and prototypes +************************************************************************/ + +/* Error codes for the compression/decompression functions. Negative + * values are errors, positive values will be used for special but + * normal events. + */ +#define LZO_E_OK 0 +#define LZO_E_ERROR (-1) +#define LZO_E_OUT_OF_MEMORY (-2) /* [lzo_alloc_func_t failure] */ +#define LZO_E_NOT_COMPRESSIBLE (-3) /* [not used right now] */ +#define LZO_E_INPUT_OVERRUN (-4) +#define LZO_E_OUTPUT_OVERRUN (-5) +#define LZO_E_LOOKBEHIND_OVERRUN (-6) +#define LZO_E_EOF_NOT_FOUND (-7) +#define LZO_E_INPUT_NOT_CONSUMED (-8) +#define LZO_E_NOT_YET_IMPLEMENTED (-9) /* [not used right now] */ +#define LZO_E_INVALID_ARGUMENT (-10) +#define LZO_E_INVALID_ALIGNMENT (-11) /* pointer argument is not properly aligned */ +#define LZO_E_OUTPUT_NOT_CONSUMED (-12) +#define LZO_E_INTERNAL_ERROR (-99) + + +#ifndef lzo_sizeof_dict_t +# define lzo_sizeof_dict_t ((unsigned)sizeof(lzo_bytep)) +#endif + +/* lzo_init() should be the first function you call. + * Check the return code ! + * + * lzo_init() is a macro to allow checking that the library and the + * compiler's view of various types are consistent. + */ +#define lzo_init() __lzo_init_v2(LZO_VERSION,(int)sizeof(short),(int)sizeof(int),\ + (int)sizeof(long),(int)sizeof(lzo_uint32_t),(int)sizeof(lzo_uint),\ + (int)lzo_sizeof_dict_t,(int)sizeof(char *),(int)sizeof(lzo_voidp),\ + (int)sizeof(lzo_callback_t)) +LZO_EXTERN(int) __lzo_init_v2(unsigned,int,int,int,int,int,int,int,int,int); + +/* version functions (useful for shared libraries) */ +LZO_EXTERN(unsigned) lzo_version(void); +LZO_EXTERN(const char *) lzo_version_string(void); +LZO_EXTERN(const char *) lzo_version_date(void); +LZO_EXTERN(const lzo_charp) _lzo_version_string(void); +LZO_EXTERN(const lzo_charp) _lzo_version_date(void); + +/* string functions */ +LZO_EXTERN(int) + lzo_memcmp(const lzo_voidp a, const lzo_voidp b, lzo_uint len); +LZO_EXTERN(lzo_voidp) + lzo_memcpy(lzo_voidp dst, const lzo_voidp src, lzo_uint len); +LZO_EXTERN(lzo_voidp) + lzo_memmove(lzo_voidp dst, const lzo_voidp src, lzo_uint len); +LZO_EXTERN(lzo_voidp) + lzo_memset(lzo_voidp buf, int c, lzo_uint len); + +/* checksum functions */ +LZO_EXTERN(lzo_uint32_t) + lzo_adler32(lzo_uint32_t c, const lzo_bytep buf, lzo_uint len); +LZO_EXTERN(lzo_uint32_t) + lzo_crc32(lzo_uint32_t c, const lzo_bytep buf, lzo_uint len); +LZO_EXTERN(const lzo_uint32_tp) + lzo_get_crc32_table(void); + +/* misc. */ +LZO_EXTERN(int) _lzo_config_check(void); +typedef union { + lzo_voidp a00; lzo_bytep a01; lzo_uint a02; lzo_xint a03; lzo_uintptr_t a04; + void *a05; unsigned char *a06; unsigned long a07; size_t a08; ptrdiff_t a09; +#if defined(lzo_int64_t) + lzo_uint64_t a10; +#endif +} lzo_align_t; + +/* align a char pointer on a boundary that is a multiple of 'size' */ +LZO_EXTERN(unsigned) __lzo_align_gap(const lzo_voidp p, lzo_uint size); +#define LZO_PTR_ALIGN_UP(p,size) \ + ((p) + (lzo_uint) __lzo_align_gap((const lzo_voidp)(p),(lzo_uint)(size))) + + +/*********************************************************************** +// deprecated macros - only for backward compatibility +************************************************************************/ + +/* deprecated - use 'lzo_bytep' instead of 'lzo_byte *' */ +#define lzo_byte unsigned char +/* deprecated type names */ +#define lzo_int32 lzo_int32_t +#define lzo_uint32 lzo_uint32_t +#define lzo_int32p lzo_int32_t __LZO_MMODEL * +#define lzo_uint32p lzo_uint32_t __LZO_MMODEL * +#define LZO_INT32_MAX LZO_INT32_C(2147483647) +#define LZO_UINT32_MAX LZO_UINT32_C(4294967295) +#if defined(lzo_int64_t) +#define lzo_int64 lzo_int64_t +#define lzo_uint64 lzo_uint64_t +#define lzo_int64p lzo_int64_t __LZO_MMODEL * +#define lzo_uint64p lzo_uint64_t __LZO_MMODEL * +#define LZO_INT64_MAX LZO_INT64_C(9223372036854775807) +#define LZO_UINT64_MAX LZO_UINT64_C(18446744073709551615) +#endif +/* deprecated types */ +typedef union { lzo_bytep a; lzo_uint b; } __lzo_pu_u; +typedef union { lzo_bytep a; lzo_uint32_t b; } __lzo_pu32_u; +/* deprecated defines */ +#if !defined(LZO_SIZEOF_LZO_UINT) +# define LZO_SIZEOF_LZO_UINT LZO_SIZEOF_LZO_INT +#endif + +#if defined(LZO_CFG_COMPAT) + +#define __LZOCONF_H 1 + +#if defined(LZO_ARCH_I086) +# define __LZO_i386 1 +#elif defined(LZO_ARCH_I386) +# define __LZO_i386 1 +#endif + +#if defined(LZO_OS_DOS16) +# define __LZO_DOS 1 +# define __LZO_DOS16 1 +#elif defined(LZO_OS_DOS32) +# define __LZO_DOS 1 +#elif defined(LZO_OS_WIN16) +# define __LZO_WIN 1 +# define __LZO_WIN16 1 +#elif defined(LZO_OS_WIN32) +# define __LZO_WIN 1 +#endif + +#define __LZO_CMODEL /*empty*/ +#define __LZO_DMODEL /*empty*/ +#define __LZO_ENTRY __LZO_CDECL +#define LZO_EXTERN_CDECL LZO_EXTERN +#define LZO_ALIGN LZO_PTR_ALIGN_UP + +#define lzo_compress_asm_t lzo_compress_t +#define lzo_decompress_asm_t lzo_decompress_t + +#endif /* LZO_CFG_COMPAT */ + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* already included */ + + +/* vim:set ts=4 sw=4 et: */ diff --git a/thirdparty/lzo/include/lzo/lzodefs.h b/thirdparty/lzo/include/lzo/lzodefs.h new file mode 100644 index 000000000..c3e2bcf5d --- /dev/null +++ b/thirdparty/lzo/include/lzo/lzodefs.h @@ -0,0 +1,3268 @@ +/* lzodefs.h -- architecture, OS and compiler specific defines + + This file is part of the LZO real-time data compression library. + + Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer + All Rights Reserved. + + The LZO library 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 2 of + the License, or (at your option) any later version. + + The LZO library 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 the LZO library; see the file COPYING. + If not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Markus F.X.J. Oberhumer + + http://www.oberhumer.com/opensource/lzo/ + */ + + +#ifndef __LZODEFS_H_INCLUDED +#define __LZODEFS_H_INCLUDED 1 + +#if defined(__CYGWIN32__) && !defined(__CYGWIN__) +# define __CYGWIN__ __CYGWIN32__ +#endif +#if 1 && defined(__INTERIX) && defined(__GNUC__) && !defined(_ALL_SOURCE) +# define _ALL_SOURCE 1 +#endif +#if defined(__mips__) && defined(__R5900__) +# if !defined(__LONG_MAX__) +# define __LONG_MAX__ 9223372036854775807L +# endif +#endif +#if 0 +#elif !defined(__LZO_LANG_OVERRIDE) +#if (defined(__clang__) || defined(__GNUC__)) && defined(__ASSEMBLER__) +# if (__ASSEMBLER__+0) <= 0 +# error "__ASSEMBLER__" +# else +# define LZO_LANG_ASSEMBLER 1 +# endif +#elif defined(__cplusplus) +# if (__cplusplus+0) <= 0 +# error "__cplusplus" +# elif (__cplusplus < 199711L) +# define LZO_LANG_CXX 1 +# elif defined(_MSC_VER) && defined(_MSVC_LANG) && (_MSVC_LANG+0 >= 201402L) && 1 +# define LZO_LANG_CXX _MSVC_LANG +# else +# define LZO_LANG_CXX __cplusplus +# endif +# define LZO_LANG_CPLUSPLUS LZO_LANG_CXX +#else +# if defined(__STDC_VERSION__) && (__STDC_VERSION__+0 >= 199409L) +# define LZO_LANG_C __STDC_VERSION__ +# else +# define LZO_LANG_C 1 +# endif +#endif +#endif +#if !defined(LZO_CFG_NO_DISABLE_WUNDEF) +#if defined(__ARMCC_VERSION) +# pragma diag_suppress 193 +#elif defined(__clang__) && defined(__clang_minor__) +# pragma clang diagnostic ignored "-Wundef" +#elif defined(__INTEL_COMPILER) +# pragma warning(disable: 193) +#elif defined(__KEIL__) && defined(__C166__) +# pragma warning disable = 322 +#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && !defined(__PATHSCALE__) +# if ((__GNUC__-0) >= 5 || ((__GNUC__-0) == 4 && (__GNUC_MINOR__-0) >= 2)) +# pragma GCC diagnostic ignored "-Wundef" +# endif +#elif defined(_MSC_VER) && !defined(__clang__) && !defined(__INTEL_COMPILER) && !defined(__MWERKS__) +# if ((_MSC_VER-0) >= 1300) +# pragma warning(disable: 4668) +# endif +#endif +#endif +#if 0 && defined(__POCC__) && defined(_WIN32) +# if (__POCC__ >= 400) +# pragma warn(disable: 2216) +# endif +#endif +#if 0 && defined(__WATCOMC__) +# if (__WATCOMC__ >= 1050) && (__WATCOMC__ < 1060) +# pragma warning 203 9 +# endif +#endif +#if defined(__BORLANDC__) && defined(__MSDOS__) && !defined(__FLAT__) +# pragma option -h +#endif +#if !(LZO_CFG_NO_DISABLE_WCRTNONSTDC) +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE 1 +#endif +#ifndef _CRT_NONSTDC_NO_WARNINGS +#define _CRT_NONSTDC_NO_WARNINGS 1 +#endif +#ifndef _CRT_SECURE_NO_DEPRECATE +#define _CRT_SECURE_NO_DEPRECATE 1 +#endif +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS 1 +#endif +#endif +#if 0 +#define LZO_0xffffUL 0xfffful +#define LZO_0xffffffffUL 0xfffffffful +#else +#define LZO_0xffffUL 65535ul +#define LZO_0xffffffffUL 4294967295ul +#endif +#define LZO_0xffffL LZO_0xffffUL +#define LZO_0xffffffffL LZO_0xffffffffUL +#if (LZO_0xffffL == LZO_0xffffffffL) +# error "your preprocessor is broken 1" +#endif +#if (16ul * 16384ul != 262144ul) +# error "your preprocessor is broken 2" +#endif +#if 0 +#if (32767 >= 4294967295ul) +# error "your preprocessor is broken 3" +#endif +#if (65535u >= 4294967295ul) +# error "your preprocessor is broken 4" +#endif +#endif +#if defined(__COUNTER__) +# ifndef LZO_CFG_USE_COUNTER +# define LZO_CFG_USE_COUNTER 1 +# endif +#else +# undef LZO_CFG_USE_COUNTER +#endif +#if (UINT_MAX == LZO_0xffffL) +#if defined(__ZTC__) && defined(__I86__) && !defined(__OS2__) +# if !defined(MSDOS) +# define MSDOS 1 +# endif +# if !defined(_MSDOS) +# define _MSDOS 1 +# endif +#elif 0 && defined(__VERSION) && defined(MB_LEN_MAX) +# if (__VERSION == 520) && (MB_LEN_MAX == 1) +# if !defined(__AZTEC_C__) +# define __AZTEC_C__ __VERSION +# endif +# if !defined(__DOS__) +# define __DOS__ 1 +# endif +# endif +#endif +#endif +#if (UINT_MAX == LZO_0xffffL) +#if defined(_MSC_VER) && defined(M_I86HM) +# define ptrdiff_t long +# define _PTRDIFF_T_DEFINED 1 +#endif +#endif +#if (UINT_MAX == LZO_0xffffL) +# undef __LZO_RENAME_A +# undef __LZO_RENAME_B +# if defined(__AZTEC_C__) && defined(__DOS__) +# define __LZO_RENAME_A 1 +# elif defined(_MSC_VER) && defined(MSDOS) +# if (_MSC_VER < 600) +# define __LZO_RENAME_A 1 +# elif (_MSC_VER < 700) +# define __LZO_RENAME_B 1 +# endif +# elif defined(__TSC__) && defined(__OS2__) +# define __LZO_RENAME_A 1 +# elif defined(__MSDOS__) && defined(__TURBOC__) && (__TURBOC__ < 0x0410) +# define __LZO_RENAME_A 1 +# elif defined(__PACIFIC__) && defined(DOS) +# if !defined(__far) +# define __far far +# endif +# if !defined(__near) +# define __near near +# endif +# endif +# if defined(__LZO_RENAME_A) +# if !defined(__cdecl) +# define __cdecl cdecl +# endif +# if !defined(__far) +# define __far far +# endif +# if !defined(__huge) +# define __huge huge +# endif +# if !defined(__near) +# define __near near +# endif +# if !defined(__pascal) +# define __pascal pascal +# endif +# if !defined(__huge) +# define __huge huge +# endif +# elif defined(__LZO_RENAME_B) +# if !defined(__cdecl) +# define __cdecl _cdecl +# endif +# if !defined(__far) +# define __far _far +# endif +# if !defined(__huge) +# define __huge _huge +# endif +# if !defined(__near) +# define __near _near +# endif +# if !defined(__pascal) +# define __pascal _pascal +# endif +# elif (defined(__PUREC__) || defined(__TURBOC__)) && defined(__TOS__) +# if !defined(__cdecl) +# define __cdecl cdecl +# endif +# if !defined(__pascal) +# define __pascal pascal +# endif +# endif +# undef __LZO_RENAME_A +# undef __LZO_RENAME_B +#endif +#if (UINT_MAX == LZO_0xffffL) +#if defined(__AZTEC_C__) && defined(__DOS__) +# define LZO_BROKEN_CDECL_ALT_SYNTAX 1 +#elif defined(_MSC_VER) && defined(MSDOS) +# if (_MSC_VER < 600) +# define LZO_BROKEN_INTEGRAL_CONSTANTS 1 +# endif +# if (_MSC_VER < 700) +# define LZO_BROKEN_INTEGRAL_PROMOTION 1 +# define LZO_BROKEN_SIZEOF 1 +# endif +#elif defined(__PACIFIC__) && defined(DOS) +# define LZO_BROKEN_INTEGRAL_CONSTANTS 1 +#elif defined(__TURBOC__) && defined(__MSDOS__) +# if (__TURBOC__ < 0x0150) +# define LZO_BROKEN_CDECL_ALT_SYNTAX 1 +# define LZO_BROKEN_INTEGRAL_CONSTANTS 1 +# define LZO_BROKEN_INTEGRAL_PROMOTION 1 +# endif +# if (__TURBOC__ < 0x0200) +# define LZO_BROKEN_SIZEOF 1 +# endif +# if (__TURBOC__ < 0x0400) && defined(__cplusplus) +# define LZO_BROKEN_CDECL_ALT_SYNTAX 1 +# endif +#elif (defined(__PUREC__) || defined(__TURBOC__)) && defined(__TOS__) +# define LZO_BROKEN_CDECL_ALT_SYNTAX 1 +# define LZO_BROKEN_SIZEOF 1 +#endif +#endif +#if defined(__WATCOMC__) && (__WATCOMC__ < 900) +# define LZO_BROKEN_INTEGRAL_CONSTANTS 1 +#endif +#if defined(_CRAY) && defined(_CRAY1) +# define LZO_BROKEN_SIGNED_RIGHT_SHIFT 1 +#endif +#define LZO_PP_STRINGIZE(x) #x +#define LZO_PP_MACRO_EXPAND(x) LZO_PP_STRINGIZE(x) +#define LZO_PP_CONCAT0() /*empty*/ +#define LZO_PP_CONCAT1(a) a +#define LZO_PP_CONCAT2(a,b) a ## b +#define LZO_PP_CONCAT3(a,b,c) a ## b ## c +#define LZO_PP_CONCAT4(a,b,c,d) a ## b ## c ## d +#define LZO_PP_CONCAT5(a,b,c,d,e) a ## b ## c ## d ## e +#define LZO_PP_CONCAT6(a,b,c,d,e,f) a ## b ## c ## d ## e ## f +#define LZO_PP_CONCAT7(a,b,c,d,e,f,g) a ## b ## c ## d ## e ## f ## g +#define LZO_PP_ECONCAT0() LZO_PP_CONCAT0() +#define LZO_PP_ECONCAT1(a) LZO_PP_CONCAT1(a) +#define LZO_PP_ECONCAT2(a,b) LZO_PP_CONCAT2(a,b) +#define LZO_PP_ECONCAT3(a,b,c) LZO_PP_CONCAT3(a,b,c) +#define LZO_PP_ECONCAT4(a,b,c,d) LZO_PP_CONCAT4(a,b,c,d) +#define LZO_PP_ECONCAT5(a,b,c,d,e) LZO_PP_CONCAT5(a,b,c,d,e) +#define LZO_PP_ECONCAT6(a,b,c,d,e,f) LZO_PP_CONCAT6(a,b,c,d,e,f) +#define LZO_PP_ECONCAT7(a,b,c,d,e,f,g) LZO_PP_CONCAT7(a,b,c,d,e,f,g) +#define LZO_PP_EMPTY /*empty*/ +#define LZO_PP_EMPTY0() /*empty*/ +#define LZO_PP_EMPTY1(a) /*empty*/ +#define LZO_PP_EMPTY2(a,b) /*empty*/ +#define LZO_PP_EMPTY3(a,b,c) /*empty*/ +#define LZO_PP_EMPTY4(a,b,c,d) /*empty*/ +#define LZO_PP_EMPTY5(a,b,c,d,e) /*empty*/ +#define LZO_PP_EMPTY6(a,b,c,d,e,f) /*empty*/ +#define LZO_PP_EMPTY7(a,b,c,d,e,f,g) /*empty*/ +#if 1 +#define LZO_CPP_STRINGIZE(x) #x +#define LZO_CPP_MACRO_EXPAND(x) LZO_CPP_STRINGIZE(x) +#define LZO_CPP_CONCAT2(a,b) a ## b +#define LZO_CPP_CONCAT3(a,b,c) a ## b ## c +#define LZO_CPP_CONCAT4(a,b,c,d) a ## b ## c ## d +#define LZO_CPP_CONCAT5(a,b,c,d,e) a ## b ## c ## d ## e +#define LZO_CPP_CONCAT6(a,b,c,d,e,f) a ## b ## c ## d ## e ## f +#define LZO_CPP_CONCAT7(a,b,c,d,e,f,g) a ## b ## c ## d ## e ## f ## g +#define LZO_CPP_ECONCAT2(a,b) LZO_CPP_CONCAT2(a,b) +#define LZO_CPP_ECONCAT3(a,b,c) LZO_CPP_CONCAT3(a,b,c) +#define LZO_CPP_ECONCAT4(a,b,c,d) LZO_CPP_CONCAT4(a,b,c,d) +#define LZO_CPP_ECONCAT5(a,b,c,d,e) LZO_CPP_CONCAT5(a,b,c,d,e) +#define LZO_CPP_ECONCAT6(a,b,c,d,e,f) LZO_CPP_CONCAT6(a,b,c,d,e,f) +#define LZO_CPP_ECONCAT7(a,b,c,d,e,f,g) LZO_CPP_CONCAT7(a,b,c,d,e,f,g) +#endif +#define __LZO_MASK_GEN(o,b) (((((o) << ((b)-((b)!=0))) - (o)) << 1) + (o)*((b)!=0)) +#if 1 && defined(__cplusplus) +# if !defined(__STDC_CONSTANT_MACROS) +# define __STDC_CONSTANT_MACROS 1 +# endif +# if !defined(__STDC_LIMIT_MACROS) +# define __STDC_LIMIT_MACROS 1 +# endif +#endif +#if defined(__cplusplus) +# define LZO_EXTERN_C extern "C" +# define LZO_EXTERN_C_BEGIN extern "C" { +# define LZO_EXTERN_C_END } +#else +# define LZO_EXTERN_C extern +# define LZO_EXTERN_C_BEGIN /*empty*/ +# define LZO_EXTERN_C_END /*empty*/ +#endif +#if !defined(__LZO_OS_OVERRIDE) +#if (LZO_OS_FREESTANDING) +# define LZO_INFO_OS "freestanding" +#elif (LZO_OS_EMBEDDED) +# define LZO_INFO_OS "embedded" +#elif 1 && defined(__IAR_SYSTEMS_ICC__) +# define LZO_OS_EMBEDDED 1 +# define LZO_INFO_OS "embedded" +#elif defined(__CYGWIN__) && defined(__GNUC__) +# define LZO_OS_CYGWIN 1 +# define LZO_INFO_OS "cygwin" +#elif defined(__EMX__) && defined(__GNUC__) +# define LZO_OS_EMX 1 +# define LZO_INFO_OS "emx" +#elif defined(__BEOS__) +# define LZO_OS_BEOS 1 +# define LZO_INFO_OS "beos" +#elif defined(__Lynx__) +# define LZO_OS_LYNXOS 1 +# define LZO_INFO_OS "lynxos" +#elif defined(__OS400__) +# define LZO_OS_OS400 1 +# define LZO_INFO_OS "os400" +#elif defined(__QNX__) +# define LZO_OS_QNX 1 +# define LZO_INFO_OS "qnx" +#elif defined(__BORLANDC__) && defined(__DPMI32__) && (__BORLANDC__ >= 0x0460) +# define LZO_OS_DOS32 1 +# define LZO_INFO_OS "dos32" +#elif defined(__BORLANDC__) && defined(__DPMI16__) +# define LZO_OS_DOS16 1 +# define LZO_INFO_OS "dos16" +#elif defined(__ZTC__) && defined(DOS386) +# define LZO_OS_DOS32 1 +# define LZO_INFO_OS "dos32" +#elif defined(__OS2__) || defined(__OS2V2__) +# if (UINT_MAX == LZO_0xffffL) +# define LZO_OS_OS216 1 +# define LZO_INFO_OS "os216" +# elif (UINT_MAX == LZO_0xffffffffL) +# define LZO_OS_OS2 1 +# define LZO_INFO_OS "os2" +# else +# error "check your limits.h header" +# endif +#elif defined(__WIN64__) || defined(_WIN64) || defined(WIN64) +# define LZO_OS_WIN64 1 +# define LZO_INFO_OS "win64" +#elif defined(__WIN32__) || defined(_WIN32) || defined(WIN32) || defined(__WINDOWS_386__) +# define LZO_OS_WIN32 1 +# define LZO_INFO_OS "win32" +#elif defined(__MWERKS__) && defined(__INTEL__) +# define LZO_OS_WIN32 1 +# define LZO_INFO_OS "win32" +#elif defined(__WINDOWS__) || defined(_WINDOWS) || defined(_Windows) +# if (UINT_MAX == LZO_0xffffL) +# define LZO_OS_WIN16 1 +# define LZO_INFO_OS "win16" +# elif (UINT_MAX == LZO_0xffffffffL) +# define LZO_OS_WIN32 1 +# define LZO_INFO_OS "win32" +# else +# error "check your limits.h header" +# endif +#elif defined(__DOS__) || defined(__MSDOS__) || defined(_MSDOS) || defined(MSDOS) || (defined(__PACIFIC__) && defined(DOS)) +# if (UINT_MAX == LZO_0xffffL) +# define LZO_OS_DOS16 1 +# define LZO_INFO_OS "dos16" +# elif (UINT_MAX == LZO_0xffffffffL) +# define LZO_OS_DOS32 1 +# define LZO_INFO_OS "dos32" +# else +# error "check your limits.h header" +# endif +#elif defined(__WATCOMC__) +# if defined(__NT__) && (UINT_MAX == LZO_0xffffL) +# define LZO_OS_DOS16 1 +# define LZO_INFO_OS "dos16" +# elif defined(__NT__) && (__WATCOMC__ < 1100) +# define LZO_OS_WIN32 1 +# define LZO_INFO_OS "win32" +# elif defined(__linux__) || defined(__LINUX__) +# define LZO_OS_POSIX 1 +# define LZO_INFO_OS "posix" +# else +# error "please specify a target using the -bt compiler option" +# endif +#elif defined(__palmos__) +# define LZO_OS_PALMOS 1 +# define LZO_INFO_OS "palmos" +#elif defined(__TOS__) || defined(__atarist__) +# define LZO_OS_TOS 1 +# define LZO_INFO_OS "tos" +#elif defined(macintosh) && !defined(__arm__) && !defined(__i386__) && !defined(__ppc__) && !defined(__x64_64__) +# define LZO_OS_MACCLASSIC 1 +# define LZO_INFO_OS "macclassic" +#elif defined(__VMS) +# define LZO_OS_VMS 1 +# define LZO_INFO_OS "vms" +#elif (defined(__mips__) && defined(__R5900__)) || defined(__MIPS_PSX2__) +# define LZO_OS_CONSOLE 1 +# define LZO_OS_CONSOLE_PS2 1 +# define LZO_INFO_OS "console" +# define LZO_INFO_OS_CONSOLE "ps2" +#elif defined(__mips__) && defined(__psp__) +# define LZO_OS_CONSOLE 1 +# define LZO_OS_CONSOLE_PSP 1 +# define LZO_INFO_OS "console" +# define LZO_INFO_OS_CONSOLE "psp" +#else +# define LZO_OS_POSIX 1 +# define LZO_INFO_OS "posix" +#endif +#if (LZO_OS_POSIX) +# if defined(_AIX) || defined(__AIX__) || defined(__aix__) +# define LZO_OS_POSIX_AIX 1 +# define LZO_INFO_OS_POSIX "aix" +# elif defined(__FreeBSD__) +# define LZO_OS_POSIX_FREEBSD 1 +# define LZO_INFO_OS_POSIX "freebsd" +# elif defined(__hpux__) || defined(__hpux) +# define LZO_OS_POSIX_HPUX 1 +# define LZO_INFO_OS_POSIX "hpux" +# elif defined(__INTERIX) +# define LZO_OS_POSIX_INTERIX 1 +# define LZO_INFO_OS_POSIX "interix" +# elif defined(__IRIX__) || defined(__irix__) +# define LZO_OS_POSIX_IRIX 1 +# define LZO_INFO_OS_POSIX "irix" +# elif defined(__linux__) || defined(__linux) || defined(__LINUX__) +# define LZO_OS_POSIX_LINUX 1 +# define LZO_INFO_OS_POSIX "linux" +# elif defined(__APPLE__) && defined(__MACH__) +# if ((__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__-0) >= 20000) +# define LZO_OS_POSIX_DARWIN 1040 +# define LZO_INFO_OS_POSIX "darwin_iphone" +# elif ((__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__-0) >= 1040) +# define LZO_OS_POSIX_DARWIN __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ +# define LZO_INFO_OS_POSIX "darwin" +# else +# define LZO_OS_POSIX_DARWIN 1 +# define LZO_INFO_OS_POSIX "darwin" +# endif +# define LZO_OS_POSIX_MACOSX LZO_OS_POSIX_DARWIN +# elif defined(__minix__) || defined(__minix) +# define LZO_OS_POSIX_MINIX 1 +# define LZO_INFO_OS_POSIX "minix" +# elif defined(__NetBSD__) +# define LZO_OS_POSIX_NETBSD 1 +# define LZO_INFO_OS_POSIX "netbsd" +# elif defined(__OpenBSD__) +# define LZO_OS_POSIX_OPENBSD 1 +# define LZO_INFO_OS_POSIX "openbsd" +# elif defined(__osf__) +# define LZO_OS_POSIX_OSF 1 +# define LZO_INFO_OS_POSIX "osf" +# elif defined(__solaris__) || defined(__sun) +# if defined(__SVR4) || defined(__svr4__) +# define LZO_OS_POSIX_SOLARIS 1 +# define LZO_INFO_OS_POSIX "solaris" +# else +# define LZO_OS_POSIX_SUNOS 1 +# define LZO_INFO_OS_POSIX "sunos" +# endif +# elif defined(__ultrix__) || defined(__ultrix) +# define LZO_OS_POSIX_ULTRIX 1 +# define LZO_INFO_OS_POSIX "ultrix" +# elif defined(_UNICOS) +# define LZO_OS_POSIX_UNICOS 1 +# define LZO_INFO_OS_POSIX "unicos" +# else +# define LZO_OS_POSIX_UNKNOWN 1 +# define LZO_INFO_OS_POSIX "unknown" +# endif +#endif +#endif +#if (LZO_OS_DOS16 || LZO_OS_OS216 || LZO_OS_WIN16) +# if (UINT_MAX != LZO_0xffffL) +# error "unexpected configuration - check your compiler defines" +# endif +# if (ULONG_MAX != LZO_0xffffffffL) +# error "unexpected configuration - check your compiler defines" +# endif +#endif +#if (LZO_OS_DOS32 || LZO_OS_OS2 || LZO_OS_WIN32 || LZO_OS_WIN64) +# if (UINT_MAX != LZO_0xffffffffL) +# error "unexpected configuration - check your compiler defines" +# endif +# if (ULONG_MAX != LZO_0xffffffffL) +# error "unexpected configuration - check your compiler defines" +# endif +#endif +#if defined(CIL) && defined(_GNUCC) && defined(__GNUC__) +# define LZO_CC_CILLY 1 +# define LZO_INFO_CC "Cilly" +# if defined(__CILLY__) +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__CILLY__) +# else +# define LZO_INFO_CCVER "unknown" +# endif +#elif 0 && defined(SDCC) && defined(__VERSION__) && !defined(__GNUC__) +# define LZO_CC_SDCC 1 +# define LZO_INFO_CC "sdcc" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(SDCC) +#elif defined(__PATHSCALE__) && defined(__PATHCC_PATCHLEVEL__) +# define LZO_CC_PATHSCALE (__PATHCC__ * 0x10000L + (__PATHCC_MINOR__-0) * 0x100 + (__PATHCC_PATCHLEVEL__-0)) +# define LZO_INFO_CC "Pathscale C" +# define LZO_INFO_CCVER __PATHSCALE__ +# if defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__VERSION__) +# define LZO_CC_PATHSCALE_GNUC (__GNUC__ * 0x10000L + (__GNUC_MINOR__-0) * 0x100 + (__GNUC_PATCHLEVEL__-0)) +# endif +#elif defined(__INTEL_COMPILER) && ((__INTEL_COMPILER-0) > 0) +# define LZO_CC_INTELC __INTEL_COMPILER +# define LZO_INFO_CC "Intel C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__INTEL_COMPILER) +# if defined(_MSC_VER) && ((_MSC_VER-0) > 0) +# define LZO_CC_INTELC_MSC _MSC_VER +# elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__VERSION__) +# define LZO_CC_INTELC_GNUC (__GNUC__ * 0x10000L + (__GNUC_MINOR__-0) * 0x100 + (__GNUC_PATCHLEVEL__-0)) +# endif +#elif defined(__POCC__) && defined(_WIN32) +# define LZO_CC_PELLESC 1 +# define LZO_INFO_CC "Pelles C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__POCC__) +#elif defined(__ARMCC_VERSION) && defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__VERSION__) +# if defined(__GNUC_PATCHLEVEL__) +# define LZO_CC_ARMCC_GNUC (__GNUC__ * 0x10000L + (__GNUC_MINOR__-0) * 0x100 + (__GNUC_PATCHLEVEL__-0)) +# else +# define LZO_CC_ARMCC_GNUC (__GNUC__ * 0x10000L + (__GNUC_MINOR__-0) * 0x100) +# endif +# define LZO_CC_ARMCC __ARMCC_VERSION +# define LZO_INFO_CC "ARM C Compiler" +# define LZO_INFO_CCVER __VERSION__ +#elif defined(__clang__) && defined(__c2__) && defined(__c2_version__) && defined(_MSC_VER) +# define LZO_CC_CLANG (__clang_major__ * 0x10000L + (__clang_minor__-0) * 0x100 + (__clang_patchlevel__-0)) +# define LZO_CC_CLANG_C2 _MSC_VER +# define LZO_CC_CLANG_VENDOR_MICROSOFT 1 +# define LZO_INFO_CC "clang/c2" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__c2_version__) +#elif defined(__clang__) && defined(__llvm__) && defined(__VERSION__) +# if defined(__clang_major__) && defined(__clang_minor__) && defined(__clang_patchlevel__) +# define LZO_CC_CLANG (__clang_major__ * 0x10000L + (__clang_minor__-0) * 0x100 + (__clang_patchlevel__-0)) +# else +# define LZO_CC_CLANG 0x010000L +# endif +# if defined(_MSC_VER) && ((_MSC_VER-0) > 0) +# define LZO_CC_CLANG_MSC _MSC_VER +# elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__VERSION__) +# define LZO_CC_CLANG_GNUC (__GNUC__ * 0x10000L + (__GNUC_MINOR__-0) * 0x100 + (__GNUC_PATCHLEVEL__-0)) +# endif +# if defined(__APPLE_CC__) +# define LZO_CC_CLANG_VENDOR_APPLE 1 +# define LZO_INFO_CC "clang/apple" +# else +# define LZO_CC_CLANG_VENDOR_LLVM 1 +# define LZO_INFO_CC "clang" +# endif +# if defined(__clang_version__) +# define LZO_INFO_CCVER __clang_version__ +# else +# define LZO_INFO_CCVER __VERSION__ +# endif +#elif defined(__llvm__) && defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__VERSION__) +# if defined(__GNUC_PATCHLEVEL__) +# define LZO_CC_LLVM_GNUC (__GNUC__ * 0x10000L + (__GNUC_MINOR__-0) * 0x100 + (__GNUC_PATCHLEVEL__-0)) +# else +# define LZO_CC_LLVM_GNUC (__GNUC__ * 0x10000L + (__GNUC_MINOR__-0) * 0x100) +# endif +# define LZO_CC_LLVM LZO_CC_LLVM_GNUC +# define LZO_INFO_CC "llvm-gcc" +# define LZO_INFO_CCVER __VERSION__ +#elif defined(__ACK__) && defined(_ACK) +# define LZO_CC_ACK 1 +# define LZO_INFO_CC "Amsterdam Compiler Kit C" +# define LZO_INFO_CCVER "unknown" +#elif defined(__ARMCC_VERSION) && !defined(__GNUC__) +# define LZO_CC_ARMCC __ARMCC_VERSION +# define LZO_CC_ARMCC_ARMCC __ARMCC_VERSION +# define LZO_INFO_CC "ARM C Compiler" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__ARMCC_VERSION) +#elif defined(__AZTEC_C__) +# define LZO_CC_AZTECC 1 +# define LZO_INFO_CC "Aztec C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__AZTEC_C__) +#elif defined(__CODEGEARC__) +# define LZO_CC_CODEGEARC 1 +# define LZO_INFO_CC "CodeGear C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__CODEGEARC__) +#elif defined(__BORLANDC__) +# define LZO_CC_BORLANDC 1 +# define LZO_INFO_CC "Borland C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__BORLANDC__) +#elif defined(_CRAYC) && defined(_RELEASE) +# define LZO_CC_CRAYC 1 +# define LZO_INFO_CC "Cray C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(_RELEASE) +#elif defined(__DMC__) && defined(__SC__) +# define LZO_CC_DMC 1 +# define LZO_INFO_CC "Digital Mars C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__DMC__) +#elif defined(__DECC) +# define LZO_CC_DECC 1 +# define LZO_INFO_CC "DEC C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__DECC) +#elif (defined(__ghs) || defined(__ghs__)) && defined(__GHS_VERSION_NUMBER) && ((__GHS_VERSION_NUMBER-0) > 0) +# define LZO_CC_GHS 1 +# define LZO_INFO_CC "Green Hills C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__GHS_VERSION_NUMBER) +# if defined(_MSC_VER) && ((_MSC_VER-0) > 0) +# define LZO_CC_GHS_MSC _MSC_VER +# elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__VERSION__) +# define LZO_CC_GHS_GNUC (__GNUC__ * 0x10000L + (__GNUC_MINOR__-0) * 0x100 + (__GNUC_PATCHLEVEL__-0)) +# endif +#elif defined(__HIGHC__) +# define LZO_CC_HIGHC 1 +# define LZO_INFO_CC "MetaWare High C" +# define LZO_INFO_CCVER "unknown" +#elif defined(__HP_aCC) && ((__HP_aCC-0) > 0) +# define LZO_CC_HPACC __HP_aCC +# define LZO_INFO_CC "HP aCC" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__HP_aCC) +#elif defined(__IAR_SYSTEMS_ICC__) +# define LZO_CC_IARC 1 +# define LZO_INFO_CC "IAR C" +# if defined(__VER__) +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__VER__) +# else +# define LZO_INFO_CCVER "unknown" +# endif +#elif defined(__IBMC__) && ((__IBMC__-0) > 0) +# define LZO_CC_IBMC __IBMC__ +# define LZO_INFO_CC "IBM C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__IBMC__) +#elif defined(__IBMCPP__) && ((__IBMCPP__-0) > 0) +# define LZO_CC_IBMC __IBMCPP__ +# define LZO_INFO_CC "IBM C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__IBMCPP__) +#elif defined(__KEIL__) && defined(__C166__) +# define LZO_CC_KEILC 1 +# define LZO_INFO_CC "Keil C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__C166__) +#elif defined(__LCC__) && defined(_WIN32) && defined(__LCCOPTIMLEVEL) +# define LZO_CC_LCCWIN32 1 +# define LZO_INFO_CC "lcc-win32" +# define LZO_INFO_CCVER "unknown" +#elif defined(__LCC__) +# define LZO_CC_LCC 1 +# define LZO_INFO_CC "lcc" +# if defined(__LCC_VERSION__) +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__LCC_VERSION__) +# else +# define LZO_INFO_CCVER "unknown" +# endif +#elif defined(__MWERKS__) && ((__MWERKS__-0) > 0) +# define LZO_CC_MWERKS __MWERKS__ +# define LZO_INFO_CC "Metrowerks C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__MWERKS__) +#elif (defined(__NDPC__) || defined(__NDPX__)) && defined(__i386) +# define LZO_CC_NDPC 1 +# define LZO_INFO_CC "Microway NDP C" +# define LZO_INFO_CCVER "unknown" +#elif defined(__PACIFIC__) +# define LZO_CC_PACIFICC 1 +# define LZO_INFO_CC "Pacific C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__PACIFIC__) +#elif defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) +# if defined(__PGIC_PATCHLEVEL__) +# define LZO_CC_PGI (__PGIC__ * 0x10000L + (__PGIC_MINOR__-0) * 0x100 + (__PGIC_PATCHLEVEL__-0)) +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__PGIC__) "." LZO_PP_MACRO_EXPAND(__PGIC_MINOR__) "." LZO_PP_MACRO_EXPAND(__PGIC_PATCHLEVEL__) +# else +# define LZO_CC_PGI (__PGIC__ * 0x10000L + (__PGIC_MINOR__-0) * 0x100) +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__PGIC__) "." LZO_PP_MACRO_EXPAND(__PGIC_MINOR__) ".0" +# endif +# define LZO_INFO_CC "Portland Group PGI C" +#elif defined(__PGI) && (defined(__linux__) || defined(__WIN32__)) +# define LZO_CC_PGI 1 +# define LZO_INFO_CC "Portland Group PGI C" +# define LZO_INFO_CCVER "unknown" +#elif defined(__PUREC__) && defined(__TOS__) +# define LZO_CC_PUREC 1 +# define LZO_INFO_CC "Pure C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__PUREC__) +#elif defined(__SC__) && defined(__ZTC__) +# define LZO_CC_SYMANTECC 1 +# define LZO_INFO_CC "Symantec C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__SC__) +#elif defined(__SUNPRO_C) +# define LZO_INFO_CC "SunPro C" +# if ((__SUNPRO_C-0) > 0) +# define LZO_CC_SUNPROC __SUNPRO_C +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__SUNPRO_C) +# else +# define LZO_CC_SUNPROC 1 +# define LZO_INFO_CCVER "unknown" +# endif +#elif defined(__SUNPRO_CC) +# define LZO_INFO_CC "SunPro C" +# if ((__SUNPRO_CC-0) > 0) +# define LZO_CC_SUNPROC __SUNPRO_CC +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__SUNPRO_CC) +# else +# define LZO_CC_SUNPROC 1 +# define LZO_INFO_CCVER "unknown" +# endif +#elif defined(__TINYC__) +# define LZO_CC_TINYC 1 +# define LZO_INFO_CC "Tiny C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__TINYC__) +#elif defined(__TSC__) +# define LZO_CC_TOPSPEEDC 1 +# define LZO_INFO_CC "TopSpeed C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__TSC__) +#elif defined(__WATCOMC__) +# define LZO_CC_WATCOMC 1 +# define LZO_INFO_CC "Watcom C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__WATCOMC__) +#elif defined(__TURBOC__) +# define LZO_CC_TURBOC 1 +# define LZO_INFO_CC "Turbo C" +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__TURBOC__) +#elif defined(__ZTC__) +# define LZO_CC_ZORTECHC 1 +# define LZO_INFO_CC "Zortech C" +# if ((__ZTC__-0) == 0x310) +# define LZO_INFO_CCVER "0x310" +# else +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(__ZTC__) +# endif +#elif defined(__GNUC__) && defined(__VERSION__) +# if defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) +# define LZO_CC_GNUC (__GNUC__ * 0x10000L + (__GNUC_MINOR__-0) * 0x100 + (__GNUC_PATCHLEVEL__-0)) +# elif defined(__GNUC_MINOR__) +# define LZO_CC_GNUC (__GNUC__ * 0x10000L + (__GNUC_MINOR__-0) * 0x100) +# else +# define LZO_CC_GNUC (__GNUC__ * 0x10000L) +# endif +# define LZO_INFO_CC "gcc" +# define LZO_INFO_CCVER __VERSION__ +#elif defined(_MSC_VER) && ((_MSC_VER-0) > 0) +# define LZO_CC_MSC _MSC_VER +# define LZO_INFO_CC "Microsoft C" +# if defined(_MSC_FULL_VER) +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(_MSC_VER) "." LZO_PP_MACRO_EXPAND(_MSC_FULL_VER) +# else +# define LZO_INFO_CCVER LZO_PP_MACRO_EXPAND(_MSC_VER) +# endif +#else +# define LZO_CC_UNKNOWN 1 +# define LZO_INFO_CC "unknown" +# define LZO_INFO_CCVER "unknown" +#endif +#if (LZO_CC_GNUC) && defined(__OPEN64__) +# if defined(__OPENCC__) && defined(__OPENCC_MINOR__) && defined(__OPENCC_PATCHLEVEL__) +# define LZO_CC_OPEN64 (__OPENCC__ * 0x10000L + (__OPENCC_MINOR__-0) * 0x100 + (__OPENCC_PATCHLEVEL__-0)) +# define LZO_CC_OPEN64_GNUC LZO_CC_GNUC +# endif +#endif +#if (LZO_CC_GNUC) && defined(__PCC__) +# if defined(__PCC__) && defined(__PCC_MINOR__) && defined(__PCC_MINORMINOR__) +# define LZO_CC_PCC (__PCC__ * 0x10000L + (__PCC_MINOR__-0) * 0x100 + (__PCC_MINORMINOR__-0)) +# define LZO_CC_PCC_GNUC LZO_CC_GNUC +# endif +#endif +#if 0 && (LZO_CC_MSC && (_MSC_VER >= 1200)) && !defined(_MSC_FULL_VER) +# error "LZO_CC_MSC: _MSC_FULL_VER is not defined" +#endif +#if !defined(__LZO_ARCH_OVERRIDE) && !(LZO_ARCH_GENERIC) && defined(_CRAY) +# if (UINT_MAX > LZO_0xffffffffL) && defined(_CRAY) +# if defined(_CRAYMPP) || defined(_CRAYT3D) || defined(_CRAYT3E) +# define LZO_ARCH_CRAY_MPP 1 +# elif defined(_CRAY1) +# define LZO_ARCH_CRAY_PVP 1 +# endif +# endif +#endif +#if !defined(__LZO_ARCH_OVERRIDE) +#if (LZO_ARCH_GENERIC) +# define LZO_INFO_ARCH "generic" +#elif (LZO_OS_DOS16 || LZO_OS_OS216 || LZO_OS_WIN16) +# define LZO_ARCH_I086 1 +# define LZO_INFO_ARCH "i086" +#elif defined(__aarch64__) || defined(_M_ARM64) +# define LZO_ARCH_ARM64 1 +# define LZO_INFO_ARCH "arm64" +#elif defined(__alpha__) || defined(__alpha) || defined(_M_ALPHA) +# define LZO_ARCH_ALPHA 1 +# define LZO_INFO_ARCH "alpha" +#elif (LZO_ARCH_CRAY_MPP) && (defined(_CRAYT3D) || defined(_CRAYT3E)) +# define LZO_ARCH_ALPHA 1 +# define LZO_INFO_ARCH "alpha" +#elif defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64) +# define LZO_ARCH_AMD64 1 +# define LZO_INFO_ARCH "amd64" +#elif defined(__arm__) || defined(_M_ARM) +# define LZO_ARCH_ARM 1 +# define LZO_INFO_ARCH "arm" +#elif defined(__IAR_SYSTEMS_ICC__) && defined(__ICCARM__) +# define LZO_ARCH_ARM 1 +# define LZO_INFO_ARCH "arm" +#elif (UINT_MAX <= LZO_0xffffL) && defined(__AVR__) +# define LZO_ARCH_AVR 1 +# define LZO_INFO_ARCH "avr" +#elif defined(__avr32__) || defined(__AVR32__) +# define LZO_ARCH_AVR32 1 +# define LZO_INFO_ARCH "avr32" +#elif defined(__bfin__) +# define LZO_ARCH_BLACKFIN 1 +# define LZO_INFO_ARCH "blackfin" +#elif (UINT_MAX == LZO_0xffffL) && defined(__C166__) +# define LZO_ARCH_C166 1 +# define LZO_INFO_ARCH "c166" +#elif defined(__cris__) +# define LZO_ARCH_CRIS 1 +# define LZO_INFO_ARCH "cris" +#elif defined(__IAR_SYSTEMS_ICC__) && defined(__ICCEZ80__) +# define LZO_ARCH_EZ80 1 +# define LZO_INFO_ARCH "ez80" +#elif defined(__H8300__) || defined(__H8300H__) || defined(__H8300S__) || defined(__H8300SX__) +# define LZO_ARCH_H8300 1 +# define LZO_INFO_ARCH "h8300" +#elif defined(__hppa__) || defined(__hppa) +# define LZO_ARCH_HPPA 1 +# define LZO_INFO_ARCH "hppa" +#elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386) +# define LZO_ARCH_I386 1 +# define LZO_ARCH_IA32 1 +# define LZO_INFO_ARCH "i386" +#elif (LZO_CC_ZORTECHC && defined(__I86__)) +# define LZO_ARCH_I386 1 +# define LZO_ARCH_IA32 1 +# define LZO_INFO_ARCH "i386" +#elif (LZO_OS_DOS32 && LZO_CC_HIGHC) && defined(_I386) +# define LZO_ARCH_I386 1 +# define LZO_ARCH_IA32 1 +# define LZO_INFO_ARCH "i386" +#elif defined(__ia64__) || defined(__ia64) || defined(_M_IA64) +# define LZO_ARCH_IA64 1 +# define LZO_INFO_ARCH "ia64" +#elif (UINT_MAX == LZO_0xffffL) && defined(__m32c__) +# define LZO_ARCH_M16C 1 +# define LZO_INFO_ARCH "m16c" +#elif defined(__IAR_SYSTEMS_ICC__) && defined(__ICCM16C__) +# define LZO_ARCH_M16C 1 +# define LZO_INFO_ARCH "m16c" +#elif defined(__m32r__) +# define LZO_ARCH_M32R 1 +# define LZO_INFO_ARCH "m32r" +#elif (LZO_OS_TOS) || defined(__m68k__) || defined(__m68000__) || defined(__mc68000__) || defined(__mc68020__) || defined(_M_M68K) +# define LZO_ARCH_M68K 1 +# define LZO_INFO_ARCH "m68k" +#elif (UINT_MAX == LZO_0xffffL) && defined(__C251__) +# define LZO_ARCH_MCS251 1 +# define LZO_INFO_ARCH "mcs251" +#elif (UINT_MAX == LZO_0xffffL) && defined(__C51__) +# define LZO_ARCH_MCS51 1 +# define LZO_INFO_ARCH "mcs51" +#elif defined(__IAR_SYSTEMS_ICC__) && defined(__ICC8051__) +# define LZO_ARCH_MCS51 1 +# define LZO_INFO_ARCH "mcs51" +#elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000) +# define LZO_ARCH_MIPS 1 +# define LZO_INFO_ARCH "mips" +#elif (UINT_MAX == LZO_0xffffL) && defined(__MSP430__) +# define LZO_ARCH_MSP430 1 +# define LZO_INFO_ARCH "msp430" +#elif defined(__IAR_SYSTEMS_ICC__) && defined(__ICC430__) +# define LZO_ARCH_MSP430 1 +# define LZO_INFO_ARCH "msp430" +#elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR) +# define LZO_ARCH_POWERPC 1 +# define LZO_INFO_ARCH "powerpc" +#elif defined(__powerpc64__) || defined(__powerpc64) || defined(__ppc64__) || defined(__PPC64__) +# define LZO_ARCH_POWERPC 1 +# define LZO_INFO_ARCH "powerpc" +#elif defined(__powerpc64le__) || defined(__powerpc64le) || defined(__ppc64le__) || defined(__PPC64LE__) +# define LZO_ARCH_POWERPC 1 +# define LZO_INFO_ARCH "powerpc" +#elif defined(__riscv) +# define LZO_ARCH_RISCV 1 +# define LZO_INFO_ARCH "riscv" +#elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x) +# define LZO_ARCH_S390 1 +# define LZO_INFO_ARCH "s390" +#elif defined(__sh__) || defined(_M_SH) +# define LZO_ARCH_SH 1 +# define LZO_INFO_ARCH "sh" +#elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8) +# define LZO_ARCH_SPARC 1 +# define LZO_INFO_ARCH "sparc" +#elif defined(__SPU__) +# define LZO_ARCH_SPU 1 +# define LZO_INFO_ARCH "spu" +#elif (UINT_MAX == LZO_0xffffL) && defined(__z80) +# define LZO_ARCH_Z80 1 +# define LZO_INFO_ARCH "z80" +#elif (LZO_ARCH_CRAY_PVP) +# if defined(_CRAYSV1) +# define LZO_ARCH_CRAY_SV1 1 +# define LZO_INFO_ARCH "cray_sv1" +# elif (_ADDR64) +# define LZO_ARCH_CRAY_T90 1 +# define LZO_INFO_ARCH "cray_t90" +# elif (_ADDR32) +# define LZO_ARCH_CRAY_YMP 1 +# define LZO_INFO_ARCH "cray_ymp" +# else +# define LZO_ARCH_CRAY_XMP 1 +# define LZO_INFO_ARCH "cray_xmp" +# endif +#else +# define LZO_ARCH_UNKNOWN 1 +# define LZO_INFO_ARCH "unknown" +#endif +#endif +#if !defined(LZO_ARCH_ARM_THUMB2) +#if (LZO_ARCH_ARM) +# if defined(__thumb__) || defined(__thumb) || defined(_M_THUMB) +# if defined(__thumb2__) +# define LZO_ARCH_ARM_THUMB2 1 +# elif 1 && defined(__TARGET_ARCH_THUMB) && ((__TARGET_ARCH_THUMB)+0 >= 4) +# define LZO_ARCH_ARM_THUMB2 1 +# elif 1 && defined(_MSC_VER) && defined(_M_THUMB) && ((_M_THUMB)+0 >= 7) +# define LZO_ARCH_ARM_THUMB2 1 +# endif +# endif +#endif +#endif +#if (LZO_ARCH_ARM_THUMB2) +# undef LZO_INFO_ARCH +# define LZO_INFO_ARCH "arm_thumb2" +#endif +#if 1 && (LZO_ARCH_UNKNOWN) && (LZO_OS_DOS32 || LZO_OS_OS2) +# error "FIXME - missing define for CPU architecture" +#endif +#if 1 && (LZO_ARCH_UNKNOWN) && (LZO_OS_WIN32) +# error "FIXME - missing LZO_OS_WIN32 define for CPU architecture" +#endif +#if 1 && (LZO_ARCH_UNKNOWN) && (LZO_OS_WIN64) +# error "FIXME - missing LZO_OS_WIN64 define for CPU architecture" +#endif +#if (LZO_OS_OS216 || LZO_OS_WIN16) +# define LZO_ARCH_I086PM 1 +#elif 1 && (LZO_OS_DOS16 && defined(BLX286)) +# define LZO_ARCH_I086PM 1 +#elif 1 && (LZO_OS_DOS16 && defined(DOSX286)) +# define LZO_ARCH_I086PM 1 +#elif 1 && (LZO_OS_DOS16 && LZO_CC_BORLANDC && defined(__DPMI16__)) +# define LZO_ARCH_I086PM 1 +#endif +#if (LZO_ARCH_AMD64 && !LZO_ARCH_X64) +# define LZO_ARCH_X64 1 +#elif (!LZO_ARCH_AMD64 && LZO_ARCH_X64) && defined(__LZO_ARCH_OVERRIDE) +# define LZO_ARCH_AMD64 1 +#endif +#if (LZO_ARCH_ARM64 && !LZO_ARCH_AARCH64) +# define LZO_ARCH_AARCH64 1 +#elif (!LZO_ARCH_ARM64 && LZO_ARCH_AARCH64) && defined(__LZO_ARCH_OVERRIDE) +# define LZO_ARCH_ARM64 1 +#endif +#if (LZO_ARCH_I386 && !LZO_ARCH_X86) +# define LZO_ARCH_X86 1 +#elif (!LZO_ARCH_I386 && LZO_ARCH_X86) && defined(__LZO_ARCH_OVERRIDE) +# define LZO_ARCH_I386 1 +#endif +#if (LZO_ARCH_AMD64 && !LZO_ARCH_X64) || (!LZO_ARCH_AMD64 && LZO_ARCH_X64) +# error "unexpected configuration - check your compiler defines" +#endif +#if (LZO_ARCH_ARM64 && !LZO_ARCH_AARCH64) || (!LZO_ARCH_ARM64 && LZO_ARCH_AARCH64) +# error "unexpected configuration - check your compiler defines" +#endif +#if (LZO_ARCH_I386 && !LZO_ARCH_X86) || (!LZO_ARCH_I386 && LZO_ARCH_X86) +# error "unexpected configuration - check your compiler defines" +#endif +#if (LZO_ARCH_ARM_THUMB1 && !LZO_ARCH_ARM) +# error "unexpected configuration - check your compiler defines" +#endif +#if (LZO_ARCH_ARM_THUMB2 && !LZO_ARCH_ARM) +# error "unexpected configuration - check your compiler defines" +#endif +#if (LZO_ARCH_ARM_THUMB1 && LZO_ARCH_ARM_THUMB2) +# error "unexpected configuration - check your compiler defines" +#endif +#if (LZO_ARCH_I086PM && !LZO_ARCH_I086) +# error "unexpected configuration - check your compiler defines" +#endif +#if (LZO_ARCH_I086) +# if (UINT_MAX != LZO_0xffffL) +# error "unexpected configuration - check your compiler defines" +# endif +# if (ULONG_MAX != LZO_0xffffffffL) +# error "unexpected configuration - check your compiler defines" +# endif +#endif +#if (LZO_ARCH_I386) +# if (UINT_MAX != LZO_0xffffL) && defined(__i386_int16__) +# error "unexpected configuration - check your compiler defines" +# endif +# if (UINT_MAX != LZO_0xffffffffL) && !defined(__i386_int16__) +# error "unexpected configuration - check your compiler defines" +# endif +# if (ULONG_MAX != LZO_0xffffffffL) +# error "unexpected configuration - check your compiler defines" +# endif +#endif +#if (LZO_ARCH_AMD64 || LZO_ARCH_I386) +# if !defined(LZO_TARGET_FEATURE_SSE2) +# if defined(__SSE2__) +# define LZO_TARGET_FEATURE_SSE2 1 +# elif defined(_MSC_VER) && (defined(_M_IX86_FP) && ((_M_IX86_FP)+0 >= 2)) +# define LZO_TARGET_FEATURE_SSE2 1 +# elif (LZO_CC_INTELC_MSC || LZO_CC_MSC) && defined(_M_AMD64) +# define LZO_TARGET_FEATURE_SSE2 1 +# endif +# endif +# if !defined(LZO_TARGET_FEATURE_SSSE3) +# if (LZO_TARGET_FEATURE_SSE2) +# if defined(__SSSE3__) +# define LZO_TARGET_FEATURE_SSSE3 1 +# elif defined(_MSC_VER) && defined(__AVX__) +# define LZO_TARGET_FEATURE_SSSE3 1 +# endif +# endif +# endif +# if !defined(LZO_TARGET_FEATURE_SSE4_2) +# if (LZO_TARGET_FEATURE_SSSE3) +# if defined(__SSE4_2__) +# define LZO_TARGET_FEATURE_SSE4_2 1 +# endif +# endif +# endif +# if !defined(LZO_TARGET_FEATURE_AVX) +# if (LZO_TARGET_FEATURE_SSSE3) +# if defined(__AVX__) +# define LZO_TARGET_FEATURE_AVX 1 +# endif +# endif +# endif +# if !defined(LZO_TARGET_FEATURE_AVX2) +# if (LZO_TARGET_FEATURE_AVX) +# if defined(__AVX2__) +# define LZO_TARGET_FEATURE_AVX2 1 +# endif +# endif +# endif +#endif +#if (LZO_TARGET_FEATURE_SSSE3 && !(LZO_TARGET_FEATURE_SSE2)) +# error "unexpected configuration - check your compiler defines" +#endif +#if (LZO_TARGET_FEATURE_SSE4_2 && !(LZO_TARGET_FEATURE_SSSE3)) +# error "unexpected configuration - check your compiler defines" +#endif +#if (LZO_TARGET_FEATURE_AVX && !(LZO_TARGET_FEATURE_SSSE3)) +# error "unexpected configuration - check your compiler defines" +#endif +#if (LZO_TARGET_FEATURE_AVX2 && !(LZO_TARGET_FEATURE_AVX)) +# error "unexpected configuration - check your compiler defines" +#endif +#if (LZO_ARCH_ARM) +# if !defined(LZO_TARGET_FEATURE_NEON) +# if defined(__ARM_NEON) && ((__ARM_NEON)+0) +# define LZO_TARGET_FEATURE_NEON 1 +# elif 1 && defined(__ARM_NEON__) && ((__ARM_NEON__)+0) +# define LZO_TARGET_FEATURE_NEON 1 +# elif 1 && defined(__TARGET_FEATURE_NEON) && ((__TARGET_FEATURE_NEON)+0) +# define LZO_TARGET_FEATURE_NEON 1 +# endif +# endif +#elif (LZO_ARCH_ARM64) +# if !defined(LZO_TARGET_FEATURE_NEON) +# if 1 +# define LZO_TARGET_FEATURE_NEON 1 +# endif +# endif +#endif +#if 0 +#elif !defined(__LZO_MM_OVERRIDE) +#if (LZO_ARCH_I086) +#if (UINT_MAX != LZO_0xffffL) +# error "unexpected configuration - check your compiler defines" +#endif +#if defined(__TINY__) || defined(M_I86TM) || defined(_M_I86TM) +# define LZO_MM_TINY 1 +#elif defined(__HUGE__) || defined(_HUGE_) || defined(M_I86HM) || defined(_M_I86HM) +# define LZO_MM_HUGE 1 +#elif defined(__SMALL__) || defined(M_I86SM) || defined(_M_I86SM) || defined(SMALL_MODEL) +# define LZO_MM_SMALL 1 +#elif defined(__MEDIUM__) || defined(M_I86MM) || defined(_M_I86MM) +# define LZO_MM_MEDIUM 1 +#elif defined(__COMPACT__) || defined(M_I86CM) || defined(_M_I86CM) +# define LZO_MM_COMPACT 1 +#elif defined(__LARGE__) || defined(M_I86LM) || defined(_M_I86LM) || defined(LARGE_MODEL) +# define LZO_MM_LARGE 1 +#elif (LZO_CC_AZTECC) +# if defined(_LARGE_CODE) && defined(_LARGE_DATA) +# define LZO_MM_LARGE 1 +# elif defined(_LARGE_CODE) +# define LZO_MM_MEDIUM 1 +# elif defined(_LARGE_DATA) +# define LZO_MM_COMPACT 1 +# else +# define LZO_MM_SMALL 1 +# endif +#elif (LZO_CC_ZORTECHC && defined(__VCM__)) +# define LZO_MM_LARGE 1 +#else +# error "unknown LZO_ARCH_I086 memory model" +#endif +#if (LZO_OS_DOS16 || LZO_OS_OS216 || LZO_OS_WIN16) +#define LZO_HAVE_MM_HUGE_PTR 1 +#define LZO_HAVE_MM_HUGE_ARRAY 1 +#if (LZO_MM_TINY) +# undef LZO_HAVE_MM_HUGE_ARRAY +#endif +#if (LZO_CC_AZTECC || LZO_CC_PACIFICC || LZO_CC_ZORTECHC) +# undef LZO_HAVE_MM_HUGE_PTR +# undef LZO_HAVE_MM_HUGE_ARRAY +#elif (LZO_CC_DMC || LZO_CC_SYMANTECC) +# undef LZO_HAVE_MM_HUGE_ARRAY +#elif (LZO_CC_MSC && defined(_QC)) +# undef LZO_HAVE_MM_HUGE_ARRAY +# if (_MSC_VER < 600) +# undef LZO_HAVE_MM_HUGE_PTR +# endif +#elif (LZO_CC_TURBOC && (__TURBOC__ < 0x0295)) +# undef LZO_HAVE_MM_HUGE_ARRAY +#endif +#if (LZO_ARCH_I086PM) && !(LZO_HAVE_MM_HUGE_PTR) +# if (LZO_OS_DOS16) +# error "unexpected configuration - check your compiler defines" +# elif (LZO_CC_ZORTECHC) +# else +# error "unexpected configuration - check your compiler defines" +# endif +#endif +#if defined(__cplusplus) +extern "C" { +#endif +#if (LZO_CC_BORLANDC && (__BORLANDC__ >= 0x0200)) + extern void __near __cdecl _AHSHIFT(void); +# define LZO_MM_AHSHIFT ((unsigned) _AHSHIFT) +#elif (LZO_CC_DMC || LZO_CC_SYMANTECC || LZO_CC_ZORTECHC) + extern void __near __cdecl _AHSHIFT(void); +# define LZO_MM_AHSHIFT ((unsigned) _AHSHIFT) +#elif (LZO_CC_MSC || LZO_CC_TOPSPEEDC) + extern void __near __cdecl _AHSHIFT(void); +# define LZO_MM_AHSHIFT ((unsigned) _AHSHIFT) +#elif (LZO_CC_TURBOC && (__TURBOC__ >= 0x0295)) + extern void __near __cdecl _AHSHIFT(void); +# define LZO_MM_AHSHIFT ((unsigned) _AHSHIFT) +#elif ((LZO_CC_AZTECC || LZO_CC_PACIFICC || LZO_CC_TURBOC) && LZO_OS_DOS16) +# define LZO_MM_AHSHIFT 12 +#elif (LZO_CC_WATCOMC) + extern unsigned char _HShift; +# define LZO_MM_AHSHIFT ((unsigned) _HShift) +#else +# error "FIXME - implement LZO_MM_AHSHIFT" +#endif +#if defined(__cplusplus) +} +#endif +#endif +#elif (LZO_ARCH_C166) +#if !defined(__MODEL__) +# error "FIXME - LZO_ARCH_C166 __MODEL__" +#elif ((__MODEL__) == 0) +# define LZO_MM_SMALL 1 +#elif ((__MODEL__) == 1) +# define LZO_MM_SMALL 1 +#elif ((__MODEL__) == 2) +# define LZO_MM_LARGE 1 +#elif ((__MODEL__) == 3) +# define LZO_MM_TINY 1 +#elif ((__MODEL__) == 4) +# define LZO_MM_XTINY 1 +#elif ((__MODEL__) == 5) +# define LZO_MM_XSMALL 1 +#else +# error "FIXME - LZO_ARCH_C166 __MODEL__" +#endif +#elif (LZO_ARCH_MCS251) +#if !defined(__MODEL__) +# error "FIXME - LZO_ARCH_MCS251 __MODEL__" +#elif ((__MODEL__) == 0) +# define LZO_MM_SMALL 1 +#elif ((__MODEL__) == 2) +# define LZO_MM_LARGE 1 +#elif ((__MODEL__) == 3) +# define LZO_MM_TINY 1 +#elif ((__MODEL__) == 4) +# define LZO_MM_XTINY 1 +#elif ((__MODEL__) == 5) +# define LZO_MM_XSMALL 1 +#else +# error "FIXME - LZO_ARCH_MCS251 __MODEL__" +#endif +#elif (LZO_ARCH_MCS51) +#if !defined(__MODEL__) +# error "FIXME - LZO_ARCH_MCS51 __MODEL__" +#elif ((__MODEL__) == 1) +# define LZO_MM_SMALL 1 +#elif ((__MODEL__) == 2) +# define LZO_MM_LARGE 1 +#elif ((__MODEL__) == 3) +# define LZO_MM_TINY 1 +#elif ((__MODEL__) == 4) +# define LZO_MM_XTINY 1 +#elif ((__MODEL__) == 5) +# define LZO_MM_XSMALL 1 +#else +# error "FIXME - LZO_ARCH_MCS51 __MODEL__" +#endif +#elif (LZO_ARCH_CRAY_PVP) +# define LZO_MM_PVP 1 +#else +# define LZO_MM_FLAT 1 +#endif +#if (LZO_MM_COMPACT) +# define LZO_INFO_MM "compact" +#elif (LZO_MM_FLAT) +# define LZO_INFO_MM "flat" +#elif (LZO_MM_HUGE) +# define LZO_INFO_MM "huge" +#elif (LZO_MM_LARGE) +# define LZO_INFO_MM "large" +#elif (LZO_MM_MEDIUM) +# define LZO_INFO_MM "medium" +#elif (LZO_MM_PVP) +# define LZO_INFO_MM "pvp" +#elif (LZO_MM_SMALL) +# define LZO_INFO_MM "small" +#elif (LZO_MM_TINY) +# define LZO_INFO_MM "tiny" +#else +# error "unknown memory model" +#endif +#endif +#if !defined(__lzo_gnuc_extension__) +#if (LZO_CC_GNUC >= 0x020800ul) +# define __lzo_gnuc_extension__ __extension__ +#elif (LZO_CC_ARMCC_GNUC || LZO_CC_CLANG || LZO_CC_LLVM || LZO_CC_PATHSCALE) +# define __lzo_gnuc_extension__ __extension__ +#elif (LZO_CC_IBMC >= 600) +# define __lzo_gnuc_extension__ __extension__ +#endif +#endif +#if !defined(__lzo_gnuc_extension__) +# define __lzo_gnuc_extension__ /*empty*/ +#endif +#if !defined(lzo_has_builtin) +#if (LZO_CC_CLANG) && defined(__has_builtin) +# define lzo_has_builtin __has_builtin +#endif +#endif +#if !defined(lzo_has_builtin) +# define lzo_has_builtin(x) 0 +#endif +#if !defined(lzo_has_attribute) +#if (LZO_CC_CLANG) && defined(__has_attribute) +# define lzo_has_attribute __has_attribute +#endif +#endif +#if !defined(lzo_has_attribute) +# define lzo_has_attribute(x) 0 +#endif +#if !defined(lzo_has_declspec_attribute) +#if (LZO_CC_CLANG) && defined(__has_declspec_attribute) +# define lzo_has_declspec_attribute __has_declspec_attribute +#endif +#endif +#if !defined(lzo_has_declspec_attribute) +# define lzo_has_declspec_attribute(x) 0 +#endif +#if !defined(lzo_has_feature) +#if (LZO_CC_CLANG) && defined(__has_feature) +# define lzo_has_feature __has_feature +#endif +#endif +#if !defined(lzo_has_feature) +# define lzo_has_feature(x) 0 +#endif +#if !defined(lzo_has_extension) +#if (LZO_CC_CLANG) && defined(__has_extension) +# define lzo_has_extension __has_extension +#elif (LZO_CC_CLANG) && defined(__has_feature) +# define lzo_has_extension __has_feature +#endif +#endif +#if !defined(lzo_has_extension) +# define lzo_has_extension(x) 0 +#endif +#if !defined(LZO_CFG_USE_NEW_STYLE_CASTS) && defined(__cplusplus) && 0 +# if (LZO_CC_GNUC && (LZO_CC_GNUC < 0x020800ul)) +# define LZO_CFG_USE_NEW_STYLE_CASTS 0 +# elif (LZO_CC_INTELC && (__INTEL_COMPILER < 1200)) +# define LZO_CFG_USE_NEW_STYLE_CASTS 0 +# else +# define LZO_CFG_USE_NEW_STYLE_CASTS 1 +# endif +#endif +#if !defined(LZO_CFG_USE_NEW_STYLE_CASTS) +# define LZO_CFG_USE_NEW_STYLE_CASTS 0 +#endif +#if !defined(__cplusplus) +# if defined(LZO_CFG_USE_NEW_STYLE_CASTS) +# undef LZO_CFG_USE_NEW_STYLE_CASTS +# endif +# define LZO_CFG_USE_NEW_STYLE_CASTS 0 +#endif +#if !defined(LZO_REINTERPRET_CAST) +# if (LZO_CFG_USE_NEW_STYLE_CASTS) +# define LZO_REINTERPRET_CAST(t,e) (reinterpret_cast (e)) +# endif +#endif +#if !defined(LZO_REINTERPRET_CAST) +# define LZO_REINTERPRET_CAST(t,e) ((t) (e)) +#endif +#if !defined(LZO_STATIC_CAST) +# if (LZO_CFG_USE_NEW_STYLE_CASTS) +# define LZO_STATIC_CAST(t,e) (static_cast (e)) +# endif +#endif +#if !defined(LZO_STATIC_CAST) +# define LZO_STATIC_CAST(t,e) ((t) (e)) +#endif +#if !defined(LZO_STATIC_CAST2) +# define LZO_STATIC_CAST2(t1,t2,e) LZO_STATIC_CAST(t1, LZO_STATIC_CAST(t2, e)) +#endif +#if !defined(LZO_UNCONST_CAST) +# if (LZO_CFG_USE_NEW_STYLE_CASTS) +# define LZO_UNCONST_CAST(t,e) (const_cast (e)) +# elif (LZO_HAVE_MM_HUGE_PTR) +# define LZO_UNCONST_CAST(t,e) ((t) (e)) +# elif (LZO_CC_ARMCC_GNUC || LZO_CC_CLANG || LZO_CC_GNUC || LZO_CC_LLVM || LZO_CC_PATHSCALE) +# define LZO_UNCONST_CAST(t,e) ((t) ((void *) ((lzo_uintptr_t) ((const void *) (e))))) +# endif +#endif +#if !defined(LZO_UNCONST_CAST) +# define LZO_UNCONST_CAST(t,e) ((t) ((void *) ((const void *) (e)))) +#endif +#if !defined(LZO_UNCONST_VOLATILE_CAST) +# if (LZO_CFG_USE_NEW_STYLE_CASTS) +# define LZO_UNCONST_VOLATILE_CAST(t,e) (const_cast (e)) +# elif (LZO_HAVE_MM_HUGE_PTR) +# define LZO_UNCONST_VOLATILE_CAST(t,e) ((t) (e)) +# elif (LZO_CC_ARMCC_GNUC || LZO_CC_CLANG || LZO_CC_GNUC || LZO_CC_LLVM || LZO_CC_PATHSCALE) +# define LZO_UNCONST_VOLATILE_CAST(t,e) ((t) ((volatile void *) ((lzo_uintptr_t) ((volatile const void *) (e))))) +# endif +#endif +#if !defined(LZO_UNCONST_VOLATILE_CAST) +# define LZO_UNCONST_VOLATILE_CAST(t,e) ((t) ((volatile void *) ((volatile const void *) (e)))) +#endif +#if !defined(LZO_UNVOLATILE_CAST) +# if (LZO_CFG_USE_NEW_STYLE_CASTS) +# define LZO_UNVOLATILE_CAST(t,e) (const_cast (e)) +# elif (LZO_HAVE_MM_HUGE_PTR) +# define LZO_UNVOLATILE_CAST(t,e) ((t) (e)) +# elif (LZO_CC_ARMCC_GNUC || LZO_CC_CLANG || LZO_CC_GNUC || LZO_CC_LLVM || LZO_CC_PATHSCALE) +# define LZO_UNVOLATILE_CAST(t,e) ((t) ((void *) ((lzo_uintptr_t) ((volatile void *) (e))))) +# endif +#endif +#if !defined(LZO_UNVOLATILE_CAST) +# define LZO_UNVOLATILE_CAST(t,e) ((t) ((void *) ((volatile void *) (e)))) +#endif +#if !defined(LZO_UNVOLATILE_CONST_CAST) +# if (LZO_CFG_USE_NEW_STYLE_CASTS) +# define LZO_UNVOLATILE_CONST_CAST(t,e) (const_cast (e)) +# elif (LZO_HAVE_MM_HUGE_PTR) +# define LZO_UNVOLATILE_CONST_CAST(t,e) ((t) (e)) +# elif (LZO_CC_ARMCC_GNUC || LZO_CC_CLANG || LZO_CC_GNUC || LZO_CC_LLVM || LZO_CC_PATHSCALE) +# define LZO_UNVOLATILE_CONST_CAST(t,e) ((t) ((const void *) ((lzo_uintptr_t) ((volatile const void *) (e))))) +# endif +#endif +#if !defined(LZO_UNVOLATILE_CONST_CAST) +# define LZO_UNVOLATILE_CONST_CAST(t,e) ((t) ((const void *) ((volatile const void *) (e)))) +#endif +#if !defined(LZO_PCAST) +# if (LZO_HAVE_MM_HUGE_PTR) +# define LZO_PCAST(t,e) ((t) (e)) +# endif +#endif +#if !defined(LZO_PCAST) +# define LZO_PCAST(t,e) LZO_STATIC_CAST(t, LZO_STATIC_CAST(void *, e)) +#endif +#if !defined(LZO_CCAST) +# if (LZO_HAVE_MM_HUGE_PTR) +# define LZO_CCAST(t,e) ((t) (e)) +# endif +#endif +#if !defined(LZO_CCAST) +# define LZO_CCAST(t,e) LZO_STATIC_CAST(t, LZO_STATIC_CAST(const void *, e)) +#endif +#if !defined(LZO_ICONV) +# define LZO_ICONV(t,e) LZO_STATIC_CAST(t, e) +#endif +#if !defined(LZO_ICAST) +# define LZO_ICAST(t,e) LZO_STATIC_CAST(t, e) +#endif +#if !defined(LZO_ITRUNC) +# define LZO_ITRUNC(t,e) LZO_STATIC_CAST(t, e) +#endif +#if !defined(__lzo_cte) +# if (LZO_CC_MSC || LZO_CC_WATCOMC) +# define __lzo_cte(e) ((void)0,(e)) +# elif 1 +# define __lzo_cte(e) ((void)0,(e)) +# endif +#endif +#if !defined(__lzo_cte) +# define __lzo_cte(e) (e) +#endif +#if !defined(LZO_BLOCK_BEGIN) +# define LZO_BLOCK_BEGIN do { +# define LZO_BLOCK_END } while __lzo_cte(0) +#endif +#if !defined(LZO_UNUSED) +# if (LZO_CC_BORLANDC && (__BORLANDC__ >= 0x0600)) +# define LZO_UNUSED(var) ((void) &var) +# elif (LZO_CC_BORLANDC || LZO_CC_HIGHC || LZO_CC_NDPC || LZO_CC_PELLESC || LZO_CC_TURBOC) +# define LZO_UNUSED(var) if (&var) ; else +# elif (LZO_CC_CLANG && (LZO_CC_CLANG >= 0x030200ul)) +# define LZO_UNUSED(var) ((void) &var) +# elif (LZO_CC_CLANG || LZO_CC_GNUC || LZO_CC_LLVM || LZO_CC_PATHSCALE) +# define LZO_UNUSED(var) ((void) var) +# elif (LZO_CC_MSC && (_MSC_VER < 900)) +# define LZO_UNUSED(var) if (&var) ; else +# elif (LZO_CC_KEILC) +# define LZO_UNUSED(var) {extern int lzo_unused__[1-2*!(sizeof(var)>0)]; (void)lzo_unused__;} +# elif (LZO_CC_PACIFICC) +# define LZO_UNUSED(var) ((void) sizeof(var)) +# elif (LZO_CC_WATCOMC) && defined(__cplusplus) +# define LZO_UNUSED(var) ((void) var) +# else +# define LZO_UNUSED(var) ((void) &var) +# endif +#endif +#if !defined(LZO_UNUSED_RESULT) +# define LZO_UNUSED_RESULT(var) LZO_UNUSED(var) +#endif +#if !defined(LZO_UNUSED_FUNC) +# if (LZO_CC_BORLANDC && (__BORLANDC__ >= 0x0600)) +# define LZO_UNUSED_FUNC(func) ((void) func) +# elif (LZO_CC_BORLANDC || LZO_CC_NDPC || LZO_CC_TURBOC) +# define LZO_UNUSED_FUNC(func) if (func) ; else +# elif (LZO_CC_CLANG || LZO_CC_LLVM) +# define LZO_UNUSED_FUNC(func) ((void) &func) +# elif (LZO_CC_MSC && (_MSC_VER < 900)) +# define LZO_UNUSED_FUNC(func) if (func) ; else +# elif (LZO_CC_MSC) +# define LZO_UNUSED_FUNC(func) ((void) &func) +# elif (LZO_CC_KEILC || LZO_CC_PELLESC) +# define LZO_UNUSED_FUNC(func) {extern int lzo_unused__[1-2*!(sizeof((int)func)>0)]; (void)lzo_unused__;} +# else +# define LZO_UNUSED_FUNC(func) ((void) func) +# endif +#endif +#if !defined(LZO_UNUSED_LABEL) +# if (LZO_CC_CLANG >= 0x020800ul) +# define LZO_UNUSED_LABEL(l) (__lzo_gnuc_extension__ ((void) ((const void *) &&l))) +# elif (LZO_CC_ARMCC || LZO_CC_CLANG || LZO_CC_INTELC || LZO_CC_WATCOMC) +# define LZO_UNUSED_LABEL(l) if __lzo_cte(0) goto l +# else +# define LZO_UNUSED_LABEL(l) switch (0) case 1:goto l +# endif +#endif +#if !defined(LZO_DEFINE_UNINITIALIZED_VAR) +# if 0 +# define LZO_DEFINE_UNINITIALIZED_VAR(type,var,init) type var +# elif 0 && (LZO_CC_GNUC) +# define LZO_DEFINE_UNINITIALIZED_VAR(type,var,init) type var = var +# else +# define LZO_DEFINE_UNINITIALIZED_VAR(type,var,init) type var = init +# endif +#endif +#if !defined(__lzo_inline) +#if (LZO_CC_TURBOC && (__TURBOC__ <= 0x0295)) +#elif defined(__cplusplus) +# define __lzo_inline inline +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__-0 >= 199901L) +# define __lzo_inline inline +#elif (LZO_CC_BORLANDC && (__BORLANDC__ >= 0x0550)) +# define __lzo_inline __inline +#elif (LZO_CC_ARMCC_GNUC || LZO_CC_CILLY || LZO_CC_CLANG || LZO_CC_GNUC || LZO_CC_LLVM || LZO_CC_PATHSCALE || LZO_CC_PGI) +# define __lzo_inline __inline__ +#elif (LZO_CC_DMC) +# define __lzo_inline __inline +#elif (LZO_CC_GHS) +# define __lzo_inline __inline__ +#elif (LZO_CC_IBMC >= 600) +# define __lzo_inline __inline__ +#elif (LZO_CC_INTELC) +# define __lzo_inline __inline +#elif (LZO_CC_MWERKS && (__MWERKS__ >= 0x2405)) +# define __lzo_inline __inline +#elif (LZO_CC_MSC && (_MSC_VER >= 900)) +# define __lzo_inline __inline +#elif (LZO_CC_SUNPROC >= 0x5100) +# define __lzo_inline __inline__ +#endif +#endif +#if defined(__lzo_inline) +# ifndef __lzo_HAVE_inline +# define __lzo_HAVE_inline 1 +# endif +#else +# define __lzo_inline /*empty*/ +#endif +#if !defined(__lzo_forceinline) +#if (LZO_CC_GNUC >= 0x030200ul) +# define __lzo_forceinline __inline__ __attribute__((__always_inline__)) +#elif (LZO_CC_IBMC >= 700) +# define __lzo_forceinline __inline__ __attribute__((__always_inline__)) +#elif (LZO_CC_INTELC_MSC && (__INTEL_COMPILER >= 450)) +# define __lzo_forceinline __forceinline +#elif (LZO_CC_INTELC_GNUC && (__INTEL_COMPILER >= 800)) +# define __lzo_forceinline __inline__ __attribute__((__always_inline__)) +#elif (LZO_CC_ARMCC_GNUC || LZO_CC_CLANG || LZO_CC_LLVM || LZO_CC_PATHSCALE) +# define __lzo_forceinline __inline__ __attribute__((__always_inline__)) +#elif (LZO_CC_MSC && (_MSC_VER >= 1200)) +# define __lzo_forceinline __forceinline +#elif (LZO_CC_PGI >= 0x0d0a00ul) +# define __lzo_forceinline __inline__ __attribute__((__always_inline__)) +#elif (LZO_CC_SUNPROC >= 0x5100) +# define __lzo_forceinline __inline__ __attribute__((__always_inline__)) +#endif +#endif +#if defined(__lzo_forceinline) +# ifndef __lzo_HAVE_forceinline +# define __lzo_HAVE_forceinline 1 +# endif +#else +# define __lzo_forceinline __lzo_inline +#endif +#if !defined(__lzo_noinline) +#if 1 && (LZO_ARCH_I386) && (LZO_CC_GNUC >= 0x040000ul) && (LZO_CC_GNUC < 0x040003ul) +# define __lzo_noinline __attribute__((__noinline__,__used__)) +#elif (LZO_CC_GNUC >= 0x030200ul) +# define __lzo_noinline __attribute__((__noinline__)) +#elif (LZO_CC_IBMC >= 700) +# define __lzo_noinline __attribute__((__noinline__)) +#elif (LZO_CC_INTELC_MSC && (__INTEL_COMPILER >= 600)) +# define __lzo_noinline __declspec(noinline) +#elif (LZO_CC_INTELC_GNUC && (__INTEL_COMPILER >= 800)) +# define __lzo_noinline __attribute__((__noinline__)) +#elif (LZO_CC_ARMCC_GNUC || LZO_CC_CLANG || LZO_CC_LLVM || LZO_CC_PATHSCALE) +# define __lzo_noinline __attribute__((__noinline__)) +#elif (LZO_CC_MSC && (_MSC_VER >= 1300)) +# define __lzo_noinline __declspec(noinline) +#elif (LZO_CC_MWERKS && (__MWERKS__ >= 0x3200) && (LZO_OS_WIN32 || LZO_OS_WIN64)) +# if defined(__cplusplus) +# else +# define __lzo_noinline __declspec(noinline) +# endif +#elif (LZO_CC_PGI >= 0x0d0a00ul) +# define __lzo_noinline __attribute__((__noinline__)) +#elif (LZO_CC_SUNPROC >= 0x5100) +# define __lzo_noinline __attribute__((__noinline__)) +#endif +#endif +#if defined(__lzo_noinline) +# ifndef __lzo_HAVE_noinline +# define __lzo_HAVE_noinline 1 +# endif +#else +# define __lzo_noinline /*empty*/ +#endif +#if (__lzo_HAVE_forceinline || __lzo_HAVE_noinline) && !(__lzo_HAVE_inline) +# error "unexpected configuration - check your compiler defines" +#endif +#if !defined(__lzo_static_inline) +#if (LZO_CC_IBMC) +# define __lzo_static_inline __lzo_gnuc_extension__ static __lzo_inline +#endif +#endif +#if !defined(__lzo_static_inline) +# define __lzo_static_inline static __lzo_inline +#endif +#if !defined(__lzo_static_forceinline) +#if (LZO_CC_IBMC) +# define __lzo_static_forceinline __lzo_gnuc_extension__ static __lzo_forceinline +#endif +#endif +#if !defined(__lzo_static_forceinline) +# define __lzo_static_forceinline static __lzo_forceinline +#endif +#if !defined(__lzo_static_noinline) +#if (LZO_CC_IBMC) +# define __lzo_static_noinline __lzo_gnuc_extension__ static __lzo_noinline +#endif +#endif +#if !defined(__lzo_static_noinline) +# define __lzo_static_noinline static __lzo_noinline +#endif +#if !defined(__lzo_c99_extern_inline) +#if defined(__GNUC_GNU_INLINE__) +# define __lzo_c99_extern_inline __lzo_inline +#elif defined(__GNUC_STDC_INLINE__) +# define __lzo_c99_extern_inline extern __lzo_inline +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__-0 >= 199901L) +# define __lzo_c99_extern_inline extern __lzo_inline +#endif +#if !defined(__lzo_c99_extern_inline) && (__lzo_HAVE_inline) +# define __lzo_c99_extern_inline __lzo_inline +#endif +#endif +#if defined(__lzo_c99_extern_inline) +# ifndef __lzo_HAVE_c99_extern_inline +# define __lzo_HAVE_c99_extern_inline 1 +# endif +#else +# define __lzo_c99_extern_inline /*empty*/ +#endif +#if !defined(__lzo_may_alias) +#if (LZO_CC_GNUC >= 0x030400ul) +# define __lzo_may_alias __attribute__((__may_alias__)) +#elif (LZO_CC_CLANG >= 0x020900ul) +# define __lzo_may_alias __attribute__((__may_alias__)) +#elif (LZO_CC_INTELC_GNUC && (__INTEL_COMPILER >= 1210)) && 0 +# define __lzo_may_alias __attribute__((__may_alias__)) +#elif (LZO_CC_PGI >= 0x0d0a00ul) && 0 +# define __lzo_may_alias __attribute__((__may_alias__)) +#endif +#endif +#if defined(__lzo_may_alias) +# ifndef __lzo_HAVE_may_alias +# define __lzo_HAVE_may_alias 1 +# endif +#else +# define __lzo_may_alias /*empty*/ +#endif +#if !defined(__lzo_noreturn) +#if (LZO_CC_GNUC >= 0x020700ul) +# define __lzo_noreturn __attribute__((__noreturn__)) +#elif (LZO_CC_IBMC >= 700) +# define __lzo_noreturn __attribute__((__noreturn__)) +#elif (LZO_CC_INTELC_MSC && (__INTEL_COMPILER >= 450)) +# define __lzo_noreturn __declspec(noreturn) +#elif (LZO_CC_INTELC_GNUC && (__INTEL_COMPILER >= 600)) +# define __lzo_noreturn __attribute__((__noreturn__)) +#elif (LZO_CC_ARMCC_GNUC || LZO_CC_CLANG || LZO_CC_LLVM || LZO_CC_PATHSCALE) +# define __lzo_noreturn __attribute__((__noreturn__)) +#elif (LZO_CC_MSC && (_MSC_VER >= 1200)) +# define __lzo_noreturn __declspec(noreturn) +#elif (LZO_CC_PGI >= 0x0d0a00ul) +# define __lzo_noreturn __attribute__((__noreturn__)) +#endif +#endif +#if defined(__lzo_noreturn) +# ifndef __lzo_HAVE_noreturn +# define __lzo_HAVE_noreturn 1 +# endif +#else +# define __lzo_noreturn /*empty*/ +#endif +#if !defined(__lzo_nothrow) +#if (LZO_CC_GNUC >= 0x030300ul) +# define __lzo_nothrow __attribute__((__nothrow__)) +#elif (LZO_CC_INTELC_MSC && (__INTEL_COMPILER >= 450)) && defined(__cplusplus) +# define __lzo_nothrow __declspec(nothrow) +#elif (LZO_CC_INTELC_GNUC && (__INTEL_COMPILER >= 900)) +# define __lzo_nothrow __attribute__((__nothrow__)) +#elif (LZO_CC_ARMCC_GNUC || LZO_CC_CLANG || LZO_CC_LLVM || LZO_CC_PATHSCALE) +# define __lzo_nothrow __attribute__((__nothrow__)) +#elif (LZO_CC_MSC && (_MSC_VER >= 1200)) && defined(__cplusplus) +# define __lzo_nothrow __declspec(nothrow) +#endif +#endif +#if defined(__lzo_nothrow) +# ifndef __lzo_HAVE_nothrow +# define __lzo_HAVE_nothrow 1 +# endif +#else +# define __lzo_nothrow /*empty*/ +#endif +#if !defined(__lzo_restrict) +#if (LZO_CC_GNUC >= 0x030400ul) +# define __lzo_restrict __restrict__ +#elif (LZO_CC_IBMC >= 800) && !defined(__cplusplus) +# define __lzo_restrict __restrict__ +#elif (LZO_CC_IBMC >= 1210) +# define __lzo_restrict __restrict__ +#elif (LZO_CC_INTELC_MSC && (__INTEL_COMPILER >= 600)) +#elif (LZO_CC_INTELC_GNUC && (__INTEL_COMPILER >= 600)) +# define __lzo_restrict __restrict__ +#elif (LZO_CC_ARMCC_GNUC || LZO_CC_CLANG || LZO_CC_LLVM) +# define __lzo_restrict __restrict__ +#elif (LZO_CC_MSC && (_MSC_VER >= 1400)) +# define __lzo_restrict __restrict +#elif (LZO_CC_PGI >= 0x0d0a00ul) +# define __lzo_restrict __restrict__ +#endif +#endif +#if defined(__lzo_restrict) +# ifndef __lzo_HAVE_restrict +# define __lzo_HAVE_restrict 1 +# endif +#else +# define __lzo_restrict /*empty*/ +#endif +#if !defined(__lzo_alignof) +#if (LZO_CC_ARMCC || LZO_CC_CILLY || LZO_CC_CLANG || LZO_CC_GNUC || LZO_CC_LLVM || LZO_CC_PATHSCALE || LZO_CC_PGI) +# define __lzo_alignof(e) __alignof__(e) +#elif (LZO_CC_GHS) && !defined(__cplusplus) +# define __lzo_alignof(e) __alignof__(e) +#elif (LZO_CC_IBMC >= 600) +# define __lzo_alignof(e) (__lzo_gnuc_extension__ __alignof__(e)) +#elif (LZO_CC_INTELC && (__INTEL_COMPILER >= 700)) +# define __lzo_alignof(e) __alignof__(e) +#elif (LZO_CC_MSC && (_MSC_VER >= 1300)) +# define __lzo_alignof(e) __alignof(e) +#elif (LZO_CC_SUNPROC >= 0x5100) +# define __lzo_alignof(e) __alignof__(e) +#endif +#endif +#if defined(__lzo_alignof) +# ifndef __lzo_HAVE_alignof +# define __lzo_HAVE_alignof 1 +# endif +#endif +#if !defined(__lzo_struct_packed) +#if (LZO_CC_CLANG && (LZO_CC_CLANG < 0x020800ul)) && defined(__cplusplus) +#elif (LZO_CC_GNUC && (LZO_CC_GNUC < 0x020700ul)) +#elif (LZO_CC_GNUC && (LZO_CC_GNUC < 0x020800ul)) && defined(__cplusplus) +#elif (LZO_CC_PCC && (LZO_CC_PCC < 0x010100ul)) +#elif (LZO_CC_SUNPROC && (LZO_CC_SUNPROC < 0x5110)) && !defined(__cplusplus) +#elif (LZO_CC_GNUC >= 0x030400ul) && !(LZO_CC_PCC_GNUC) && (LZO_ARCH_AMD64 || LZO_ARCH_I386) +# define __lzo_struct_packed(s) struct s { +# define __lzo_struct_packed_end() } __attribute__((__gcc_struct__,__packed__)); +# define __lzo_struct_packed_ma_end() } __lzo_may_alias __attribute__((__gcc_struct__,__packed__)); +#elif (LZO_CC_ARMCC || LZO_CC_CLANG || LZO_CC_GNUC || LZO_CC_INTELC_GNUC || LZO_CC_LLVM || LZO_CC_PATHSCALE || (LZO_CC_PGI >= 0x0d0a00ul) || (LZO_CC_SUNPROC >= 0x5100)) +# define __lzo_struct_packed(s) struct s { +# define __lzo_struct_packed_end() } __attribute__((__packed__)); +# define __lzo_struct_packed_ma_end() } __lzo_may_alias __attribute__((__packed__)); +#elif (LZO_CC_IBMC >= 700) +# define __lzo_struct_packed(s) __lzo_gnuc_extension__ struct s { +# define __lzo_struct_packed_end() } __attribute__((__packed__)); +# define __lzo_struct_packed_ma_end() } __lzo_may_alias __attribute__((__packed__)); +#elif (LZO_CC_INTELC_MSC) || (LZO_CC_MSC && (_MSC_VER >= 1300)) +# define __lzo_struct_packed(s) __pragma(pack(push,1)) struct s { +# define __lzo_struct_packed_end() } __pragma(pack(pop)); +#elif (LZO_CC_WATCOMC && (__WATCOMC__ >= 900)) +# define __lzo_struct_packed(s) _Packed struct s { +# define __lzo_struct_packed_end() }; +#endif +#endif +#if defined(__lzo_struct_packed) && !defined(__lzo_struct_packed_ma) +# define __lzo_struct_packed_ma(s) __lzo_struct_packed(s) +#endif +#if defined(__lzo_struct_packed_end) && !defined(__lzo_struct_packed_ma_end) +# define __lzo_struct_packed_ma_end() __lzo_struct_packed_end() +#endif +#if !defined(__lzo_byte_struct) +#if defined(__lzo_struct_packed) +# define __lzo_byte_struct(s,n) __lzo_struct_packed(s) unsigned char a[n]; __lzo_struct_packed_end() +# define __lzo_byte_struct_ma(s,n) __lzo_struct_packed_ma(s) unsigned char a[n]; __lzo_struct_packed_ma_end() +#elif (LZO_CC_CILLY || LZO_CC_CLANG || LZO_CC_PGI || (LZO_CC_SUNPROC >= 0x5100)) +# define __lzo_byte_struct(s,n) struct s { unsigned char a[n]; } __attribute__((__packed__)); +# define __lzo_byte_struct_ma(s,n) struct s { unsigned char a[n]; } __lzo_may_alias __attribute__((__packed__)); +#endif +#endif +#if defined(__lzo_byte_struct) && !defined(__lzo_byte_struct_ma) +# define __lzo_byte_struct_ma(s,n) __lzo_byte_struct(s,n) +#endif +#if !defined(__lzo_struct_align16) && (__lzo_HAVE_alignof) +#if (LZO_CC_GNUC && (LZO_CC_GNUC < 0x030000ul)) +#elif (LZO_CC_CLANG && (LZO_CC_CLANG < 0x020800ul)) && defined(__cplusplus) +#elif (LZO_CC_CILLY || LZO_CC_PCC) +#elif (LZO_CC_INTELC_MSC) || (LZO_CC_MSC && (_MSC_VER >= 1300)) +# define __lzo_struct_align16(s) struct __declspec(align(16)) s { +# define __lzo_struct_align16_end() }; +# define __lzo_struct_align32(s) struct __declspec(align(32)) s { +# define __lzo_struct_align32_end() }; +# define __lzo_struct_align64(s) struct __declspec(align(64)) s { +# define __lzo_struct_align64_end() }; +#elif (LZO_CC_ARMCC || LZO_CC_CLANG || LZO_CC_GNUC || (LZO_CC_IBMC >= 700) || LZO_CC_INTELC_GNUC || LZO_CC_LLVM || LZO_CC_PATHSCALE) +# define __lzo_struct_align16(s) struct s { +# define __lzo_struct_align16_end() } __attribute__((__aligned__(16))); +# define __lzo_struct_align32(s) struct s { +# define __lzo_struct_align32_end() } __attribute__((__aligned__(32))); +# define __lzo_struct_align64(s) struct s { +# define __lzo_struct_align64_end() } __attribute__((__aligned__(64))); +#endif +#endif +#if !defined(__lzo_union_um) +#if (LZO_CC_CLANG && (LZO_CC_CLANG < 0x020800ul)) && defined(__cplusplus) +#elif (LZO_CC_GNUC && (LZO_CC_GNUC < 0x020700ul)) +#elif (LZO_CC_GNUC && (LZO_CC_GNUC < 0x020800ul)) && defined(__cplusplus) +#elif (LZO_CC_INTELC_GNUC && (__INTEL_COMPILER < 810)) +#elif (LZO_CC_PCC && (LZO_CC_PCC < 0x010100ul)) +#elif (LZO_CC_SUNPROC && (LZO_CC_SUNPROC < 0x5110)) && !defined(__cplusplus) +#elif (LZO_CC_ARMCC || LZO_CC_CLANG || LZO_CC_GNUC || LZO_CC_INTELC_GNUC || LZO_CC_LLVM || LZO_CC_PATHSCALE || (LZO_CC_PGI >= 0x0d0a00ul) || (LZO_CC_SUNPROC >= 0x5100)) +# define __lzo_union_am(s) union s { +# define __lzo_union_am_end() } __lzo_may_alias; +# define __lzo_union_um(s) union s { +# define __lzo_union_um_end() } __lzo_may_alias __attribute__((__packed__)); +#elif (LZO_CC_IBMC >= 700) +# define __lzo_union_am(s) __lzo_gnuc_extension__ union s { +# define __lzo_union_am_end() } __lzo_may_alias; +# define __lzo_union_um(s) __lzo_gnuc_extension__ union s { +# define __lzo_union_um_end() } __lzo_may_alias __attribute__((__packed__)); +#elif (LZO_CC_INTELC_MSC) || (LZO_CC_MSC && (_MSC_VER >= 1300)) +# define __lzo_union_um(s) __pragma(pack(push,1)) union s { +# define __lzo_union_um_end() } __pragma(pack(pop)); +#elif (LZO_CC_WATCOMC && (__WATCOMC__ >= 900)) +# define __lzo_union_um(s) _Packed union s { +# define __lzo_union_um_end() }; +#endif +#endif +#if !defined(__lzo_union_am) +# define __lzo_union_am(s) union s { +# define __lzo_union_am_end() }; +#endif +#if !defined(__lzo_constructor) +#if (LZO_CC_GNUC >= 0x030400ul) +# define __lzo_constructor __attribute__((__constructor__,__used__)) +#elif (LZO_CC_GNUC >= 0x020700ul) +# define __lzo_constructor __attribute__((__constructor__)) +#elif (LZO_CC_INTELC_GNUC && (__INTEL_COMPILER >= 800)) +# define __lzo_constructor __attribute__((__constructor__,__used__)) +#elif (LZO_CC_ARMCC_GNUC || LZO_CC_CLANG || LZO_CC_LLVM || LZO_CC_PATHSCALE) +# define __lzo_constructor __attribute__((__constructor__)) +#endif +#endif +#if defined(__lzo_constructor) +# ifndef __lzo_HAVE_constructor +# define __lzo_HAVE_constructor 1 +# endif +#endif +#if !defined(__lzo_destructor) +#if (LZO_CC_GNUC >= 0x030400ul) +# define __lzo_destructor __attribute__((__destructor__,__used__)) +#elif (LZO_CC_GNUC >= 0x020700ul) +# define __lzo_destructor __attribute__((__destructor__)) +#elif (LZO_CC_INTELC_GNUC && (__INTEL_COMPILER >= 800)) +# define __lzo_destructor __attribute__((__destructor__,__used__)) +#elif (LZO_CC_ARMCC_GNUC || LZO_CC_CLANG || LZO_CC_LLVM || LZO_CC_PATHSCALE) +# define __lzo_destructor __attribute__((__destructor__)) +#endif +#endif +#if defined(__lzo_destructor) +# ifndef __lzo_HAVE_destructor +# define __lzo_HAVE_destructor 1 +# endif +#endif +#if (__lzo_HAVE_destructor) && !(__lzo_HAVE_constructor) +# error "unexpected configuration - check your compiler defines" +#endif +#if !defined(__lzo_likely) && !defined(__lzo_unlikely) +#if (LZO_CC_GNUC >= 0x030200ul) +# define __lzo_likely(e) (__builtin_expect(!!(e),1)) +# define __lzo_unlikely(e) (__builtin_expect(!!(e),0)) +#elif (LZO_CC_IBMC >= 1010) +# define __lzo_likely(e) (__builtin_expect(!!(e),1)) +# define __lzo_unlikely(e) (__builtin_expect(!!(e),0)) +#elif (LZO_CC_INTELC && (__INTEL_COMPILER >= 800)) +# define __lzo_likely(e) (__builtin_expect(!!(e),1)) +# define __lzo_unlikely(e) (__builtin_expect(!!(e),0)) +#elif (LZO_CC_CLANG && LZO_CC_CLANG_C2) +#elif (LZO_CC_ARMCC_GNUC || LZO_CC_CLANG || LZO_CC_LLVM || LZO_CC_PATHSCALE) +# define __lzo_likely(e) (__builtin_expect(!!(e),1)) +# define __lzo_unlikely(e) (__builtin_expect(!!(e),0)) +#endif +#endif +#if defined(__lzo_likely) +# ifndef __lzo_HAVE_likely +# define __lzo_HAVE_likely 1 +# endif +#else +# define __lzo_likely(e) (e) +#endif +#if defined(__lzo_very_likely) +# ifndef __lzo_HAVE_very_likely +# define __lzo_HAVE_very_likely 1 +# endif +#else +# define __lzo_very_likely(e) __lzo_likely(e) +#endif +#if defined(__lzo_unlikely) +# ifndef __lzo_HAVE_unlikely +# define __lzo_HAVE_unlikely 1 +# endif +#else +# define __lzo_unlikely(e) (e) +#endif +#if defined(__lzo_very_unlikely) +# ifndef __lzo_HAVE_very_unlikely +# define __lzo_HAVE_very_unlikely 1 +# endif +#else +# define __lzo_very_unlikely(e) __lzo_unlikely(e) +#endif +#if !defined(__lzo_loop_forever) +# if (LZO_CC_IBMC) +# define __lzo_loop_forever() LZO_BLOCK_BEGIN for (;;) { ; } LZO_BLOCK_END +# else +# define __lzo_loop_forever() do { ; } while __lzo_cte(1) +# endif +#endif +#if !defined(__lzo_unreachable) +#if (LZO_CC_CLANG && (LZO_CC_CLANG >= 0x020800ul)) && lzo_has_builtin(__builtin_unreachable) +# define __lzo_unreachable() __builtin_unreachable(); +#elif (LZO_CC_GNUC >= 0x040500ul) +# define __lzo_unreachable() __builtin_unreachable(); +#elif (LZO_CC_INTELC_GNUC && (__INTEL_COMPILER >= 1300)) && 1 +# define __lzo_unreachable() __builtin_unreachable(); +#endif +#endif +#if defined(__lzo_unreachable) +# ifndef __lzo_HAVE_unreachable +# define __lzo_HAVE_unreachable 1 +# endif +#else +# if 0 +# define __lzo_unreachable() ((void)0); +# else +# define __lzo_unreachable() __lzo_loop_forever(); +# endif +#endif +#if !defined(lzo_unused_funcs_impl) +# if 1 && (LZO_CC_ARMCC_GNUC || LZO_CC_CLANG || (LZO_CC_GNUC >= 0x020700ul) || LZO_CC_INTELC_GNUC || LZO_CC_LLVM || LZO_CC_PATHSCALE || LZO_CC_PGI) +# define lzo_unused_funcs_impl(r,f) static r __attribute__((__unused__)) f +# elif 1 && (LZO_CC_BORLANDC || LZO_CC_GNUC) +# define lzo_unused_funcs_impl(r,f) static r f +# else +# define lzo_unused_funcs_impl(r,f) __lzo_static_forceinline r f +# endif +#endif +#ifndef __LZO_CTA_NAME +#if (LZO_CFG_USE_COUNTER) +# define __LZO_CTA_NAME(a) LZO_PP_ECONCAT2(a,__COUNTER__) +#else +# define __LZO_CTA_NAME(a) LZO_PP_ECONCAT2(a,__LINE__) +#endif +#endif +#if !defined(LZO_COMPILE_TIME_ASSERT_HEADER) +# if (LZO_CC_AZTECC || LZO_CC_ZORTECHC) +# define LZO_COMPILE_TIME_ASSERT_HEADER(e) LZO_EXTERN_C_BEGIN extern int __LZO_CTA_NAME(lzo_cta__)[1-!(e)]; LZO_EXTERN_C_END +# elif (LZO_CC_DMC || LZO_CC_SYMANTECC) +# define LZO_COMPILE_TIME_ASSERT_HEADER(e) LZO_EXTERN_C_BEGIN extern int __LZO_CTA_NAME(lzo_cta__)[1u-2*!(e)]; LZO_EXTERN_C_END +# elif (LZO_CC_TURBOC && (__TURBOC__ == 0x0295)) +# define LZO_COMPILE_TIME_ASSERT_HEADER(e) LZO_EXTERN_C_BEGIN extern int __LZO_CTA_NAME(lzo_cta__)[1-!(e)]; LZO_EXTERN_C_END +# elif (LZO_CC_CLANG && (LZO_CC_CLANG < 0x020900ul)) && defined(__cplusplus) +# define LZO_COMPILE_TIME_ASSERT_HEADER(e) LZO_EXTERN_C_BEGIN int __LZO_CTA_NAME(lzo_cta_f__)(int [1-2*!(e)]); LZO_EXTERN_C_END +# elif (LZO_CC_GNUC) && defined(__CHECKER__) && defined(__SPARSE_CHECKER__) +# define LZO_COMPILE_TIME_ASSERT_HEADER(e) LZO_EXTERN_C_BEGIN enum {__LZO_CTA_NAME(lzo_cta_e__)=1/!!(e)} __attribute__((__unused__)); LZO_EXTERN_C_END +# else +# define LZO_COMPILE_TIME_ASSERT_HEADER(e) LZO_EXTERN_C_BEGIN extern int __LZO_CTA_NAME(lzo_cta__)[1-2*!(e)]; LZO_EXTERN_C_END +# endif +#endif +#if !defined(LZO_COMPILE_TIME_ASSERT) +# if (LZO_CC_AZTECC) +# define LZO_COMPILE_TIME_ASSERT(e) {typedef int __LZO_CTA_NAME(lzo_cta_t__)[1-!(e)];} +# elif (LZO_CC_CLANG && (LZO_CC_CLANG >= 0x030000ul)) +# define LZO_COMPILE_TIME_ASSERT(e) {typedef int __LZO_CTA_NAME(lzo_cta_t__)[1-2*!(e)] __attribute__((__unused__));} +# elif (LZO_CC_DMC || LZO_CC_PACIFICC || LZO_CC_SYMANTECC || LZO_CC_ZORTECHC) +# define LZO_COMPILE_TIME_ASSERT(e) switch(0) case 1:case !(e):break; +# elif (LZO_CC_GNUC) && defined(__CHECKER__) && defined(__SPARSE_CHECKER__) +# define LZO_COMPILE_TIME_ASSERT(e) {(void) (0/!!(e));} +# elif (LZO_CC_GNUC >= 0x040700ul) && (LZO_CFG_USE_COUNTER) && defined(__cplusplus) +# define LZO_COMPILE_TIME_ASSERT(e) {enum {__LZO_CTA_NAME(lzo_cta_e__)=1/!!(e)} __attribute__((__unused__));} +# elif (LZO_CC_GNUC >= 0x040700ul) +# define LZO_COMPILE_TIME_ASSERT(e) {typedef int __LZO_CTA_NAME(lzo_cta_t__)[1-2*!(e)] __attribute__((__unused__));} +# elif (LZO_CC_MSC && (_MSC_VER < 900)) +# define LZO_COMPILE_TIME_ASSERT(e) switch(0) case 1:case !(e):break; +# elif (LZO_CC_TURBOC && (__TURBOC__ == 0x0295)) +# define LZO_COMPILE_TIME_ASSERT(e) switch(0) case 1:case !(e):break; +# else +# define LZO_COMPILE_TIME_ASSERT(e) {typedef int __LZO_CTA_NAME(lzo_cta_t__)[1-2*!(e)];} +# endif +#endif +#if (LZO_LANG_ASSEMBLER) +# undef LZO_COMPILE_TIME_ASSERT_HEADER +# define LZO_COMPILE_TIME_ASSERT_HEADER(e) /*empty*/ +#else +LZO_COMPILE_TIME_ASSERT_HEADER(1 == 1) +#if defined(__cplusplus) +extern "C" { LZO_COMPILE_TIME_ASSERT_HEADER(2 == 2) } +#endif +LZO_COMPILE_TIME_ASSERT_HEADER(3 == 3) +#endif +#if (LZO_ARCH_I086 || LZO_ARCH_I386) && (LZO_OS_DOS16 || LZO_OS_DOS32 || LZO_OS_OS2 || LZO_OS_OS216 || LZO_OS_WIN16 || LZO_OS_WIN32 || LZO_OS_WIN64) +# if (LZO_CC_GNUC || LZO_CC_HIGHC || LZO_CC_NDPC || LZO_CC_PACIFICC) +# elif (LZO_CC_DMC || LZO_CC_SYMANTECC || LZO_CC_ZORTECHC) +# define __lzo_cdecl __cdecl +# define __lzo_cdecl_atexit /*empty*/ +# define __lzo_cdecl_main __cdecl +# if (LZO_OS_OS2 && (LZO_CC_DMC || LZO_CC_SYMANTECC)) +# define __lzo_cdecl_qsort __pascal +# elif (LZO_OS_OS2 && (LZO_CC_ZORTECHC)) +# define __lzo_cdecl_qsort _stdcall +# else +# define __lzo_cdecl_qsort __cdecl +# endif +# elif (LZO_CC_WATCOMC) +# define __lzo_cdecl __cdecl +# else +# define __lzo_cdecl __cdecl +# define __lzo_cdecl_atexit __cdecl +# define __lzo_cdecl_main __cdecl +# define __lzo_cdecl_qsort __cdecl +# endif +# if (LZO_CC_GNUC || LZO_CC_HIGHC || LZO_CC_NDPC || LZO_CC_PACIFICC || LZO_CC_WATCOMC) +# elif (LZO_OS_OS2 && (LZO_CC_DMC || LZO_CC_SYMANTECC)) +# define __lzo_cdecl_sighandler __pascal +# elif (LZO_OS_OS2 && (LZO_CC_ZORTECHC)) +# define __lzo_cdecl_sighandler _stdcall +# elif (LZO_CC_MSC && (_MSC_VER >= 1400)) && defined(_M_CEE_PURE) +# define __lzo_cdecl_sighandler __clrcall +# elif (LZO_CC_MSC && (_MSC_VER >= 600 && _MSC_VER < 700)) +# if defined(_DLL) +# define __lzo_cdecl_sighandler _far _cdecl _loadds +# elif defined(_MT) +# define __lzo_cdecl_sighandler _far _cdecl +# else +# define __lzo_cdecl_sighandler _cdecl +# endif +# else +# define __lzo_cdecl_sighandler __cdecl +# endif +#elif (LZO_ARCH_I386) && (LZO_CC_WATCOMC) +# define __lzo_cdecl __cdecl +#elif (LZO_ARCH_M68K && LZO_OS_TOS && (LZO_CC_PUREC || LZO_CC_TURBOC)) +# define __lzo_cdecl cdecl +#endif +#if !defined(__lzo_cdecl) +# define __lzo_cdecl /*empty*/ +#endif +#if !defined(__lzo_cdecl_atexit) +# define __lzo_cdecl_atexit /*empty*/ +#endif +#if !defined(__lzo_cdecl_main) +# define __lzo_cdecl_main /*empty*/ +#endif +#if !defined(__lzo_cdecl_qsort) +# define __lzo_cdecl_qsort /*empty*/ +#endif +#if !defined(__lzo_cdecl_sighandler) +# define __lzo_cdecl_sighandler /*empty*/ +#endif +#if !defined(__lzo_cdecl_va) +# define __lzo_cdecl_va __lzo_cdecl +#endif +#if !(LZO_CFG_NO_WINDOWS_H) +#if !defined(LZO_HAVE_WINDOWS_H) +#if (LZO_OS_CYGWIN || (LZO_OS_EMX && defined(__RSXNT__)) || LZO_OS_WIN32 || LZO_OS_WIN64) +# if (LZO_CC_WATCOMC && (__WATCOMC__ < 1000)) +# elif ((LZO_OS_WIN32 && defined(__PW32__)) && (LZO_CC_GNUC && (LZO_CC_GNUC < 0x030000ul))) +# elif ((LZO_OS_CYGWIN || defined(__MINGW32__)) && (LZO_CC_GNUC && (LZO_CC_GNUC < 0x025f00ul))) +# else +# define LZO_HAVE_WINDOWS_H 1 +# endif +#endif +#endif +#endif +#define LZO_SIZEOF_CHAR 1 +#ifndef LZO_SIZEOF_SHORT +#if defined(SIZEOF_SHORT) +# define LZO_SIZEOF_SHORT (SIZEOF_SHORT) +#elif defined(__SIZEOF_SHORT__) +# define LZO_SIZEOF_SHORT (__SIZEOF_SHORT__) +#endif +#endif +#ifndef LZO_SIZEOF_INT +#if defined(SIZEOF_INT) +# define LZO_SIZEOF_INT (SIZEOF_INT) +#elif defined(__SIZEOF_INT__) +# define LZO_SIZEOF_INT (__SIZEOF_INT__) +#endif +#endif +#ifndef LZO_SIZEOF_LONG +#if defined(SIZEOF_LONG) +# define LZO_SIZEOF_LONG (SIZEOF_LONG) +#elif defined(__SIZEOF_LONG__) +# define LZO_SIZEOF_LONG (__SIZEOF_LONG__) +#endif +#endif +#ifndef LZO_SIZEOF_LONG_LONG +#if defined(SIZEOF_LONG_LONG) +# define LZO_SIZEOF_LONG_LONG (SIZEOF_LONG_LONG) +#elif defined(__SIZEOF_LONG_LONG__) +# define LZO_SIZEOF_LONG_LONG (__SIZEOF_LONG_LONG__) +#endif +#endif +#ifndef LZO_SIZEOF___INT16 +#if defined(SIZEOF___INT16) +# define LZO_SIZEOF___INT16 (SIZEOF___INT16) +#endif +#endif +#ifndef LZO_SIZEOF___INT32 +#if defined(SIZEOF___INT32) +# define LZO_SIZEOF___INT32 (SIZEOF___INT32) +#endif +#endif +#ifndef LZO_SIZEOF___INT64 +#if defined(SIZEOF___INT64) +# define LZO_SIZEOF___INT64 (SIZEOF___INT64) +#endif +#endif +#ifndef LZO_SIZEOF_VOID_P +#if defined(SIZEOF_VOID_P) +# define LZO_SIZEOF_VOID_P (SIZEOF_VOID_P) +#elif defined(__SIZEOF_POINTER__) +# define LZO_SIZEOF_VOID_P (__SIZEOF_POINTER__) +#endif +#endif +#ifndef LZO_SIZEOF_SIZE_T +#if defined(SIZEOF_SIZE_T) +# define LZO_SIZEOF_SIZE_T (SIZEOF_SIZE_T) +#elif defined(__SIZEOF_SIZE_T__) +# define LZO_SIZEOF_SIZE_T (__SIZEOF_SIZE_T__) +#endif +#endif +#ifndef LZO_SIZEOF_PTRDIFF_T +#if defined(SIZEOF_PTRDIFF_T) +# define LZO_SIZEOF_PTRDIFF_T (SIZEOF_PTRDIFF_T) +#elif defined(__SIZEOF_PTRDIFF_T__) +# define LZO_SIZEOF_PTRDIFF_T (__SIZEOF_PTRDIFF_T__) +#endif +#endif +#define __LZO_LSR(x,b) (((x)+0ul) >> (b)) +#if !defined(LZO_SIZEOF_SHORT) +# if (LZO_ARCH_CRAY_PVP) +# define LZO_SIZEOF_SHORT 8 +# elif (USHRT_MAX == LZO_0xffffL) +# define LZO_SIZEOF_SHORT 2 +# elif (__LZO_LSR(USHRT_MAX,7) == 1) +# define LZO_SIZEOF_SHORT 1 +# elif (__LZO_LSR(USHRT_MAX,15) == 1) +# define LZO_SIZEOF_SHORT 2 +# elif (__LZO_LSR(USHRT_MAX,31) == 1) +# define LZO_SIZEOF_SHORT 4 +# elif (__LZO_LSR(USHRT_MAX,63) == 1) +# define LZO_SIZEOF_SHORT 8 +# elif (__LZO_LSR(USHRT_MAX,127) == 1) +# define LZO_SIZEOF_SHORT 16 +# else +# error "LZO_SIZEOF_SHORT" +# endif +#endif +LZO_COMPILE_TIME_ASSERT_HEADER(LZO_SIZEOF_SHORT == sizeof(short)) +#if !defined(LZO_SIZEOF_INT) +# if (LZO_ARCH_CRAY_PVP) +# define LZO_SIZEOF_INT 8 +# elif (UINT_MAX == LZO_0xffffL) +# define LZO_SIZEOF_INT 2 +# elif (UINT_MAX == LZO_0xffffffffL) +# define LZO_SIZEOF_INT 4 +# elif (__LZO_LSR(UINT_MAX,7) == 1) +# define LZO_SIZEOF_INT 1 +# elif (__LZO_LSR(UINT_MAX,15) == 1) +# define LZO_SIZEOF_INT 2 +# elif (__LZO_LSR(UINT_MAX,31) == 1) +# define LZO_SIZEOF_INT 4 +# elif (__LZO_LSR(UINT_MAX,63) == 1) +# define LZO_SIZEOF_INT 8 +# elif (__LZO_LSR(UINT_MAX,127) == 1) +# define LZO_SIZEOF_INT 16 +# else +# error "LZO_SIZEOF_INT" +# endif +#endif +LZO_COMPILE_TIME_ASSERT_HEADER(LZO_SIZEOF_INT == sizeof(int)) +#if !defined(LZO_SIZEOF_LONG) +# if (ULONG_MAX == LZO_0xffffffffL) +# define LZO_SIZEOF_LONG 4 +# elif (__LZO_LSR(ULONG_MAX,7) == 1) +# define LZO_SIZEOF_LONG 1 +# elif (__LZO_LSR(ULONG_MAX,15) == 1) +# define LZO_SIZEOF_LONG 2 +# elif (__LZO_LSR(ULONG_MAX,31) == 1) +# define LZO_SIZEOF_LONG 4 +# elif (__LZO_LSR(ULONG_MAX,39) == 1) +# define LZO_SIZEOF_LONG 5 +# elif (__LZO_LSR(ULONG_MAX,63) == 1) +# define LZO_SIZEOF_LONG 8 +# elif (__LZO_LSR(ULONG_MAX,127) == 1) +# define LZO_SIZEOF_LONG 16 +# else +# error "LZO_SIZEOF_LONG" +# endif +#endif +LZO_COMPILE_TIME_ASSERT_HEADER(LZO_SIZEOF_LONG == sizeof(long)) +#if !defined(LZO_SIZEOF_LONG_LONG) && !defined(LZO_SIZEOF___INT64) +#if (LZO_SIZEOF_LONG > 0 && LZO_SIZEOF_LONG < 8) +# if defined(__LONG_MAX__) && defined(__LONG_LONG_MAX__) +# if (LZO_CC_GNUC >= 0x030300ul) +# if ((__LONG_MAX__-0) == (__LONG_LONG_MAX__-0)) +# define LZO_SIZEOF_LONG_LONG LZO_SIZEOF_LONG +# elif (__LZO_LSR(__LONG_LONG_MAX__,30) == 1) +# define LZO_SIZEOF_LONG_LONG 4 +# endif +# endif +# endif +#endif +#endif +#if !defined(LZO_SIZEOF_LONG_LONG) && !defined(LZO_SIZEOF___INT64) +#if (LZO_SIZEOF_LONG > 0 && LZO_SIZEOF_LONG < 8) +#if (LZO_ARCH_I086 && LZO_CC_DMC) +#elif (LZO_CC_CILLY) && defined(__GNUC__) +# define LZO_SIZEOF_LONG_LONG 8 +#elif (LZO_CC_ARMCC_GNUC || LZO_CC_CLANG || LZO_CC_GNUC || LZO_CC_LLVM || LZO_CC_PATHSCALE) +# define LZO_SIZEOF_LONG_LONG 8 +#elif ((LZO_OS_WIN32 || LZO_OS_WIN64 || defined(_WIN32)) && LZO_CC_MSC && (_MSC_VER >= 1400)) +# define LZO_SIZEOF_LONG_LONG 8 +#elif (LZO_OS_WIN64 || defined(_WIN64)) +# define LZO_SIZEOF___INT64 8 +#elif (LZO_ARCH_I386 && (LZO_CC_DMC)) +# define LZO_SIZEOF_LONG_LONG 8 +#elif (LZO_ARCH_I386 && (LZO_CC_SYMANTECC && (__SC__ >= 0x700))) +# define LZO_SIZEOF_LONG_LONG 8 +#elif (LZO_ARCH_I386 && (LZO_CC_INTELC && defined(__linux__))) +# define LZO_SIZEOF_LONG_LONG 8 +#elif (LZO_ARCH_I386 && (LZO_CC_MWERKS || LZO_CC_PELLESC || LZO_CC_PGI || LZO_CC_SUNPROC)) +# define LZO_SIZEOF_LONG_LONG 8 +#elif (LZO_ARCH_I386 && (LZO_CC_INTELC || LZO_CC_MSC)) +# define LZO_SIZEOF___INT64 8 +#elif ((LZO_OS_WIN32 || defined(_WIN32)) && (LZO_CC_MSC)) +# define LZO_SIZEOF___INT64 8 +#elif (LZO_ARCH_I386 && (LZO_CC_BORLANDC && (__BORLANDC__ >= 0x0520))) +# define LZO_SIZEOF___INT64 8 +#elif (LZO_ARCH_I386 && (LZO_CC_WATCOMC && (__WATCOMC__ >= 1100))) +# define LZO_SIZEOF___INT64 8 +#elif (LZO_CC_GHS && defined(__LLONG_BIT) && ((__LLONG_BIT-0) == 64)) +# define LZO_SIZEOF_LONG_LONG 8 +#elif (LZO_CC_WATCOMC && defined(_INTEGRAL_MAX_BITS) && ((_INTEGRAL_MAX_BITS-0) == 64)) +# define LZO_SIZEOF___INT64 8 +#elif (LZO_OS_OS400 || defined(__OS400__)) && defined(__LLP64_IFC__) +# define LZO_SIZEOF_LONG_LONG 8 +#elif (defined(__vms) || defined(__VMS)) && ((__INITIAL_POINTER_SIZE-0) == 64) +# define LZO_SIZEOF_LONG_LONG 8 +#elif (LZO_CC_SDCC) && (LZO_SIZEOF_INT == 2) +#elif 1 && defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +# define LZO_SIZEOF_LONG_LONG 8 +#endif +#endif +#endif +#if defined(__cplusplus) && (LZO_CC_GNUC) +# if (LZO_CC_GNUC < 0x020800ul) +# undef LZO_SIZEOF_LONG_LONG +# endif +#endif +#if (LZO_CFG_NO_LONG_LONG) +# undef LZO_SIZEOF_LONG_LONG +#elif defined(__NO_LONG_LONG) +# undef LZO_SIZEOF_LONG_LONG +#elif defined(_NO_LONGLONG) +# undef LZO_SIZEOF_LONG_LONG +#endif +#if !defined(LZO_WORDSIZE) +#if (LZO_ARCH_ALPHA) +# define LZO_WORDSIZE 8 +#elif (LZO_ARCH_AMD64) +# define LZO_WORDSIZE 8 +#elif (LZO_ARCH_ARM64) +# define LZO_WORDSIZE 8 +#elif (LZO_ARCH_AVR) +# define LZO_WORDSIZE 1 +#elif (LZO_ARCH_H8300) +# if defined(__H8300H__) || defined(__H8300S__) || defined(__H8300SX__) +# define LZO_WORDSIZE 4 +# else +# define LZO_WORDSIZE 2 +# endif +#elif (LZO_ARCH_I086) +# define LZO_WORDSIZE 2 +#elif (LZO_ARCH_IA64) +# define LZO_WORDSIZE 8 +#elif (LZO_ARCH_M16C) +# define LZO_WORDSIZE 2 +#elif (LZO_ARCH_SPU) +# define LZO_WORDSIZE 4 +#elif (LZO_ARCH_Z80) +# define LZO_WORDSIZE 1 +#elif (LZO_SIZEOF_LONG == 8) && ((defined(__mips__) && defined(__R5900__)) || defined(__MIPS_PSX2__)) +# define LZO_WORDSIZE 8 +#elif (LZO_OS_OS400 || defined(__OS400__)) +# define LZO_WORDSIZE 8 +#elif (defined(__vms) || defined(__VMS)) && (__INITIAL_POINTER_SIZE+0 == 64) +# define LZO_WORDSIZE 8 +#endif +#endif +#if !defined(LZO_SIZEOF_VOID_P) +#if defined(__ILP32__) || defined(__ILP32) || defined(_ILP32) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(int) == 4) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(long) == 4) +# define LZO_SIZEOF_VOID_P 4 +#elif defined(__ILP64__) || defined(__ILP64) || defined(_ILP64) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(int) == 8) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(long) == 8) +# define LZO_SIZEOF_VOID_P 8 +#elif defined(__LLP64__) || defined(__LLP64) || defined(_LLP64) || defined(_WIN64) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(long) == 4) +# define LZO_SIZEOF_VOID_P 8 +#elif defined(__LP64__) || defined(__LP64) || defined(_LP64) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(long) == 8) +# define LZO_SIZEOF_VOID_P 8 +#elif (LZO_ARCH_AVR) +# define LZO_SIZEOF_VOID_P 2 +#elif (LZO_ARCH_C166 || LZO_ARCH_MCS51 || LZO_ARCH_MCS251 || LZO_ARCH_MSP430) +# define LZO_SIZEOF_VOID_P 2 +#elif (LZO_ARCH_H8300) +# if defined(__H8300H__) || defined(__H8300S__) || defined(__H8300SX__) + LZO_COMPILE_TIME_ASSERT_HEADER(LZO_WORDSIZE == 4) +# if defined(__NORMAL_MODE__) +# define LZO_SIZEOF_VOID_P 2 +# else +# define LZO_SIZEOF_VOID_P 4 +# endif +# else + LZO_COMPILE_TIME_ASSERT_HEADER(LZO_WORDSIZE == 2) +# define LZO_SIZEOF_VOID_P 2 +# endif +# if (LZO_CC_GNUC && (LZO_CC_GNUC < 0x040000ul)) && (LZO_SIZEOF_INT == 4) +# define LZO_SIZEOF_SIZE_T LZO_SIZEOF_INT +# define LZO_SIZEOF_PTRDIFF_T LZO_SIZEOF_INT +# endif +#elif (LZO_ARCH_I086) +# if (LZO_MM_TINY || LZO_MM_SMALL || LZO_MM_MEDIUM) +# define LZO_SIZEOF_VOID_P 2 +# elif (LZO_MM_COMPACT || LZO_MM_LARGE || LZO_MM_HUGE) +# define LZO_SIZEOF_VOID_P 4 +# else +# error "invalid LZO_ARCH_I086 memory model" +# endif +#elif (LZO_ARCH_M16C) +# if defined(__m32c_cpu__) || defined(__m32cm_cpu__) +# define LZO_SIZEOF_VOID_P 4 +# else +# define LZO_SIZEOF_VOID_P 2 +# endif +#elif (LZO_ARCH_SPU) +# define LZO_SIZEOF_VOID_P 4 +#elif (LZO_ARCH_Z80) +# define LZO_SIZEOF_VOID_P 2 +#elif (LZO_SIZEOF_LONG == 8) && ((defined(__mips__) && defined(__R5900__)) || defined(__MIPS_PSX2__)) +# define LZO_SIZEOF_VOID_P 4 +#elif (LZO_OS_OS400 || defined(__OS400__)) +# if defined(__LLP64_IFC__) +# define LZO_SIZEOF_VOID_P 8 +# define LZO_SIZEOF_SIZE_T LZO_SIZEOF_LONG +# define LZO_SIZEOF_PTRDIFF_T LZO_SIZEOF_LONG +# else +# define LZO_SIZEOF_VOID_P 16 +# define LZO_SIZEOF_SIZE_T LZO_SIZEOF_LONG +# define LZO_SIZEOF_PTRDIFF_T LZO_SIZEOF_LONG +# endif +#elif (defined(__vms) || defined(__VMS)) && (__INITIAL_POINTER_SIZE+0 == 64) +# define LZO_SIZEOF_VOID_P 8 +# define LZO_SIZEOF_SIZE_T LZO_SIZEOF_LONG +# define LZO_SIZEOF_PTRDIFF_T LZO_SIZEOF_LONG +#endif +#endif +#if !defined(LZO_SIZEOF_VOID_P) +# define LZO_SIZEOF_VOID_P LZO_SIZEOF_LONG +#endif +LZO_COMPILE_TIME_ASSERT_HEADER(LZO_SIZEOF_VOID_P == sizeof(void *)) +#if !defined(LZO_SIZEOF_SIZE_T) +#if (LZO_ARCH_I086 || LZO_ARCH_M16C) +# define LZO_SIZEOF_SIZE_T 2 +#endif +#endif +#if !defined(LZO_SIZEOF_SIZE_T) +# define LZO_SIZEOF_SIZE_T LZO_SIZEOF_VOID_P +#endif +#if defined(offsetof) +LZO_COMPILE_TIME_ASSERT_HEADER(LZO_SIZEOF_SIZE_T == sizeof(size_t)) +#endif +#if !defined(LZO_SIZEOF_PTRDIFF_T) +#if (LZO_ARCH_I086) +# if (LZO_MM_TINY || LZO_MM_SMALL || LZO_MM_MEDIUM || LZO_MM_HUGE) +# define LZO_SIZEOF_PTRDIFF_T LZO_SIZEOF_VOID_P +# elif (LZO_MM_COMPACT || LZO_MM_LARGE) +# if (LZO_CC_BORLANDC || LZO_CC_TURBOC) +# define LZO_SIZEOF_PTRDIFF_T 4 +# else +# define LZO_SIZEOF_PTRDIFF_T 2 +# endif +# else +# error "invalid LZO_ARCH_I086 memory model" +# endif +#endif +#endif +#if !defined(LZO_SIZEOF_PTRDIFF_T) +# define LZO_SIZEOF_PTRDIFF_T LZO_SIZEOF_SIZE_T +#endif +#if defined(offsetof) +LZO_COMPILE_TIME_ASSERT_HEADER(LZO_SIZEOF_PTRDIFF_T == sizeof(ptrdiff_t)) +#endif +#if !defined(LZO_WORDSIZE) +# define LZO_WORDSIZE LZO_SIZEOF_VOID_P +#endif +#if (LZO_ABI_NEUTRAL_ENDIAN) +# undef LZO_ABI_BIG_ENDIAN +# undef LZO_ABI_LITTLE_ENDIAN +#elif !(LZO_ABI_BIG_ENDIAN) && !(LZO_ABI_LITTLE_ENDIAN) +#if (LZO_ARCH_ALPHA) && (LZO_ARCH_CRAY_MPP) +# define LZO_ABI_BIG_ENDIAN 1 +#elif (LZO_ARCH_IA64) && (LZO_OS_POSIX_LINUX || LZO_OS_WIN64) +# define LZO_ABI_LITTLE_ENDIAN 1 +#elif (LZO_ARCH_ALPHA || LZO_ARCH_AMD64 || LZO_ARCH_BLACKFIN || LZO_ARCH_CRIS || LZO_ARCH_I086 || LZO_ARCH_I386 || LZO_ARCH_MSP430 || LZO_ARCH_RISCV) +# define LZO_ABI_LITTLE_ENDIAN 1 +#elif (LZO_ARCH_AVR32 || LZO_ARCH_M68K || LZO_ARCH_S390 || LZO_ARCH_SPU) +# define LZO_ABI_BIG_ENDIAN 1 +#elif 1 && defined(__IAR_SYSTEMS_ICC__) && defined(__LITTLE_ENDIAN__) +# if (__LITTLE_ENDIAN__ == 1) +# define LZO_ABI_LITTLE_ENDIAN 1 +# else +# define LZO_ABI_BIG_ENDIAN 1 +# endif +#elif 1 && defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) +# define LZO_ABI_BIG_ENDIAN 1 +#elif 1 && defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__) +# define LZO_ABI_LITTLE_ENDIAN 1 +#elif 1 && (LZO_ARCH_ARM) && defined(__ARM_BIG_ENDIAN) && ((__ARM_BIG_ENDIAN)+0) +# define LZO_ABI_BIG_ENDIAN 1 +#elif 1 && (LZO_ARCH_ARM) && defined(__ARMEB__) && !defined(__ARMEL__) +# define LZO_ABI_BIG_ENDIAN 1 +#elif 1 && (LZO_ARCH_ARM) && defined(__ARMEL__) && !defined(__ARMEB__) +# define LZO_ABI_LITTLE_ENDIAN 1 +#elif 1 && (LZO_ARCH_ARM) && defined(_MSC_VER) && defined(_WIN32) +# define LZO_ABI_LITTLE_ENDIAN 1 +#elif 1 && (LZO_ARCH_ARM && LZO_CC_ARMCC_ARMCC) +# if defined(__BIG_ENDIAN) && defined(__LITTLE_ENDIAN) +# error "unexpected configuration - check your compiler defines" +# elif defined(__BIG_ENDIAN) +# define LZO_ABI_BIG_ENDIAN 1 +# else +# define LZO_ABI_LITTLE_ENDIAN 1 +# endif +# define LZO_ABI_LITTLE_ENDIAN 1 +#elif 1 && (LZO_ARCH_ARM64) && defined(__ARM_BIG_ENDIAN) && ((__ARM_BIG_ENDIAN)+0) +# define LZO_ABI_BIG_ENDIAN 1 +#elif 1 && (LZO_ARCH_ARM64) && defined(__AARCH64EB__) && !defined(__AARCH64EL__) +# define LZO_ABI_BIG_ENDIAN 1 +#elif 1 && (LZO_ARCH_ARM64) && defined(__AARCH64EL__) && !defined(__AARCH64EB__) +# define LZO_ABI_LITTLE_ENDIAN 1 +#elif 1 && (LZO_ARCH_ARM64) && defined(_MSC_VER) && defined(_WIN32) +# define LZO_ABI_LITTLE_ENDIAN 1 +#elif 1 && (LZO_ARCH_MIPS) && defined(__MIPSEB__) && !defined(__MIPSEL__) +# define LZO_ABI_BIG_ENDIAN 1 +#elif 1 && (LZO_ARCH_MIPS) && defined(__MIPSEL__) && !defined(__MIPSEB__) +# define LZO_ABI_LITTLE_ENDIAN 1 +#endif +#endif +#if (LZO_ABI_BIG_ENDIAN) && (LZO_ABI_LITTLE_ENDIAN) +# error "unexpected configuration - check your compiler defines" +#endif +#if (LZO_ABI_BIG_ENDIAN) +# define LZO_INFO_ABI_ENDIAN "be" +#elif (LZO_ABI_LITTLE_ENDIAN) +# define LZO_INFO_ABI_ENDIAN "le" +#elif (LZO_ABI_NEUTRAL_ENDIAN) +# define LZO_INFO_ABI_ENDIAN "neutral" +#endif +#if (LZO_SIZEOF_INT == 1 && LZO_SIZEOF_LONG == 2 && LZO_SIZEOF_VOID_P == 2) +# define LZO_ABI_I8LP16 1 +# define LZO_INFO_ABI_PM "i8lp16" +#elif (LZO_SIZEOF_INT == 2 && LZO_SIZEOF_LONG == 2 && LZO_SIZEOF_VOID_P == 2) +# define LZO_ABI_ILP16 1 +# define LZO_INFO_ABI_PM "ilp16" +#elif (LZO_SIZEOF_INT == 2 && LZO_SIZEOF_LONG == 4 && LZO_SIZEOF_VOID_P == 4) +# define LZO_ABI_LP32 1 +# define LZO_INFO_ABI_PM "lp32" +#elif (LZO_SIZEOF_INT == 4 && LZO_SIZEOF_LONG == 4 && LZO_SIZEOF_VOID_P == 4) +# define LZO_ABI_ILP32 1 +# define LZO_INFO_ABI_PM "ilp32" +#elif (LZO_SIZEOF_INT == 4 && LZO_SIZEOF_LONG == 4 && LZO_SIZEOF_VOID_P == 8 && LZO_SIZEOF_SIZE_T == 8) +# define LZO_ABI_LLP64 1 +# define LZO_INFO_ABI_PM "llp64" +#elif (LZO_SIZEOF_INT == 4 && LZO_SIZEOF_LONG == 8 && LZO_SIZEOF_VOID_P == 8) +# define LZO_ABI_LP64 1 +# define LZO_INFO_ABI_PM "lp64" +#elif (LZO_SIZEOF_INT == 8 && LZO_SIZEOF_LONG == 8 && LZO_SIZEOF_VOID_P == 8) +# define LZO_ABI_ILP64 1 +# define LZO_INFO_ABI_PM "ilp64" +#elif (LZO_SIZEOF_INT == 4 && LZO_SIZEOF_LONG == 8 && LZO_SIZEOF_VOID_P == 4) +# define LZO_ABI_IP32L64 1 +# define LZO_INFO_ABI_PM "ip32l64" +#endif +#if (LZO_SIZEOF_INT == 4 && LZO_SIZEOF_VOID_P == 4 && LZO_WORDSIZE == 8) +# define LZO_ABI_IP32W64 1 +# ifndef LZO_INFO_ABI_PM +# define LZO_INFO_ABI_PM "ip32w64" +# endif +#endif +#if 0 +#elif !defined(__LZO_LIBC_OVERRIDE) +#if (LZO_LIBC_NAKED) +# define LZO_INFO_LIBC "naked" +#elif (LZO_LIBC_FREESTANDING) +# define LZO_INFO_LIBC "freestanding" +#elif (LZO_LIBC_MOSTLY_FREESTANDING) +# define LZO_INFO_LIBC "mfreestanding" +#elif (LZO_LIBC_ISOC90) +# define LZO_INFO_LIBC "isoc90" +#elif (LZO_LIBC_ISOC99) +# define LZO_INFO_LIBC "isoc99" +#elif (LZO_CC_ARMCC_ARMCC) && defined(__ARMCLIB_VERSION) +# define LZO_LIBC_ISOC90 1 +# define LZO_INFO_LIBC "isoc90" +#elif defined(__dietlibc__) +# define LZO_LIBC_DIETLIBC 1 +# define LZO_INFO_LIBC "dietlibc" +#elif defined(_NEWLIB_VERSION) +# define LZO_LIBC_NEWLIB 1 +# define LZO_INFO_LIBC "newlib" +#elif defined(__UCLIBC__) && defined(__UCLIBC_MAJOR__) && defined(__UCLIBC_MINOR__) +# if defined(__UCLIBC_SUBLEVEL__) +# define LZO_LIBC_UCLIBC (__UCLIBC_MAJOR__ * 0x10000L + (__UCLIBC_MINOR__-0) * 0x100 + (__UCLIBC_SUBLEVEL__-0)) +# else +# define LZO_LIBC_UCLIBC 0x00090bL +# endif +# define LZO_INFO_LIBC "uc" "libc" +#elif defined(__GLIBC__) && defined(__GLIBC_MINOR__) +# define LZO_LIBC_GLIBC (__GLIBC__ * 0x10000L + (__GLIBC_MINOR__-0) * 0x100) +# define LZO_INFO_LIBC "glibc" +#elif (LZO_CC_MWERKS) && defined(__MSL__) +# define LZO_LIBC_MSL __MSL__ +# define LZO_INFO_LIBC "msl" +#elif 1 && defined(__IAR_SYSTEMS_ICC__) +# define LZO_LIBC_ISOC90 1 +# define LZO_INFO_LIBC "isoc90" +#else +# define LZO_LIBC_DEFAULT 1 +# define LZO_INFO_LIBC "default" +#endif +#endif +#if (LZO_ARCH_I386 && (LZO_OS_DOS32 || LZO_OS_WIN32) && (LZO_CC_DMC || LZO_CC_INTELC || LZO_CC_MSC || LZO_CC_PELLESC)) +# define LZO_ASM_SYNTAX_MSC 1 +#elif (LZO_OS_WIN64 && (LZO_CC_DMC || LZO_CC_INTELC || LZO_CC_MSC || LZO_CC_PELLESC)) +#elif (LZO_ARCH_I386 && LZO_CC_GNUC && (LZO_CC_GNUC == 0x011f00ul)) +#elif (LZO_ARCH_I386 && (LZO_CC_CLANG || LZO_CC_GNUC || LZO_CC_INTELC || LZO_CC_PATHSCALE)) +# define LZO_ASM_SYNTAX_GNUC 1 +#elif (LZO_ARCH_AMD64 && (LZO_CC_CLANG || LZO_CC_GNUC || LZO_CC_INTELC || LZO_CC_PATHSCALE)) +# define LZO_ASM_SYNTAX_GNUC 1 +#elif (LZO_CC_GNUC) +# define LZO_ASM_SYNTAX_GNUC 1 +#endif +#if (LZO_ASM_SYNTAX_GNUC) +#if (LZO_ARCH_I386 && LZO_CC_GNUC && (LZO_CC_GNUC < 0x020000ul)) +# define __LZO_ASM_CLOBBER "ax" +# define __LZO_ASM_CLOBBER_LIST_CC /*empty*/ +# define __LZO_ASM_CLOBBER_LIST_CC_MEMORY /*empty*/ +# define __LZO_ASM_CLOBBER_LIST_EMPTY /*empty*/ +#elif (LZO_CC_INTELC && (__INTEL_COMPILER < 1000)) +# define __LZO_ASM_CLOBBER "memory" +# define __LZO_ASM_CLOBBER_LIST_CC /*empty*/ +# define __LZO_ASM_CLOBBER_LIST_CC_MEMORY : "memory" +# define __LZO_ASM_CLOBBER_LIST_EMPTY /*empty*/ +#else +# define __LZO_ASM_CLOBBER "cc", "memory" +# define __LZO_ASM_CLOBBER_LIST_CC : "cc" +# define __LZO_ASM_CLOBBER_LIST_CC_MEMORY : "cc", "memory" +# define __LZO_ASM_CLOBBER_LIST_EMPTY /*empty*/ +#endif +#endif +#if (LZO_ARCH_ALPHA) +# define LZO_OPT_AVOID_UINT_INDEX 1 +#elif (LZO_ARCH_AMD64) +# define LZO_OPT_AVOID_INT_INDEX 1 +# define LZO_OPT_AVOID_UINT_INDEX 1 +# ifndef LZO_OPT_UNALIGNED16 +# define LZO_OPT_UNALIGNED16 1 +# endif +# ifndef LZO_OPT_UNALIGNED32 +# define LZO_OPT_UNALIGNED32 1 +# endif +# ifndef LZO_OPT_UNALIGNED64 +# define LZO_OPT_UNALIGNED64 1 +# endif +#elif (LZO_ARCH_ARM) +# if defined(__ARM_FEATURE_UNALIGNED) +# if ((__ARM_FEATURE_UNALIGNED)+0) +# ifndef LZO_OPT_UNALIGNED16 +# define LZO_OPT_UNALIGNED16 1 +# endif +# ifndef LZO_OPT_UNALIGNED32 +# define LZO_OPT_UNALIGNED32 1 +# endif +# endif +# elif 1 && (LZO_ARCH_ARM_THUMB2) +# ifndef LZO_OPT_UNALIGNED16 +# define LZO_OPT_UNALIGNED16 1 +# endif +# ifndef LZO_OPT_UNALIGNED32 +# define LZO_OPT_UNALIGNED32 1 +# endif +# elif 1 && defined(__ARM_ARCH) && ((__ARM_ARCH)+0 >= 7) +# ifndef LZO_OPT_UNALIGNED16 +# define LZO_OPT_UNALIGNED16 1 +# endif +# ifndef LZO_OPT_UNALIGNED32 +# define LZO_OPT_UNALIGNED32 1 +# endif +# elif 1 && defined(__TARGET_ARCH_ARM) && ((__TARGET_ARCH_ARM)+0 >= 7) +# ifndef LZO_OPT_UNALIGNED16 +# define LZO_OPT_UNALIGNED16 1 +# endif +# ifndef LZO_OPT_UNALIGNED32 +# define LZO_OPT_UNALIGNED32 1 +# endif +# elif 1 && defined(__TARGET_ARCH_ARM) && ((__TARGET_ARCH_ARM)+0 >= 6) && (defined(__TARGET_PROFILE_A) || defined(__TARGET_PROFILE_R)) +# ifndef LZO_OPT_UNALIGNED16 +# define LZO_OPT_UNALIGNED16 1 +# endif +# ifndef LZO_OPT_UNALIGNED32 +# define LZO_OPT_UNALIGNED32 1 +# endif +# elif 1 && defined(_MSC_VER) && defined(_M_ARM) && ((_M_ARM)+0 >= 7) +# ifndef LZO_OPT_UNALIGNED16 +# define LZO_OPT_UNALIGNED16 1 +# endif +# ifndef LZO_OPT_UNALIGNED32 +# define LZO_OPT_UNALIGNED32 1 +# endif +# endif +#elif (LZO_ARCH_ARM64) +# ifndef LZO_OPT_UNALIGNED16 +# define LZO_OPT_UNALIGNED16 1 +# endif +# ifndef LZO_OPT_UNALIGNED32 +# define LZO_OPT_UNALIGNED32 1 +# endif +# ifndef LZO_OPT_UNALIGNED64 +# define LZO_OPT_UNALIGNED64 1 +# endif +#elif (LZO_ARCH_CRIS) +# ifndef LZO_OPT_UNALIGNED16 +# define LZO_OPT_UNALIGNED16 1 +# endif +# ifndef LZO_OPT_UNALIGNED32 +# define LZO_OPT_UNALIGNED32 1 +# endif +#elif (LZO_ARCH_I386) +# ifndef LZO_OPT_UNALIGNED16 +# define LZO_OPT_UNALIGNED16 1 +# endif +# ifndef LZO_OPT_UNALIGNED32 +# define LZO_OPT_UNALIGNED32 1 +# endif +#elif (LZO_ARCH_IA64) +# define LZO_OPT_AVOID_INT_INDEX 1 +# define LZO_OPT_AVOID_UINT_INDEX 1 +# define LZO_OPT_PREFER_POSTINC 1 +#elif (LZO_ARCH_M68K) +# define LZO_OPT_PREFER_POSTINC 1 +# define LZO_OPT_PREFER_PREDEC 1 +# if defined(__mc68020__) && !defined(__mcoldfire__) +# ifndef LZO_OPT_UNALIGNED16 +# define LZO_OPT_UNALIGNED16 1 +# endif +# ifndef LZO_OPT_UNALIGNED32 +# define LZO_OPT_UNALIGNED32 1 +# endif +# endif +#elif (LZO_ARCH_MIPS) +# define LZO_OPT_AVOID_UINT_INDEX 1 +#elif (LZO_ARCH_POWERPC) +# define LZO_OPT_PREFER_PREINC 1 +# define LZO_OPT_PREFER_PREDEC 1 +# if (LZO_ABI_BIG_ENDIAN) || (LZO_WORDSIZE == 8) +# ifndef LZO_OPT_UNALIGNED16 +# define LZO_OPT_UNALIGNED16 1 +# endif +# ifndef LZO_OPT_UNALIGNED32 +# define LZO_OPT_UNALIGNED32 1 +# endif +# if (LZO_WORDSIZE == 8) +# ifndef LZO_OPT_UNALIGNED64 +# define LZO_OPT_UNALIGNED64 1 +# endif +# endif +# endif +#elif (LZO_ARCH_RISCV) +# define LZO_OPT_AVOID_UINT_INDEX 1 +# ifndef LZO_OPT_UNALIGNED16 +# define LZO_OPT_UNALIGNED16 1 +# endif +# ifndef LZO_OPT_UNALIGNED32 +# define LZO_OPT_UNALIGNED32 1 +# endif +# if (LZO_WORDSIZE == 8) +# ifndef LZO_OPT_UNALIGNED64 +# define LZO_OPT_UNALIGNED64 1 +# endif +# endif +#elif (LZO_ARCH_S390) +# ifndef LZO_OPT_UNALIGNED16 +# define LZO_OPT_UNALIGNED16 1 +# endif +# ifndef LZO_OPT_UNALIGNED32 +# define LZO_OPT_UNALIGNED32 1 +# endif +# if (LZO_WORDSIZE == 8) +# ifndef LZO_OPT_UNALIGNED64 +# define LZO_OPT_UNALIGNED64 1 +# endif +# endif +#elif (LZO_ARCH_SH) +# define LZO_OPT_PREFER_POSTINC 1 +# define LZO_OPT_PREFER_PREDEC 1 +#endif +#ifndef LZO_CFG_NO_INLINE_ASM +#if (LZO_ABI_NEUTRAL_ENDIAN) || (LZO_ARCH_GENERIC) +# define LZO_CFG_NO_INLINE_ASM 1 +#elif (LZO_CC_LLVM) +# define LZO_CFG_NO_INLINE_ASM 1 +#endif +#endif +#if (LZO_CFG_NO_INLINE_ASM) +# undef LZO_ASM_SYNTAX_MSC +# undef LZO_ASM_SYNTAX_GNUC +# undef __LZO_ASM_CLOBBER +# undef __LZO_ASM_CLOBBER_LIST_CC +# undef __LZO_ASM_CLOBBER_LIST_CC_MEMORY +# undef __LZO_ASM_CLOBBER_LIST_EMPTY +#endif +#ifndef LZO_CFG_NO_UNALIGNED +#if (LZO_ABI_NEUTRAL_ENDIAN) || (LZO_ARCH_GENERIC) +# define LZO_CFG_NO_UNALIGNED 1 +#endif +#endif +#if (LZO_CFG_NO_UNALIGNED) +# undef LZO_OPT_UNALIGNED16 +# undef LZO_OPT_UNALIGNED32 +# undef LZO_OPT_UNALIGNED64 +#endif +#if defined(__LZO_INFOSTR_MM) +#elif (LZO_MM_FLAT) && (defined(__LZO_INFOSTR_PM) || defined(LZO_INFO_ABI_PM)) +# define __LZO_INFOSTR_MM "" +#elif defined(LZO_INFO_MM) +# define __LZO_INFOSTR_MM "." LZO_INFO_MM +#else +# define __LZO_INFOSTR_MM "" +#endif +#if defined(__LZO_INFOSTR_PM) +#elif defined(LZO_INFO_ABI_PM) +# define __LZO_INFOSTR_PM "." LZO_INFO_ABI_PM +#else +# define __LZO_INFOSTR_PM "" +#endif +#if defined(__LZO_INFOSTR_ENDIAN) +#elif defined(LZO_INFO_ABI_ENDIAN) +# define __LZO_INFOSTR_ENDIAN "." LZO_INFO_ABI_ENDIAN +#else +# define __LZO_INFOSTR_ENDIAN "" +#endif +#if defined(__LZO_INFOSTR_OSNAME) +#elif defined(LZO_INFO_OS_CONSOLE) +# define __LZO_INFOSTR_OSNAME LZO_INFO_OS "." LZO_INFO_OS_CONSOLE +#elif defined(LZO_INFO_OS_POSIX) +# define __LZO_INFOSTR_OSNAME LZO_INFO_OS "." LZO_INFO_OS_POSIX +#else +# define __LZO_INFOSTR_OSNAME LZO_INFO_OS +#endif +#if defined(__LZO_INFOSTR_LIBC) +#elif defined(LZO_INFO_LIBC) +# define __LZO_INFOSTR_LIBC "." LZO_INFO_LIBC +#else +# define __LZO_INFOSTR_LIBC "" +#endif +#if defined(__LZO_INFOSTR_CCVER) +#elif defined(LZO_INFO_CCVER) +# define __LZO_INFOSTR_CCVER " " LZO_INFO_CCVER +#else +# define __LZO_INFOSTR_CCVER "" +#endif +#define LZO_INFO_STRING \ + LZO_INFO_ARCH __LZO_INFOSTR_MM __LZO_INFOSTR_PM __LZO_INFOSTR_ENDIAN \ + " " __LZO_INFOSTR_OSNAME __LZO_INFOSTR_LIBC " " LZO_INFO_CC __LZO_INFOSTR_CCVER +#if !(LZO_CFG_SKIP_LZO_TYPES) +#if (!(LZO_SIZEOF_SHORT+0 > 0 && LZO_SIZEOF_INT+0 > 0 && LZO_SIZEOF_LONG+0 > 0)) +# error "missing defines for sizes" +#endif +#if (!(LZO_SIZEOF_PTRDIFF_T+0 > 0 && LZO_SIZEOF_SIZE_T+0 > 0 && LZO_SIZEOF_VOID_P+0 > 0)) +# error "missing defines for sizes" +#endif +#define LZO_TYPEOF_CHAR 1u +#define LZO_TYPEOF_SHORT 2u +#define LZO_TYPEOF_INT 3u +#define LZO_TYPEOF_LONG 4u +#define LZO_TYPEOF_LONG_LONG 5u +#define LZO_TYPEOF___INT8 17u +#define LZO_TYPEOF___INT16 18u +#define LZO_TYPEOF___INT32 19u +#define LZO_TYPEOF___INT64 20u +#define LZO_TYPEOF___INT128 21u +#define LZO_TYPEOF___INT256 22u +#define LZO_TYPEOF___MODE_QI 33u +#define LZO_TYPEOF___MODE_HI 34u +#define LZO_TYPEOF___MODE_SI 35u +#define LZO_TYPEOF___MODE_DI 36u +#define LZO_TYPEOF___MODE_TI 37u +#define LZO_TYPEOF_CHAR_P 129u +#if !defined(lzo_llong_t) +#if (LZO_SIZEOF_LONG_LONG+0 > 0) +# if !(LZO_LANG_ASSEMBLER) + __lzo_gnuc_extension__ typedef long long lzo_llong_t__; + __lzo_gnuc_extension__ typedef unsigned long long lzo_ullong_t__; +# endif +# define lzo_llong_t lzo_llong_t__ +# define lzo_ullong_t lzo_ullong_t__ +#endif +#endif +#if !defined(lzo_int16e_t) +#if (LZO_CFG_PREFER_TYPEOF_ACC_INT16E_T == LZO_TYPEOF_SHORT) && (LZO_SIZEOF_SHORT != 2) +# undef LZO_CFG_PREFER_TYPEOF_ACC_INT16E_T +#endif +#if (LZO_SIZEOF_LONG == 2) && !(LZO_CFG_PREFER_TYPEOF_ACC_INT16E_T == LZO_TYPEOF_SHORT) +# define lzo_int16e_t long +# define lzo_uint16e_t unsigned long +# define LZO_TYPEOF_LZO_INT16E_T LZO_TYPEOF_LONG +#elif (LZO_SIZEOF_INT == 2) && !(LZO_CFG_PREFER_TYPEOF_ACC_INT16E_T == LZO_TYPEOF_SHORT) +# define lzo_int16e_t int +# define lzo_uint16e_t unsigned int +# define LZO_TYPEOF_LZO_INT16E_T LZO_TYPEOF_INT +#elif (LZO_SIZEOF_SHORT == 2) +# define lzo_int16e_t short int +# define lzo_uint16e_t unsigned short int +# define LZO_TYPEOF_LZO_INT16E_T LZO_TYPEOF_SHORT +#elif 1 && !(LZO_CFG_TYPE_NO_MODE_HI) && (LZO_CC_CLANG || (LZO_CC_GNUC >= 0x025f00ul) || LZO_CC_LLVM) +# if !(LZO_LANG_ASSEMBLER) + typedef int lzo_int16e_hi_t__ __attribute__((__mode__(__HI__))); + typedef unsigned int lzo_uint16e_hi_t__ __attribute__((__mode__(__HI__))); +# endif +# define lzo_int16e_t lzo_int16e_hi_t__ +# define lzo_uint16e_t lzo_uint16e_hi_t__ +# define LZO_TYPEOF_LZO_INT16E_T LZO_TYPEOF___MODE_HI +#elif (LZO_SIZEOF___INT16 == 2) +# define lzo_int16e_t __int16 +# define lzo_uint16e_t unsigned __int16 +# define LZO_TYPEOF_LZO_INT16E_T LZO_TYPEOF___INT16 +#else +#endif +#endif +#if defined(lzo_int16e_t) +# define LZO_SIZEOF_LZO_INT16E_T 2 + LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int16e_t) == 2) + LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int16e_t) == LZO_SIZEOF_LZO_INT16E_T) +#endif +#if !defined(lzo_int32e_t) +#if (LZO_CFG_PREFER_TYPEOF_ACC_INT32E_T == LZO_TYPEOF_INT) && (LZO_SIZEOF_INT != 4) +# undef LZO_CFG_PREFER_TYPEOF_ACC_INT32E_T +#endif +#if (LZO_SIZEOF_LONG == 4) && !(LZO_CFG_PREFER_TYPEOF_ACC_INT32E_T == LZO_TYPEOF_INT) +# define lzo_int32e_t long int +# define lzo_uint32e_t unsigned long int +# define LZO_TYPEOF_LZO_INT32E_T LZO_TYPEOF_LONG +#elif (LZO_SIZEOF_INT == 4) +# define lzo_int32e_t int +# define lzo_uint32e_t unsigned int +# define LZO_TYPEOF_LZO_INT32E_T LZO_TYPEOF_INT +#elif (LZO_SIZEOF_SHORT == 4) +# define lzo_int32e_t short int +# define lzo_uint32e_t unsigned short int +# define LZO_TYPEOF_LZO_INT32E_T LZO_TYPEOF_SHORT +#elif (LZO_SIZEOF_LONG_LONG == 4) +# define lzo_int32e_t lzo_llong_t +# define lzo_uint32e_t lzo_ullong_t +# define LZO_TYPEOF_LZO_INT32E_T LZO_TYPEOF_LONG_LONG +#elif 1 && !(LZO_CFG_TYPE_NO_MODE_SI) && (LZO_CC_CLANG || (LZO_CC_GNUC >= 0x025f00ul) || LZO_CC_LLVM) && (__INT_MAX__+0 > 2147483647L) +# if !(LZO_LANG_ASSEMBLER) + typedef int lzo_int32e_si_t__ __attribute__((__mode__(__SI__))); + typedef unsigned int lzo_uint32e_si_t__ __attribute__((__mode__(__SI__))); +# endif +# define lzo_int32e_t lzo_int32e_si_t__ +# define lzo_uint32e_t lzo_uint32e_si_t__ +# define LZO_TYPEOF_LZO_INT32E_T LZO_TYPEOF___MODE_SI +#elif 1 && !(LZO_CFG_TYPE_NO_MODE_SI) && (LZO_CC_GNUC >= 0x025f00ul) && defined(__AVR__) && (__LONG_MAX__+0 == 32767L) +# if !(LZO_LANG_ASSEMBLER) + typedef int lzo_int32e_si_t__ __attribute__((__mode__(__SI__))); + typedef unsigned int lzo_uint32e_si_t__ __attribute__((__mode__(__SI__))); +# endif +# define lzo_int32e_t lzo_int32e_si_t__ +# define lzo_uint32e_t lzo_uint32e_si_t__ +# define LZO_INT32_C(c) (c##LL) +# define LZO_UINT32_C(c) (c##ULL) +# define LZO_TYPEOF_LZO_INT32E_T LZO_TYPEOF___MODE_SI +#elif (LZO_SIZEOF___INT32 == 4) +# define lzo_int32e_t __int32 +# define lzo_uint32e_t unsigned __int32 +# define LZO_TYPEOF_LZO_INT32E_T LZO_TYPEOF___INT32 +#else +#endif +#endif +#if defined(lzo_int32e_t) +# define LZO_SIZEOF_LZO_INT32E_T 4 + LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int32e_t) == 4) + LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int32e_t) == LZO_SIZEOF_LZO_INT32E_T) +#endif +#if !defined(lzo_int64e_t) +#if (LZO_SIZEOF___INT64 == 8) +# if (LZO_CC_BORLANDC) && !defined(LZO_CFG_PREFER_TYPEOF_ACC_INT64E_T) +# define LZO_CFG_PREFER_TYPEOF_ACC_INT64E_T LZO_TYPEOF___INT64 +# endif +#endif +#if (LZO_CFG_PREFER_TYPEOF_ACC_INT64E_T == LZO_TYPEOF_LONG_LONG) && (LZO_SIZEOF_LONG_LONG != 8) +# undef LZO_CFG_PREFER_TYPEOF_ACC_INT64E_T +#endif +#if (LZO_CFG_PREFER_TYPEOF_ACC_INT64E_T == LZO_TYPEOF___INT64) && (LZO_SIZEOF___INT64 != 8) +# undef LZO_CFG_PREFER_TYPEOF_ACC_INT64E_T +#endif +#if (LZO_SIZEOF_INT == 8) && (LZO_SIZEOF_INT < LZO_SIZEOF_LONG) +# define lzo_int64e_t int +# define lzo_uint64e_t unsigned int +# define LZO_TYPEOF_LZO_INT64E_T LZO_TYPEOF_INT +#elif (LZO_SIZEOF_LONG == 8) && !(LZO_CFG_PREFER_TYPEOF_ACC_INT64E_T == LZO_TYPEOF_LONG_LONG) && !(LZO_CFG_PREFER_TYPEOF_ACC_INT64E_T == LZO_TYPEOF___INT64) +# define lzo_int64e_t long int +# define lzo_uint64e_t unsigned long int +# define LZO_TYPEOF_LZO_INT64E_T LZO_TYPEOF_LONG +#elif (LZO_SIZEOF_LONG_LONG == 8) && !(LZO_CFG_PREFER_TYPEOF_ACC_INT64E_T == LZO_TYPEOF___INT64) +# define lzo_int64e_t lzo_llong_t +# define lzo_uint64e_t lzo_ullong_t +# define LZO_TYPEOF_LZO_INT64E_T LZO_TYPEOF_LONG_LONG +# if (LZO_CC_BORLANDC) +# define LZO_INT64_C(c) ((c) + 0ll) +# define LZO_UINT64_C(c) ((c) + 0ull) +# elif 0 +# define LZO_INT64_C(c) (__lzo_gnuc_extension__ (c##LL)) +# define LZO_UINT64_C(c) (__lzo_gnuc_extension__ (c##ULL)) +# else +# define LZO_INT64_C(c) (c##LL) +# define LZO_UINT64_C(c) (c##ULL) +# endif +#elif (LZO_SIZEOF___INT64 == 8) +# define lzo_int64e_t __int64 +# define lzo_uint64e_t unsigned __int64 +# define LZO_TYPEOF_LZO_INT64E_T LZO_TYPEOF___INT64 +# if (LZO_CC_BORLANDC) +# define LZO_INT64_C(c) ((c) + 0i64) +# define LZO_UINT64_C(c) ((c) + 0ui64) +# else +# define LZO_INT64_C(c) (c##i64) +# define LZO_UINT64_C(c) (c##ui64) +# endif +#else +#endif +#endif +#if defined(lzo_int64e_t) +# define LZO_SIZEOF_LZO_INT64E_T 8 + LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int64e_t) == 8) + LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int64e_t) == LZO_SIZEOF_LZO_INT64E_T) +#endif +#if !defined(lzo_int32l_t) +#if defined(lzo_int32e_t) +# define lzo_int32l_t lzo_int32e_t +# define lzo_uint32l_t lzo_uint32e_t +# define LZO_SIZEOF_LZO_INT32L_T LZO_SIZEOF_LZO_INT32E_T +# define LZO_TYPEOF_LZO_INT32L_T LZO_TYPEOF_LZO_INT32E_T +#elif (LZO_SIZEOF_INT >= 4) && (LZO_SIZEOF_INT < LZO_SIZEOF_LONG) +# define lzo_int32l_t int +# define lzo_uint32l_t unsigned int +# define LZO_SIZEOF_LZO_INT32L_T LZO_SIZEOF_INT +# define LZO_TYPEOF_LZO_INT32L_T LZO_SIZEOF_INT +#elif (LZO_SIZEOF_LONG >= 4) +# define lzo_int32l_t long int +# define lzo_uint32l_t unsigned long int +# define LZO_SIZEOF_LZO_INT32L_T LZO_SIZEOF_LONG +# define LZO_TYPEOF_LZO_INT32L_T LZO_SIZEOF_LONG +#else +# error "lzo_int32l_t" +#endif +#endif +#if 1 + LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int32l_t) >= 4) + LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int32l_t) == LZO_SIZEOF_LZO_INT32L_T) +#endif +#if !defined(lzo_int64l_t) +#if defined(lzo_int64e_t) +# define lzo_int64l_t lzo_int64e_t +# define lzo_uint64l_t lzo_uint64e_t +# define LZO_SIZEOF_LZO_INT64L_T LZO_SIZEOF_LZO_INT64E_T +# define LZO_TYPEOF_LZO_INT64L_T LZO_TYPEOF_LZO_INT64E_T +#else +#endif +#endif +#if defined(lzo_int64l_t) + LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int64l_t) >= 8) + LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int64l_t) == LZO_SIZEOF_LZO_INT64L_T) +#endif +#if !defined(lzo_int32f_t) +#if (LZO_SIZEOF_SIZE_T >= 8) +# define lzo_int32f_t lzo_int64l_t +# define lzo_uint32f_t lzo_uint64l_t +# define LZO_SIZEOF_LZO_INT32F_T LZO_SIZEOF_LZO_INT64L_T +# define LZO_TYPEOF_LZO_INT32F_T LZO_TYPEOF_LZO_INT64L_T +#else +# define lzo_int32f_t lzo_int32l_t +# define lzo_uint32f_t lzo_uint32l_t +# define LZO_SIZEOF_LZO_INT32F_T LZO_SIZEOF_LZO_INT32L_T +# define LZO_TYPEOF_LZO_INT32F_T LZO_TYPEOF_LZO_INT32L_T +#endif +#endif +#if 1 + LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int32f_t) >= 4) + LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int32f_t) == LZO_SIZEOF_LZO_INT32F_T) +#endif +#if !defined(lzo_int64f_t) +#if defined(lzo_int64l_t) +# define lzo_int64f_t lzo_int64l_t +# define lzo_uint64f_t lzo_uint64l_t +# define LZO_SIZEOF_LZO_INT64F_T LZO_SIZEOF_LZO_INT64L_T +# define LZO_TYPEOF_LZO_INT64F_T LZO_TYPEOF_LZO_INT64L_T +#else +#endif +#endif +#if defined(lzo_int64f_t) + LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int64f_t) >= 8) + LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int64f_t) == LZO_SIZEOF_LZO_INT64F_T) +#endif +#if !defined(lzo_intptr_t) +#if 1 && (LZO_OS_OS400 && (LZO_SIZEOF_VOID_P == 16)) +# define __LZO_INTPTR_T_IS_POINTER 1 +# if !(LZO_LANG_ASSEMBLER) + typedef char * lzo_intptr_t; + typedef char * lzo_uintptr_t; +# endif +# define lzo_intptr_t lzo_intptr_t +# define lzo_uintptr_t lzo_uintptr_t +# define LZO_SIZEOF_LZO_INTPTR_T LZO_SIZEOF_VOID_P +# define LZO_TYPEOF_LZO_INTPTR_T LZO_TYPEOF_CHAR_P +#elif (LZO_CC_MSC && (_MSC_VER >= 1300) && (LZO_SIZEOF_VOID_P == 4) && (LZO_SIZEOF_INT == 4)) +# if !(LZO_LANG_ASSEMBLER) + typedef __w64 int lzo_intptr_t; + typedef __w64 unsigned int lzo_uintptr_t; +# endif +# define lzo_intptr_t lzo_intptr_t +# define lzo_uintptr_t lzo_uintptr_t +# define LZO_SIZEOF_LZO_INTPTR_T LZO_SIZEOF_INT +# define LZO_TYPEOF_LZO_INTPTR_T LZO_TYPEOF_INT +#elif (LZO_SIZEOF_SHORT == LZO_SIZEOF_VOID_P) && (LZO_SIZEOF_INT > LZO_SIZEOF_VOID_P) +# define lzo_intptr_t short +# define lzo_uintptr_t unsigned short +# define LZO_SIZEOF_LZO_INTPTR_T LZO_SIZEOF_SHORT +# define LZO_TYPEOF_LZO_INTPTR_T LZO_TYPEOF_SHORT +#elif (LZO_SIZEOF_INT >= LZO_SIZEOF_VOID_P) && (LZO_SIZEOF_INT < LZO_SIZEOF_LONG) +# define lzo_intptr_t int +# define lzo_uintptr_t unsigned int +# define LZO_SIZEOF_LZO_INTPTR_T LZO_SIZEOF_INT +# define LZO_TYPEOF_LZO_INTPTR_T LZO_TYPEOF_INT +#elif (LZO_SIZEOF_LONG >= LZO_SIZEOF_VOID_P) +# define lzo_intptr_t long +# define lzo_uintptr_t unsigned long +# define LZO_SIZEOF_LZO_INTPTR_T LZO_SIZEOF_LONG +# define LZO_TYPEOF_LZO_INTPTR_T LZO_TYPEOF_LONG +#elif (LZO_SIZEOF_LZO_INT64L_T >= LZO_SIZEOF_VOID_P) +# define lzo_intptr_t lzo_int64l_t +# define lzo_uintptr_t lzo_uint64l_t +# define LZO_SIZEOF_LZO_INTPTR_T LZO_SIZEOF_LZO_INT64L_T +# define LZO_TYPEOF_LZO_INTPTR_T LZO_TYPEOF_LZO_INT64L_T +#else +# error "lzo_intptr_t" +#endif +#endif +#if 1 + LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_intptr_t) >= sizeof(void *)) + LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_intptr_t) == sizeof(lzo_uintptr_t)) +#endif +#if !defined(lzo_word_t) +#if defined(LZO_WORDSIZE) && (LZO_WORDSIZE+0 > 0) +#if (LZO_WORDSIZE == LZO_SIZEOF_LZO_INTPTR_T) && !(__LZO_INTPTR_T_IS_POINTER) +# define lzo_word_t lzo_uintptr_t +# define lzo_sword_t lzo_intptr_t +# define LZO_SIZEOF_LZO_WORD_T LZO_SIZEOF_LZO_INTPTR_T +# define LZO_TYPEOF_LZO_WORD_T LZO_TYPEOF_LZO_INTPTR_T +#elif (LZO_WORDSIZE == LZO_SIZEOF_LONG) +# define lzo_word_t unsigned long +# define lzo_sword_t long +# define LZO_SIZEOF_LZO_WORD_T LZO_SIZEOF_LONG +# define LZO_TYPEOF_LZO_WORD_T LZO_TYPEOF_LONG +#elif (LZO_WORDSIZE == LZO_SIZEOF_INT) +# define lzo_word_t unsigned int +# define lzo_sword_t int +# define LZO_SIZEOF_LZO_WORD_T LZO_SIZEOF_INT +# define LZO_TYPEOF_LZO_WORD_T LZO_TYPEOF_INT +#elif (LZO_WORDSIZE == LZO_SIZEOF_SHORT) +# define lzo_word_t unsigned short +# define lzo_sword_t short +# define LZO_SIZEOF_LZO_WORD_T LZO_SIZEOF_SHORT +# define LZO_TYPEOF_LZO_WORD_T LZO_TYPEOF_SHORT +#elif (LZO_WORDSIZE == 1) +# define lzo_word_t unsigned char +# define lzo_sword_t signed char +# define LZO_SIZEOF_LZO_WORD_T 1 +# define LZO_TYPEOF_LZO_WORD_T LZO_TYPEOF_CHAR +#elif (LZO_WORDSIZE == LZO_SIZEOF_LZO_INT64L_T) +# define lzo_word_t lzo_uint64l_t +# define lzo_sword_t lzo_int64l_t +# define LZO_SIZEOF_LZO_WORD_T LZO_SIZEOF_LZO_INT64L_T +# define LZO_TYPEOF_LZO_WORD_T LZO_SIZEOF_LZO_INT64L_T +#elif (LZO_ARCH_SPU) && (LZO_CC_GNUC) +#if 0 +# if !(LZO_LANG_ASSEMBLER) + typedef unsigned lzo_word_t __attribute__((__mode__(__V16QI__))); + typedef int lzo_sword_t __attribute__((__mode__(__V16QI__))); +# endif +# define lzo_word_t lzo_word_t +# define lzo_sword_t lzo_sword_t +# define LZO_SIZEOF_LZO_WORD_T 16 +# define LZO_TYPEOF_LZO_WORD_T LZO_TYPEOF___MODE_V16QI +#endif +#else +# error "lzo_word_t" +#endif +#endif +#endif +#if 1 && defined(lzo_word_t) + LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_word_t) == LZO_WORDSIZE) + LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_sword_t) == LZO_WORDSIZE) +#endif +#if 1 +#define lzo_int8_t signed char +#define lzo_uint8_t unsigned char +#define LZO_SIZEOF_LZO_INT8_T 1 +#define LZO_TYPEOF_LZO_INT8_T LZO_TYPEOF_CHAR +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int8_t) == 1) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int8_t) == sizeof(lzo_uint8_t)) +#endif +#if defined(lzo_int16e_t) +#define lzo_int16_t lzo_int16e_t +#define lzo_uint16_t lzo_uint16e_t +#define LZO_SIZEOF_LZO_INT16_T LZO_SIZEOF_LZO_INT16E_T +#define LZO_TYPEOF_LZO_INT16_T LZO_TYPEOF_LZO_INT16E_T +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int16_t) == 2) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int16_t) == sizeof(lzo_uint16_t)) +#endif +#if defined(lzo_int32e_t) +#define lzo_int32_t lzo_int32e_t +#define lzo_uint32_t lzo_uint32e_t +#define LZO_SIZEOF_LZO_INT32_T LZO_SIZEOF_LZO_INT32E_T +#define LZO_TYPEOF_LZO_INT32_T LZO_TYPEOF_LZO_INT32E_T +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int32_t) == 4) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int32_t) == sizeof(lzo_uint32_t)) +#endif +#if defined(lzo_int64e_t) +#define lzo_int64_t lzo_int64e_t +#define lzo_uint64_t lzo_uint64e_t +#define LZO_SIZEOF_LZO_INT64_T LZO_SIZEOF_LZO_INT64E_T +#define LZO_TYPEOF_LZO_INT64_T LZO_TYPEOF_LZO_INT64E_T +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int64_t) == 8) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int64_t) == sizeof(lzo_uint64_t)) +#endif +#if 1 +#define lzo_int_least32_t lzo_int32l_t +#define lzo_uint_least32_t lzo_uint32l_t +#define LZO_SIZEOF_LZO_INT_LEAST32_T LZO_SIZEOF_LZO_INT32L_T +#define LZO_TYPEOF_LZO_INT_LEAST32_T LZO_TYPEOF_LZO_INT32L_T +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int_least32_t) >= 4) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int_least32_t) == sizeof(lzo_uint_least32_t)) +#endif +#if defined(lzo_int64l_t) +#define lzo_int_least64_t lzo_int64l_t +#define lzo_uint_least64_t lzo_uint64l_t +#define LZO_SIZEOF_LZO_INT_LEAST64_T LZO_SIZEOF_LZO_INT64L_T +#define LZO_TYPEOF_LZO_INT_LEAST64_T LZO_TYPEOF_LZO_INT64L_T +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int_least64_t) >= 8) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int_least64_t) == sizeof(lzo_uint_least64_t)) +#endif +#if 1 +#define lzo_int_fast32_t lzo_int32f_t +#define lzo_uint_fast32_t lzo_uint32f_t +#define LZO_SIZEOF_LZO_INT_FAST32_T LZO_SIZEOF_LZO_INT32F_T +#define LZO_TYPEOF_LZO_INT_FAST32_T LZO_TYPEOF_LZO_INT32F_T +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int_fast32_t) >= 4) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int_fast32_t) == sizeof(lzo_uint_fast32_t)) +#endif +#if defined(lzo_int64f_t) +#define lzo_int_fast64_t lzo_int64f_t +#define lzo_uint_fast64_t lzo_uint64f_t +#define LZO_SIZEOF_LZO_INT_FAST64_T LZO_SIZEOF_LZO_INT64F_T +#define LZO_TYPEOF_LZO_INT_FAST64_T LZO_TYPEOF_LZO_INT64F_T +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int_fast64_t) >= 8) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int_fast64_t) == sizeof(lzo_uint_fast64_t)) +#endif +#if !defined(LZO_INT16_C) +# if (LZO_BROKEN_INTEGRAL_CONSTANTS) && (LZO_SIZEOF_INT >= 2) +# define LZO_INT16_C(c) ((c) + 0) +# define LZO_UINT16_C(c) ((c) + 0U) +# elif (LZO_BROKEN_INTEGRAL_CONSTANTS) && (LZO_SIZEOF_LONG >= 2) +# define LZO_INT16_C(c) ((c) + 0L) +# define LZO_UINT16_C(c) ((c) + 0UL) +# elif (LZO_SIZEOF_INT >= 2) +# define LZO_INT16_C(c) (c) +# define LZO_UINT16_C(c) (c##U) +# elif (LZO_SIZEOF_LONG >= 2) +# define LZO_INT16_C(c) (c##L) +# define LZO_UINT16_C(c) (c##UL) +# else +# error "LZO_INT16_C" +# endif +#endif +#if !defined(LZO_INT32_C) +# if (LZO_BROKEN_INTEGRAL_CONSTANTS) && (LZO_SIZEOF_INT >= 4) +# define LZO_INT32_C(c) ((c) + 0) +# define LZO_UINT32_C(c) ((c) + 0U) +# elif (LZO_BROKEN_INTEGRAL_CONSTANTS) && (LZO_SIZEOF_LONG >= 4) +# define LZO_INT32_C(c) ((c) + 0L) +# define LZO_UINT32_C(c) ((c) + 0UL) +# elif (LZO_SIZEOF_INT >= 4) +# define LZO_INT32_C(c) (c) +# define LZO_UINT32_C(c) (c##U) +# elif (LZO_SIZEOF_LONG >= 4) +# define LZO_INT32_C(c) (c##L) +# define LZO_UINT32_C(c) (c##UL) +# elif (LZO_SIZEOF_LONG_LONG >= 4) +# define LZO_INT32_C(c) (c##LL) +# define LZO_UINT32_C(c) (c##ULL) +# else +# error "LZO_INT32_C" +# endif +#endif +#if !defined(LZO_INT64_C) && defined(lzo_int64l_t) +# if (LZO_BROKEN_INTEGRAL_CONSTANTS) && (LZO_SIZEOF_INT >= 8) +# define LZO_INT64_C(c) ((c) + 0) +# define LZO_UINT64_C(c) ((c) + 0U) +# elif (LZO_BROKEN_INTEGRAL_CONSTANTS) && (LZO_SIZEOF_LONG >= 8) +# define LZO_INT64_C(c) ((c) + 0L) +# define LZO_UINT64_C(c) ((c) + 0UL) +# elif (LZO_SIZEOF_INT >= 8) +# define LZO_INT64_C(c) (c) +# define LZO_UINT64_C(c) (c##U) +# elif (LZO_SIZEOF_LONG >= 8) +# define LZO_INT64_C(c) (c##L) +# define LZO_UINT64_C(c) (c##UL) +# else +# error "LZO_INT64_C" +# endif +#endif +#endif + +#endif /* already included */ + +/* vim:set ts=4 sw=4 et: */ diff --git a/thirdparty/lzo/src/config1x.h b/thirdparty/lzo/src/config1x.h new file mode 100644 index 000000000..f3c93efbb --- /dev/null +++ b/thirdparty/lzo/src/config1x.h @@ -0,0 +1,106 @@ +/* config1x.h -- configuration for the LZO1X algorithm + + This file is part of the LZO real-time data compression library. + + Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer + All Rights Reserved. + + The LZO library 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 2 of + the License, or (at your option) any later version. + + The LZO library 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 the LZO library; see the file COPYING. + If not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Markus F.X.J. Oberhumer + + http://www.oberhumer.com/opensource/lzo/ + */ + + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the library and is subject + to change. + */ + + +#ifndef __LZO_CONFIG1X_H +#define __LZO_CONFIG1X_H 1 + +#if !defined(LZO1X) && !defined(LZO1Y) && !defined(LZO1Z) +# define LZO1X 1 +#endif + +#include "lzo_conf.h" +#if !defined(__LZO_IN_MINILZO) +#include +#endif + + +/*********************************************************************** +// +************************************************************************/ + +#ifndef LZO_EOF_CODE +#define LZO_EOF_CODE 1 +#endif +#undef LZO_DETERMINISTIC + +#define M1_MAX_OFFSET 0x0400 +#ifndef M2_MAX_OFFSET +#define M2_MAX_OFFSET 0x0800 +#endif +#define M3_MAX_OFFSET 0x4000 +#define M4_MAX_OFFSET 0xbfff + +#define MX_MAX_OFFSET (M1_MAX_OFFSET + M2_MAX_OFFSET) + +#define M1_MIN_LEN 2 +#define M1_MAX_LEN 2 +#define M2_MIN_LEN 3 +#ifndef M2_MAX_LEN +#define M2_MAX_LEN 8 +#endif +#define M3_MIN_LEN 3 +#define M3_MAX_LEN 33 +#define M4_MIN_LEN 3 +#define M4_MAX_LEN 9 + +#define M1_MARKER 0 +#define M2_MARKER 64 +#define M3_MARKER 32 +#define M4_MARKER 16 + + +/*********************************************************************** +// +************************************************************************/ + +#ifndef MIN_LOOKAHEAD +#define MIN_LOOKAHEAD (M2_MAX_LEN + 1) +#endif + +#if defined(LZO_NEED_DICT_H) + +#ifndef LZO_HASH +#define LZO_HASH LZO_HASH_LZO_INCREMENTAL_B +#endif +#define DL_MIN_LEN M2_MIN_LEN +#include "lzo_dict.h" + +#endif + + + +#endif /* already included */ + + +/* vim:set ts=4 sw=4 et: */ diff --git a/thirdparty/lzo/src/lzo1_d.ch b/thirdparty/lzo/src/lzo1_d.ch new file mode 100644 index 000000000..bedc7ce8c --- /dev/null +++ b/thirdparty/lzo/src/lzo1_d.ch @@ -0,0 +1,156 @@ +/* lzo1_d.ch -- common decompression stuff + + This file is part of the LZO real-time data compression library. + + Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer + All Rights Reserved. + + The LZO library 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 2 of + the License, or (at your option) any later version. + + The LZO library 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 the LZO library; see the file COPYING. + If not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Markus F.X.J. Oberhumer + + http://www.oberhumer.com/opensource/lzo/ + */ + + + +#if defined(LZO_TEST_OVERRUN) +# if !defined(LZO_TEST_OVERRUN_INPUT) +# define LZO_TEST_OVERRUN_INPUT 2 +# endif +# if !defined(LZO_TEST_OVERRUN_OUTPUT) +# define LZO_TEST_OVERRUN_OUTPUT 2 +# endif +# if !defined(LZO_TEST_OVERRUN_LOOKBEHIND) +# define LZO_TEST_OVERRUN_LOOKBEHIND 1 +# endif +#endif + + +/*********************************************************************** +// Overrun detection is internally handled by these macros: +// +// TEST_IP test input overrun at loop begin +// NEED_IP test input overrun at every input byte +// +// TEST_OP test output overrun at loop begin +// NEED_OP test output overrun at every output byte +// +// TEST_LB test match position +// +// The fastest decompressor results when testing for no overruns +// and using LZO_EOF_CODE. +************************************************************************/ + +#undef TEST_IP +#undef TEST_OP +#undef TEST_IP_AND_TEST_OP +#undef TEST_LB +#undef TEST_LBO +#undef NEED_IP +#undef NEED_OP +#undef TEST_IV +#undef TEST_OV +#undef HAVE_TEST_IP +#undef HAVE_TEST_OP +#undef HAVE_NEED_IP +#undef HAVE_NEED_OP +#undef HAVE_ANY_IP +#undef HAVE_ANY_OP + + +#if defined(LZO_TEST_OVERRUN_INPUT) +# if (LZO_TEST_OVERRUN_INPUT >= 1) +# define TEST_IP (ip < ip_end) +# endif +# if (LZO_TEST_OVERRUN_INPUT >= 2) +# define NEED_IP(x) \ + if ((lzo_uint)(ip_end - ip) < (lzo_uint)(x)) goto input_overrun +# define TEST_IV(x) if ((x) > (lzo_uint)0 - (511)) goto input_overrun +# endif +#endif + +#if defined(LZO_TEST_OVERRUN_OUTPUT) +# if (LZO_TEST_OVERRUN_OUTPUT >= 1) +# define TEST_OP (op <= op_end) +# endif +# if (LZO_TEST_OVERRUN_OUTPUT >= 2) +# undef TEST_OP /* don't need both of the tests here */ +# define NEED_OP(x) \ + if ((lzo_uint)(op_end - op) < (lzo_uint)(x)) goto output_overrun +# define TEST_OV(x) if ((x) > (lzo_uint)0 - (511)) goto output_overrun +# endif +#endif + +#if defined(LZO_TEST_OVERRUN_LOOKBEHIND) +# define TEST_LB(m_pos) if (PTR_LT(m_pos,out) || PTR_GE(m_pos,op)) goto lookbehind_overrun +# define TEST_LBO(m_pos,o) if (PTR_LT(m_pos,out) || PTR_GE(m_pos,op-(o))) goto lookbehind_overrun +#else +# define TEST_LB(m_pos) ((void) 0) +# define TEST_LBO(m_pos,o) ((void) 0) +#endif + + +#if !defined(LZO_EOF_CODE) && !defined(TEST_IP) + /* if we have no EOF code, we have to test for the end of the input */ +# define TEST_IP (ip < ip_end) +#endif + + +#if defined(TEST_IP) +# define HAVE_TEST_IP 1 +#else +# define TEST_IP 1 +#endif +#if defined(TEST_OP) +# define HAVE_TEST_OP 1 +#else +# define TEST_OP 1 +#endif + +#if defined(HAVE_TEST_IP) && defined(HAVE_TEST_OP) +# define TEST_IP_AND_TEST_OP (TEST_IP && TEST_OP) +#elif defined(HAVE_TEST_IP) +# define TEST_IP_AND_TEST_OP TEST_IP +#elif defined(HAVE_TEST_OP) +# define TEST_IP_AND_TEST_OP TEST_OP +#else +# define TEST_IP_AND_TEST_OP 1 +#endif + +#if defined(NEED_IP) +# define HAVE_NEED_IP 1 +#else +# define NEED_IP(x) ((void) 0) +# define TEST_IV(x) ((void) 0) +#endif +#if defined(NEED_OP) +# define HAVE_NEED_OP 1 +#else +# define NEED_OP(x) ((void) 0) +# define TEST_OV(x) ((void) 0) +#endif + + +#if defined(HAVE_TEST_IP) || defined(HAVE_NEED_IP) +# define HAVE_ANY_IP 1 +#endif +#if defined(HAVE_TEST_OP) || defined(HAVE_NEED_OP) +# define HAVE_ANY_OP 1 +#endif + + +/* vim:set ts=4 sw=4 et: */ diff --git a/thirdparty/lzo/src/lzo1x_1.c b/thirdparty/lzo/src/lzo1x_1.c new file mode 100644 index 000000000..a659393f2 --- /dev/null +++ b/thirdparty/lzo/src/lzo1x_1.c @@ -0,0 +1,57 @@ +/* lzo1x_1.c -- LZO1X-1 compression + + This file is part of the LZO real-time data compression library. + + Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer + All Rights Reserved. + + The LZO library 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 2 of + the License, or (at your option) any later version. + + The LZO library 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 the LZO library; see the file COPYING. + If not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Markus F.X.J. Oberhumer + + http://www.oberhumer.com/opensource/lzo/ + */ + + +#include "lzo_conf.h" +#if 1 && defined(UA_GET_LE32) +#undef LZO_DICT_USE_PTR +#define LZO_DICT_USE_PTR 0 +#undef lzo_dict_t +#define lzo_dict_t lzo_uint16_t +#endif + +#define LZO_NEED_DICT_H 1 +#ifndef D_BITS +#define D_BITS 14 +#endif +#define D_INDEX1(d,p) d = DM(DMUL(0x21,DX3(p,5,5,6)) >> 5) +#define D_INDEX2(d,p) d = (d & (D_MASK & 0x7ff)) ^ (D_HIGH | 0x1f) +#if 1 +#define DINDEX(dv,p) DM(((DMUL(0x1824429d,dv)) >> (32-D_BITS))) +#else +#define DINDEX(dv,p) DM((dv) + ((dv) >> (32-D_BITS))) +#endif +#include "config1x.h" +#define LZO_DETERMINISTIC !(LZO_DICT_USE_PTR) + +#ifndef DO_COMPRESS +#define DO_COMPRESS lzo1x_1_compress +#endif + +#include "lzo1x_c.ch" + +/* vim:set ts=4 sw=4 et: */ diff --git a/thirdparty/lzo/src/lzo1x_c.ch b/thirdparty/lzo/src/lzo1x_c.ch new file mode 100644 index 000000000..be19b2b74 --- /dev/null +++ b/thirdparty/lzo/src/lzo1x_c.ch @@ -0,0 +1,403 @@ +/* lzo1x_c.ch -- implementation of the LZO1[XY]-1 compression algorithm + + This file is part of the LZO real-time data compression library. + + Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer + All Rights Reserved. + + The LZO library 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 2 of + the License, or (at your option) any later version. + + The LZO library 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 the LZO library; see the file COPYING. + If not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Markus F.X.J. Oberhumer + + http://www.oberhumer.com/opensource/lzo/ + */ + + + +#if 1 && defined(DO_COMPRESS) && !defined(do_compress) + /* choose a unique name to better help PGO optimizations */ +# define do_compress LZO_PP_ECONCAT2(DO_COMPRESS,_core) +#endif + + +/*********************************************************************** +// compress a block of data. +************************************************************************/ + +static __lzo_noinline lzo_uint +do_compress ( const lzo_bytep in , lzo_uint in_len, + lzo_bytep out, lzo_uintp out_len, + lzo_uint ti, lzo_voidp wrkmem) +{ + const lzo_bytep ip; + lzo_bytep op; + const lzo_bytep const in_end = in + in_len; + const lzo_bytep const ip_end = in + in_len - 20; + const lzo_bytep ii; + lzo_dict_p const dict = (lzo_dict_p) wrkmem; + + op = out; + ip = in; + ii = ip; + + ip += ti < 4 ? 4 - ti : 0; + for (;;) + { + const lzo_bytep m_pos; +#if !(LZO_DETERMINISTIC) + LZO_DEFINE_UNINITIALIZED_VAR(lzo_uint, m_off, 0); + lzo_uint m_len; + lzo_uint dindex; +next: + if __lzo_unlikely(ip >= ip_end) + break; + DINDEX1(dindex,ip); + GINDEX(m_pos,m_off,dict,dindex,in); + if (LZO_CHECK_MPOS_NON_DET(m_pos,m_off,in,ip,M4_MAX_OFFSET)) + goto literal; +#if 1 + if (m_off <= M2_MAX_OFFSET || m_pos[3] == ip[3]) + goto try_match; + DINDEX2(dindex,ip); +#endif + GINDEX(m_pos,m_off,dict,dindex,in); + if (LZO_CHECK_MPOS_NON_DET(m_pos,m_off,in,ip,M4_MAX_OFFSET)) + goto literal; + if (m_off <= M2_MAX_OFFSET || m_pos[3] == ip[3]) + goto try_match; + goto literal; + +try_match: +#if (LZO_OPT_UNALIGNED32) + if (UA_GET_NE32(m_pos) != UA_GET_NE32(ip)) +#else + if (m_pos[0] != ip[0] || m_pos[1] != ip[1] || m_pos[2] != ip[2] || m_pos[3] != ip[3]) +#endif + { + /* a literal */ +literal: + UPDATE_I(dict,0,dindex,ip,in); + ip += 1 + ((ip - ii) >> 5); + continue; + } +/*match:*/ + UPDATE_I(dict,0,dindex,ip,in); +#else + lzo_uint m_off; + lzo_uint m_len; + { + lzo_uint32_t dv; + lzo_uint dindex; +literal: + ip += 1 + ((ip - ii) >> 5); +next: + if __lzo_unlikely(ip >= ip_end) + break; + dv = UA_GET_LE32(ip); + dindex = DINDEX(dv,ip); + GINDEX(m_off,m_pos,in+dict,dindex,in); + UPDATE_I(dict,0,dindex,ip,in); + if __lzo_unlikely(dv != UA_GET_LE32(m_pos)) + goto literal; + } +#endif + + /* a match */ + + ii -= ti; ti = 0; + { + lzo_uint t = pd(ip,ii); + if (t != 0) + { + if (t <= 3) + { + op[-2] = LZO_BYTE(op[-2] | t); +#if (LZO_OPT_UNALIGNED32) + UA_COPY4(op, ii); + op += t; +#else + { do *op++ = *ii++; while (--t > 0); } +#endif + } +#if (LZO_OPT_UNALIGNED32) || (LZO_OPT_UNALIGNED64) + else if (t <= 16) + { + *op++ = LZO_BYTE(t - 3); + UA_COPY8(op, ii); + UA_COPY8(op+8, ii+8); + op += t; + } +#endif + else + { + if (t <= 18) + *op++ = LZO_BYTE(t - 3); + else + { + lzo_uint tt = t - 18; + *op++ = 0; + while __lzo_unlikely(tt > 255) + { + tt -= 255; + UA_SET1(op, 0); + op++; + } + assert(tt > 0); + *op++ = LZO_BYTE(tt); + } +#if (LZO_OPT_UNALIGNED32) || (LZO_OPT_UNALIGNED64) + do { + UA_COPY8(op, ii); + UA_COPY8(op+8, ii+8); + op += 16; ii += 16; t -= 16; + } while (t >= 16); if (t > 0) +#endif + { do *op++ = *ii++; while (--t > 0); } + } + } + } + m_len = 4; + { +#if (LZO_OPT_UNALIGNED64) + lzo_uint64_t v; + v = UA_GET_NE64(ip + m_len) ^ UA_GET_NE64(m_pos + m_len); + if __lzo_unlikely(v == 0) { + do { + m_len += 8; + v = UA_GET_NE64(ip + m_len) ^ UA_GET_NE64(m_pos + m_len); + if __lzo_unlikely(ip + m_len >= ip_end) + goto m_len_done; + } while (v == 0); + } +#if (LZO_ABI_BIG_ENDIAN) && defined(lzo_bitops_ctlz64) + m_len += lzo_bitops_ctlz64(v) / CHAR_BIT; +#elif (LZO_ABI_BIG_ENDIAN) + if ((v >> (64 - CHAR_BIT)) == 0) do { + v <<= CHAR_BIT; + m_len += 1; + } while ((v >> (64 - CHAR_BIT)) == 0); +#elif (LZO_ABI_LITTLE_ENDIAN) && defined(lzo_bitops_cttz64) + m_len += lzo_bitops_cttz64(v) / CHAR_BIT; +#elif (LZO_ABI_LITTLE_ENDIAN) + if ((v & UCHAR_MAX) == 0) do { + v >>= CHAR_BIT; + m_len += 1; + } while ((v & UCHAR_MAX) == 0); +#else + if (ip[m_len] == m_pos[m_len]) do { + m_len += 1; + } while (ip[m_len] == m_pos[m_len]); +#endif +#elif (LZO_OPT_UNALIGNED32) + lzo_uint32_t v; + v = UA_GET_NE32(ip + m_len) ^ UA_GET_NE32(m_pos + m_len); + if __lzo_unlikely(v == 0) { + do { + m_len += 4; + v = UA_GET_NE32(ip + m_len) ^ UA_GET_NE32(m_pos + m_len); + if (v != 0) + break; + m_len += 4; + v = UA_GET_NE32(ip + m_len) ^ UA_GET_NE32(m_pos + m_len); + if __lzo_unlikely(ip + m_len >= ip_end) + goto m_len_done; + } while (v == 0); + } +#if (LZO_ABI_BIG_ENDIAN) && defined(lzo_bitops_ctlz32) + m_len += lzo_bitops_ctlz32(v) / CHAR_BIT; +#elif (LZO_ABI_BIG_ENDIAN) + if ((v >> (32 - CHAR_BIT)) == 0) do { + v <<= CHAR_BIT; + m_len += 1; + } while ((v >> (32 - CHAR_BIT)) == 0); +#elif (LZO_ABI_LITTLE_ENDIAN) && defined(lzo_bitops_cttz32) + m_len += lzo_bitops_cttz32(v) / CHAR_BIT; +#elif (LZO_ABI_LITTLE_ENDIAN) + if ((v & UCHAR_MAX) == 0) do { + v >>= CHAR_BIT; + m_len += 1; + } while ((v & UCHAR_MAX) == 0); +#else + if (ip[m_len] == m_pos[m_len]) do { + m_len += 1; + } while (ip[m_len] == m_pos[m_len]); +#endif +#else + if __lzo_unlikely(ip[m_len] == m_pos[m_len]) { + do { + m_len += 1; + if (ip[m_len] != m_pos[m_len]) + break; + m_len += 1; + if (ip[m_len] != m_pos[m_len]) + break; + m_len += 1; + if (ip[m_len] != m_pos[m_len]) + break; + m_len += 1; + if (ip[m_len] != m_pos[m_len]) + break; + m_len += 1; + if (ip[m_len] != m_pos[m_len]) + break; + m_len += 1; + if (ip[m_len] != m_pos[m_len]) + break; + m_len += 1; + if (ip[m_len] != m_pos[m_len]) + break; + m_len += 1; + if __lzo_unlikely(ip + m_len >= ip_end) + goto m_len_done; + } while (ip[m_len] == m_pos[m_len]); + } +#endif + } +m_len_done: + m_off = pd(ip,m_pos); + ip += m_len; + ii = ip; + if (m_len <= M2_MAX_LEN && m_off <= M2_MAX_OFFSET) + { + m_off -= 1; +#if defined(LZO1X) + *op++ = LZO_BYTE(((m_len - 1) << 5) | ((m_off & 7) << 2)); + *op++ = LZO_BYTE(m_off >> 3); +#elif defined(LZO1Y) + *op++ = LZO_BYTE(((m_len + 1) << 4) | ((m_off & 3) << 2)); + *op++ = LZO_BYTE(m_off >> 2); +#endif + } + else if (m_off <= M3_MAX_OFFSET) + { + m_off -= 1; + if (m_len <= M3_MAX_LEN) + *op++ = LZO_BYTE(M3_MARKER | (m_len - 2)); + else + { + m_len -= M3_MAX_LEN; + *op++ = M3_MARKER | 0; + while __lzo_unlikely(m_len > 255) + { + m_len -= 255; + UA_SET1(op, 0); + op++; + } + *op++ = LZO_BYTE(m_len); + } + *op++ = LZO_BYTE(m_off << 2); + *op++ = LZO_BYTE(m_off >> 6); + } + else + { + m_off -= 0x4000; + if (m_len <= M4_MAX_LEN) + *op++ = LZO_BYTE(M4_MARKER | ((m_off >> 11) & 8) | (m_len - 2)); + else + { + m_len -= M4_MAX_LEN; + *op++ = LZO_BYTE(M4_MARKER | ((m_off >> 11) & 8)); + while __lzo_unlikely(m_len > 255) + { + m_len -= 255; + UA_SET1(op, 0); + op++; + } + *op++ = LZO_BYTE(m_len); + } + *op++ = LZO_BYTE(m_off << 2); + *op++ = LZO_BYTE(m_off >> 6); + } + goto next; + } + + *out_len = pd(op, out); + return pd(in_end,ii-ti); +} + + +/*********************************************************************** +// public entry point +************************************************************************/ + +LZO_PUBLIC(int) +DO_COMPRESS ( const lzo_bytep in , lzo_uint in_len, + lzo_bytep out, lzo_uintp out_len, + lzo_voidp wrkmem ) +{ + const lzo_bytep ip = in; + lzo_bytep op = out; + lzo_uint l = in_len; + lzo_uint t = 0; + + while (l > 20) + { + lzo_uint ll = l; + lzo_uintptr_t ll_end; +#if 0 || (LZO_DETERMINISTIC) + ll = LZO_MIN(ll, 49152); +#endif + ll_end = (lzo_uintptr_t)ip + ll; + if ((ll_end + ((t + ll) >> 5)) <= ll_end || (const lzo_bytep)(ll_end + ((t + ll) >> 5)) <= ip + ll) + break; +#if (LZO_DETERMINISTIC) + lzo_memset(wrkmem, 0, ((lzo_uint)1 << D_BITS) * sizeof(lzo_dict_t)); +#endif + t = do_compress(ip,ll,op,out_len,t,wrkmem); + ip += ll; + op += *out_len; + l -= ll; + } + t += l; + + if (t > 0) + { + const lzo_bytep ii = in + in_len - t; + + if (op == out && t <= 238) + *op++ = LZO_BYTE(17 + t); + else if (t <= 3) + op[-2] = LZO_BYTE(op[-2] | t); + else if (t <= 18) + *op++ = LZO_BYTE(t - 3); + else + { + lzo_uint tt = t - 18; + + *op++ = 0; + while (tt > 255) + { + tt -= 255; + UA_SET1(op, 0); + op++; + } + assert(tt > 0); + *op++ = LZO_BYTE(tt); + } + UA_COPYN(op, ii, t); + op += t; + } + + *op++ = M4_MARKER | 1; + *op++ = 0; + *op++ = 0; + + *out_len = pd(op, out); + return LZO_E_OK; +} + + +/* vim:set ts=4 sw=4 et: */ diff --git a/thirdparty/lzo/src/lzo1x_d.ch b/thirdparty/lzo/src/lzo1x_d.ch new file mode 100644 index 000000000..b6c6d9947 --- /dev/null +++ b/thirdparty/lzo/src/lzo1x_d.ch @@ -0,0 +1,475 @@ +/* lzo1x_d.ch -- implementation of the LZO1X decompression algorithm + + This file is part of the LZO real-time data compression library. + + Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer + All Rights Reserved. + + The LZO library 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 2 of + the License, or (at your option) any later version. + + The LZO library 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 the LZO library; see the file COPYING. + If not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Markus F.X.J. Oberhumer + + http://www.oberhumer.com/opensource/lzo/ + */ + + +#include "lzo1_d.ch" + + +/*********************************************************************** +// decompress a block of data. +************************************************************************/ + +#if defined(DO_DECOMPRESS) +LZO_PUBLIC(int) +DO_DECOMPRESS ( const lzo_bytep in , lzo_uint in_len, + lzo_bytep out, lzo_uintp out_len, + lzo_voidp wrkmem ) +#endif +{ + lzo_bytep op; + const lzo_bytep ip; + lzo_uint t; +#if defined(COPY_DICT) + lzo_uint m_off; + const lzo_bytep dict_end; +#else + const lzo_bytep m_pos; +#endif + + const lzo_bytep const ip_end = in + in_len; +#if defined(HAVE_ANY_OP) + lzo_bytep const op_end = out + *out_len; +#endif +#if defined(LZO1Z) + lzo_uint last_m_off = 0; +#endif + + LZO_UNUSED(wrkmem); + +#if defined(COPY_DICT) + if (dict) + { + if (dict_len > M4_MAX_OFFSET) + { + dict += dict_len - M4_MAX_OFFSET; + dict_len = M4_MAX_OFFSET; + } + dict_end = dict + dict_len; + } + else + { + dict_len = 0; + dict_end = NULL; + } +#endif /* COPY_DICT */ + + *out_len = 0; + + op = out; + ip = in; + + NEED_IP(1); + if (*ip > 17) + { + t = *ip++ - 17; + if (t < 4) + goto match_next; + assert(t > 0); NEED_OP(t); NEED_IP(t+3); + do *op++ = *ip++; while (--t > 0); + goto first_literal_run; + } + + for (;;) + { + NEED_IP(3); + t = *ip++; + if (t >= 16) + goto match; + /* a literal run */ + if (t == 0) + { + while (*ip == 0) + { + t += 255; + ip++; + TEST_IV(t); + NEED_IP(1); + } + t += 15 + *ip++; + } + /* copy literals */ + assert(t > 0); NEED_OP(t+3); NEED_IP(t+6); +#if (LZO_OPT_UNALIGNED64) && (LZO_OPT_UNALIGNED32) + t += 3; + if (t >= 8) do + { + UA_COPY8(op,ip); + op += 8; ip += 8; t -= 8; + } while (t >= 8); + if (t >= 4) + { + UA_COPY4(op,ip); + op += 4; ip += 4; t -= 4; + } + if (t > 0) + { + *op++ = *ip++; + if (t > 1) { *op++ = *ip++; if (t > 2) { *op++ = *ip++; } } + } +#elif (LZO_OPT_UNALIGNED32) || (LZO_ALIGNED_OK_4) +#if !(LZO_OPT_UNALIGNED32) + if (PTR_ALIGNED2_4(op,ip)) + { +#endif + UA_COPY4(op,ip); + op += 4; ip += 4; + if (--t > 0) + { + if (t >= 4) + { + do { + UA_COPY4(op,ip); + op += 4; ip += 4; t -= 4; + } while (t >= 4); + if (t > 0) do *op++ = *ip++; while (--t > 0); + } + else + do *op++ = *ip++; while (--t > 0); + } +#if !(LZO_OPT_UNALIGNED32) + } + else +#endif +#endif +#if !(LZO_OPT_UNALIGNED32) + { + *op++ = *ip++; *op++ = *ip++; *op++ = *ip++; + do *op++ = *ip++; while (--t > 0); + } +#endif + + +first_literal_run: + + + t = *ip++; + if (t >= 16) + goto match; +#if defined(COPY_DICT) +#if defined(LZO1Z) + m_off = (1 + M2_MAX_OFFSET) + (t << 6) + (*ip++ >> 2); + last_m_off = m_off; +#else + m_off = (1 + M2_MAX_OFFSET) + (t >> 2) + (*ip++ << 2); +#endif + NEED_OP(3); + t = 3; COPY_DICT(t,m_off) +#else /* !COPY_DICT */ +#if defined(LZO1Z) + t = (1 + M2_MAX_OFFSET) + (t << 6) + (*ip++ >> 2); + m_pos = op - t; + last_m_off = t; +#else + m_pos = op - (1 + M2_MAX_OFFSET); + m_pos -= t >> 2; + m_pos -= *ip++ << 2; +#endif + TEST_LB(m_pos); NEED_OP(3); + *op++ = *m_pos++; *op++ = *m_pos++; *op++ = *m_pos; +#endif /* COPY_DICT */ + goto match_done; + + + /* handle matches */ + for (;;) { +match: + if (t >= 64) /* a M2 match */ + { +#if defined(COPY_DICT) +#if defined(LZO1X) + m_off = 1 + ((t >> 2) & 7) + (*ip++ << 3); + t = (t >> 5) - 1; +#elif defined(LZO1Y) + m_off = 1 + ((t >> 2) & 3) + (*ip++ << 2); + t = (t >> 4) - 3; +#elif defined(LZO1Z) + m_off = t & 0x1f; + if (m_off >= 0x1c) + m_off = last_m_off; + else + { + m_off = 1 + (m_off << 6) + (*ip++ >> 2); + last_m_off = m_off; + } + t = (t >> 5) - 1; +#endif +#else /* !COPY_DICT */ +#if defined(LZO1X) + m_pos = op - 1; + m_pos -= (t >> 2) & 7; + m_pos -= *ip++ << 3; + t = (t >> 5) - 1; +#elif defined(LZO1Y) + m_pos = op - 1; + m_pos -= (t >> 2) & 3; + m_pos -= *ip++ << 2; + t = (t >> 4) - 3; +#elif defined(LZO1Z) + { + lzo_uint off = t & 0x1f; + m_pos = op; + if (off >= 0x1c) + { + assert(last_m_off > 0); + m_pos -= last_m_off; + } + else + { + off = 1 + (off << 6) + (*ip++ >> 2); + m_pos -= off; + last_m_off = off; + } + } + t = (t >> 5) - 1; +#endif + TEST_LB(m_pos); assert(t > 0); NEED_OP(t+3-1); + goto copy_match; +#endif /* COPY_DICT */ + } + else if (t >= 32) /* a M3 match */ + { + t &= 31; + if (t == 0) + { + while (*ip == 0) + { + t += 255; + ip++; + TEST_OV(t); + NEED_IP(1); + } + t += 31 + *ip++; + NEED_IP(2); + } +#if defined(COPY_DICT) +#if defined(LZO1Z) + m_off = 1 + (ip[0] << 6) + (ip[1] >> 2); + last_m_off = m_off; +#else + m_off = 1 + (ip[0] >> 2) + (ip[1] << 6); +#endif +#else /* !COPY_DICT */ +#if defined(LZO1Z) + { + lzo_uint off = 1 + (ip[0] << 6) + (ip[1] >> 2); + m_pos = op - off; + last_m_off = off; + } +#elif (LZO_OPT_UNALIGNED16) && (LZO_ABI_LITTLE_ENDIAN) + m_pos = op - 1; + m_pos -= UA_GET_LE16(ip) >> 2; +#else + m_pos = op - 1; + m_pos -= (ip[0] >> 2) + (ip[1] << 6); +#endif +#endif /* COPY_DICT */ + ip += 2; + } + else if (t >= 16) /* a M4 match */ + { +#if defined(COPY_DICT) + m_off = (t & 8) << 11; +#else /* !COPY_DICT */ + m_pos = op; + m_pos -= (t & 8) << 11; +#endif /* COPY_DICT */ + t &= 7; + if (t == 0) + { + while (*ip == 0) + { + t += 255; + ip++; + TEST_OV(t); + NEED_IP(1); + } + t += 7 + *ip++; + NEED_IP(2); + } +#if defined(COPY_DICT) +#if defined(LZO1Z) + m_off += (ip[0] << 6) + (ip[1] >> 2); +#else + m_off += (ip[0] >> 2) + (ip[1] << 6); +#endif + ip += 2; + if (m_off == 0) + goto eof_found; + m_off += 0x4000; +#if defined(LZO1Z) + last_m_off = m_off; +#endif +#else /* !COPY_DICT */ +#if defined(LZO1Z) + m_pos -= (ip[0] << 6) + (ip[1] >> 2); +#elif (LZO_OPT_UNALIGNED16) && (LZO_ABI_LITTLE_ENDIAN) + m_pos -= UA_GET_LE16(ip) >> 2; +#else + m_pos -= (ip[0] >> 2) + (ip[1] << 6); +#endif + ip += 2; + if (m_pos == op) + goto eof_found; + m_pos -= 0x4000; +#if defined(LZO1Z) + last_m_off = pd((const lzo_bytep)op, m_pos); +#endif +#endif /* COPY_DICT */ + } + else /* a M1 match */ + { +#if defined(COPY_DICT) +#if defined(LZO1Z) + m_off = 1 + (t << 6) + (*ip++ >> 2); + last_m_off = m_off; +#else + m_off = 1 + (t >> 2) + (*ip++ << 2); +#endif + NEED_OP(2); + t = 2; COPY_DICT(t,m_off) +#else /* !COPY_DICT */ +#if defined(LZO1Z) + t = 1 + (t << 6) + (*ip++ >> 2); + m_pos = op - t; + last_m_off = t; +#else + m_pos = op - 1; + m_pos -= t >> 2; + m_pos -= *ip++ << 2; +#endif + TEST_LB(m_pos); NEED_OP(2); + *op++ = *m_pos++; *op++ = *m_pos; +#endif /* COPY_DICT */ + goto match_done; + } + + /* copy match */ +#if defined(COPY_DICT) + + NEED_OP(t+3-1); + t += 3-1; COPY_DICT(t,m_off) + +#else /* !COPY_DICT */ + + TEST_LB(m_pos); assert(t > 0); NEED_OP(t+3-1); +#if (LZO_OPT_UNALIGNED64) && (LZO_OPT_UNALIGNED32) + if (op - m_pos >= 8) + { + t += (3 - 1); + if (t >= 8) do + { + UA_COPY8(op,m_pos); + op += 8; m_pos += 8; t -= 8; + } while (t >= 8); + if (t >= 4) + { + UA_COPY4(op,m_pos); + op += 4; m_pos += 4; t -= 4; + } + if (t > 0) + { + *op++ = m_pos[0]; + if (t > 1) { *op++ = m_pos[1]; if (t > 2) { *op++ = m_pos[2]; } } + } + } + else +#elif (LZO_OPT_UNALIGNED32) || (LZO_ALIGNED_OK_4) +#if !(LZO_OPT_UNALIGNED32) + if (t >= 2 * 4 - (3 - 1) && PTR_ALIGNED2_4(op,m_pos)) + { + assert((op - m_pos) >= 4); /* both pointers are aligned */ +#else + if (t >= 2 * 4 - (3 - 1) && (op - m_pos) >= 4) + { +#endif + UA_COPY4(op,m_pos); + op += 4; m_pos += 4; t -= 4 - (3 - 1); + do { + UA_COPY4(op,m_pos); + op += 4; m_pos += 4; t -= 4; + } while (t >= 4); + if (t > 0) do *op++ = *m_pos++; while (--t > 0); + } + else +#endif + { +copy_match: + *op++ = *m_pos++; *op++ = *m_pos++; + do *op++ = *m_pos++; while (--t > 0); + } + +#endif /* COPY_DICT */ + +match_done: +#if defined(LZO1Z) + t = ip[-1] & 3; +#else + t = ip[-2] & 3; +#endif + if (t == 0) + break; + + /* copy literals */ +match_next: + assert(t > 0); assert(t < 4); NEED_OP(t); NEED_IP(t+3); +#if 0 + do *op++ = *ip++; while (--t > 0); +#else + *op++ = *ip++; + if (t > 1) { *op++ = *ip++; if (t > 2) { *op++ = *ip++; } } +#endif + t = *ip++; + } + } + +eof_found: + *out_len = pd(op, out); + return (ip == ip_end ? LZO_E_OK : + (ip < ip_end ? LZO_E_INPUT_NOT_CONSUMED : LZO_E_INPUT_OVERRUN)); + + +#if defined(HAVE_NEED_IP) +input_overrun: + *out_len = pd(op, out); + return LZO_E_INPUT_OVERRUN; +#endif + +#if defined(HAVE_NEED_OP) +output_overrun: + *out_len = pd(op, out); + return LZO_E_OUTPUT_OVERRUN; +#endif + +#if defined(LZO_TEST_OVERRUN_LOOKBEHIND) +lookbehind_overrun: + *out_len = pd(op, out); + return LZO_E_LOOKBEHIND_OVERRUN; +#endif +} + + +/* vim:set ts=4 sw=4 et: */ diff --git a/thirdparty/lzo/src/lzo1x_d2.c b/thirdparty/lzo/src/lzo1x_d2.c new file mode 100644 index 000000000..8b7c316af --- /dev/null +++ b/thirdparty/lzo/src/lzo1x_d2.c @@ -0,0 +1,61 @@ +/* lzo1x_d2.c -- LZO1X decompression with overrun testing + + This file is part of the LZO real-time data compression library. + + Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer + All Rights Reserved. + + The LZO library 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 2 of + the License, or (at your option) any later version. + + The LZO library 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 the LZO library; see the file COPYING. + If not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Markus F.X.J. Oberhumer + + http://www.oberhumer.com/opensource/lzo/ + */ + + +#include "config1x.h" + +#define LZO_TEST_OVERRUN 1 +#define DO_DECOMPRESS lzo1x_decompress_safe + +#include "lzo1x_d.ch" + +#if defined(LZO_ARCH_I386) && defined(LZO_USE_ASM) +LZO_EXTERN(int) lzo1x_decompress_asm_safe + (const lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem); +LZO_PUBLIC(int) lzo1x_decompress_asm_safe + (const lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem) +{ + return lzo1x_decompress_safe(src, src_len, dst, dst_len, wrkmem); +} +LZO_EXTERN(int) lzo1x_decompress_asm_fast_safe + (const lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem); +LZO_PUBLIC(int) lzo1x_decompress_asm_fast_safe + (const lzo_bytep src, lzo_uint src_len, + lzo_bytep dst, lzo_uintp dst_len, + lzo_voidp wrkmem) +{ + return lzo1x_decompress_safe(src, src_len, dst, dst_len, wrkmem); +} +#endif + +/* vim:set ts=4 sw=4 et: */ diff --git a/thirdparty/lzo/src/lzo_conf.h b/thirdparty/lzo/src/lzo_conf.h new file mode 100644 index 000000000..aa9a2b6b8 --- /dev/null +++ b/thirdparty/lzo/src/lzo_conf.h @@ -0,0 +1,436 @@ +/* lzo_conf.h -- main internal configuration file for the the LZO library + + This file is part of the LZO real-time data compression library. + + Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer + All Rights Reserved. + + The LZO library 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 2 of + the License, or (at your option) any later version. + + The LZO library 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 the LZO library; see the file COPYING. + If not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Markus F.X.J. Oberhumer + + http://www.oberhumer.com/opensource/lzo/ + */ + + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the library and is subject + to change. + */ + + +#ifndef __LZO_CONF_H +#define __LZO_CONF_H 1 + +#if !defined(__LZO_IN_MINILZO) +#if defined(LZO_CFG_FREESTANDING) && (LZO_CFG_FREESTANDING) +# define LZO_LIBC_FREESTANDING 1 +# define LZO_OS_FREESTANDING 1 +#endif +#if defined(LZO_CFG_EXTRA_CONFIG_HEADER) +# include LZO_CFG_EXTRA_CONFIG_HEADER +#endif +#if defined(__LZOCONF_H) || defined(__LZOCONF_H_INCLUDED) +# error "include this file first" +#endif +#if defined(LZO_CFG_BUILD_DLL) && (LZO_CFG_BUILD_DLL+0) && !defined(__LZO_EXPORT1) && !defined(__LZO_EXPORT2) && 0 + /* idea: we could auto-define __LZO_EXPORT1 for DLL exports */ +#ifndef __LZODEFS_H_INCLUDED +#if defined(LZO_HAVE_CONFIG_H) +# include +#endif +#include +#include +#include +#endif + /* #define __LZO_EXPORT1 __attribute__((__visibility__("default"))) */ + /* #define __LZO_EXPORT1 __declspec(dllexport) */ +#endif +#include +#if defined(LZO_CFG_EXTRA_CONFIG_HEADER2) +# include LZO_CFG_EXTRA_CONFIG_HEADER2 +#endif +#endif /* !defined(__LZO_IN_MINILZO) */ + +#if !defined(__LZOCONF_H_INCLUDED) || (LZO_VERSION+0 != 0x20a0) +# error "version mismatch" +#endif + + +/*********************************************************************** +// pragmas +************************************************************************/ + +#if (LZO_CC_MSC && (_MSC_VER >= 1000 && _MSC_VER < 1100)) + /* disable bogus "unreachable code" warnings */ +# pragma warning(disable: 4702) +#endif +#if (LZO_CC_MSC && (_MSC_VER >= 1000)) +# pragma warning(disable: 4127 4701) + /* disable warnings about inlining */ +# pragma warning(disable: 4514 4710 4711) +#endif +#if (LZO_CC_MSC && (_MSC_VER >= 1300)) + /* disable '-Wall' warnings in system header files */ +# pragma warning(disable: 4820) +#endif +#if (LZO_CC_MSC && (_MSC_VER >= 1800)) + /* disable '-Wall' warnings in system header files */ +# pragma warning(disable: 4746) +#endif +#if (LZO_CC_INTELC && (__INTEL_COMPILER >= 900)) + /* disable pedantic warnings in system header files */ +# pragma warning(disable: 1684) +#endif + +#if (LZO_CC_SUNPROC) +#if !defined(__cplusplus) +# pragma error_messages(off,E_END_OF_LOOP_CODE_NOT_REACHED) +# pragma error_messages(off,E_LOOP_NOT_ENTERED_AT_TOP) +# pragma error_messages(off,E_STATEMENT_NOT_REACHED) +#endif +#endif + + +/*********************************************************************** +// function types +************************************************************************/ + +#if !defined(__LZO_NOEXPORT1) +# define __LZO_NOEXPORT1 /*empty*/ +#endif +#if !defined(__LZO_NOEXPORT2) +# define __LZO_NOEXPORT2 /*empty*/ +#endif + +#if 1 +# define LZO_PUBLIC_DECL(r) LZO_EXTERN(r) +#endif +#if 1 +# define LZO_PUBLIC_IMPL(r) LZO_PUBLIC(r) +#endif +#if !defined(LZO_LOCAL_DECL) +# define LZO_LOCAL_DECL(r) __LZO_EXTERN_C LZO_LOCAL_IMPL(r) +#endif +#if !defined(LZO_LOCAL_IMPL) +# define LZO_LOCAL_IMPL(r) __LZO_NOEXPORT1 r __LZO_NOEXPORT2 __LZO_CDECL +#endif +#if 1 +# define LZO_STATIC_DECL(r) LZO_PRIVATE(r) +#endif +#if 1 +# define LZO_STATIC_IMPL(r) LZO_PRIVATE(r) +#endif + + +/*********************************************************************** +// +************************************************************************/ + +#if defined(__LZO_IN_MINILZO) || (LZO_CFG_FREESTANDING) +#elif 1 +# include +#else +# define LZO_WANT_ACC_INCD_H 1 +#endif +#if defined(LZO_HAVE_CONFIG_H) +# define LZO_CFG_NO_CONFIG_HEADER 1 +#endif +#include "lzo_supp.h" + +/* Integral types */ +#if 1 || defined(lzo_int8_t) || defined(lzo_uint8_t) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int8_t) == 1) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_uint8_t) == 1) +#endif +#if 1 || defined(lzo_int16_t) || defined(lzo_uint16_t) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int16_t) == 2) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_uint16_t) == 2) +#endif +#if 1 || defined(lzo_int32_t) || defined(lzo_uint32_t) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int32_t) == 4) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_uint32_t) == 4) +#endif +#if defined(lzo_int64_t) || defined(lzo_uint64_t) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_int64_t) == 8) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(lzo_uint64_t) == 8) +#endif + +#if (LZO_CFG_FREESTANDING) +# undef HAVE_MEMCMP +# undef HAVE_MEMCPY +# undef HAVE_MEMMOVE +# undef HAVE_MEMSET +#endif + +#if !(HAVE_MEMCMP) +# undef memcmp +# define memcmp(a,b,c) lzo_memcmp(a,b,c) +#else +# undef lzo_memcmp +# define lzo_memcmp(a,b,c) memcmp(a,b,c) +#endif +#if !(HAVE_MEMCPY) +# undef memcpy +# define memcpy(a,b,c) lzo_memcpy(a,b,c) +#else +# undef lzo_memcpy +# define lzo_memcpy(a,b,c) memcpy(a,b,c) +#endif +#if !(HAVE_MEMMOVE) +# undef memmove +# define memmove(a,b,c) lzo_memmove(a,b,c) +#else +# undef lzo_memmove +# define lzo_memmove(a,b,c) memmove(a,b,c) +#endif +#if !(HAVE_MEMSET) +# undef memset +# define memset(a,b,c) lzo_memset(a,b,c) +#else +# undef lzo_memset +# define lzo_memset(a,b,c) memset(a,b,c) +#endif + +#undef NDEBUG +#if (LZO_CFG_FREESTANDING) +# undef LZO_DEBUG +# define NDEBUG 1 +# undef assert +# define assert(e) ((void)0) +#else +# if !defined(LZO_DEBUG) +# define NDEBUG 1 +# endif +# include +#endif + +#if 0 && defined(__BOUNDS_CHECKING_ON) +# include +#else +# define BOUNDS_CHECKING_OFF_DURING(stmt) stmt +# define BOUNDS_CHECKING_OFF_IN_EXPR(expr) (expr) +#endif + +#if (LZO_CFG_PGO) +# undef __lzo_likely +# undef __lzo_unlikely +# define __lzo_likely(e) (e) +# define __lzo_unlikely(e) (e) +#endif + +#undef _ +#undef __ +#undef ___ +#undef ____ +#undef _p0 +#undef _p1 +#undef _p2 +#undef _p3 +#undef _p4 +#undef _s0 +#undef _s1 +#undef _s2 +#undef _s3 +#undef _s4 +#undef _ww + + +/*********************************************************************** +// +************************************************************************/ + +#if 1 +# define LZO_BYTE(x) ((unsigned char) (x)) +#else +# define LZO_BYTE(x) ((unsigned char) ((x) & 0xff)) +#endif + +#define LZO_MAX(a,b) ((a) >= (b) ? (a) : (b)) +#define LZO_MIN(a,b) ((a) <= (b) ? (a) : (b)) +#define LZO_MAX3(a,b,c) ((a) >= (b) ? LZO_MAX(a,c) : LZO_MAX(b,c)) +#define LZO_MIN3(a,b,c) ((a) <= (b) ? LZO_MIN(a,c) : LZO_MIN(b,c)) + +#define lzo_sizeof(type) ((lzo_uint) (sizeof(type))) + +#define LZO_HIGH(array) ((lzo_uint) (sizeof(array)/sizeof(*(array)))) + +/* this always fits into 32 bits */ +#define LZO_SIZE(bits) (1u << (bits)) +#define LZO_MASK(bits) (LZO_SIZE(bits) - 1) + +#define LZO_USIZE(bits) ((lzo_uint) 1 << (bits)) +#define LZO_UMASK(bits) (LZO_USIZE(bits) - 1) + +#if !defined(DMUL) +#if 0 + /* 32*32 multiplies may be faster than 64*64 on some 64-bit machines, + * but then we need extra casts from unsigned<->size_t */ +# define DMUL(a,b) ((lzo_xint) ((lzo_uint32_t)(a) * (lzo_uint32_t)(b))) +#else +# define DMUL(a,b) ((lzo_xint) ((a) * (b))) +#endif +#endif + + +/*********************************************************************** +// compiler and architecture specific stuff +************************************************************************/ + +/* Some defines that indicate if memory can be accessed at unaligned + * memory addresses. You should also test that this is actually faster + * even if it is allowed by your system. + */ + +#include "lzo_func.h" + +#ifndef UA_SET1 +#define UA_SET1 LZO_MEMOPS_SET1 +#endif +#ifndef UA_SET2 +#define UA_SET2 LZO_MEMOPS_SET2 +#endif +#ifndef UA_SET3 +#define UA_SET3 LZO_MEMOPS_SET3 +#endif +#ifndef UA_SET4 +#define UA_SET4 LZO_MEMOPS_SET4 +#endif +#ifndef UA_MOVE1 +#define UA_MOVE1 LZO_MEMOPS_MOVE1 +#endif +#ifndef UA_MOVE2 +#define UA_MOVE2 LZO_MEMOPS_MOVE2 +#endif +#ifndef UA_MOVE3 +#define UA_MOVE3 LZO_MEMOPS_MOVE3 +#endif +#ifndef UA_MOVE4 +#define UA_MOVE4 LZO_MEMOPS_MOVE4 +#endif +#ifndef UA_MOVE8 +#define UA_MOVE8 LZO_MEMOPS_MOVE8 +#endif +#ifndef UA_COPY1 +#define UA_COPY1 LZO_MEMOPS_COPY1 +#endif +#ifndef UA_COPY2 +#define UA_COPY2 LZO_MEMOPS_COPY2 +#endif +#ifndef UA_COPY3 +#define UA_COPY3 LZO_MEMOPS_COPY3 +#endif +#ifndef UA_COPY4 +#define UA_COPY4 LZO_MEMOPS_COPY4 +#endif +#ifndef UA_COPY8 +#define UA_COPY8 LZO_MEMOPS_COPY8 +#endif +#ifndef UA_COPYN +#define UA_COPYN LZO_MEMOPS_COPYN +#endif +#ifndef UA_COPYN_X +#define UA_COPYN_X LZO_MEMOPS_COPYN +#endif +#ifndef UA_GET_LE16 +#define UA_GET_LE16 LZO_MEMOPS_GET_LE16 +#endif +#ifndef UA_GET_LE32 +#define UA_GET_LE32 LZO_MEMOPS_GET_LE32 +#endif +#ifdef LZO_MEMOPS_GET_LE64 +#ifndef UA_GET_LE64 +#define UA_GET_LE64 LZO_MEMOPS_GET_LE64 +#endif +#endif +#ifndef UA_GET_NE16 +#define UA_GET_NE16 LZO_MEMOPS_GET_NE16 +#endif +#ifndef UA_GET_NE32 +#define UA_GET_NE32 LZO_MEMOPS_GET_NE32 +#endif +#ifdef LZO_MEMOPS_GET_NE64 +#ifndef UA_GET_NE64 +#define UA_GET_NE64 LZO_MEMOPS_GET_NE64 +#endif +#endif +#ifndef UA_PUT_LE16 +#define UA_PUT_LE16 LZO_MEMOPS_PUT_LE16 +#endif +#ifndef UA_PUT_LE32 +#define UA_PUT_LE32 LZO_MEMOPS_PUT_LE32 +#endif +#ifndef UA_PUT_NE16 +#define UA_PUT_NE16 LZO_MEMOPS_PUT_NE16 +#endif +#ifndef UA_PUT_NE32 +#define UA_PUT_NE32 LZO_MEMOPS_PUT_NE32 +#endif + + +/* Fast memcpy that copies multiples of 8 byte chunks. + * len is the number of bytes. + * note: all parameters must be lvalues, len >= 8 + * dest and src advance, len is undefined afterwards + */ + +#define MEMCPY8_DS(dest,src,len) \ + lzo_memcpy(dest,src,len); dest += len; src += len + +#define BZERO8_PTR(s,l,n) \ + lzo_memset((lzo_voidp)(s),0,(lzo_uint)(l)*(n)) + +#define MEMCPY_DS(dest,src,len) \ + do *dest++ = *src++; while (--len > 0) + + +/*********************************************************************** +// +************************************************************************/ + +LZO_EXTERN(const lzo_bytep) lzo_copyright(void); + +#include "lzo_ptr.h" + +/* Generate compressed data in a deterministic way. + * This is fully portable, and compression can be faster as well. + * A reason NOT to be deterministic is when the block size is + * very small (e.g. 8kB) or the dictionary is big, because + * then the initialization of the dictionary becomes a relevant + * magnitude for compression speed. + */ +#ifndef LZO_DETERMINISTIC +#define LZO_DETERMINISTIC 1 +#endif + + +#ifndef LZO_DICT_USE_PTR +#define LZO_DICT_USE_PTR 1 +#endif + +#if (LZO_DICT_USE_PTR) +# define lzo_dict_t const lzo_bytep +# define lzo_dict_p lzo_dict_t * +#else +# define lzo_dict_t lzo_uint +# define lzo_dict_p lzo_dict_t * +#endif + + +#endif /* already included */ + + +/* vim:set ts=4 sw=4 et: */ diff --git a/thirdparty/lzo/src/lzo_dict.h b/thirdparty/lzo/src/lzo_dict.h new file mode 100644 index 000000000..e48addb17 --- /dev/null +++ b/thirdparty/lzo/src/lzo_dict.h @@ -0,0 +1,307 @@ +/* lzo_dict.h -- dictionary definitions for the the LZO library + + This file is part of the LZO real-time data compression library. + + Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer + All Rights Reserved. + + The LZO library 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 2 of + the License, or (at your option) any later version. + + The LZO library 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 the LZO library; see the file COPYING. + If not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Markus F.X.J. Oberhumer + + http://www.oberhumer.com/opensource/lzo/ + */ + + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the library and is subject + to change. + */ + + +#ifndef __LZO_DICT_H +#define __LZO_DICT_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + + + +/*********************************************************************** +// dictionary size +************************************************************************/ + +/* dictionary needed for compression */ +#if !defined(D_BITS) && defined(DBITS) +# define D_BITS DBITS +#endif +#if !defined(D_BITS) +# error "D_BITS is not defined" +#endif +#if (D_BITS < 16) +# define D_SIZE LZO_SIZE(D_BITS) +# define D_MASK LZO_MASK(D_BITS) +#else +# define D_SIZE LZO_USIZE(D_BITS) +# define D_MASK LZO_UMASK(D_BITS) +#endif +#define D_HIGH ((D_MASK >> 1) + 1) + + +/* dictionary depth */ +#if !defined(DD_BITS) +# define DD_BITS 0 +#endif +#define DD_SIZE LZO_SIZE(DD_BITS) +#define DD_MASK LZO_MASK(DD_BITS) + +/* dictionary length */ +#if !defined(DL_BITS) +# define DL_BITS (D_BITS - DD_BITS) +#endif +#if (DL_BITS < 16) +# define DL_SIZE LZO_SIZE(DL_BITS) +# define DL_MASK LZO_MASK(DL_BITS) +#else +# define DL_SIZE LZO_USIZE(DL_BITS) +# define DL_MASK LZO_UMASK(DL_BITS) +#endif + + +#if (D_BITS != DL_BITS + DD_BITS) +# error "D_BITS does not match" +#endif +#if (D_BITS < 6 || D_BITS > 18) +# error "invalid D_BITS" +#endif +#if (DL_BITS < 6 || DL_BITS > 20) +# error "invalid DL_BITS" +#endif +#if (DD_BITS < 0 || DD_BITS > 6) +# error "invalid DD_BITS" +#endif + + +#if !defined(DL_MIN_LEN) +# define DL_MIN_LEN 3 +#endif +#if !defined(DL_SHIFT) +# define DL_SHIFT ((DL_BITS + (DL_MIN_LEN - 1)) / DL_MIN_LEN) +#endif + + + +/*********************************************************************** +// dictionary access +************************************************************************/ + +#define LZO_HASH_GZIP 1 +#define LZO_HASH_GZIP_INCREMENTAL 2 +#define LZO_HASH_LZO_INCREMENTAL_A 3 +#define LZO_HASH_LZO_INCREMENTAL_B 4 + +#if !defined(LZO_HASH) +# error "choose a hashing strategy" +#endif + +#undef DM +#undef DX + +#if (DL_MIN_LEN == 3) +# define _DV2_A(p,shift1,shift2) \ + (((( (lzo_xint)((p)[0]) << shift1) ^ (p)[1]) << shift2) ^ (p)[2]) +# define _DV2_B(p,shift1,shift2) \ + (((( (lzo_xint)((p)[2]) << shift1) ^ (p)[1]) << shift2) ^ (p)[0]) +# define _DV3_B(p,shift1,shift2,shift3) \ + ((_DV2_B((p)+1,shift1,shift2) << (shift3)) ^ (p)[0]) +#elif (DL_MIN_LEN == 2) +# define _DV2_A(p,shift1,shift2) \ + (( (lzo_xint)(p[0]) << shift1) ^ p[1]) +# define _DV2_B(p,shift1,shift2) \ + (( (lzo_xint)(p[1]) << shift1) ^ p[2]) +#else +# error "invalid DL_MIN_LEN" +#endif +#define _DV_A(p,shift) _DV2_A(p,shift,shift) +#define _DV_B(p,shift) _DV2_B(p,shift,shift) +#define DA2(p,s1,s2) \ + (((((lzo_xint)((p)[2]) << (s2)) + (p)[1]) << (s1)) + (p)[0]) +#define DS2(p,s1,s2) \ + (((((lzo_xint)((p)[2]) << (s2)) - (p)[1]) << (s1)) - (p)[0]) +#define DX2(p,s1,s2) \ + (((((lzo_xint)((p)[2]) << (s2)) ^ (p)[1]) << (s1)) ^ (p)[0]) +#define DA3(p,s1,s2,s3) ((DA2((p)+1,s2,s3) << (s1)) + (p)[0]) +#define DS3(p,s1,s2,s3) ((DS2((p)+1,s2,s3) << (s1)) - (p)[0]) +#define DX3(p,s1,s2,s3) ((DX2((p)+1,s2,s3) << (s1)) ^ (p)[0]) +#define DMS(v,s) ((lzo_uint) (((v) & (D_MASK >> (s))) << (s))) +#define DM(v) DMS(v,0) + + +#if (LZO_HASH == LZO_HASH_GZIP) + /* hash function like in gzip/zlib (deflate) */ +# define _DINDEX(dv,p) (_DV_A((p),DL_SHIFT)) + +#elif (LZO_HASH == LZO_HASH_GZIP_INCREMENTAL) + /* incremental hash like in gzip/zlib (deflate) */ +# define __LZO_HASH_INCREMENTAL 1 +# define DVAL_FIRST(dv,p) dv = _DV_A((p),DL_SHIFT) +# define DVAL_NEXT(dv,p) dv = (((dv) << DL_SHIFT) ^ p[2]) +# define _DINDEX(dv,p) (dv) +# define DVAL_LOOKAHEAD DL_MIN_LEN + +#elif (LZO_HASH == LZO_HASH_LZO_INCREMENTAL_A) + /* incremental LZO hash version A */ +# define __LZO_HASH_INCREMENTAL 1 +# define DVAL_FIRST(dv,p) dv = _DV_A((p),5) +# define DVAL_NEXT(dv,p) \ + dv ^= (lzo_xint)(p[-1]) << (2*5); dv = (((dv) << 5) ^ p[2]) +# define _DINDEX(dv,p) ((DMUL(0x9f5f,dv)) >> 5) +# define DVAL_LOOKAHEAD DL_MIN_LEN + +#elif (LZO_HASH == LZO_HASH_LZO_INCREMENTAL_B) + /* incremental LZO hash version B */ +# define __LZO_HASH_INCREMENTAL 1 +# define DVAL_FIRST(dv,p) dv = _DV_B((p),5) +# define DVAL_NEXT(dv,p) \ + dv ^= p[-1]; dv = (((dv) >> 5) ^ ((lzo_xint)(p[2]) << (2*5))) +# define _DINDEX(dv,p) ((DMUL(0x9f5f,dv)) >> 5) +# define DVAL_LOOKAHEAD DL_MIN_LEN + +#else +# error "choose a hashing strategy" +#endif + + +#ifndef DINDEX +#define DINDEX(dv,p) ((lzo_uint)((_DINDEX(dv,p)) & DL_MASK) << DD_BITS) +#endif +#if !defined(DINDEX1) && defined(D_INDEX1) +#define DINDEX1 D_INDEX1 +#endif +#if !defined(DINDEX2) && defined(D_INDEX2) +#define DINDEX2 D_INDEX2 +#endif + + + +#if !defined(__LZO_HASH_INCREMENTAL) +# define DVAL_FIRST(dv,p) ((void) 0) +# define DVAL_NEXT(dv,p) ((void) 0) +# define DVAL_LOOKAHEAD 0 +#endif + + +#if !defined(DVAL_ASSERT) +#if defined(__LZO_HASH_INCREMENTAL) && !defined(NDEBUG) +#if 1 && (LZO_CC_ARMCC_GNUC || LZO_CC_CLANG || (LZO_CC_GNUC >= 0x020700ul) || LZO_CC_INTELC_GNUC || LZO_CC_LLVM || LZO_CC_PATHSCALE || LZO_CC_PGI) +static void __attribute__((__unused__)) +#else +static void +#endif +DVAL_ASSERT(lzo_xint dv, const lzo_bytep p) +{ + lzo_xint df; + DVAL_FIRST(df,(p)); + assert(DINDEX(dv,p) == DINDEX(df,p)); +} +#else +# define DVAL_ASSERT(dv,p) ((void) 0) +#endif +#endif + + + +/*********************************************************************** +// dictionary updating +************************************************************************/ + +#if (LZO_DICT_USE_PTR) +# define DENTRY(p,in) (p) +# define GINDEX(m_pos,m_off,dict,dindex,in) m_pos = dict[dindex] +#else +# define DENTRY(p,in) ((lzo_dict_t) pd(p, in)) +# define GINDEX(m_pos,m_off,dict,dindex,in) m_off = dict[dindex] +#endif + + +#if (DD_BITS == 0) + +# define UPDATE_D(dict,drun,dv,p,in) dict[ DINDEX(dv,p) ] = DENTRY(p,in) +# define UPDATE_I(dict,drun,index,p,in) dict[index] = DENTRY(p,in) +# define UPDATE_P(ptr,drun,p,in) (ptr)[0] = DENTRY(p,in) + +#else + +# define UPDATE_D(dict,drun,dv,p,in) \ + dict[ DINDEX(dv,p) + drun++ ] = DENTRY(p,in); drun &= DD_MASK +# define UPDATE_I(dict,drun,index,p,in) \ + dict[ (index) + drun++ ] = DENTRY(p,in); drun &= DD_MASK +# define UPDATE_P(ptr,drun,p,in) \ + (ptr) [ drun++ ] = DENTRY(p,in); drun &= DD_MASK + +#endif + + +/*********************************************************************** +// test for a match +************************************************************************/ + +#if (LZO_DICT_USE_PTR) + +/* m_pos is either NULL or a valid pointer */ +#define LZO_CHECK_MPOS_DET(m_pos,m_off,in,ip,max_offset) \ + (m_pos == NULL || (m_off = pd(ip, m_pos)) > max_offset) + +/* m_pos may point anywhere... */ +#define LZO_CHECK_MPOS_NON_DET(m_pos,m_off,in,ip,max_offset) \ + (BOUNDS_CHECKING_OFF_IN_EXPR(( \ + m_pos = ip - (lzo_uint) PTR_DIFF(ip,m_pos), \ + PTR_LT(m_pos,in) || \ + (m_off = (lzo_uint) PTR_DIFF(ip,m_pos)) == 0 || \ + m_off > max_offset ))) + +#else + +#define LZO_CHECK_MPOS_DET(m_pos,m_off,in,ip,max_offset) \ + (m_off == 0 || \ + ((m_off = pd(ip, in) - m_off) > max_offset) || \ + (m_pos = (ip) - (m_off), 0) ) + +#define LZO_CHECK_MPOS_NON_DET(m_pos,m_off,in,ip,max_offset) \ + (pd(ip, in) <= m_off || \ + ((m_off = pd(ip, in) - m_off) > max_offset) || \ + (m_pos = (ip) - (m_off), 0) ) + +#endif + + +#if (LZO_DETERMINISTIC) +# define LZO_CHECK_MPOS LZO_CHECK_MPOS_DET +#else +# define LZO_CHECK_MPOS LZO_CHECK_MPOS_NON_DET +#endif + + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* already included */ + + +/* vim:set ts=4 sw=4 et: */ diff --git a/thirdparty/lzo/src/lzo_dll.ch b/thirdparty/lzo/src/lzo_dll.ch new file mode 100644 index 000000000..d66839e1b --- /dev/null +++ b/thirdparty/lzo/src/lzo_dll.ch @@ -0,0 +1,50 @@ +/* lzo_dll.ch -- DLL initialization of the LZO library + + This file is part of the LZO real-time data compression library. + + Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer + All Rights Reserved. + + The LZO library 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 2 of + the License, or (at your option) any later version. + + The LZO library 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 the LZO library; see the file COPYING. + If not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Markus F.X.J. Oberhumer + + http://www.oberhumer.com/opensource/lzo/ + */ + + +/*********************************************************************** +// Windows 16 bit + Watcom C + DLL +************************************************************************/ + +#if (LZO_OS_WIN16 && LZO_CC_WATCOMC) && defined(__SW_BD) + +/* don't pull in - we don't need it */ +#if 0 +BOOL FAR PASCAL LibMain ( HANDLE hInstance, WORD wDataSegment, + WORD wHeapSize, LPSTR lpszCmdLine ) +#else +int __far __pascal LibMain ( int a, short b, short c, long d ) +#endif +{ + LZO_UNUSED(a); LZO_UNUSED(b); LZO_UNUSED(c); LZO_UNUSED(d); + return 1; +} + +#endif + + +/* vim:set ts=4 sw=4 et: */ diff --git a/thirdparty/lzo/src/lzo_func.h b/thirdparty/lzo/src/lzo_func.h new file mode 100644 index 000000000..f3ac8e344 --- /dev/null +++ b/thirdparty/lzo/src/lzo_func.h @@ -0,0 +1,491 @@ +/* lzo_func.h -- functions + + This file is part of the LZO real-time data compression library. + + Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer + All Rights Reserved. + + The LZO library 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 2 of + the License, or (at your option) any later version. + + The LZO library 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 the LZO library; see the file COPYING. + If not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Markus F.X.J. Oberhumer + + http://www.oberhumer.com/opensource/lzo/ + */ + + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the library and is subject + to change. + */ + + +#ifndef __LZO_FUNC_H +#define __LZO_FUNC_H 1 + + +/*********************************************************************** +// bitops +************************************************************************/ + +#if !defined(LZO_BITOPS_USE_ASM_BITSCAN) && !defined(LZO_BITOPS_USE_GNUC_BITSCAN) && !defined(LZO_BITOPS_USE_MSC_BITSCAN) +#if 1 && (LZO_ARCH_AMD64) && (LZO_CC_GNUC && (LZO_CC_GNUC < 0x040000ul)) && (LZO_ASM_SYNTAX_GNUC) +#define LZO_BITOPS_USE_ASM_BITSCAN 1 +#elif (LZO_CC_CLANG || (LZO_CC_GNUC >= 0x030400ul) || (LZO_CC_INTELC_GNUC && (__INTEL_COMPILER >= 1000)) || (LZO_CC_LLVM && (!defined(__llvm_tools_version__) || (__llvm_tools_version__+0 >= 0x010500ul)))) +#define LZO_BITOPS_USE_GNUC_BITSCAN 1 +#elif (LZO_OS_WIN32 || LZO_OS_WIN64) && ((LZO_CC_INTELC_MSC && (__INTEL_COMPILER >= 1010)) || (LZO_CC_MSC && (_MSC_VER >= 1400))) +#define LZO_BITOPS_USE_MSC_BITSCAN 1 +#if (LZO_CC_MSC) && (LZO_ARCH_AMD64 || LZO_ARCH_I386) +#include +#endif +#if (LZO_CC_MSC) && (LZO_ARCH_AMD64 || LZO_ARCH_I386) +#pragma intrinsic(_BitScanReverse) +#pragma intrinsic(_BitScanForward) +#endif +#if (LZO_CC_MSC) && (LZO_ARCH_AMD64) +#pragma intrinsic(_BitScanReverse64) +#pragma intrinsic(_BitScanForward64) +#endif +#endif +#endif + +__lzo_static_forceinline unsigned lzo_bitops_ctlz32_func(lzo_uint32_t v) +{ +#if (LZO_BITOPS_USE_MSC_BITSCAN) && (LZO_ARCH_AMD64 || LZO_ARCH_I386) + unsigned long r; (void) _BitScanReverse(&r, v); return (unsigned) r ^ 31; +#define lzo_bitops_ctlz32(v) lzo_bitops_ctlz32_func(v) +#elif (LZO_BITOPS_USE_ASM_BITSCAN) && (LZO_ARCH_AMD64 || LZO_ARCH_I386) && (LZO_ASM_SYNTAX_GNUC) + lzo_uint32_t r; + __asm__("bsr %1,%0" : "=r" (r) : "rm" (v) __LZO_ASM_CLOBBER_LIST_CC); + return (unsigned) r ^ 31; +#define lzo_bitops_ctlz32(v) lzo_bitops_ctlz32_func(v) +#elif (LZO_BITOPS_USE_GNUC_BITSCAN) && (LZO_SIZEOF_INT == 4) + unsigned r; r = (unsigned) __builtin_clz(v); return r; +#define lzo_bitops_ctlz32(v) ((unsigned) __builtin_clz(v)) +#elif (LZO_BITOPS_USE_GNUC_BITSCAN) && (LZO_SIZEOF_LONG == 8) && (LZO_WORDSIZE >= 8) + unsigned r; r = (unsigned) __builtin_clzl(v); return r ^ 32; +#define lzo_bitops_ctlz32(v) (((unsigned) __builtin_clzl(v)) ^ 32) +#else + LZO_UNUSED(v); return 0; +#endif +} + +#if defined(lzo_uint64_t) +__lzo_static_forceinline unsigned lzo_bitops_ctlz64_func(lzo_uint64_t v) +{ +#if (LZO_BITOPS_USE_MSC_BITSCAN) && (LZO_ARCH_AMD64) + unsigned long r; (void) _BitScanReverse64(&r, v); return (unsigned) r ^ 63; +#define lzo_bitops_ctlz64(v) lzo_bitops_ctlz64_func(v) +#elif (LZO_BITOPS_USE_ASM_BITSCAN) && (LZO_ARCH_AMD64) && (LZO_ASM_SYNTAX_GNUC) + lzo_uint64_t r; + __asm__("bsr %1,%0" : "=r" (r) : "rm" (v) __LZO_ASM_CLOBBER_LIST_CC); + return (unsigned) r ^ 63; +#define lzo_bitops_ctlz64(v) lzo_bitops_ctlz64_func(v) +#elif (LZO_BITOPS_USE_GNUC_BITSCAN) && (LZO_SIZEOF_LONG == 8) && (LZO_WORDSIZE >= 8) + unsigned r; r = (unsigned) __builtin_clzl(v); return r; +#define lzo_bitops_ctlz64(v) ((unsigned) __builtin_clzl(v)) +#elif (LZO_BITOPS_USE_GNUC_BITSCAN) && (LZO_SIZEOF_LONG_LONG == 8) && (LZO_WORDSIZE >= 8) + unsigned r; r = (unsigned) __builtin_clzll(v); return r; +#define lzo_bitops_ctlz64(v) ((unsigned) __builtin_clzll(v)) +#else + LZO_UNUSED(v); return 0; +#endif +} +#endif + +__lzo_static_forceinline unsigned lzo_bitops_cttz32_func(lzo_uint32_t v) +{ +#if (LZO_BITOPS_USE_MSC_BITSCAN) && (LZO_ARCH_AMD64 || LZO_ARCH_I386) + unsigned long r; (void) _BitScanForward(&r, v); return (unsigned) r; +#define lzo_bitops_cttz32(v) lzo_bitops_cttz32_func(v) +#elif (LZO_BITOPS_USE_ASM_BITSCAN) && (LZO_ARCH_AMD64 || LZO_ARCH_I386) && (LZO_ASM_SYNTAX_GNUC) + lzo_uint32_t r; + __asm__("bsf %1,%0" : "=r" (r) : "rm" (v) __LZO_ASM_CLOBBER_LIST_CC); + return (unsigned) r; +#define lzo_bitops_cttz32(v) lzo_bitops_cttz32_func(v) +#elif (LZO_BITOPS_USE_GNUC_BITSCAN) && (LZO_SIZEOF_INT >= 4) + unsigned r; r = (unsigned) __builtin_ctz(v); return r; +#define lzo_bitops_cttz32(v) ((unsigned) __builtin_ctz(v)) +#else + LZO_UNUSED(v); return 0; +#endif +} + +#if defined(lzo_uint64_t) +__lzo_static_forceinline unsigned lzo_bitops_cttz64_func(lzo_uint64_t v) +{ +#if (LZO_BITOPS_USE_MSC_BITSCAN) && (LZO_ARCH_AMD64) + unsigned long r; (void) _BitScanForward64(&r, v); return (unsigned) r; +#define lzo_bitops_cttz64(v) lzo_bitops_cttz64_func(v) +#elif (LZO_BITOPS_USE_ASM_BITSCAN) && (LZO_ARCH_AMD64) && (LZO_ASM_SYNTAX_GNUC) + lzo_uint64_t r; + __asm__("bsf %1,%0" : "=r" (r) : "rm" (v) __LZO_ASM_CLOBBER_LIST_CC); + return (unsigned) r; +#define lzo_bitops_cttz64(v) lzo_bitops_cttz64_func(v) +#elif (LZO_BITOPS_USE_GNUC_BITSCAN) && (LZO_SIZEOF_LONG >= 8) && (LZO_WORDSIZE >= 8) + unsigned r; r = (unsigned) __builtin_ctzl(v); return r; +#define lzo_bitops_cttz64(v) ((unsigned) __builtin_ctzl(v)) +#elif (LZO_BITOPS_USE_GNUC_BITSCAN) && (LZO_SIZEOF_LONG_LONG >= 8) && (LZO_WORDSIZE >= 8) + unsigned r; r = (unsigned) __builtin_ctzll(v); return r; +#define lzo_bitops_cttz64(v) ((unsigned) __builtin_ctzll(v)) +#else + LZO_UNUSED(v); return 0; +#endif +} +#endif + +lzo_unused_funcs_impl(void, lzo_bitops_unused_funcs)(void) +{ + LZO_UNUSED_FUNC(lzo_bitops_unused_funcs); + LZO_UNUSED_FUNC(lzo_bitops_ctlz32_func); + LZO_UNUSED_FUNC(lzo_bitops_cttz32_func); +#if defined(lzo_uint64_t) + LZO_UNUSED_FUNC(lzo_bitops_ctlz64_func); + LZO_UNUSED_FUNC(lzo_bitops_cttz64_func); +#endif +} + + +/*********************************************************************** +// memops +************************************************************************/ + +#if defined(__lzo_alignof) && !(LZO_CFG_NO_UNALIGNED) +/* CBUG: disabled because of gcc bug 64516 */ +#if !defined(lzo_memops_tcheck__) && 0 +#define lzo_memops_tcheck__(t,a,b) ((void)0, sizeof(t) == (a) && __lzo_alignof(t) == (b)) +#endif +#endif +#ifndef lzo_memops_TU0p +#define lzo_memops_TU0p void __LZO_MMODEL * +#endif +#ifndef lzo_memops_TU1p +#define lzo_memops_TU1p unsigned char __LZO_MMODEL * +#endif +#ifndef lzo_memops_TU2p +#if (LZO_OPT_UNALIGNED16) +typedef lzo_uint16_t __lzo_may_alias lzo_memops_TU2; +#define lzo_memops_TU2p volatile lzo_memops_TU2 * +#elif defined(__lzo_byte_struct) +__lzo_byte_struct(lzo_memops_TU2_struct,2) +typedef struct lzo_memops_TU2_struct lzo_memops_TU2; +#else +struct lzo_memops_TU2_struct { unsigned char a[2]; } __lzo_may_alias; +typedef struct lzo_memops_TU2_struct lzo_memops_TU2; +#endif +#ifndef lzo_memops_TU2p +#define lzo_memops_TU2p lzo_memops_TU2 * +#endif +#endif +#ifndef lzo_memops_TU4p +#if (LZO_OPT_UNALIGNED32) +typedef lzo_uint32_t __lzo_may_alias lzo_memops_TU4; +#define lzo_memops_TU4p volatile lzo_memops_TU4 __LZO_MMODEL * +#elif defined(__lzo_byte_struct) +__lzo_byte_struct(lzo_memops_TU4_struct,4) +typedef struct lzo_memops_TU4_struct lzo_memops_TU4; +#else +struct lzo_memops_TU4_struct { unsigned char a[4]; } __lzo_may_alias; +typedef struct lzo_memops_TU4_struct lzo_memops_TU4; +#endif +#ifndef lzo_memops_TU4p +#define lzo_memops_TU4p lzo_memops_TU4 __LZO_MMODEL * +#endif +#endif +#ifndef lzo_memops_TU8p +#if (LZO_OPT_UNALIGNED64) +typedef lzo_uint64_t __lzo_may_alias lzo_memops_TU8; +#define lzo_memops_TU8p volatile lzo_memops_TU8 __LZO_MMODEL * +#elif defined(__lzo_byte_struct) +__lzo_byte_struct(lzo_memops_TU8_struct,8) +typedef struct lzo_memops_TU8_struct lzo_memops_TU8; +#else +struct lzo_memops_TU8_struct { unsigned char a[8]; } __lzo_may_alias; +typedef struct lzo_memops_TU8_struct lzo_memops_TU8; +#endif +#ifndef lzo_memops_TU8p +#define lzo_memops_TU8p lzo_memops_TU8 __LZO_MMODEL * +#endif +#endif +#ifndef lzo_memops_set_TU1p +#define lzo_memops_set_TU1p volatile lzo_memops_TU1p +#endif +#ifndef lzo_memops_move_TU1p +#define lzo_memops_move_TU1p lzo_memops_TU1p +#endif +#define LZO_MEMOPS_SET1(dd,cc) \ + LZO_BLOCK_BEGIN \ + lzo_memops_set_TU1p d__1 = (lzo_memops_set_TU1p) (lzo_memops_TU0p) (dd); \ + d__1[0] = LZO_BYTE(cc); \ + LZO_BLOCK_END +#define LZO_MEMOPS_SET2(dd,cc) \ + LZO_BLOCK_BEGIN \ + lzo_memops_set_TU1p d__2 = (lzo_memops_set_TU1p) (lzo_memops_TU0p) (dd); \ + d__2[0] = LZO_BYTE(cc); d__2[1] = LZO_BYTE(cc); \ + LZO_BLOCK_END +#define LZO_MEMOPS_SET3(dd,cc) \ + LZO_BLOCK_BEGIN \ + lzo_memops_set_TU1p d__3 = (lzo_memops_set_TU1p) (lzo_memops_TU0p) (dd); \ + d__3[0] = LZO_BYTE(cc); d__3[1] = LZO_BYTE(cc); d__3[2] = LZO_BYTE(cc); \ + LZO_BLOCK_END +#define LZO_MEMOPS_SET4(dd,cc) \ + LZO_BLOCK_BEGIN \ + lzo_memops_set_TU1p d__4 = (lzo_memops_set_TU1p) (lzo_memops_TU0p) (dd); \ + d__4[0] = LZO_BYTE(cc); d__4[1] = LZO_BYTE(cc); d__4[2] = LZO_BYTE(cc); d__4[3] = LZO_BYTE(cc); \ + LZO_BLOCK_END +#define LZO_MEMOPS_MOVE1(dd,ss) \ + LZO_BLOCK_BEGIN \ + lzo_memops_move_TU1p d__1 = (lzo_memops_move_TU1p) (lzo_memops_TU0p) (dd); \ + const lzo_memops_move_TU1p s__1 = (const lzo_memops_move_TU1p) (const lzo_memops_TU0p) (ss); \ + d__1[0] = s__1[0]; \ + LZO_BLOCK_END +#define LZO_MEMOPS_MOVE2(dd,ss) \ + LZO_BLOCK_BEGIN \ + lzo_memops_move_TU1p d__2 = (lzo_memops_move_TU1p) (lzo_memops_TU0p) (dd); \ + const lzo_memops_move_TU1p s__2 = (const lzo_memops_move_TU1p) (const lzo_memops_TU0p) (ss); \ + d__2[0] = s__2[0]; d__2[1] = s__2[1]; \ + LZO_BLOCK_END +#define LZO_MEMOPS_MOVE3(dd,ss) \ + LZO_BLOCK_BEGIN \ + lzo_memops_move_TU1p d__3 = (lzo_memops_move_TU1p) (lzo_memops_TU0p) (dd); \ + const lzo_memops_move_TU1p s__3 = (const lzo_memops_move_TU1p) (const lzo_memops_TU0p) (ss); \ + d__3[0] = s__3[0]; d__3[1] = s__3[1]; d__3[2] = s__3[2]; \ + LZO_BLOCK_END +#define LZO_MEMOPS_MOVE4(dd,ss) \ + LZO_BLOCK_BEGIN \ + lzo_memops_move_TU1p d__4 = (lzo_memops_move_TU1p) (lzo_memops_TU0p) (dd); \ + const lzo_memops_move_TU1p s__4 = (const lzo_memops_move_TU1p) (const lzo_memops_TU0p) (ss); \ + d__4[0] = s__4[0]; d__4[1] = s__4[1]; d__4[2] = s__4[2]; d__4[3] = s__4[3]; \ + LZO_BLOCK_END +#define LZO_MEMOPS_MOVE8(dd,ss) \ + LZO_BLOCK_BEGIN \ + lzo_memops_move_TU1p d__8 = (lzo_memops_move_TU1p) (lzo_memops_TU0p) (dd); \ + const lzo_memops_move_TU1p s__8 = (const lzo_memops_move_TU1p) (const lzo_memops_TU0p) (ss); \ + d__8[0] = s__8[0]; d__8[1] = s__8[1]; d__8[2] = s__8[2]; d__8[3] = s__8[3]; \ + d__8[4] = s__8[4]; d__8[5] = s__8[5]; d__8[6] = s__8[6]; d__8[7] = s__8[7]; \ + LZO_BLOCK_END +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(*(lzo_memops_TU1p)0)==1) +#define LZO_MEMOPS_COPY1(dd,ss) LZO_MEMOPS_MOVE1(dd,ss) +#if (LZO_OPT_UNALIGNED16) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(*(lzo_memops_TU2p)0)==2) +#define LZO_MEMOPS_COPY2(dd,ss) \ + * (lzo_memops_TU2p) (lzo_memops_TU0p) (dd) = * (const lzo_memops_TU2p) (const lzo_memops_TU0p) (ss) +#elif defined(lzo_memops_tcheck__) +#define LZO_MEMOPS_COPY2(dd,ss) \ + LZO_BLOCK_BEGIN if (lzo_memops_tcheck__(lzo_memops_TU2,2,1)) { \ + * (lzo_memops_TU2p) (lzo_memops_TU0p) (dd) = * (const lzo_memops_TU2p) (const lzo_memops_TU0p) (ss); \ + } else { LZO_MEMOPS_MOVE2(dd,ss); } LZO_BLOCK_END +#else +#define LZO_MEMOPS_COPY2(dd,ss) LZO_MEMOPS_MOVE2(dd,ss) +#endif +#if (LZO_OPT_UNALIGNED32) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(*(lzo_memops_TU4p)0)==4) +#define LZO_MEMOPS_COPY4(dd,ss) \ + * (lzo_memops_TU4p) (lzo_memops_TU0p) (dd) = * (const lzo_memops_TU4p) (const lzo_memops_TU0p) (ss) +#elif defined(lzo_memops_tcheck__) +#define LZO_MEMOPS_COPY4(dd,ss) \ + LZO_BLOCK_BEGIN if (lzo_memops_tcheck__(lzo_memops_TU4,4,1)) { \ + * (lzo_memops_TU4p) (lzo_memops_TU0p) (dd) = * (const lzo_memops_TU4p) (const lzo_memops_TU0p) (ss); \ + } else { LZO_MEMOPS_MOVE4(dd,ss); } LZO_BLOCK_END +#else +#define LZO_MEMOPS_COPY4(dd,ss) LZO_MEMOPS_MOVE4(dd,ss) +#endif +#if (LZO_WORDSIZE != 8) +#define LZO_MEMOPS_COPY8(dd,ss) \ + LZO_BLOCK_BEGIN LZO_MEMOPS_COPY4(dd,ss); LZO_MEMOPS_COPY4((lzo_memops_TU1p)(lzo_memops_TU0p)(dd)+4,(const lzo_memops_TU1p)(const lzo_memops_TU0p)(ss)+4); LZO_BLOCK_END +#else +#if (LZO_OPT_UNALIGNED64) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(*(lzo_memops_TU8p)0)==8) +#define LZO_MEMOPS_COPY8(dd,ss) \ + * (lzo_memops_TU8p) (lzo_memops_TU0p) (dd) = * (const lzo_memops_TU8p) (const lzo_memops_TU0p) (ss) +#elif (LZO_OPT_UNALIGNED32) +#define LZO_MEMOPS_COPY8(dd,ss) \ + LZO_BLOCK_BEGIN LZO_MEMOPS_COPY4(dd,ss); LZO_MEMOPS_COPY4((lzo_memops_TU1p)(lzo_memops_TU0p)(dd)+4,(const lzo_memops_TU1p)(const lzo_memops_TU0p)(ss)+4); LZO_BLOCK_END +#elif defined(lzo_memops_tcheck__) +#define LZO_MEMOPS_COPY8(dd,ss) \ + LZO_BLOCK_BEGIN if (lzo_memops_tcheck__(lzo_memops_TU8,8,1)) { \ + * (lzo_memops_TU8p) (lzo_memops_TU0p) (dd) = * (const lzo_memops_TU8p) (const lzo_memops_TU0p) (ss); \ + } else { LZO_MEMOPS_MOVE8(dd,ss); } LZO_BLOCK_END +#else +#define LZO_MEMOPS_COPY8(dd,ss) LZO_MEMOPS_MOVE8(dd,ss) +#endif +#endif +#define LZO_MEMOPS_COPYN(dd,ss,nn) \ + LZO_BLOCK_BEGIN \ + lzo_memops_TU1p d__n = (lzo_memops_TU1p) (lzo_memops_TU0p) (dd); \ + const lzo_memops_TU1p s__n = (const lzo_memops_TU1p) (const lzo_memops_TU0p) (ss); \ + lzo_uint n__n = (nn); \ + while ((void)0, n__n >= 8) { LZO_MEMOPS_COPY8(d__n, s__n); d__n += 8; s__n += 8; n__n -= 8; } \ + if ((void)0, n__n >= 4) { LZO_MEMOPS_COPY4(d__n, s__n); d__n += 4; s__n += 4; n__n -= 4; } \ + if ((void)0, n__n > 0) do { *d__n++ = *s__n++; } while (--n__n > 0); \ + LZO_BLOCK_END + +__lzo_static_forceinline lzo_uint16_t lzo_memops_get_le16(const lzo_voidp ss) +{ + lzo_uint16_t v; +#if (LZO_ABI_LITTLE_ENDIAN) + LZO_MEMOPS_COPY2(&v, ss); +#elif (LZO_OPT_UNALIGNED16 && LZO_ARCH_POWERPC && LZO_ABI_BIG_ENDIAN) && (LZO_ASM_SYNTAX_GNUC) + const lzo_memops_TU2p s = (const lzo_memops_TU2p) ss; + unsigned long vv; + __asm__("lhbrx %0,0,%1" : "=r" (vv) : "r" (s), "m" (*s)); + v = (lzo_uint16_t) vv; +#else + const lzo_memops_TU1p s = (const lzo_memops_TU1p) ss; + v = (lzo_uint16_t) (((lzo_uint16_t)s[0]) | ((lzo_uint16_t)s[1] << 8)); +#endif + return v; +} +#if (LZO_OPT_UNALIGNED16) && (LZO_ABI_LITTLE_ENDIAN) +#define LZO_MEMOPS_GET_LE16(ss) (* (const lzo_memops_TU2p) (const lzo_memops_TU0p) (ss)) +#else +#define LZO_MEMOPS_GET_LE16(ss) lzo_memops_get_le16(ss) +#endif + +__lzo_static_forceinline lzo_uint32_t lzo_memops_get_le32(const lzo_voidp ss) +{ + lzo_uint32_t v; +#if (LZO_ABI_LITTLE_ENDIAN) + LZO_MEMOPS_COPY4(&v, ss); +#elif (LZO_OPT_UNALIGNED32 && LZO_ARCH_POWERPC && LZO_ABI_BIG_ENDIAN) && (LZO_ASM_SYNTAX_GNUC) + const lzo_memops_TU4p s = (const lzo_memops_TU4p) ss; + unsigned long vv; + __asm__("lwbrx %0,0,%1" : "=r" (vv) : "r" (s), "m" (*s)); + v = (lzo_uint32_t) vv; +#else + const lzo_memops_TU1p s = (const lzo_memops_TU1p) ss; + v = (lzo_uint32_t) (((lzo_uint32_t)s[0]) | ((lzo_uint32_t)s[1] << 8) | ((lzo_uint32_t)s[2] << 16) | ((lzo_uint32_t)s[3] << 24)); +#endif + return v; +} +#if (LZO_OPT_UNALIGNED32) && (LZO_ABI_LITTLE_ENDIAN) +#define LZO_MEMOPS_GET_LE32(ss) (* (const lzo_memops_TU4p) (const lzo_memops_TU0p) (ss)) +#else +#define LZO_MEMOPS_GET_LE32(ss) lzo_memops_get_le32(ss) +#endif + +#if (LZO_OPT_UNALIGNED64) && (LZO_ABI_LITTLE_ENDIAN) +#define LZO_MEMOPS_GET_LE64(ss) (* (const lzo_memops_TU8p) (const lzo_memops_TU0p) (ss)) +#endif + +__lzo_static_forceinline lzo_uint16_t lzo_memops_get_ne16(const lzo_voidp ss) +{ + lzo_uint16_t v; + LZO_MEMOPS_COPY2(&v, ss); + return v; +} +#if (LZO_OPT_UNALIGNED16) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(*(lzo_memops_TU2p)0)==2) +#define LZO_MEMOPS_GET_NE16(ss) (* (const lzo_memops_TU2p) (const lzo_memops_TU0p) (ss)) +#else +#define LZO_MEMOPS_GET_NE16(ss) lzo_memops_get_ne16(ss) +#endif + +__lzo_static_forceinline lzo_uint32_t lzo_memops_get_ne32(const lzo_voidp ss) +{ + lzo_uint32_t v; + LZO_MEMOPS_COPY4(&v, ss); + return v; +} +#if (LZO_OPT_UNALIGNED32) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(*(lzo_memops_TU4p)0)==4) +#define LZO_MEMOPS_GET_NE32(ss) (* (const lzo_memops_TU4p) (const lzo_memops_TU0p) (ss)) +#else +#define LZO_MEMOPS_GET_NE32(ss) lzo_memops_get_ne32(ss) +#endif + +#if (LZO_OPT_UNALIGNED64) +LZO_COMPILE_TIME_ASSERT_HEADER(sizeof(*(lzo_memops_TU8p)0)==8) +#define LZO_MEMOPS_GET_NE64(ss) (* (const lzo_memops_TU8p) (const lzo_memops_TU0p) (ss)) +#endif + +__lzo_static_forceinline void lzo_memops_put_le16(lzo_voidp dd, lzo_uint16_t vv) +{ +#if (LZO_ABI_LITTLE_ENDIAN) + LZO_MEMOPS_COPY2(dd, &vv); +#elif (LZO_OPT_UNALIGNED16 && LZO_ARCH_POWERPC && LZO_ABI_BIG_ENDIAN) && (LZO_ASM_SYNTAX_GNUC) + lzo_memops_TU2p d = (lzo_memops_TU2p) dd; + unsigned long v = vv; + __asm__("sthbrx %2,0,%1" : "=m" (*d) : "r" (d), "r" (v)); +#else + lzo_memops_TU1p d = (lzo_memops_TU1p) dd; + d[0] = LZO_BYTE((vv ) & 0xff); + d[1] = LZO_BYTE((vv >> 8) & 0xff); +#endif +} +#if (LZO_OPT_UNALIGNED16) && (LZO_ABI_LITTLE_ENDIAN) +#define LZO_MEMOPS_PUT_LE16(dd,vv) (* (lzo_memops_TU2p) (lzo_memops_TU0p) (dd) = (vv)) +#else +#define LZO_MEMOPS_PUT_LE16(dd,vv) lzo_memops_put_le16(dd,vv) +#endif + +__lzo_static_forceinline void lzo_memops_put_le32(lzo_voidp dd, lzo_uint32_t vv) +{ +#if (LZO_ABI_LITTLE_ENDIAN) + LZO_MEMOPS_COPY4(dd, &vv); +#elif (LZO_OPT_UNALIGNED32 && LZO_ARCH_POWERPC && LZO_ABI_BIG_ENDIAN) && (LZO_ASM_SYNTAX_GNUC) + lzo_memops_TU4p d = (lzo_memops_TU4p) dd; + unsigned long v = vv; + __asm__("stwbrx %2,0,%1" : "=m" (*d) : "r" (d), "r" (v)); +#else + lzo_memops_TU1p d = (lzo_memops_TU1p) dd; + d[0] = LZO_BYTE((vv ) & 0xff); + d[1] = LZO_BYTE((vv >> 8) & 0xff); + d[2] = LZO_BYTE((vv >> 16) & 0xff); + d[3] = LZO_BYTE((vv >> 24) & 0xff); +#endif +} +#if (LZO_OPT_UNALIGNED32) && (LZO_ABI_LITTLE_ENDIAN) +#define LZO_MEMOPS_PUT_LE32(dd,vv) (* (lzo_memops_TU4p) (lzo_memops_TU0p) (dd) = (vv)) +#else +#define LZO_MEMOPS_PUT_LE32(dd,vv) lzo_memops_put_le32(dd,vv) +#endif + +__lzo_static_forceinline void lzo_memops_put_ne16(lzo_voidp dd, lzo_uint16_t vv) +{ + LZO_MEMOPS_COPY2(dd, &vv); +} +#if (LZO_OPT_UNALIGNED16) +#define LZO_MEMOPS_PUT_NE16(dd,vv) (* (lzo_memops_TU2p) (lzo_memops_TU0p) (dd) = (vv)) +#else +#define LZO_MEMOPS_PUT_NE16(dd,vv) lzo_memops_put_ne16(dd,vv) +#endif + +__lzo_static_forceinline void lzo_memops_put_ne32(lzo_voidp dd, lzo_uint32_t vv) +{ + LZO_MEMOPS_COPY4(dd, &vv); +} +#if (LZO_OPT_UNALIGNED32) +#define LZO_MEMOPS_PUT_NE32(dd,vv) (* (lzo_memops_TU4p) (lzo_memops_TU0p) (dd) = (vv)) +#else +#define LZO_MEMOPS_PUT_NE32(dd,vv) lzo_memops_put_ne32(dd,vv) +#endif + +lzo_unused_funcs_impl(void, lzo_memops_unused_funcs)(void) +{ + LZO_UNUSED_FUNC(lzo_memops_unused_funcs); + LZO_UNUSED_FUNC(lzo_memops_get_le16); + LZO_UNUSED_FUNC(lzo_memops_get_le32); + LZO_UNUSED_FUNC(lzo_memops_get_ne16); + LZO_UNUSED_FUNC(lzo_memops_get_ne32); + LZO_UNUSED_FUNC(lzo_memops_put_le16); + LZO_UNUSED_FUNC(lzo_memops_put_le32); + LZO_UNUSED_FUNC(lzo_memops_put_ne16); + LZO_UNUSED_FUNC(lzo_memops_put_ne32); +} + +#endif /* already included */ + +/* vim:set ts=4 sw=4 et: */ diff --git a/thirdparty/lzo/src/lzo_init.c b/thirdparty/lzo/src/lzo_init.c new file mode 100644 index 000000000..31fdabe52 --- /dev/null +++ b/thirdparty/lzo/src/lzo_init.c @@ -0,0 +1,239 @@ +/* lzo_init.c -- initialization of the LZO library + + This file is part of the LZO real-time data compression library. + + Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer + All Rights Reserved. + + The LZO library 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 2 of + the License, or (at your option) any later version. + + The LZO library 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 the LZO library; see the file COPYING. + If not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Markus F.X.J. Oberhumer + + http://www.oberhumer.com/opensource/lzo/ + */ + + +#include "lzo_conf.h" + + +/*********************************************************************** +// Runtime check of the assumptions about the size of builtin types, +// memory model, byte order and other low-level constructs. +// +// We are really paranoid here - LZO should either fail +// at startup or not at all. +// +// Because of inlining much of these functions evaluates to nothing. +// +// And while many of the tests seem highly obvious and redundant they are +// here to catch compiler/optimizer bugs. Yes, these do exist. +************************************************************************/ + +#if !defined(__LZO_IN_MINILZO) + +#define LZO_WANT_ACC_CHK_CH 1 +#undef LZOCHK_ASSERT +#include "lzo_supp.h" + + LZOCHK_ASSERT((LZO_UINT32_C(1) << (int)(8*sizeof(LZO_UINT32_C(1))-1)) > 0) + LZOCHK_ASSERT_IS_SIGNED_T(lzo_int) + LZOCHK_ASSERT_IS_UNSIGNED_T(lzo_uint) +#if !(__LZO_UINTPTR_T_IS_POINTER) + LZOCHK_ASSERT_IS_UNSIGNED_T(lzo_uintptr_t) +#endif + LZOCHK_ASSERT(sizeof(lzo_uintptr_t) >= sizeof(lzo_voidp)) + LZOCHK_ASSERT_IS_UNSIGNED_T(lzo_xint) + +#endif +#undef LZOCHK_ASSERT + + +/*********************************************************************** +// +************************************************************************/ + +union lzo_config_check_union { + lzo_uint a[2]; + unsigned char b[2*LZO_MAX(8,sizeof(lzo_uint))]; +#if defined(lzo_uint64_t) + lzo_uint64_t c[2]; +#endif +}; + + +#if 0 +#define u2p(ptr,off) ((lzo_voidp) (((lzo_bytep)(lzo_voidp)(ptr)) + (off))) +#else +static __lzo_noinline lzo_voidp u2p(lzo_voidp ptr, lzo_uint off) +{ + return (lzo_voidp) ((lzo_bytep) ptr + off); +} +#endif + + +LZO_PUBLIC(int) +_lzo_config_check(void) +{ +#if (LZO_CC_CLANG && (LZO_CC_CLANG >= 0x030100ul && LZO_CC_CLANG < 0x030300ul)) +# if 0 + /* work around a clang 3.1 and clang 3.2 compiler bug; clang 3.3 and 3.4 work */ + volatile +# endif +#endif + union lzo_config_check_union u; + lzo_voidp p; + unsigned r = 1; + + u.a[0] = u.a[1] = 0; + p = u2p(&u, 0); + r &= ((* (lzo_bytep) p) == 0); +#if !(LZO_CFG_NO_CONFIG_CHECK) +#if (LZO_ABI_BIG_ENDIAN) + u.a[0] = u.a[1] = 0; u.b[sizeof(lzo_uint) - 1] = 128; + p = u2p(&u, 0); + r &= ((* (lzo_uintp) p) == 128); +#endif +#if (LZO_ABI_LITTLE_ENDIAN) + u.a[0] = u.a[1] = 0; u.b[0] = 128; + p = u2p(&u, 0); + r &= ((* (lzo_uintp) p) == 128); +#endif + u.a[0] = u.a[1] = 0; + u.b[0] = 1; u.b[3] = 2; + p = u2p(&u, 1); + r &= UA_GET_NE16(p) == 0; + r &= UA_GET_LE16(p) == 0; + u.b[1] = 128; + r &= UA_GET_LE16(p) == 128; + u.b[2] = 129; + r &= UA_GET_LE16(p) == LZO_UINT16_C(0x8180); +#if (LZO_ABI_BIG_ENDIAN) + r &= UA_GET_NE16(p) == LZO_UINT16_C(0x8081); +#endif +#if (LZO_ABI_LITTLE_ENDIAN) + r &= UA_GET_NE16(p) == LZO_UINT16_C(0x8180); +#endif + u.a[0] = u.a[1] = 0; + u.b[0] = 3; u.b[5] = 4; + p = u2p(&u, 1); + r &= UA_GET_NE32(p) == 0; + r &= UA_GET_LE32(p) == 0; + u.b[1] = 128; + r &= UA_GET_LE32(p) == 128; + u.b[2] = 129; u.b[3] = 130; u.b[4] = 131; + r &= UA_GET_LE32(p) == LZO_UINT32_C(0x83828180); +#if (LZO_ABI_BIG_ENDIAN) + r &= UA_GET_NE32(p) == LZO_UINT32_C(0x80818283); +#endif +#if (LZO_ABI_LITTLE_ENDIAN) + r &= UA_GET_NE32(p) == LZO_UINT32_C(0x83828180); +#endif +#if defined(UA_GET_NE64) + u.c[0] = u.c[1] = 0; + u.b[0] = 5; u.b[9] = 6; + p = u2p(&u, 1); + u.c[0] = u.c[1] = 0; + r &= UA_GET_NE64(p) == 0; +#if defined(UA_GET_LE64) + r &= UA_GET_LE64(p) == 0; + u.b[1] = 128; + r &= UA_GET_LE64(p) == 128; +#endif +#endif +#if defined(lzo_bitops_ctlz32) + { unsigned i = 0; lzo_uint32_t v; + for (v = 1; v != 0 && r == 1; v <<= 1, i++) { + r &= lzo_bitops_ctlz32(v) == 31 - i; + r &= lzo_bitops_ctlz32_func(v) == 31 - i; + }} +#endif +#if defined(lzo_bitops_ctlz64) + { unsigned i = 0; lzo_uint64_t v; + for (v = 1; v != 0 && r == 1; v <<= 1, i++) { + r &= lzo_bitops_ctlz64(v) == 63 - i; + r &= lzo_bitops_ctlz64_func(v) == 63 - i; + }} +#endif +#if defined(lzo_bitops_cttz32) + { unsigned i = 0; lzo_uint32_t v; + for (v = 1; v != 0 && r == 1; v <<= 1, i++) { + r &= lzo_bitops_cttz32(v) == i; + r &= lzo_bitops_cttz32_func(v) == i; + }} +#endif +#if defined(lzo_bitops_cttz64) + { unsigned i = 0; lzo_uint64_t v; + for (v = 1; v != 0 && r == 1; v <<= 1, i++) { + r &= lzo_bitops_cttz64(v) == i; + r &= lzo_bitops_cttz64_func(v) == i; + }} +#endif +#endif + LZO_UNUSED_FUNC(lzo_bitops_unused_funcs); + + return r == 1 ? LZO_E_OK : LZO_E_ERROR; +} + + +/*********************************************************************** +// +************************************************************************/ + +LZO_PUBLIC(int) +__lzo_init_v2(unsigned v, int s1, int s2, int s3, int s4, int s5, + int s6, int s7, int s8, int s9) +{ + int r; + +#if defined(__LZO_IN_MINILZO) +#elif (LZO_CC_MSC && ((_MSC_VER) < 700)) +#else +#define LZO_WANT_ACC_CHK_CH 1 +#undef LZOCHK_ASSERT +#define LZOCHK_ASSERT(expr) LZO_COMPILE_TIME_ASSERT(expr) +#include "lzo_supp.h" +#endif +#undef LZOCHK_ASSERT + + if (v == 0) + return LZO_E_ERROR; + + r = (s1 == -1 || s1 == (int) sizeof(short)) && + (s2 == -1 || s2 == (int) sizeof(int)) && + (s3 == -1 || s3 == (int) sizeof(long)) && + (s4 == -1 || s4 == (int) sizeof(lzo_uint32_t)) && + (s5 == -1 || s5 == (int) sizeof(lzo_uint)) && + (s6 == -1 || s6 == (int) lzo_sizeof_dict_t) && + (s7 == -1 || s7 == (int) sizeof(char *)) && + (s8 == -1 || s8 == (int) sizeof(lzo_voidp)) && + (s9 == -1 || s9 == (int) sizeof(lzo_callback_t)); + if (!r) + return LZO_E_ERROR; + + r = _lzo_config_check(); + if (r != LZO_E_OK) + return r; + + return r; +} + + +#if !defined(__LZO_IN_MINILZO) +#include "lzo_dll.ch" +#endif + + +/* vim:set ts=4 sw=4 et: */ diff --git a/thirdparty/lzo/src/lzo_ptr.h b/thirdparty/lzo/src/lzo_ptr.h new file mode 100644 index 000000000..8d7ee4483 --- /dev/null +++ b/thirdparty/lzo/src/lzo_ptr.h @@ -0,0 +1,123 @@ +/* lzo_ptr.h -- low-level pointer constructs + + This file is part of the LZO real-time data compression library. + + Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer + All Rights Reserved. + + The LZO library 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 2 of + the License, or (at your option) any later version. + + The LZO library 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 the LZO library; see the file COPYING. + If not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Markus F.X.J. Oberhumer + + http://www.oberhumer.com/opensource/lzo/ + */ + + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the library and is subject + to change. + */ + + +#ifndef __LZO_PTR_H +#define __LZO_PTR_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + + +/*********************************************************************** +// +************************************************************************/ + +/* Always use the safe (=integral) version for pointer-comparisons. + * The compiler should optimize away the additional casts anyway. + * + * Note that this only works if the representation and ordering + * of the pointer and the integral is the same (at bit level). + */ + +#if (LZO_ARCH_I086) +#error "LZO_ARCH_I086 is unsupported" +#elif (LZO_MM_PVP) +#error "LZO_MM_PVP is unsupported" +#else +#define PTR(a) ((lzo_uintptr_t) (a)) +#define PTR_LINEAR(a) PTR(a) +#define PTR_ALIGNED_4(a) ((PTR_LINEAR(a) & 3) == 0) +#define PTR_ALIGNED_8(a) ((PTR_LINEAR(a) & 7) == 0) +#define PTR_ALIGNED2_4(a,b) (((PTR_LINEAR(a) | PTR_LINEAR(b)) & 3) == 0) +#define PTR_ALIGNED2_8(a,b) (((PTR_LINEAR(a) | PTR_LINEAR(b)) & 7) == 0) +#endif + +#define PTR_LT(a,b) (PTR(a) < PTR(b)) +#define PTR_GE(a,b) (PTR(a) >= PTR(b)) +#define PTR_DIFF(a,b) (PTR(a) - PTR(b)) +#define pd(a,b) ((lzo_uint) ((a)-(b))) + + +LZO_EXTERN(lzo_uintptr_t) +__lzo_ptr_linear(const lzo_voidp ptr); + + +typedef union +{ + char a_char; + unsigned char a_uchar; + short a_short; + unsigned short a_ushort; + int a_int; + unsigned int a_uint; + long a_long; + unsigned long a_ulong; + lzo_int a_lzo_int; + lzo_uint a_lzo_uint; + lzo_xint a_lzo_xint; + lzo_int16_t a_lzo_int16_t; + lzo_uint16_t a_lzo_uint16_t; + lzo_int32_t a_lzo_int32_t; + lzo_uint32_t a_lzo_uint32_t; +#if defined(lzo_uint64_t) + lzo_int64_t a_lzo_int64_t; + lzo_uint64_t a_lzo_uint64_t; +#endif + size_t a_size_t; + ptrdiff_t a_ptrdiff_t; + lzo_uintptr_t a_lzo_uintptr_t; + void * a_void_p; + char * a_char_p; + unsigned char * a_uchar_p; + const void * a_c_void_p; + const char * a_c_char_p; + const unsigned char * a_c_uchar_p; + lzo_voidp a_lzo_voidp; + lzo_bytep a_lzo_bytep; + const lzo_voidp a_c_lzo_voidp; + const lzo_bytep a_c_lzo_bytep; +} +lzo_full_align_t; + + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* already included */ + + +/* vim:set ts=4 sw=4 et: */ diff --git a/thirdparty/lzo/src/lzo_supp.h b/thirdparty/lzo/src/lzo_supp.h new file mode 100644 index 000000000..a2c90210d --- /dev/null +++ b/thirdparty/lzo/src/lzo_supp.h @@ -0,0 +1,3678 @@ +/* lzo_supp.h -- architecture, OS and compiler specific defines + + This file is part of the LZO real-time data compression library. + + Copyright (C) 1996-2017 Markus Franz Xaver Johannes Oberhumer + All Rights Reserved. + + The LZO library 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 2 of + the License, or (at your option) any later version. + + The LZO library 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 the LZO library; see the file COPYING. + If not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Markus F.X.J. Oberhumer + + http://www.oberhumer.com/opensource/lzo/ + */ + + +#ifndef __LZO_SUPP_H_INCLUDED +#define __LZO_SUPP_H_INCLUDED 1 +#if (LZO_CFG_NO_CONFIG_HEADER) +#elif defined(LZO_CFG_CONFIG_HEADER) +#else +#if !(LZO_CFG_AUTO_NO_HEADERS) +#if (LZO_LIBC_NAKED) +#elif (LZO_LIBC_FREESTANDING) +# define HAVE_LIMITS_H 1 +# define HAVE_STDARG_H 1 +# define HAVE_STDDEF_H 1 +#elif (LZO_LIBC_MOSTLY_FREESTANDING) +# define HAVE_LIMITS_H 1 +# define HAVE_SETJMP_H 1 +# define HAVE_STDARG_H 1 +# define HAVE_STDDEF_H 1 +# define HAVE_STDIO_H 1 +# define HAVE_STRING_H 1 +#else +#define STDC_HEADERS 1 +#define HAVE_ASSERT_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_DIRENT_H 1 +#define HAVE_ERRNO_H 1 +#define HAVE_FCNTL_H 1 +#define HAVE_FLOAT_H 1 +#define HAVE_LIMITS_H 1 +#define HAVE_MALLOC_H 1 +#define HAVE_MEMORY_H 1 +#define HAVE_SETJMP_H 1 +#define HAVE_SIGNAL_H 1 +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 +#define HAVE_STDIO_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STRING_H 1 +#define HAVE_TIME_H 1 +#define HAVE_UNISTD_H 1 +#define HAVE_UTIME_H 1 +#define HAVE_SYS_STAT_H 1 +#define HAVE_SYS_TIME_H 1 +#define HAVE_SYS_TYPES_H 1 +#if (LZO_OS_POSIX) +# if (LZO_OS_POSIX_AIX) +# define HAVE_SYS_RESOURCE_H 1 +# elif (LZO_OS_POSIX_DARWIN || LZO_OS_POSIX_FREEBSD || LZO_OS_POSIX_NETBSD || LZO_OS_POSIX_OPENBSD) +# define HAVE_STRINGS_H 1 +# undef HAVE_MALLOC_H +# elif (LZO_OS_POSIX_HPUX || LZO_OS_POSIX_INTERIX) +# define HAVE_ALLOCA_H 1 +# elif (LZO_OS_POSIX_DARWIN && LZO_LIBC_MSL) +# undef HAVE_SYS_TIME_H +# undef HAVE_SYS_TYPES_H +# elif (LZO_OS_POSIX_SOLARIS || LZO_OS_POSIX_SUNOS) +# define HAVE_ALLOCA_H 1 +# endif +# if (LZO_LIBC_DIETLIBC || LZO_LIBC_GLIBC || LZO_LIBC_UCLIBC) +# define HAVE_STRINGS_H 1 +# define HAVE_SYS_MMAN_H 1 +# define HAVE_SYS_RESOURCE_H 1 +# define HAVE_SYS_WAIT_H 1 +# endif +# if (LZO_LIBC_NEWLIB) +# undef HAVE_STRINGS_H +# endif +#elif (LZO_OS_CYGWIN) +# define HAVE_IO_H 1 +#elif (LZO_OS_EMX) +# define HAVE_ALLOCA_H 1 +# define HAVE_IO_H 1 +#elif (LZO_ARCH_M68K && LZO_OS_TOS && LZO_CC_GNUC) +# if !defined(__MINT__) +# undef HAVE_MALLOC_H +# endif +#elif (LZO_ARCH_M68K && LZO_OS_TOS && (LZO_CC_PUREC || LZO_CC_TURBOC)) +# undef HAVE_DIRENT_H +# undef HAVE_FCNTL_H +# undef HAVE_MALLOC_H +# undef HAVE_MEMORY_H +# undef HAVE_UNISTD_H +# undef HAVE_UTIME_H +# undef HAVE_SYS_STAT_H +# undef HAVE_SYS_TIME_H +# undef HAVE_SYS_TYPES_H +#endif +#if (LZO_OS_DOS16 || LZO_OS_DOS32 || LZO_OS_OS2 || LZO_OS_OS216 || LZO_OS_WIN16 || LZO_OS_WIN32 || LZO_OS_WIN64) +#define HAVE_CONIO_H 1 +#define HAVE_DIRECT_H 1 +#define HAVE_DOS_H 1 +#define HAVE_IO_H 1 +#define HAVE_SHARE_H 1 +#if (LZO_CC_AZTECC) +# undef HAVE_CONIO_H +# undef HAVE_DIRECT_H +# undef HAVE_DIRENT_H +# undef HAVE_MALLOC_H +# undef HAVE_SHARE_H +# undef HAVE_UNISTD_H +# undef HAVE_UTIME_H +# undef HAVE_SYS_STAT_H +# undef HAVE_SYS_TIME_H +# undef HAVE_SYS_TYPES_H +#elif (LZO_CC_BORLANDC) +# undef HAVE_UNISTD_H +# undef HAVE_SYS_TIME_H +# if (LZO_OS_WIN32 || LZO_OS_WIN64) +# undef HAVE_DIRENT_H +# endif +# if (__BORLANDC__ < 0x0400) +# undef HAVE_DIRENT_H +# undef HAVE_UTIME_H +# endif +#elif (LZO_CC_DMC) +# undef HAVE_DIRENT_H +# undef HAVE_UNISTD_H +# define HAVE_SYS_DIRENT_H 1 +#elif (LZO_OS_DOS32 && LZO_CC_GNUC) && defined(__DJGPP__) +#elif (LZO_OS_DOS32 && LZO_CC_HIGHC) +# define HAVE_ALLOCA_H 1 +# undef HAVE_DIRENT_H +# undef HAVE_UNISTD_H +#elif (LZO_CC_IBMC && LZO_OS_OS2) +# undef HAVE_DOS_H +# undef HAVE_DIRENT_H +# undef HAVE_UNISTD_H +# undef HAVE_UTIME_H +# undef HAVE_SYS_TIME_H +# define HAVE_SYS_UTIME_H 1 +#elif (LZO_CC_CLANG_C2 || LZO_CC_CLANG_MSC || LZO_CC_GHS || LZO_CC_INTELC_MSC || LZO_CC_MSC) +# undef HAVE_DIRENT_H +# undef HAVE_UNISTD_H +# undef HAVE_UTIME_H +# undef HAVE_SYS_TIME_H +# define HAVE_SYS_UTIME_H 1 +#elif (LZO_CC_LCCWIN32) +# undef HAVE_DIRENT_H +# undef HAVE_DOS_H +# undef HAVE_UNISTD_H +# undef HAVE_SYS_TIME_H +#elif (LZO_OS_WIN32 && LZO_CC_GNUC) && defined(__MINGW32__) +# undef HAVE_UTIME_H +# define HAVE_SYS_UTIME_H 1 +#elif (LZO_OS_WIN32 && LZO_LIBC_MSL) +# define HAVE_ALLOCA_H 1 +# undef HAVE_DOS_H +# undef HAVE_SHARE_H +# undef HAVE_SYS_TIME_H +#elif (LZO_CC_NDPC) +# undef HAVE_DIRENT_H +# undef HAVE_DOS_H +# undef HAVE_UNISTD_H +# undef HAVE_UTIME_H +# undef HAVE_SYS_TIME_H +#elif (LZO_CC_PACIFICC) +# undef HAVE_DIRECT_H +# undef HAVE_DIRENT_H +# undef HAVE_FCNTL_H +# undef HAVE_IO_H +# undef HAVE_MALLOC_H +# undef HAVE_MEMORY_H +# undef HAVE_SHARE_H +# undef HAVE_UNISTD_H +# undef HAVE_UTIME_H +# undef HAVE_SYS_STAT_H +# undef HAVE_SYS_TIME_H +# undef HAVE_SYS_TYPES_H +#elif (LZO_OS_WIN32 && LZO_CC_PELLESC) +# undef HAVE_DIRENT_H +# undef HAVE_DOS_H +# undef HAVE_MALLOC_H +# undef HAVE_SHARE_H +# undef HAVE_UNISTD_H +# undef HAVE_UTIME_H +# undef HAVE_SYS_TIME_H +# if (__POCC__ < 280) +# else +# define HAVE_SYS_UTIME_H 1 +# endif +#elif (LZO_OS_WIN32 && LZO_CC_PGI) && defined(__MINGW32__) +# undef HAVE_UTIME_H +# define HAVE_SYS_UTIME_H 1 +#elif (LZO_OS_WIN32 && LZO_CC_GNUC) && defined(__PW32__) +#elif (LZO_CC_SYMANTECC) +# undef HAVE_DIRENT_H +# undef HAVE_UNISTD_H +# if (__SC__ < 0x700) +# undef HAVE_UTIME_H +# undef HAVE_SYS_TIME_H +# endif +#elif (LZO_CC_TOPSPEEDC) +# undef HAVE_DIRENT_H +# undef HAVE_UNISTD_H +# undef HAVE_UTIME_H +# undef HAVE_SYS_STAT_H +# undef HAVE_SYS_TIME_H +# undef HAVE_SYS_TYPES_H +#elif (LZO_CC_TURBOC) +# undef HAVE_UNISTD_H +# undef HAVE_SYS_TIME_H +# undef HAVE_SYS_TYPES_H +# if (LZO_OS_WIN32 || LZO_OS_WIN64) +# undef HAVE_DIRENT_H +# endif +# if (__TURBOC__ < 0x0200) +# undef HAVE_SIGNAL_H +# endif +# if (__TURBOC__ < 0x0400) +# undef HAVE_DIRECT_H +# undef HAVE_DIRENT_H +# undef HAVE_MALLOC_H +# undef HAVE_MEMORY_H +# undef HAVE_UTIME_H +# endif +#elif (LZO_CC_WATCOMC) +# undef HAVE_DIRENT_H +# undef HAVE_UTIME_H +# undef HAVE_SYS_TIME_H +# define HAVE_SYS_UTIME_H 1 +# if (__WATCOMC__ < 950) +# undef HAVE_UNISTD_H +# endif +#elif (LZO_CC_ZORTECHC) +# undef HAVE_DIRENT_H +# undef HAVE_MEMORY_H +# undef HAVE_UNISTD_H +# undef HAVE_UTIME_H +# undef HAVE_SYS_TIME_H +#endif +#endif +#if (LZO_OS_CONSOLE) +# undef HAVE_DIRENT_H +#endif +#if (LZO_OS_EMBEDDED) +# undef HAVE_DIRENT_H +#endif +#if (LZO_LIBC_ISOC90 || LZO_LIBC_ISOC99) +# undef HAVE_DIRENT_H +# undef HAVE_FCNTL_H +# undef HAVE_MALLOC_H +# undef HAVE_UNISTD_H +# undef HAVE_UTIME_H +# undef HAVE_SYS_STAT_H +# undef HAVE_SYS_TIME_H +# undef HAVE_SYS_TYPES_H +#endif +#if (LZO_LIBC_GLIBC >= 0x020100ul) +# define HAVE_STDINT_H 1 +#elif (LZO_LIBC_DIETLIBC) +# undef HAVE_STDINT_H +#elif (LZO_LIBC_UCLIBC) +# define HAVE_STDINT_H 1 +#elif (LZO_CC_BORLANDC) && (__BORLANDC__ >= 0x560) +# undef HAVE_STDINT_H +#elif (LZO_CC_DMC) && (__DMC__ >= 0x825) +# define HAVE_STDINT_H 1 +#endif +#if (HAVE_SYS_TIME_H && HAVE_TIME_H) +# define TIME_WITH_SYS_TIME 1 +#endif +#endif +#endif +#if !(LZO_CFG_AUTO_NO_FUNCTIONS) +#if (LZO_LIBC_NAKED) +#elif (LZO_LIBC_FREESTANDING) +#elif (LZO_LIBC_MOSTLY_FREESTANDING) +# define HAVE_LONGJMP 1 +# define HAVE_MEMCMP 1 +# define HAVE_MEMCPY 1 +# define HAVE_MEMMOVE 1 +# define HAVE_MEMSET 1 +# define HAVE_SETJMP 1 +#else +#define HAVE_ACCESS 1 +#define HAVE_ALLOCA 1 +#define HAVE_ATEXIT 1 +#define HAVE_ATOI 1 +#define HAVE_ATOL 1 +#define HAVE_CHMOD 1 +#define HAVE_CHOWN 1 +#define HAVE_CTIME 1 +#define HAVE_DIFFTIME 1 +#define HAVE_FILENO 1 +#define HAVE_FSTAT 1 +#define HAVE_GETENV 1 +#define HAVE_GETTIMEOFDAY 1 +#define HAVE_GMTIME 1 +#define HAVE_ISATTY 1 +#define HAVE_LOCALTIME 1 +#define HAVE_LONGJMP 1 +#define HAVE_LSTAT 1 +#define HAVE_MEMCMP 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMSET 1 +#define HAVE_MKDIR 1 +#define HAVE_MKTIME 1 +#define HAVE_QSORT 1 +#define HAVE_RAISE 1 +#define HAVE_RMDIR 1 +#define HAVE_SETJMP 1 +#define HAVE_SIGNAL 1 +#define HAVE_SNPRINTF 1 +#define HAVE_STAT 1 +#define HAVE_STRCHR 1 +#define HAVE_STRDUP 1 +#define HAVE_STRERROR 1 +#define HAVE_STRFTIME 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_TIME 1 +#define HAVE_UMASK 1 +#define HAVE_UTIME 1 +#define HAVE_VSNPRINTF 1 +#if (LZO_OS_BEOS || LZO_OS_CYGWIN || LZO_OS_POSIX || LZO_OS_QNX || LZO_OS_VMS) +# define HAVE_STRCASECMP 1 +# define HAVE_STRNCASECMP 1 +#elif (LZO_OS_WIN32 && LZO_CC_GNUC) && defined(__PW32__) +# define HAVE_STRCASECMP 1 +# define HAVE_STRNCASECMP 1 +#else +# define HAVE_STRICMP 1 +# define HAVE_STRNICMP 1 +#endif +#if (LZO_OS_POSIX) +# if (LZO_OS_POSIX_AIX) +# define HAVE_GETRUSAGE 1 +# elif (LZO_OS_POSIX_DARWIN && LZO_LIBC_MSL) +# undef HAVE_CHOWN +# undef HAVE_LSTAT +# elif (LZO_OS_POSIX_UNICOS) +# undef HAVE_ALLOCA +# undef HAVE_SNPRINTF +# undef HAVE_VSNPRINTF +# endif +# if (LZO_CC_TINYC) +# undef HAVE_ALLOCA +# endif +# if (LZO_LIBC_DIETLIBC || LZO_LIBC_GLIBC || LZO_LIBC_UCLIBC) +# define HAVE_GETRUSAGE 1 +# define HAVE_GETPAGESIZE 1 +# define HAVE_MMAP 1 +# define HAVE_MPROTECT 1 +# define HAVE_MUNMAP 1 +# endif +#elif (LZO_OS_CYGWIN) +# if (LZO_CC_GNUC < 0x025a00ul) +# undef HAVE_GETTIMEOFDAY +# undef HAVE_LSTAT +# endif +# if (LZO_CC_GNUC < 0x025f00ul) +# undef HAVE_SNPRINTF +# undef HAVE_VSNPRINTF +# endif +#elif (LZO_OS_EMX) +# undef HAVE_CHOWN +# undef HAVE_LSTAT +#elif (LZO_ARCH_M68K && LZO_OS_TOS && LZO_CC_GNUC) +# if !defined(__MINT__) +# undef HAVE_SNPRINTF +# undef HAVE_VSNPRINTF +# endif +#elif (LZO_ARCH_M68K && LZO_OS_TOS && (LZO_CC_PUREC || LZO_CC_TURBOC)) +# undef HAVE_ALLOCA +# undef HAVE_ACCESS +# undef HAVE_CHMOD +# undef HAVE_CHOWN +# undef HAVE_FSTAT +# undef HAVE_GETTIMEOFDAY +# undef HAVE_LSTAT +# undef HAVE_SNPRINTF +# undef HAVE_UMASK +# undef HAVE_UTIME +# undef HAVE_VSNPRINTF +#endif +#if (LZO_OS_DOS16 || LZO_OS_DOS32 || LZO_OS_OS2 || LZO_OS_OS216 || LZO_OS_WIN16 || LZO_OS_WIN32 || LZO_OS_WIN64) +#undef HAVE_CHOWN +#undef HAVE_GETTIMEOFDAY +#undef HAVE_LSTAT +#undef HAVE_UMASK +#if (LZO_CC_AZTECC) +# undef HAVE_ALLOCA +# undef HAVE_DIFFTIME +# undef HAVE_FSTAT +# undef HAVE_STRDUP +# undef HAVE_SNPRINTF +# undef HAVE_UTIME +# undef HAVE_VSNPRINTF +#elif (LZO_CC_BORLANDC) +# if (__BORLANDC__ < 0x0400) +# undef HAVE_ALLOCA +# undef HAVE_UTIME +# endif +# if ((__BORLANDC__ < 0x0410) && LZO_OS_WIN16) +# undef HAVE_ALLOCA +# endif +# if (__BORLANDC__ < 0x0550) +# undef HAVE_SNPRINTF +# undef HAVE_VSNPRINTF +# endif +#elif (LZO_CC_DMC) +# if (LZO_OS_WIN16) +# undef HAVE_ALLOCA +# endif +# define snprintf _snprintf +# define vsnprintf _vsnprintf +#elif (LZO_OS_DOS32 && LZO_CC_GNUC) && defined(__DJGPP__) +# undef HAVE_SNPRINTF +# undef HAVE_VSNPRINTF +#elif (LZO_OS_DOS32 && LZO_CC_HIGHC) +# undef HAVE_SNPRINTF +# undef HAVE_VSNPRINTF +#elif (LZO_CC_GHS) +# undef HAVE_ALLOCA +# ifndef snprintf +# define snprintf _snprintf +# endif +# ifndef vsnprintf +# define vsnprintf _vsnprintf +# endif +#elif (LZO_CC_IBMC) +# undef HAVE_SNPRINTF +# undef HAVE_VSNPRINTF +#elif (LZO_CC_CLANG_MSC || LZO_CC_INTELC_MSC) +# ifndef snprintf +# define snprintf _snprintf +# endif +# ifndef vsnprintf +# define vsnprintf _vsnprintf +# endif +#elif (LZO_CC_LCCWIN32) +# define utime _utime +#elif (LZO_CC_CLANG_C2 || LZO_CC_MSC) +# if (_MSC_VER < 600) +# undef HAVE_STRFTIME +# endif +# if (_MSC_VER < 700) +# undef HAVE_SNPRINTF +# undef HAVE_VSNPRINTF +# elif (_MSC_VER < 1500) +# ifndef snprintf +# define snprintf _snprintf +# endif +# ifndef vsnprintf +# define vsnprintf _vsnprintf +# endif +# elif (_MSC_VER < 1900) +# ifndef snprintf +# define snprintf _snprintf +# endif +# endif +# if ((_MSC_VER < 800) && LZO_OS_WIN16) +# undef HAVE_ALLOCA +# endif +# if (LZO_ARCH_I086) && defined(__cplusplus) +# undef HAVE_LONGJMP +# undef HAVE_SETJMP +# endif +#elif (LZO_OS_WIN32 && LZO_CC_GNUC) && defined(__MINGW32__) +# if (LZO_CC_GNUC < 0x025f00ul) +# undef HAVE_SNPRINTF +# undef HAVE_VSNPRINTF +# else +# define snprintf _snprintf +# define vsnprintf _vsnprintf +# endif +#elif (LZO_OS_WIN32 && LZO_LIBC_MSL) +# if (__MSL__ < 0x8000ul) +# undef HAVE_CHMOD +# endif +#elif (LZO_CC_NDPC) +# undef HAVE_ALLOCA +# undef HAVE_SNPRINTF +# undef HAVE_STRNICMP +# undef HAVE_UTIME +# undef HAVE_VSNPRINTF +# if defined(__cplusplus) +# undef HAVE_STAT +# endif +#elif (LZO_CC_PACIFICC) +# undef HAVE_ACCESS +# undef HAVE_ALLOCA +# undef HAVE_CHMOD +# undef HAVE_DIFFTIME +# undef HAVE_FSTAT +# undef HAVE_MKTIME +# undef HAVE_RAISE +# undef HAVE_SNPRINTF +# undef HAVE_STRFTIME +# undef HAVE_UTIME +# undef HAVE_VSNPRINTF +#elif (LZO_OS_WIN32 && LZO_CC_PELLESC) +# if (__POCC__ < 280) +# define alloca _alloca +# undef HAVE_UTIME +# endif +#elif (LZO_OS_WIN32 && LZO_CC_PGI) && defined(__MINGW32__) +# define snprintf _snprintf +# define vsnprintf _vsnprintf +#elif (LZO_OS_WIN32 && LZO_CC_GNUC) && defined(__PW32__) +# undef HAVE_SNPRINTF +# undef HAVE_VSNPRINTF +#elif (LZO_CC_SYMANTECC) +# if (LZO_OS_WIN16 && (LZO_MM_MEDIUM || LZO_MM_LARGE || LZO_MM_HUGE)) +# undef HAVE_ALLOCA +# endif +# if (__SC__ < 0x600) +# undef HAVE_SNPRINTF +# undef HAVE_VSNPRINTF +# else +# define snprintf _snprintf +# define vsnprintf _vsnprintf +# endif +# if (__SC__ < 0x700) +# undef HAVE_DIFFTIME +# undef HAVE_UTIME +# endif +#elif (LZO_CC_TOPSPEEDC) +# undef HAVE_SNPRINTF +# undef HAVE_VSNPRINTF +#elif (LZO_CC_TURBOC) +# undef HAVE_ALLOCA +# undef HAVE_SNPRINTF +# undef HAVE_VSNPRINTF +# if (__TURBOC__ < 0x0200) +# undef HAVE_RAISE +# undef HAVE_SIGNAL +# endif +# if (__TURBOC__ < 0x0295) +# undef HAVE_MKTIME +# undef HAVE_STRFTIME +# endif +# if (__TURBOC__ < 0x0400) +# undef HAVE_UTIME +# endif +#elif (LZO_CC_WATCOMC) +# if (__WATCOMC__ < 1100) +# undef HAVE_SNPRINTF +# undef HAVE_VSNPRINTF +# elif (__WATCOMC__ < 1200) +# define snprintf _snprintf +# define vsnprintf _vsnprintf +# endif +#elif (LZO_CC_ZORTECHC) +# if (LZO_OS_WIN16 && (LZO_MM_MEDIUM || LZO_MM_LARGE || LZO_MM_HUGE)) +# undef HAVE_ALLOCA +# endif +# undef HAVE_DIFFTIME +# undef HAVE_SNPRINTF +# undef HAVE_UTIME +# undef HAVE_VSNPRINTF +#endif +#endif +#if (LZO_OS_CONSOLE) +# undef HAVE_ACCESS +# undef HAVE_CHMOD +# undef HAVE_CHOWN +# undef HAVE_GETTIMEOFDAY +# undef HAVE_LSTAT +# undef HAVE_TIME +# undef HAVE_UMASK +# undef HAVE_UTIME +#endif +#if (LZO_LIBC_ISOC90 || LZO_LIBC_ISOC99) +# undef HAVE_ACCESS +# undef HAVE_CHMOD +# undef HAVE_CHOWN +# undef HAVE_FILENO +# undef HAVE_FSTAT +# undef HAVE_GETTIMEOFDAY +# undef HAVE_LSTAT +# undef HAVE_STAT +# undef HAVE_UMASK +# undef HAVE_UTIME +# if 1 +# undef HAVE_ALLOCA +# undef HAVE_ISATTY +# undef HAVE_MKDIR +# undef HAVE_RMDIR +# undef HAVE_STRDUP +# undef HAVE_STRICMP +# undef HAVE_STRNICMP +# endif +#endif +#endif +#endif +#if !(LZO_CFG_AUTO_NO_SIZES) +#if !defined(SIZEOF_SHORT) && defined(LZO_SIZEOF_SHORT) +# define SIZEOF_SHORT LZO_SIZEOF_SHORT +#endif +#if !defined(SIZEOF_INT) && defined(LZO_SIZEOF_INT) +# define SIZEOF_INT LZO_SIZEOF_INT +#endif +#if !defined(SIZEOF_LONG) && defined(LZO_SIZEOF_LONG) +# define SIZEOF_LONG LZO_SIZEOF_LONG +#endif +#if !defined(SIZEOF_LONG_LONG) && defined(LZO_SIZEOF_LONG_LONG) +# define SIZEOF_LONG_LONG LZO_SIZEOF_LONG_LONG +#endif +#if !defined(SIZEOF___INT32) && defined(LZO_SIZEOF___INT32) +# define SIZEOF___INT32 LZO_SIZEOF___INT32 +#endif +#if !defined(SIZEOF___INT64) && defined(LZO_SIZEOF___INT64) +# define SIZEOF___INT64 LZO_SIZEOF___INT64 +#endif +#if !defined(SIZEOF_VOID_P) && defined(LZO_SIZEOF_VOID_P) +# define SIZEOF_VOID_P LZO_SIZEOF_VOID_P +#endif +#if !defined(SIZEOF_SIZE_T) && defined(LZO_SIZEOF_SIZE_T) +# define SIZEOF_SIZE_T LZO_SIZEOF_SIZE_T +#endif +#if !defined(SIZEOF_PTRDIFF_T) && defined(LZO_SIZEOF_PTRDIFF_T) +# define SIZEOF_PTRDIFF_T LZO_SIZEOF_PTRDIFF_T +#endif +#endif +#if (HAVE_SIGNAL) && !defined(RETSIGTYPE) +# define RETSIGTYPE void +#endif +#endif +#if !(LZO_CFG_SKIP_LZO_TYPES) +#if 1 && !defined(lzo_signo_t) && defined(__linux__) && defined(__dietlibc__) && (LZO_SIZEOF_INT != 4) +# define lzo_signo_t lzo_int32e_t +#endif +#if !defined(lzo_signo_t) +# define lzo_signo_t int +#endif +#if defined(__cplusplus) +extern "C" { +#endif +#if (LZO_BROKEN_CDECL_ALT_SYNTAX) +typedef void __lzo_cdecl_sighandler (*lzo_sighandler_t)(lzo_signo_t); +#else +typedef void (__lzo_cdecl_sighandler *lzo_sighandler_t)(lzo_signo_t); +#endif +#if defined(__cplusplus) +} +#endif +#endif +#endif +#if defined(LZO_WANT_ACC_INCD_H) +# undef LZO_WANT_ACC_INCD_H +#ifndef __LZO_INCD_H_INCLUDED +#define __LZO_INCD_H_INCLUDED 1 +#if (LZO_LIBC_NAKED) +#ifndef __LZO_FALLBACK_STDDEF_H_INCLUDED +#define __LZO_FALLBACK_STDDEF_H_INCLUDED 1 +#if defined(__PTRDIFF_TYPE__) +typedef __PTRDIFF_TYPE__ lzo_fallback_ptrdiff_t; +#elif defined(__MIPS_PSX2__) +typedef int lzo_fallback_ptrdiff_t; +#else +typedef long lzo_fallback_ptrdiff_t; +#endif +#if defined(__SIZE_TYPE__) +typedef __SIZE_TYPE__ lzo_fallback_size_t; +#elif defined(__MIPS_PSX2__) +typedef unsigned int lzo_fallback_size_t; +#else +typedef unsigned long lzo_fallback_size_t; +#endif +#if !defined(ptrdiff_t) +typedef lzo_fallback_ptrdiff_t ptrdiff_t; +#ifndef _PTRDIFF_T_DEFINED +#define _PTRDIFF_T_DEFINED 1 +#endif +#endif +#if !defined(size_t) +typedef lzo_fallback_size_t size_t; +#ifndef _SIZE_T_DEFINED +#define _SIZE_T_DEFINED 1 +#endif +#endif +#if !defined(__cplusplus) && !defined(wchar_t) +typedef unsigned short wchar_t; +#ifndef _WCHAR_T_DEFINED +#define _WCHAR_T_DEFINED 1 +#endif +#endif +#ifndef NULL +#if defined(__cplusplus) && defined(__GNUC__) && (__GNUC__ >= 4) +#define NULL __null +#elif defined(__cplusplus) +#define NULL 0 +#else +#define NULL ((void*)0) +#endif +#endif +#ifndef offsetof +#define offsetof(s,m) ((size_t)((ptrdiff_t)&(((s*)0)->m))) +#endif +#endif +#elif (LZO_LIBC_FREESTANDING) +# if defined(HAVE_STDDEF_H) && (HAVE_STDDEF_H+0) +# include +# endif +# if defined(HAVE_STDINT_H) && (HAVE_STDINT_H+0) +# include +# endif +#elif (LZO_LIBC_MOSTLY_FREESTANDING) +# if defined(HAVE_STDIO_H) && (HAVE_STDIO_H+0) +# include +# endif +# if defined(HAVE_STDDEF_H) && (HAVE_STDDEF_H+0) +# include +# endif +# if defined(HAVE_STDINT_H) && (HAVE_STDINT_H+0) +# include +# endif +#else +#include +#if defined(HAVE_TIME_H) && (HAVE_TIME_H+0) && defined(__MSL__) && defined(__cplusplus) +# include +#endif +#if defined(HAVE_SYS_TYPES_H) && (HAVE_SYS_TYPES_H+0) +# include +#endif +#if defined(HAVE_SYS_STAT_H) && (HAVE_SYS_STAT_H+0) +# include +#endif +#if defined(STDC_HEADERS) && (STDC_HEADERS+0) +# include +#elif defined(HAVE_STDLIB_H) && (HAVE_STDLIB_H+0) +# include +#endif +#include +#if defined(HAVE_STRING_H) && (HAVE_STRING_H+0) +# if defined(STDC_HEADERS) && (STDC_HEADERS+0) +# elif defined(HAVE_MEMORY_H) && (HAVE_MEMORY_H+0) +# include +# endif +# include +#endif +#if defined(HAVE_STRINGS_H) && (HAVE_STRINGS_H+0) +# include +#endif +#if defined(HAVE_INTTYPES_H) && (HAVE_INTTYPES_H+0) +# include +#endif +#if defined(HAVE_STDINT_H) && (HAVE_STDINT_H+0) +# include +#endif +#if defined(HAVE_UNISTD_H) && (HAVE_UNISTD_H+0) +# include +#endif +#endif +#endif +#endif +#if defined(LZO_WANT_ACC_INCE_H) +# undef LZO_WANT_ACC_INCE_H +#ifndef __LZO_INCE_H_INCLUDED +#define __LZO_INCE_H_INCLUDED 1 +#if (LZO_LIBC_NAKED) +#elif (LZO_LIBC_FREESTANDING) +#elif (LZO_LIBC_MOSTLY_FREESTANDING) +# if (HAVE_SETJMP_H) +# include +# endif +#else +#if (HAVE_STDARG_H) +# include +#endif +#if (HAVE_CTYPE_H) +# include +#endif +#if (HAVE_ERRNO_H) +# include +#endif +#if (HAVE_MALLOC_H) +# include +#endif +#if (HAVE_ALLOCA_H) +# include +#endif +#if (HAVE_FCNTL_H) +# include +#endif +#if (HAVE_DIRENT_H) +# include +#endif +#if (HAVE_SETJMP_H) +# include +#endif +#if (HAVE_SIGNAL_H) +# include +#endif +#if (HAVE_SYS_TIME_H && HAVE_TIME_H) +# include +# include +#elif (HAVE_TIME_H) +# include +#endif +#if (HAVE_UTIME_H) +# include +#elif (HAVE_SYS_UTIME_H) +# include +#endif +#if (HAVE_IO_H) +# include +#endif +#if (HAVE_DOS_H) +# include +#endif +#if (HAVE_DIRECT_H) +# include +#endif +#if (HAVE_SHARE_H) +# include +#endif +#if (LZO_CC_NDPC) +# include +#endif +#if defined(__TOS__) && (defined(__PUREC__) || defined(__TURBOC__)) +# include +#endif +#endif +#endif +#endif +#if defined(LZO_WANT_ACC_INCI_H) +# undef LZO_WANT_ACC_INCI_H +#ifndef __LZO_INCI_H_INCLUDED +#define __LZO_INCI_H_INCLUDED 1 +#if (LZO_LIBC_NAKED) +#elif (LZO_LIBC_FREESTANDING) +#elif (LZO_LIBC_MOSTLY_FREESTANDING) +#else +#if (LZO_OS_TOS && (LZO_CC_PUREC || LZO_CC_TURBOC)) +# include +#elif (LZO_HAVE_WINDOWS_H) +# if 1 && !defined(WIN32_LEAN_AND_MEAN) +# define WIN32_LEAN_AND_MEAN 1 +# endif +# if 1 && !defined(_WIN32_WINNT) +# define _WIN32_WINNT 0x0400 +# endif +# include +# if (LZO_CC_BORLANDC || LZO_CC_TURBOC) +# include +# endif +#elif (LZO_OS_DOS16 || LZO_OS_DOS32 || LZO_OS_WIN16) +# if (LZO_CC_AZTECC) +# include +# include +# elif (LZO_CC_BORLANDC || LZO_CC_TURBOC) +# include +# include +# elif (LZO_OS_DOS32 && LZO_CC_GNUC) && defined(__DJGPP__) +# include +# elif (LZO_CC_PACIFICC) +# include +# include +# include +# elif (LZO_CC_WATCOMC) +# include +# endif +#elif (LZO_OS_OS216) +# if (LZO_CC_WATCOMC) +# include +# endif +#endif +#if (HAVE_SYS_MMAN_H) +# include +#endif +#if (HAVE_SYS_RESOURCE_H) +# include +#endif +#if (LZO_OS_DOS16 || LZO_OS_OS216 || LZO_OS_WIN16) +# if defined(FP_OFF) +# define LZO_PTR_FP_OFF(x) FP_OFF(x) +# elif defined(_FP_OFF) +# define LZO_PTR_FP_OFF(x) _FP_OFF(x) +# else +# define LZO_PTR_FP_OFF(x) (((const unsigned __far*)&(x))[0]) +# endif +# if defined(FP_SEG) +# define LZO_PTR_FP_SEG(x) FP_SEG(x) +# elif defined(_FP_SEG) +# define LZO_PTR_FP_SEG(x) _FP_SEG(x) +# else +# define LZO_PTR_FP_SEG(x) (((const unsigned __far*)&(x))[1]) +# endif +# if defined(MK_FP) +# define LZO_PTR_MK_FP(s,o) MK_FP(s,o) +# elif defined(_MK_FP) +# define LZO_PTR_MK_FP(s,o) _MK_FP(s,o) +# else +# define LZO_PTR_MK_FP(s,o) ((void __far*)(((unsigned long)(s)<<16)+(unsigned)(o))) +# endif +# if 0 +# undef LZO_PTR_FP_OFF +# undef LZO_PTR_FP_SEG +# undef LZO_PTR_MK_FP +# define LZO_PTR_FP_OFF(x) (((const unsigned __far*)&(x))[0]) +# define LZO_PTR_FP_SEG(x) (((const unsigned __far*)&(x))[1]) +# define LZO_PTR_MK_FP(s,o) ((void __far*)(((unsigned long)(s)<<16)+(unsigned)(o))) +# endif +#endif +#endif +#endif +#endif +#if defined(LZO_WANT_ACC_LIB_H) +# undef LZO_WANT_ACC_LIB_H +#ifndef __LZO_LIB_H_INCLUDED +#define __LZO_LIB_H_INCLUDED 1 +#if !defined(__LZOLIB_FUNCNAME) +# define __LZOLIB_FUNCNAME(f) f +#endif +#if !defined(LZOLIB_EXTERN) +# define LZOLIB_EXTERN(r,f) extern r __LZOLIB_FUNCNAME(f) +#endif +#if !defined(LZOLIB_EXTERN_NOINLINE) +# if defined(__lzo_noinline) +# define LZOLIB_EXTERN_NOINLINE(r,f) extern __lzo_noinline r __LZOLIB_FUNCNAME(f) +# else +# define LZOLIB_EXTERN_NOINLINE(r,f) extern r __LZOLIB_FUNCNAME(f) +# endif +#endif +#if (LZO_SIZEOF_LONG > LZO_SIZEOF_VOID_P) +# define lzolib_handle_t long +#else +# define lzolib_handle_t lzo_intptr_t +#endif +#if 0 +LZOLIB_EXTERN(int, lzo_ascii_digit) (int); +LZOLIB_EXTERN(int, lzo_ascii_islower) (int); +LZOLIB_EXTERN(int, lzo_ascii_isupper) (int); +LZOLIB_EXTERN(int, lzo_ascii_tolower) (int); +LZOLIB_EXTERN(int, lzo_ascii_toupper) (int); +LZOLIB_EXTERN(int, lzo_ascii_utolower) (int); +LZOLIB_EXTERN(int, lzo_ascii_utoupper) (int); +#endif +#define lzo_ascii_isdigit(c) ((LZO_ICAST(unsigned, c) - 48) < 10) +#define lzo_ascii_islower(c) ((LZO_ICAST(unsigned, c) - 97) < 26) +#define lzo_ascii_isupper(c) ((LZO_ICAST(unsigned, c) - 65) < 26) +#define lzo_ascii_tolower(c) (LZO_ICAST(int, c) + (lzo_ascii_isupper(c) << 5)) +#define lzo_ascii_toupper(c) (LZO_ICAST(int, c) - (lzo_ascii_islower(c) << 5)) +#define lzo_ascii_utolower(c) lzo_ascii_tolower(LZO_ITRUNC(unsigned char, c)) +#define lzo_ascii_utoupper(c) lzo_ascii_toupper(LZO_ITRUNC(unsigned char, c)) +#ifndef lzo_hsize_t +#if (LZO_HAVE_MM_HUGE_PTR) +# define lzo_hsize_t unsigned long +# define lzo_hvoid_p void __huge * +# define lzo_hchar_p char __huge * +# define lzo_hchar_pp char __huge * __huge * +# define lzo_hbyte_p unsigned char __huge * +#else +# define lzo_hsize_t size_t +# define lzo_hvoid_p void * +# define lzo_hchar_p char * +# define lzo_hchar_pp char ** +# define lzo_hbyte_p unsigned char * +#endif +#endif +LZOLIB_EXTERN(lzo_hvoid_p, lzo_halloc) (lzo_hsize_t); +LZOLIB_EXTERN(void, lzo_hfree) (lzo_hvoid_p); +#if (LZO_OS_DOS16 || LZO_OS_OS216) +LZOLIB_EXTERN(void __far*, lzo_dos_alloc) (unsigned long); +LZOLIB_EXTERN(int, lzo_dos_free) (void __far*); +#endif +LZOLIB_EXTERN(int, lzo_hmemcmp) (const lzo_hvoid_p, const lzo_hvoid_p, lzo_hsize_t); +LZOLIB_EXTERN(lzo_hvoid_p, lzo_hmemcpy) (lzo_hvoid_p, const lzo_hvoid_p, lzo_hsize_t); +LZOLIB_EXTERN(lzo_hvoid_p, lzo_hmemmove) (lzo_hvoid_p, const lzo_hvoid_p, lzo_hsize_t); +LZOLIB_EXTERN(lzo_hvoid_p, lzo_hmemset) (lzo_hvoid_p, int, lzo_hsize_t); +LZOLIB_EXTERN(lzo_hsize_t, lzo_hstrlen) (const lzo_hchar_p); +LZOLIB_EXTERN(int, lzo_hstrcmp) (const lzo_hchar_p, const lzo_hchar_p); +LZOLIB_EXTERN(int, lzo_hstrncmp)(const lzo_hchar_p, const lzo_hchar_p, lzo_hsize_t); +LZOLIB_EXTERN(int, lzo_ascii_hstricmp) (const lzo_hchar_p, const lzo_hchar_p); +LZOLIB_EXTERN(int, lzo_ascii_hstrnicmp)(const lzo_hchar_p, const lzo_hchar_p, lzo_hsize_t); +LZOLIB_EXTERN(int, lzo_ascii_hmemicmp) (const lzo_hvoid_p, const lzo_hvoid_p, lzo_hsize_t); +LZOLIB_EXTERN(lzo_hchar_p, lzo_hstrstr) (const lzo_hchar_p, const lzo_hchar_p); +LZOLIB_EXTERN(lzo_hchar_p, lzo_ascii_hstristr) (const lzo_hchar_p, const lzo_hchar_p); +LZOLIB_EXTERN(lzo_hvoid_p, lzo_hmemmem) (const lzo_hvoid_p, lzo_hsize_t, const lzo_hvoid_p, lzo_hsize_t); +LZOLIB_EXTERN(lzo_hvoid_p, lzo_ascii_hmemimem) (const lzo_hvoid_p, lzo_hsize_t, const lzo_hvoid_p, lzo_hsize_t); +LZOLIB_EXTERN(lzo_hchar_p, lzo_hstrcpy) (lzo_hchar_p, const lzo_hchar_p); +LZOLIB_EXTERN(lzo_hchar_p, lzo_hstrcat) (lzo_hchar_p, const lzo_hchar_p); +LZOLIB_EXTERN(lzo_hsize_t, lzo_hstrlcpy) (lzo_hchar_p, const lzo_hchar_p, lzo_hsize_t); +LZOLIB_EXTERN(lzo_hsize_t, lzo_hstrlcat) (lzo_hchar_p, const lzo_hchar_p, lzo_hsize_t); +LZOLIB_EXTERN(int, lzo_hstrscpy) (lzo_hchar_p, const lzo_hchar_p, lzo_hsize_t); +LZOLIB_EXTERN(int, lzo_hstrscat) (lzo_hchar_p, const lzo_hchar_p, lzo_hsize_t); +LZOLIB_EXTERN(lzo_hchar_p, lzo_hstrccpy) (lzo_hchar_p, const lzo_hchar_p, int); +LZOLIB_EXTERN(lzo_hvoid_p, lzo_hmemccpy) (lzo_hvoid_p, const lzo_hvoid_p, int, lzo_hsize_t); +LZOLIB_EXTERN(lzo_hchar_p, lzo_hstrchr) (const lzo_hchar_p, int); +LZOLIB_EXTERN(lzo_hchar_p, lzo_hstrrchr) (const lzo_hchar_p, int); +LZOLIB_EXTERN(lzo_hchar_p, lzo_ascii_hstrichr) (const lzo_hchar_p, int); +LZOLIB_EXTERN(lzo_hchar_p, lzo_ascii_hstrrichr) (const lzo_hchar_p, int); +LZOLIB_EXTERN(lzo_hvoid_p, lzo_hmemchr) (const lzo_hvoid_p, int, lzo_hsize_t); +LZOLIB_EXTERN(lzo_hvoid_p, lzo_hmemrchr) (const lzo_hvoid_p, int, lzo_hsize_t); +LZOLIB_EXTERN(lzo_hvoid_p, lzo_ascii_hmemichr) (const lzo_hvoid_p, int, lzo_hsize_t); +LZOLIB_EXTERN(lzo_hvoid_p, lzo_ascii_hmemrichr) (const lzo_hvoid_p, int, lzo_hsize_t); +LZOLIB_EXTERN(lzo_hsize_t, lzo_hstrspn) (const lzo_hchar_p, const lzo_hchar_p); +LZOLIB_EXTERN(lzo_hsize_t, lzo_hstrrspn) (const lzo_hchar_p, const lzo_hchar_p); +LZOLIB_EXTERN(lzo_hsize_t, lzo_hstrcspn) (const lzo_hchar_p, const lzo_hchar_p); +LZOLIB_EXTERN(lzo_hsize_t, lzo_hstrrcspn) (const lzo_hchar_p, const lzo_hchar_p); +LZOLIB_EXTERN(lzo_hchar_p, lzo_hstrpbrk) (const lzo_hchar_p, const lzo_hchar_p); +LZOLIB_EXTERN(lzo_hchar_p, lzo_hstrrpbrk) (const lzo_hchar_p, const lzo_hchar_p); +LZOLIB_EXTERN(lzo_hchar_p, lzo_hstrsep) (lzo_hchar_pp, const lzo_hchar_p); +LZOLIB_EXTERN(lzo_hchar_p, lzo_hstrrsep) (lzo_hchar_pp, const lzo_hchar_p); +LZOLIB_EXTERN(lzo_hchar_p, lzo_ascii_hstrlwr) (lzo_hchar_p); +LZOLIB_EXTERN(lzo_hchar_p, lzo_ascii_hstrupr) (lzo_hchar_p); +LZOLIB_EXTERN(lzo_hvoid_p, lzo_ascii_hmemlwr) (lzo_hvoid_p, lzo_hsize_t); +LZOLIB_EXTERN(lzo_hvoid_p, lzo_ascii_hmemupr) (lzo_hvoid_p, lzo_hsize_t); +LZOLIB_EXTERN(lzo_hsize_t, lzo_hfread) (void *, lzo_hvoid_p, lzo_hsize_t); +LZOLIB_EXTERN(lzo_hsize_t, lzo_hfwrite) (void *, const lzo_hvoid_p, lzo_hsize_t); +#if (LZO_HAVE_MM_HUGE_PTR) +LZOLIB_EXTERN(long, lzo_hread) (int, lzo_hvoid_p, long); +LZOLIB_EXTERN(long, lzo_hwrite) (int, const lzo_hvoid_p, long); +#endif +LZOLIB_EXTERN(long, lzo_safe_hread) (int, lzo_hvoid_p, long); +LZOLIB_EXTERN(long, lzo_safe_hwrite) (int, const lzo_hvoid_p, long); +LZOLIB_EXTERN(unsigned, lzo_ua_get_be16) (const lzo_hvoid_p); +LZOLIB_EXTERN(lzo_uint32l_t, lzo_ua_get_be24) (const lzo_hvoid_p); +LZOLIB_EXTERN(lzo_uint32l_t, lzo_ua_get_be32) (const lzo_hvoid_p); +LZOLIB_EXTERN(void, lzo_ua_set_be16) (lzo_hvoid_p, unsigned); +LZOLIB_EXTERN(void, lzo_ua_set_be24) (lzo_hvoid_p, lzo_uint32l_t); +LZOLIB_EXTERN(void, lzo_ua_set_be32) (lzo_hvoid_p, lzo_uint32l_t); +LZOLIB_EXTERN(unsigned, lzo_ua_get_le16) (const lzo_hvoid_p); +LZOLIB_EXTERN(lzo_uint32l_t, lzo_ua_get_le24) (const lzo_hvoid_p); +LZOLIB_EXTERN(lzo_uint32l_t, lzo_ua_get_le32) (const lzo_hvoid_p); +LZOLIB_EXTERN(void, lzo_ua_set_le16) (lzo_hvoid_p, unsigned); +LZOLIB_EXTERN(void, lzo_ua_set_le24) (lzo_hvoid_p, lzo_uint32l_t); +LZOLIB_EXTERN(void, lzo_ua_set_le32) (lzo_hvoid_p, lzo_uint32l_t); +#if defined(lzo_int64l_t) +LZOLIB_EXTERN(lzo_uint64l_t, lzo_ua_get_be64) (const lzo_hvoid_p); +LZOLIB_EXTERN(void, lzo_ua_set_be64) (lzo_hvoid_p, lzo_uint64l_t); +LZOLIB_EXTERN(lzo_uint64l_t, lzo_ua_get_le64) (const lzo_hvoid_p); +LZOLIB_EXTERN(void, lzo_ua_set_le64) (lzo_hvoid_p, lzo_uint64l_t); +#endif +LZOLIB_EXTERN_NOINLINE(short, lzo_vget_short) (short, int); +LZOLIB_EXTERN_NOINLINE(int, lzo_vget_int) (int, int); +LZOLIB_EXTERN_NOINLINE(long, lzo_vget_long) (long, int); +#if defined(lzo_int64l_t) +LZOLIB_EXTERN_NOINLINE(lzo_int64l_t, lzo_vget_lzo_int64l_t) (lzo_int64l_t, int); +#endif +LZOLIB_EXTERN_NOINLINE(lzo_hsize_t, lzo_vget_lzo_hsize_t) (lzo_hsize_t, int); +#if !(LZO_CFG_NO_FLOAT) +LZOLIB_EXTERN_NOINLINE(float, lzo_vget_float) (float, int); +#endif +#if !(LZO_CFG_NO_DOUBLE) +LZOLIB_EXTERN_NOINLINE(double, lzo_vget_double) (double, int); +#endif +LZOLIB_EXTERN_NOINLINE(lzo_hvoid_p, lzo_vget_lzo_hvoid_p) (lzo_hvoid_p, int); +LZOLIB_EXTERN_NOINLINE(const lzo_hvoid_p, lzo_vget_lzo_hvoid_cp) (const lzo_hvoid_p, int); +#if !defined(LZO_FN_PATH_MAX) +#if (LZO_OS_DOS16 || LZO_OS_WIN16) +# define LZO_FN_PATH_MAX 143 +#elif (LZO_OS_DOS32 || LZO_OS_OS2 || LZO_OS_OS216 || LZO_OS_WIN32 || LZO_OS_WIN64) +# define LZO_FN_PATH_MAX 259 +#elif (LZO_OS_TOS) +# define LZO_FN_PATH_MAX 259 +#endif +#endif +#if !defined(LZO_FN_PATH_MAX) +# define LZO_FN_PATH_MAX 1023 +#endif +#if !defined(LZO_FN_NAME_MAX) +#if (LZO_OS_DOS16 || LZO_OS_WIN16) +# define LZO_FN_NAME_MAX 12 +#elif (LZO_ARCH_M68K && LZO_OS_TOS && (LZO_CC_PUREC || LZO_CC_TURBOC)) +# define LZO_FN_NAME_MAX 12 +#elif (LZO_OS_DOS32 && LZO_CC_GNUC) && defined(__DJGPP__) +#elif (LZO_OS_DOS32) +# define LZO_FN_NAME_MAX 12 +#endif +#endif +#if !defined(LZO_FN_NAME_MAX) +# define LZO_FN_NAME_MAX LZO_FN_PATH_MAX +#endif +#define LZO_FNMATCH_NOESCAPE 1 +#define LZO_FNMATCH_PATHNAME 2 +#define LZO_FNMATCH_PATHSTAR 4 +#define LZO_FNMATCH_PERIOD 8 +#define LZO_FNMATCH_ASCII_CASEFOLD 16 +LZOLIB_EXTERN(int, lzo_fnmatch) (const lzo_hchar_p, const lzo_hchar_p, int); +#undef __LZOLIB_USE_OPENDIR +#if (HAVE_DIRENT_H || LZO_CC_WATCOMC) +# define __LZOLIB_USE_OPENDIR 1 +# if (LZO_OS_DOS32 && defined(__BORLANDC__)) +# elif (LZO_OS_DOS32 && LZO_CC_GNUC) && defined(__DJGPP__) +# elif (LZO_OS_OS2 || LZO_OS_OS216) +# elif (LZO_ARCH_M68K && LZO_OS_TOS && LZO_CC_GNUC) +# elif (LZO_OS_WIN32 && !(LZO_HAVE_WINDOWS_H)) +# elif (LZO_OS_DOS16 || LZO_OS_DOS32 || LZO_OS_OS2 || LZO_OS_OS216 || LZO_OS_TOS || LZO_OS_WIN16 || LZO_OS_WIN32 || LZO_OS_WIN64) +# undef __LZOLIB_USE_OPENDIR +# endif +#endif +typedef struct +{ +#if defined(__LZOLIB_USE_OPENDIR) + void* u_dirp; +# if (LZO_CC_WATCOMC) + unsigned short f_time; + unsigned short f_date; + unsigned long f_size; +# endif + char f_name[LZO_FN_NAME_MAX+1]; +#elif (LZO_OS_WIN32 || LZO_OS_WIN64) + lzolib_handle_t u_handle; + unsigned f_attr; + unsigned f_size_low; + unsigned f_size_high; + char f_name[LZO_FN_NAME_MAX+1]; +#elif (LZO_OS_DOS16 || LZO_OS_DOS32 || LZO_OS_TOS || LZO_OS_WIN16) + char u_dta[21]; + unsigned char f_attr; + unsigned short f_time; + unsigned short f_date; + unsigned short f_size_low; + unsigned short f_size_high; + char f_name[LZO_FN_NAME_MAX+1]; + char u_dirp; +#else + void* u_dirp; + char f_name[LZO_FN_NAME_MAX+1]; +#endif +} lzo_dir_t; +#ifndef lzo_dir_p +#define lzo_dir_p lzo_dir_t * +#endif +LZOLIB_EXTERN(int, lzo_opendir) (lzo_dir_p, const char*); +LZOLIB_EXTERN(int, lzo_readdir) (lzo_dir_p); +LZOLIB_EXTERN(int, lzo_closedir) (lzo_dir_p); +#if (LZO_CC_GNUC) && (defined(__CYGWIN__) || defined(__MINGW32__)) +# define lzo_alloca(x) __builtin_alloca((x)) +#elif (LZO_CC_GNUC) && (LZO_OS_CONSOLE_PS2) +# define lzo_alloca(x) __builtin_alloca((x)) +#elif (LZO_CC_BORLANDC || LZO_CC_LCC) && defined(__linux__) +#elif (HAVE_ALLOCA) +# define lzo_alloca(x) LZO_STATIC_CAST(void *, alloca((x))) +#endif +#if (LZO_OS_DOS32 && LZO_CC_GNUC) && defined(__DJGPP__) +# define lzo_stackavail() stackavail() +#elif (LZO_ARCH_I086 && LZO_CC_BORLANDC && (__BORLANDC__ >= 0x0410)) +# define lzo_stackavail() stackavail() +#elif (LZO_ARCH_I086 && LZO_CC_BORLANDC && (__BORLANDC__ >= 0x0400)) +# if (LZO_OS_WIN16) && (LZO_MM_TINY || LZO_MM_SMALL || LZO_MM_MEDIUM) +# else +# define lzo_stackavail() stackavail() +# endif +#elif ((LZO_ARCH_I086 || LZO_ARCH_I386) && (LZO_CC_DMC || LZO_CC_SYMANTECC)) +# define lzo_stackavail() stackavail() +#elif ((LZO_ARCH_I086) && LZO_CC_MSC && (_MSC_VER >= 700)) +# define lzo_stackavail() _stackavail() +#elif ((LZO_ARCH_I086) && LZO_CC_MSC) +# define lzo_stackavail() stackavail() +#elif ((LZO_ARCH_I086 || LZO_ARCH_I386) && LZO_CC_TURBOC && (__TURBOC__ >= 0x0450)) +# define lzo_stackavail() stackavail() +#elif (LZO_ARCH_I086 && LZO_CC_TURBOC && (__TURBOC__ >= 0x0400)) + LZO_EXTERN_C size_t __cdecl stackavail(void); +# define lzo_stackavail() stackavail() +#elif ((LZO_ARCH_I086 || LZO_ARCH_I386) && (LZO_CC_WATCOMC)) +# define lzo_stackavail() stackavail() +#elif (LZO_ARCH_I086 && LZO_CC_ZORTECHC) +# define lzo_stackavail() _chkstack() +#endif +LZOLIB_EXTERN(lzo_intptr_t, lzo_get_osfhandle) (int); +LZOLIB_EXTERN(const char *, lzo_getenv) (const char *); +LZOLIB_EXTERN(int, lzo_isatty) (int); +LZOLIB_EXTERN(int, lzo_mkdir) (const char*, unsigned); +LZOLIB_EXTERN(int, lzo_rmdir) (const char*); +LZOLIB_EXTERN(int, lzo_response) (int*, char***); +LZOLIB_EXTERN(int, lzo_set_binmode) (int, int); +#if defined(lzo_int32e_t) +LZOLIB_EXTERN(lzo_int32e_t, lzo_muldiv32s) (lzo_int32e_t, lzo_int32e_t, lzo_int32e_t); +LZOLIB_EXTERN(lzo_uint32e_t, lzo_muldiv32u) (lzo_uint32e_t, lzo_uint32e_t, lzo_uint32e_t); +#endif +LZOLIB_EXTERN(void, lzo_wildargv) (int*, char***); +LZOLIB_EXTERN_NOINLINE(void, lzo_debug_break) (void); +LZOLIB_EXTERN_NOINLINE(void, lzo_debug_nop) (void); +LZOLIB_EXTERN_NOINLINE(int, lzo_debug_align_check_query) (void); +LZOLIB_EXTERN_NOINLINE(int, lzo_debug_align_check_enable) (int); +LZOLIB_EXTERN_NOINLINE(unsigned, lzo_debug_running_on_qemu) (void); +LZOLIB_EXTERN_NOINLINE(unsigned, lzo_debug_running_on_valgrind) (void); +#if defined(lzo_int32e_t) +LZOLIB_EXTERN(int, lzo_tsc_read) (lzo_uint32e_t*); +#endif +struct lzo_pclock_handle_t; +struct lzo_pclock_t; +typedef struct lzo_pclock_handle_t lzo_pclock_handle_t; +typedef struct lzo_pclock_t lzo_pclock_t; +#ifndef lzo_pclock_handle_p +#define lzo_pclock_handle_p lzo_pclock_handle_t * +#endif +#ifndef lzo_pclock_p +#define lzo_pclock_p lzo_pclock_t * +#endif +#define LZO_PCLOCK_REALTIME 0 +#define LZO_PCLOCK_MONOTONIC 1 +#define LZO_PCLOCK_PROCESS_CPUTIME_ID 2 +#define LZO_PCLOCK_THREAD_CPUTIME_ID 3 +typedef int (*lzo_pclock_gettime_t) (lzo_pclock_handle_p, lzo_pclock_p); +struct lzo_pclock_handle_t { + lzolib_handle_t h; + int mode; + int read_error; + const char* name; + lzo_pclock_gettime_t gettime; +#if defined(lzo_int64l_t) + lzo_uint64l_t ticks_base; +#endif +}; +struct lzo_pclock_t { +#if defined(lzo_int64l_t) + lzo_int64l_t tv_sec; +#else + lzo_int32l_t tv_sec_high; + lzo_uint32l_t tv_sec_low; +#endif + lzo_uint32l_t tv_nsec; +}; +LZOLIB_EXTERN(int, lzo_pclock_open) (lzo_pclock_handle_p, int); +LZOLIB_EXTERN(int, lzo_pclock_open_default) (lzo_pclock_handle_p); +LZOLIB_EXTERN(int, lzo_pclock_close) (lzo_pclock_handle_p); +LZOLIB_EXTERN(void, lzo_pclock_read) (lzo_pclock_handle_p, lzo_pclock_p); +#if !(LZO_CFG_NO_DOUBLE) +LZOLIB_EXTERN(double, lzo_pclock_get_elapsed) (lzo_pclock_handle_p, const lzo_pclock_p, const lzo_pclock_p); +#endif +LZOLIB_EXTERN(int, lzo_pclock_flush_cpu_cache) (lzo_pclock_handle_p, unsigned); +struct lzo_getopt_t; +typedef struct lzo_getopt_t lzo_getopt_t; +#ifndef lzo_getopt_p +#define lzo_getopt_p lzo_getopt_t * +#endif +struct lzo_getopt_longopt_t; +typedef struct lzo_getopt_longopt_t lzo_getopt_longopt_t; +#ifndef lzo_getopt_longopt_p +#define lzo_getopt_longopt_p lzo_getopt_longopt_t * +#endif +struct lzo_getopt_longopt_t { + const char* name; + int has_arg; + int* flag; + int val; +}; +typedef void (*lzo_getopt_opterr_t)(lzo_getopt_p, const char*, void *); +struct lzo_getopt_t { + void *user; + const char *progname; + int bad_option; + char *optarg; + lzo_getopt_opterr_t opterr; + int optind; + int optopt; + int errcount; + int argc; char** argv; + int eof; int shortpos; + int pending_rotate_first, pending_rotate_middle; +}; +enum { LZO_GETOPT_NO_ARG, LZO_GETOPT_REQUIRED_ARG, LZO_GETOPT_OPTIONAL_ARG, LZO_GETOPT_EXACT_ARG = 0x10 }; +enum { LZO_GETOPT_PERMUTE, LZO_GETOPT_RETURN_IN_ORDER, LZO_GETOPT_REQUIRE_ORDER }; +LZOLIB_EXTERN(void, lzo_getopt_init) (lzo_getopt_p g, + int start_argc, int argc, char** argv); +LZOLIB_EXTERN(int, lzo_getopt) (lzo_getopt_p g, + const char* shortopts, + const lzo_getopt_longopt_p longopts, + int* longind); +typedef struct { + lzo_uint32l_t seed; +} lzo_rand31_t; +#ifndef lzo_rand31_p +#define lzo_rand31_p lzo_rand31_t * +#endif +LZOLIB_EXTERN(void, lzo_srand31) (lzo_rand31_p, lzo_uint32l_t); +LZOLIB_EXTERN(lzo_uint32l_t, lzo_rand31) (lzo_rand31_p); +#if defined(lzo_int64l_t) +typedef struct { + lzo_uint64l_t seed; +} lzo_rand48_t; +#ifndef lzo_rand48_p +#define lzo_rand48_p lzo_rand48_t * +#endif +LZOLIB_EXTERN(void, lzo_srand48) (lzo_rand48_p, lzo_uint32l_t); +LZOLIB_EXTERN(lzo_uint32l_t, lzo_rand48) (lzo_rand48_p); +LZOLIB_EXTERN(lzo_uint32l_t, lzo_rand48_r32) (lzo_rand48_p); +#endif +#if defined(lzo_int64l_t) +typedef struct { + lzo_uint64l_t seed; +} lzo_rand64_t; +#ifndef lzo_rand64_p +#define lzo_rand64_p lzo_rand64_t * +#endif +LZOLIB_EXTERN(void, lzo_srand64) (lzo_rand64_p, lzo_uint64l_t); +LZOLIB_EXTERN(lzo_uint32l_t, lzo_rand64) (lzo_rand64_p); +LZOLIB_EXTERN(lzo_uint32l_t, lzo_rand64_r32) (lzo_rand64_p); +#endif +typedef struct { + unsigned n; + lzo_uint32l_t s[624]; +} lzo_randmt_t; +#ifndef lzo_randmt_p +#define lzo_randmt_p lzo_randmt_t * +#endif +LZOLIB_EXTERN(void, lzo_srandmt) (lzo_randmt_p, lzo_uint32l_t); +LZOLIB_EXTERN(lzo_uint32l_t, lzo_randmt) (lzo_randmt_p); +LZOLIB_EXTERN(lzo_uint32l_t, lzo_randmt_r32) (lzo_randmt_p); +#if defined(lzo_int64l_t) +typedef struct { + unsigned n; + lzo_uint64l_t s[312]; +} lzo_randmt64_t; +#ifndef lzo_randmt64_p +#define lzo_randmt64_p lzo_randmt64_t * +#endif +LZOLIB_EXTERN(void, lzo_srandmt64) (lzo_randmt64_p, lzo_uint64l_t); +LZOLIB_EXTERN(lzo_uint64l_t, lzo_randmt64_r64) (lzo_randmt64_p); +#endif +#define LZO_SPAWN_P_WAIT 0 +#define LZO_SPAWN_P_NOWAIT 1 +LZOLIB_EXTERN(int, lzo_spawnv) (int mode, const char* fn, const char* const * argv); +LZOLIB_EXTERN(int, lzo_spawnvp) (int mode, const char* fn, const char* const * argv); +LZOLIB_EXTERN(int, lzo_spawnve) (int mode, const char* fn, const char* const * argv, const char * const envp); +#endif +#endif +#if defined(LZO_WANT_ACC_CXX_H) +# undef LZO_WANT_ACC_CXX_H +#ifndef __LZO_CXX_H_INCLUDED +#define __LZO_CXX_H_INCLUDED 1 +#if defined(__cplusplus) +#if defined(LZO_CXX_NOTHROW) +#elif (LZO_CC_GNUC && (LZO_CC_GNUC < 0x020800ul)) +#elif (LZO_CC_BORLANDC && (__BORLANDC__ < 0x0450)) +#elif (LZO_CC_GHS && !defined(__EXCEPTIONS)) +#elif (LZO_CC_HIGHC) +#elif (LZO_CC_MSC && (_MSC_VER < 1100)) +#elif (LZO_CC_NDPC) +#elif (LZO_CC_TURBOC) +#elif (LZO_CC_WATCOMC && !defined(_CPPUNWIND)) +#elif (LZO_CC_ZORTECHC) +#else +# define LZO_CXX_NOTHROW throw() +#endif +#if !defined(LZO_CXX_NOTHROW) +# define LZO_CXX_NOTHROW /*empty*/ +#endif +#if defined(__LZO_CXX_DO_NEW) +#elif (LZO_CC_GHS || LZO_CC_NDPC || LZO_CC_PGI) +# define __LZO_CXX_DO_NEW { return 0; } +#elif ((LZO_CC_BORLANDC || LZO_CC_TURBOC) && LZO_ARCH_I086) +# define __LZO_CXX_DO_NEW { return 0; } +#else +# define __LZO_CXX_DO_NEW ; +#endif +#if defined(__LZO_CXX_DO_DELETE) +#elif (LZO_CC_BORLANDC || LZO_CC_TURBOC) +# define __LZO_CXX_DO_DELETE { } +#else +# define __LZO_CXX_DO_DELETE LZO_CXX_NOTHROW { } +#endif +#if (LZO_CC_BORLANDC && (__BORLANDC__ < 0x0450)) +#elif (LZO_CC_MSC && LZO_MM_HUGE) +# define LZO_CXX_DISABLE_NEW_DELETE private: +#elif (LZO_CC_MSC && (_MSC_VER < 1100)) +#elif (LZO_CC_NDPC) +#elif (LZO_CC_SYMANTECC || LZO_CC_ZORTECHC) +#elif (LZO_CC_TURBOC) +#elif (LZO_CC_WATCOMC && (__WATCOMC__ < 1100)) +#else +# define __LZO_CXX_HAVE_ARRAY_NEW 1 +#endif +#if (__LZO_CXX_HAVE_ARRAY_NEW) +# define __LZO_CXX_HAVE_PLACEMENT_NEW 1 +#endif +#if (__LZO_CXX_HAVE_PLACEMENT_NEW) +# if (LZO_CC_GNUC >= 0x030000ul) +# define __LZO_CXX_HAVE_PLACEMENT_DELETE 1 +# elif (LZO_CC_INTELC) +# define __LZO_CXX_HAVE_PLACEMENT_DELETE 1 +# elif (LZO_CC_MSC && (_MSC_VER >= 1200)) +# define __LZO_CXX_HAVE_PLACEMENT_DELETE 1 +# elif (LZO_CC_CLANG || LZO_CC_LLVM || LZO_CC_PATHSCALE) +# define __LZO_CXX_HAVE_PLACEMENT_DELETE 1 +# elif (LZO_CC_PGI) +# define __LZO_CXX_HAVE_PLACEMENT_DELETE 1 +# endif +#endif +#if defined(LZO_CXX_DISABLE_NEW_DELETE) +#elif defined(new) || defined(delete) +# define LZO_CXX_DISABLE_NEW_DELETE private: +#elif (LZO_CC_GNUC && (LZO_CC_GNUC < 0x025b00ul)) +# define LZO_CXX_DISABLE_NEW_DELETE private: +#elif (LZO_CC_HIGHC) +# define LZO_CXX_DISABLE_NEW_DELETE private: +#elif !(__LZO_CXX_HAVE_ARRAY_NEW) +# define LZO_CXX_DISABLE_NEW_DELETE \ + protected: static void operator delete(void*) __LZO_CXX_DO_DELETE \ + protected: static void* operator new(size_t) __LZO_CXX_DO_NEW \ + private: +#else +# define LZO_CXX_DISABLE_NEW_DELETE \ + protected: static void operator delete(void*) __LZO_CXX_DO_DELETE \ + static void operator delete[](void*) __LZO_CXX_DO_DELETE \ + private: static void* operator new(size_t) __LZO_CXX_DO_NEW \ + static void* operator new[](size_t) __LZO_CXX_DO_NEW +#endif +#if defined(LZO_CXX_TRIGGER_FUNCTION) +#else +# define LZO_CXX_TRIGGER_FUNCTION \ + protected: virtual const void* lzo_cxx_trigger_function() const; \ + private: +#endif +#if defined(LZO_CXX_TRIGGER_FUNCTION_IMPL) +#else +# define LZO_CXX_TRIGGER_FUNCTION_IMPL(klass) \ + const void* klass::lzo_cxx_trigger_function() const { return LZO_STATIC_CAST(const void *, 0); } +#endif +#endif +#endif +#endif +#if defined(LZO_WANT_ACC_CHK_CH) +# undef LZO_WANT_ACC_CHK_CH +#if !defined(LZOCHK_ASSERT) +# define LZOCHK_ASSERT(expr) LZO_COMPILE_TIME_ASSERT_HEADER(expr) +#endif +#if !defined(LZOCHK_ASSERT_SIGN_T) +# define LZOCHK_ASSERT_SIGN_T(type,relop) \ + LZOCHK_ASSERT( LZO_STATIC_CAST(type, -1) relop LZO_STATIC_CAST(type, 0)) \ + LZOCHK_ASSERT( LZO_STATIC_CAST(type, ~LZO_STATIC_CAST(type, 0)) relop LZO_STATIC_CAST(type, 0)) \ + LZOCHK_ASSERT( LZO_STATIC_CAST(type, ~LZO_STATIC_CAST(type, 0)) == LZO_STATIC_CAST(type, -1)) +#endif +#if !defined(LZOCHK_ASSERT_IS_SIGNED_T) +# define LZOCHK_ASSERT_IS_SIGNED_T(type) LZOCHK_ASSERT_SIGN_T(type,<) +#endif +#if !defined(LZOCHK_ASSERT_IS_UNSIGNED_T) +# if (LZO_BROKEN_INTEGRAL_PROMOTION) +# define LZOCHK_ASSERT_IS_UNSIGNED_T(type) \ + LZOCHK_ASSERT( LZO_STATIC_CAST(type, -1) > LZO_STATIC_CAST(type, 0) ) +# else +# define LZOCHK_ASSERT_IS_UNSIGNED_T(type) LZOCHK_ASSERT_SIGN_T(type,>) +# endif +#endif +#if defined(LZOCHK_CFG_PEDANTIC) +#if (LZO_CC_BORLANDC && (__BORLANDC__ >= 0x0550) && (__BORLANDC__ < 0x0560)) +# pragma option push -w-8055 +#elif (LZO_CC_BORLANDC && (__BORLANDC__ >= 0x0530) && (__BORLANDC__ < 0x0550)) +# pragma option push -w-osh +#endif +#endif +#if (LZO_0xffffffffL - LZO_UINT32_C(4294967294) != 1) +# error "preprocessor error" +#endif +#if (LZO_0xffffffffL - LZO_UINT32_C(0xfffffffd) != 2) +# error "preprocessor error" +#endif +#if +0 +# error "preprocessor error" +#endif +#if -0 +# error "preprocessor error" +#endif +#if +0 != 0 +# error "preprocessor error" +#endif +#if -0 != 0 +# error "preprocessor error" +#endif +#define LZOCHK_VAL 1 +#define LZOCHK_TMP1 LZOCHK_VAL +#undef LZOCHK_VAL +#define LZOCHK_VAL 2 +#define LZOCHK_TMP2 LZOCHK_VAL +#if (LZOCHK_TMP1 != 2) +# error "preprocessor error 3a" +#endif +#if (LZOCHK_TMP2 != 2) +# error "preprocessor error 3b" +#endif +#undef LZOCHK_VAL +#if (LZOCHK_TMP2) +# error "preprocessor error 3c" +#endif +#if (LZOCHK_TMP2 + 0 != 0) +# error "preprocessor error 3d" +#endif +#undef LZOCHK_TMP1 +#undef LZOCHK_TMP2 +#if 0 || defined(LZOCHK_CFG_PEDANTIC) +# if (LZO_ARCH_MIPS) && defined(_MIPS_SZINT) + LZOCHK_ASSERT((_MIPS_SZINT) == 8 * sizeof(int)) +# endif +# if (LZO_ARCH_MIPS) && defined(_MIPS_SZLONG) + LZOCHK_ASSERT((_MIPS_SZLONG) == 8 * sizeof(long)) +# endif +# if (LZO_ARCH_MIPS) && defined(_MIPS_SZPTR) + LZOCHK_ASSERT((_MIPS_SZPTR) == 8 * sizeof(void *)) +# endif +#endif + LZOCHK_ASSERT(1 == 1) + LZOCHK_ASSERT(__LZO_MASK_GEN(1u,1) == 1u) + LZOCHK_ASSERT(__LZO_MASK_GEN(1u,2) == 3u) + LZOCHK_ASSERT(__LZO_MASK_GEN(1u,3) == 7u) + LZOCHK_ASSERT(__LZO_MASK_GEN(1u,8) == 255u) +#if (LZO_SIZEOF_INT >= 2) + LZOCHK_ASSERT(__LZO_MASK_GEN(1,15) == 32767) + LZOCHK_ASSERT(__LZO_MASK_GEN(1u,16) == 0xffffU) + LZOCHK_ASSERT(__LZO_MASK_GEN(0u,16) == 0u) +#endif + LZOCHK_ASSERT(__LZO_MASK_GEN(1ul,16) == 0xffffUL) + LZOCHK_ASSERT(__LZO_MASK_GEN(0ul,16) == 0ul) +#if (LZO_SIZEOF_INT >= 4) + LZOCHK_ASSERT(__LZO_MASK_GEN(1,31) == 2147483647) + LZOCHK_ASSERT(__LZO_MASK_GEN(1u,32) == 0xffffffffU) + LZOCHK_ASSERT(__LZO_MASK_GEN(0u,32) == 0u) +#endif +#if (LZO_SIZEOF_LONG >= 4) + LZOCHK_ASSERT(__LZO_MASK_GEN(1ul,32) == 0xffffffffUL) + LZOCHK_ASSERT(__LZO_MASK_GEN(0ul,32) == 0ul) +#endif +#if (LZO_SIZEOF_LONG >= 8) + LZOCHK_ASSERT(__LZO_MASK_GEN(1ul,64) == 0xffffffffffffffffUL) + LZOCHK_ASSERT(__LZO_MASK_GEN(0ul,64) == 0ul) +#endif +#if !(LZO_BROKEN_INTEGRAL_PROMOTION) + LZOCHK_ASSERT(__LZO_MASK_GEN(1u,LZO_SIZEOF_INT*8) == ~0u) + LZOCHK_ASSERT(__LZO_MASK_GEN(1ul,LZO_SIZEOF_LONG*8) == ~0ul) +#endif +#if 1 + LZOCHK_ASSERT(__LZO_MASK_GEN(0,0) == 0) + LZOCHK_ASSERT(__LZO_MASK_GEN(1,0) == 0) + LZOCHK_ASSERT(__LZO_MASK_GEN(2,0) == 0) + LZOCHK_ASSERT(__LZO_MASK_GEN(4,0) == 0) +#endif +#if 1 + LZOCHK_ASSERT(__LZO_MASK_GEN(2,1) == 2) + LZOCHK_ASSERT(__LZO_MASK_GEN(4,1) == 4) + LZOCHK_ASSERT(__LZO_MASK_GEN(8,1) == 8) + LZOCHK_ASSERT(__LZO_MASK_GEN(2,2) == 2+4) + LZOCHK_ASSERT(__LZO_MASK_GEN(4,2) == 4+8) + LZOCHK_ASSERT(__LZO_MASK_GEN(8,2) == 8+16) + LZOCHK_ASSERT(__LZO_MASK_GEN(2,3) == 2+4+8) + LZOCHK_ASSERT(__LZO_MASK_GEN(4,3) == 4+8+16) + LZOCHK_ASSERT(__LZO_MASK_GEN(8,3) == 8+16+32) + LZOCHK_ASSERT(__LZO_MASK_GEN(7,1) == 7) + LZOCHK_ASSERT(__LZO_MASK_GEN(7,2) == 7+14) + LZOCHK_ASSERT(__LZO_MASK_GEN(7,3) == 7+14+28) +#endif +#if !(LZO_BROKEN_SIGNED_RIGHT_SHIFT) + LZOCHK_ASSERT(((-1) >> 7) == -1) +#endif + LZOCHK_ASSERT(((1) >> 7) == 0) +#if (LZO_CC_INTELC && (__INTEL_COMPILER >= 900)) +# pragma warning(push) +# pragma warning(disable: 1025) +#endif + LZOCHK_ASSERT((~0l & ~0) == ~0l) + LZOCHK_ASSERT((~0l & ~0u) == ~0u) + LZOCHK_ASSERT((~0ul & ~0) == ~0ul) + LZOCHK_ASSERT((~0ul & ~0u) == ~0u) +#if defined(__MSDOS__) && defined(__TURBOC__) && (__TURBOC__ < 0x0150) +#elif (LZO_SIZEOF_INT == 2) + LZOCHK_ASSERT((~0l & ~0u) == 0xffffU) + LZOCHK_ASSERT((~0ul & ~0u) == 0xffffU) +#elif (LZO_SIZEOF_INT == 4) + LZOCHK_ASSERT((~0l & ~0u) == 0xffffffffU) + LZOCHK_ASSERT((~0ul & ~0u) == 0xffffffffU) +#endif +#if (LZO_CC_INTELC && (__INTEL_COMPILER >= 900)) +# pragma warning(pop) +#endif + LZOCHK_ASSERT_IS_SIGNED_T(signed char) + LZOCHK_ASSERT_IS_UNSIGNED_T(unsigned char) + LZOCHK_ASSERT(sizeof(signed char) == sizeof(char)) + LZOCHK_ASSERT(sizeof(unsigned char) == sizeof(char)) + LZOCHK_ASSERT(sizeof(char) == 1) +#if (LZO_CC_CILLY) && (!defined(__CILLY__) || (__CILLY__ < 0x010302L)) +#else + LZOCHK_ASSERT(sizeof(char) == sizeof(LZO_STATIC_CAST(char, 0))) +#endif +#if defined(__cplusplus) + LZOCHK_ASSERT(sizeof('\0') == sizeof(char)) +#else +# if (LZO_CC_DMC) +# else + LZOCHK_ASSERT(sizeof('\0') == sizeof(int)) +# endif +#endif +#if defined(__lzo_alignof) + LZOCHK_ASSERT(__lzo_alignof(char) == 1) + LZOCHK_ASSERT(__lzo_alignof(signed char) == 1) + LZOCHK_ASSERT(__lzo_alignof(unsigned char) == 1) +#if defined(lzo_int16e_t) + LZOCHK_ASSERT(__lzo_alignof(lzo_int16e_t) >= 1) + LZOCHK_ASSERT(__lzo_alignof(lzo_int16e_t) <= 2) +#endif +#if defined(lzo_int32e_t) + LZOCHK_ASSERT(__lzo_alignof(lzo_int32e_t) >= 1) + LZOCHK_ASSERT(__lzo_alignof(lzo_int32e_t) <= 4) +#endif +#endif + LZOCHK_ASSERT_IS_SIGNED_T(short) + LZOCHK_ASSERT_IS_UNSIGNED_T(unsigned short) + LZOCHK_ASSERT(sizeof(short) == sizeof(unsigned short)) +#if !(LZO_ABI_I8LP16) + LZOCHK_ASSERT(sizeof(short) >= 2) +#endif + LZOCHK_ASSERT(sizeof(short) >= sizeof(char)) +#if (LZO_CC_CILLY) && (!defined(__CILLY__) || (__CILLY__ < 0x010302L)) +#else + LZOCHK_ASSERT(sizeof(short) == sizeof(LZO_STATIC_CAST(short, 0))) +#endif +#if (LZO_SIZEOF_SHORT > 0) + LZOCHK_ASSERT(sizeof(short) == LZO_SIZEOF_SHORT) +#endif + LZOCHK_ASSERT_IS_SIGNED_T(int) + LZOCHK_ASSERT_IS_UNSIGNED_T(unsigned int) + LZOCHK_ASSERT(sizeof(int) == sizeof(unsigned int)) +#if !(LZO_ABI_I8LP16) + LZOCHK_ASSERT(sizeof(int) >= 2) +#endif + LZOCHK_ASSERT(sizeof(int) >= sizeof(short)) + LZOCHK_ASSERT(sizeof(int) == sizeof(0)) + LZOCHK_ASSERT(sizeof(int) == sizeof(LZO_STATIC_CAST(int, 0))) +#if (LZO_SIZEOF_INT > 0) + LZOCHK_ASSERT(sizeof(int) == LZO_SIZEOF_INT) +#endif + LZOCHK_ASSERT(sizeof(0) == sizeof(int)) + LZOCHK_ASSERT_IS_SIGNED_T(long) + LZOCHK_ASSERT_IS_UNSIGNED_T(unsigned long) + LZOCHK_ASSERT(sizeof(long) == sizeof(unsigned long)) +#if !(LZO_ABI_I8LP16) + LZOCHK_ASSERT(sizeof(long) >= 4) +#endif + LZOCHK_ASSERT(sizeof(long) >= sizeof(int)) + LZOCHK_ASSERT(sizeof(long) == sizeof(0L)) + LZOCHK_ASSERT(sizeof(long) == sizeof(LZO_STATIC_CAST(long, 0))) +#if (LZO_SIZEOF_LONG > 0) + LZOCHK_ASSERT(sizeof(long) == LZO_SIZEOF_LONG) +#endif + LZOCHK_ASSERT(sizeof(0L) == sizeof(long)) + LZOCHK_ASSERT_IS_UNSIGNED_T(size_t) + LZOCHK_ASSERT(sizeof(size_t) >= sizeof(int)) + LZOCHK_ASSERT(sizeof(size_t) == sizeof(sizeof(0))) +#if (LZO_SIZEOF_SIZE_T > 0) + LZOCHK_ASSERT(sizeof(size_t) == LZO_SIZEOF_SIZE_T) +#endif + LZOCHK_ASSERT_IS_SIGNED_T(ptrdiff_t) + LZOCHK_ASSERT(sizeof(ptrdiff_t) >= sizeof(int)) + LZOCHK_ASSERT(sizeof(ptrdiff_t) >= sizeof(size_t)) +#if !(LZO_BROKEN_SIZEOF) + LZOCHK_ASSERT(sizeof(ptrdiff_t) == sizeof(LZO_STATIC_CAST(char*, 0) - LZO_STATIC_CAST(char*, 0))) +# if (LZO_HAVE_MM_HUGE_PTR) + LZOCHK_ASSERT(4 == sizeof(LZO_STATIC_CAST(char __huge*, 0) - LZO_STATIC_CAST(char __huge*, 0))) +# endif +#endif +#if (LZO_SIZEOF_PTRDIFF_T > 0) + LZOCHK_ASSERT(sizeof(ptrdiff_t) == LZO_SIZEOF_PTRDIFF_T) +#endif + LZOCHK_ASSERT(sizeof(void*) >= sizeof(char*)) +#if (LZO_SIZEOF_VOID_P > 0) + LZOCHK_ASSERT(sizeof(void*) == LZO_SIZEOF_VOID_P) + LZOCHK_ASSERT(sizeof(char*) == LZO_SIZEOF_VOID_P) +#endif +#if (LZO_HAVE_MM_HUGE_PTR) + LZOCHK_ASSERT(4 == sizeof(void __huge*)) + LZOCHK_ASSERT(4 == sizeof(char __huge*)) +#endif +#if (LZO_ABI_I8LP16) + LZOCHK_ASSERT((((1u << 7) + 1) >> 7) == 1) + LZOCHK_ASSERT((((1ul << 15) + 1) >> 15) == 1) +#else + LZOCHK_ASSERT((((1u << 15) + 1) >> 15) == 1) + LZOCHK_ASSERT((((1ul << 31) + 1) >> 31) == 1) +#endif +#if defined(LZOCHK_CFG_PEDANTIC) +#if defined(__MSDOS__) && defined(__TURBOC__) && (__TURBOC__ < 0x0150) +#else + LZOCHK_ASSERT((1 << (8*LZO_SIZEOF_INT-1)) < 0) +#endif +#endif + LZOCHK_ASSERT((1u << (8*LZO_SIZEOF_INT-1)) > 0) +#if defined(LZOCHK_CFG_PEDANTIC) + LZOCHK_ASSERT((1l << (8*LZO_SIZEOF_LONG-1)) < 0) +#endif + LZOCHK_ASSERT((1ul << (8*LZO_SIZEOF_LONG-1)) > 0) +#if defined(lzo_int16e_t) + LZOCHK_ASSERT(sizeof(lzo_int16e_t) == 2) + LZOCHK_ASSERT(sizeof(lzo_int16e_t) == LZO_SIZEOF_LZO_INT16E_T) + LZOCHK_ASSERT(sizeof(lzo_uint16e_t) == 2) + LZOCHK_ASSERT(sizeof(lzo_int16e_t) == sizeof(lzo_uint16e_t)) + LZOCHK_ASSERT_IS_SIGNED_T(lzo_int16e_t) + LZOCHK_ASSERT_IS_UNSIGNED_T(lzo_uint16e_t) +#if defined(__MSDOS__) && defined(__TURBOC__) && (__TURBOC__ < 0x0150) +#else + LZOCHK_ASSERT((LZO_STATIC_CAST(lzo_uint16e_t, (~LZO_STATIC_CAST(lzo_uint16e_t,0ul))) >> 15) == 1) +#endif + LZOCHK_ASSERT( LZO_STATIC_CAST(lzo_int16e_t, (1 + ~LZO_STATIC_CAST(lzo_int16e_t, 0))) == 0) +#if defined(LZOCHK_CFG_PEDANTIC) + LZOCHK_ASSERT( LZO_STATIC_CAST(lzo_uint16e_t, (1 + ~LZO_STATIC_CAST(lzo_uint16e_t, 0))) == 0) +#endif +#endif +#if defined(lzo_int32e_t) + LZOCHK_ASSERT(sizeof(lzo_int32e_t) == 4) + LZOCHK_ASSERT(sizeof(lzo_int32e_t) == LZO_SIZEOF_LZO_INT32E_T) + LZOCHK_ASSERT(sizeof(lzo_uint32e_t) == 4) + LZOCHK_ASSERT(sizeof(lzo_int32e_t) == sizeof(lzo_uint32e_t)) + LZOCHK_ASSERT_IS_SIGNED_T(lzo_int32e_t) + LZOCHK_ASSERT(((( LZO_STATIC_CAST(lzo_int32e_t, 1) << 30) + 1) >> 30) == 1) + LZOCHK_ASSERT_IS_UNSIGNED_T(lzo_uint32e_t) + LZOCHK_ASSERT(((( LZO_STATIC_CAST(lzo_uint32e_t, 1) << 31) + 1) >> 31) == 1) + LZOCHK_ASSERT((LZO_STATIC_CAST(lzo_uint32e_t, (~LZO_STATIC_CAST(lzo_uint32e_t, 0ul))) >> 31) == 1) + LZOCHK_ASSERT( LZO_STATIC_CAST(lzo_int32e_t, (1 + ~LZO_STATIC_CAST(lzo_int32e_t, 0))) == 0) +#if defined(LZOCHK_CFG_PEDANTIC) + LZOCHK_ASSERT( LZO_STATIC_CAST(lzo_uint32e_t, (1 + ~LZO_STATIC_CAST(lzo_uint32e_t, 0))) == 0) +#endif +#endif +#if defined(lzo_int32e_t) + LZOCHK_ASSERT(sizeof(lzo_int32l_t) >= sizeof(lzo_int32e_t)) +#endif + LZOCHK_ASSERT(sizeof(lzo_int32l_t) >= 4) + LZOCHK_ASSERT(sizeof(lzo_int32l_t) == LZO_SIZEOF_LZO_INT32L_T) + LZOCHK_ASSERT(sizeof(lzo_uint32l_t) >= 4) + LZOCHK_ASSERT(sizeof(lzo_int32l_t) == sizeof(lzo_uint32l_t)) + LZOCHK_ASSERT_IS_SIGNED_T(lzo_int32l_t) + LZOCHK_ASSERT(((( LZO_STATIC_CAST(lzo_int32l_t, 1) << 30) + 1) >> 30) == 1) + LZOCHK_ASSERT_IS_UNSIGNED_T(lzo_uint32l_t) + LZOCHK_ASSERT(((( LZO_STATIC_CAST(lzo_uint32l_t, 1) << 31) + 1) >> 31) == 1) + LZOCHK_ASSERT(sizeof(lzo_int32f_t) >= sizeof(int)) +#if defined(lzo_int32e_t) + LZOCHK_ASSERT(sizeof(lzo_int32f_t) >= sizeof(lzo_int32e_t)) +#endif + LZOCHK_ASSERT(sizeof(lzo_int32f_t) >= sizeof(lzo_int32l_t)) + LZOCHK_ASSERT(sizeof(lzo_int32f_t) >= 4) + LZOCHK_ASSERT(sizeof(lzo_int32f_t) >= sizeof(lzo_int32l_t)) + LZOCHK_ASSERT(sizeof(lzo_int32f_t) == LZO_SIZEOF_LZO_INT32F_T) + LZOCHK_ASSERT(sizeof(lzo_uint32f_t) >= 4) + LZOCHK_ASSERT(sizeof(lzo_uint32f_t) >= sizeof(lzo_uint32l_t)) + LZOCHK_ASSERT(sizeof(lzo_int32f_t) == sizeof(lzo_uint32f_t)) + LZOCHK_ASSERT_IS_SIGNED_T(lzo_int32f_t) + LZOCHK_ASSERT(((( LZO_STATIC_CAST(lzo_int32f_t, 1) << 30) + 1) >> 30) == 1) + LZOCHK_ASSERT_IS_UNSIGNED_T(lzo_uint32f_t) + LZOCHK_ASSERT(((( LZO_STATIC_CAST(lzo_uint32f_t, 1) << 31) + 1) >> 31) == 1) +#if defined(lzo_int64e_t) + LZOCHK_ASSERT(sizeof(lzo_int64e_t) == 8) + LZOCHK_ASSERT(sizeof(lzo_int64e_t) == LZO_SIZEOF_LZO_INT64E_T) + LZOCHK_ASSERT(sizeof(lzo_uint64e_t) == 8) + LZOCHK_ASSERT(sizeof(lzo_int64e_t) == sizeof(lzo_uint64e_t)) + LZOCHK_ASSERT_IS_SIGNED_T(lzo_int64e_t) +#if (LZO_CC_BORLANDC && (__BORLANDC__ < 0x0530)) +#else + LZOCHK_ASSERT_IS_UNSIGNED_T(lzo_uint64e_t) +#endif +#endif +#if defined(lzo_int64l_t) +#if defined(lzo_int64e_t) + LZOCHK_ASSERT(sizeof(lzo_int64l_t) >= sizeof(lzo_int64e_t)) +#endif + LZOCHK_ASSERT(sizeof(lzo_int64l_t) >= 8) + LZOCHK_ASSERT(sizeof(lzo_int64l_t) == LZO_SIZEOF_LZO_INT64L_T) + LZOCHK_ASSERT(sizeof(lzo_uint64l_t) >= 8) + LZOCHK_ASSERT(sizeof(lzo_int64l_t) == sizeof(lzo_uint64l_t)) + LZOCHK_ASSERT_IS_SIGNED_T(lzo_int64l_t) + LZOCHK_ASSERT(((( LZO_STATIC_CAST(lzo_int64l_t, 1) << 62) + 1) >> 62) == 1) + LZOCHK_ASSERT(((( LZO_INT64_C(1) << 62) + 1) >> 62) == 1) +#if (LZO_CC_BORLANDC && (__BORLANDC__ < 0x0530)) +#else + LZOCHK_ASSERT_IS_UNSIGNED_T(lzo_uint64l_t) + LZOCHK_ASSERT(LZO_UINT64_C(18446744073709551615) > 0) +#endif + LZOCHK_ASSERT(((( LZO_STATIC_CAST(lzo_uint64l_t, 1) << 63) + 1) >> 63) == 1) + LZOCHK_ASSERT(((( LZO_UINT64_C(1) << 63) + 1) >> 63) == 1) +#if (LZO_CC_GNUC && (LZO_CC_GNUC < 0x020600ul)) + LZOCHK_ASSERT(LZO_INT64_C(9223372036854775807) > LZO_INT64_C(0)) +#else + LZOCHK_ASSERT(LZO_INT64_C(9223372036854775807) > 0) +#endif + LZOCHK_ASSERT(LZO_INT64_C(-9223372036854775807) - 1 < 0) + LZOCHK_ASSERT( LZO_INT64_C(9223372036854775807) % LZO_INT32_C(2147483629) == 721) + LZOCHK_ASSERT( LZO_INT64_C(9223372036854775807) % LZO_INT32_C(2147483647) == 1) + LZOCHK_ASSERT(LZO_UINT64_C(9223372036854775807) % LZO_UINT32_C(2147483629) == 721) + LZOCHK_ASSERT(LZO_UINT64_C(9223372036854775807) % LZO_UINT32_C(2147483647) == 1) +#endif +#if defined(lzo_int64f_t) +#if defined(lzo_int64e_t) + LZOCHK_ASSERT(sizeof(lzo_int64f_t) >= sizeof(lzo_int64e_t)) +#endif + LZOCHK_ASSERT(sizeof(lzo_int64f_t) >= sizeof(lzo_int64l_t)) + LZOCHK_ASSERT(sizeof(lzo_int64f_t) >= 8) + LZOCHK_ASSERT(sizeof(lzo_int64f_t) >= sizeof(lzo_int64l_t)) + LZOCHK_ASSERT(sizeof(lzo_int64f_t) == LZO_SIZEOF_LZO_INT64F_T) + LZOCHK_ASSERT(sizeof(lzo_uint64f_t) >= 8) + LZOCHK_ASSERT(sizeof(lzo_uint64f_t) >= sizeof(lzo_uint64l_t)) + LZOCHK_ASSERT(sizeof(lzo_int64f_t) == sizeof(lzo_uint64f_t)) + LZOCHK_ASSERT_IS_SIGNED_T(lzo_int64f_t) +#if (LZO_CC_BORLANDC && (__BORLANDC__ < 0x0530)) +#else + LZOCHK_ASSERT_IS_UNSIGNED_T(lzo_uint64f_t) +#endif +#endif +#if !defined(__LZO_INTPTR_T_IS_POINTER) + LZOCHK_ASSERT_IS_SIGNED_T(lzo_intptr_t) + LZOCHK_ASSERT_IS_UNSIGNED_T(lzo_uintptr_t) +#endif + LZOCHK_ASSERT(sizeof(lzo_intptr_t) >= sizeof(void *)) + LZOCHK_ASSERT(sizeof(lzo_intptr_t) == LZO_SIZEOF_LZO_INTPTR_T) + LZOCHK_ASSERT(sizeof(lzo_intptr_t) == sizeof(lzo_uintptr_t)) +#if defined(lzo_word_t) + LZOCHK_ASSERT(LZO_WORDSIZE == LZO_SIZEOF_LZO_WORD_T) + LZOCHK_ASSERT_IS_UNSIGNED_T(lzo_word_t) + LZOCHK_ASSERT_IS_SIGNED_T(lzo_sword_t) + LZOCHK_ASSERT(sizeof(lzo_word_t) == LZO_SIZEOF_LZO_WORD_T) + LZOCHK_ASSERT(sizeof(lzo_word_t) == sizeof(lzo_sword_t)) +#endif + LZOCHK_ASSERT(sizeof(lzo_int8_t) == 1) + LZOCHK_ASSERT(sizeof(lzo_uint8_t) == 1) + LZOCHK_ASSERT(sizeof(lzo_int8_t) == sizeof(lzo_uint8_t)) + LZOCHK_ASSERT_IS_SIGNED_T(lzo_int8_t) + LZOCHK_ASSERT_IS_UNSIGNED_T(lzo_uint8_t) +#if defined(LZO_INT16_C) + LZOCHK_ASSERT(sizeof(LZO_INT16_C(0)) >= 2) + LZOCHK_ASSERT(sizeof(LZO_UINT16_C(0)) >= 2) + LZOCHK_ASSERT((LZO_UINT16_C(0xffff) >> 15) == 1) +#endif +#if defined(LZO_INT32_C) + LZOCHK_ASSERT(sizeof(LZO_INT32_C(0)) >= 4) + LZOCHK_ASSERT(sizeof(LZO_UINT32_C(0)) >= 4) + LZOCHK_ASSERT((LZO_UINT32_C(0xffffffff) >> 31) == 1) +#endif +#if defined(LZO_INT64_C) +#if (LZO_CC_BORLANDC && (__BORLANDC__ < 0x0560)) +#else + LZOCHK_ASSERT(sizeof(LZO_INT64_C(0)) >= 8) + LZOCHK_ASSERT(sizeof(LZO_UINT64_C(0)) >= 8) +#endif + LZOCHK_ASSERT((LZO_UINT64_C(0xffffffffffffffff) >> 63) == 1) + LZOCHK_ASSERT((LZO_UINT64_C(0xffffffffffffffff) & ~0) == LZO_UINT64_C(0xffffffffffffffff)) + LZOCHK_ASSERT((LZO_UINT64_C(0xffffffffffffffff) & ~0l) == LZO_UINT64_C(0xffffffffffffffff)) +#if (LZO_SIZEOF_INT == 4) +# if (LZO_CC_GNUC && (LZO_CC_GNUC < 0x020000ul)) +# else + LZOCHK_ASSERT((LZO_UINT64_C(0xffffffffffffffff) & (~0u+0u)) == 0xffffffffu) +# endif +#endif +#if (LZO_SIZEOF_LONG == 4) +# if (LZO_CC_GNUC && (LZO_CC_GNUC < 0x020000ul)) +# else + LZOCHK_ASSERT((LZO_UINT64_C(0xffffffffffffffff) & (~0ul+0ul)) == 0xfffffffful) +# endif +#endif +#endif +#if (LZO_MM_TINY || LZO_MM_SMALL || LZO_MM_MEDIUM) + LZOCHK_ASSERT(sizeof(void*) == 2) + LZOCHK_ASSERT(sizeof(ptrdiff_t) == 2) +#elif (LZO_MM_COMPACT || LZO_MM_LARGE || LZO_MM_HUGE) + LZOCHK_ASSERT(sizeof(void*) == 4) +#endif +#if (LZO_MM_TINY || LZO_MM_SMALL || LZO_MM_COMPACT) + LZOCHK_ASSERT(sizeof(void (*)(void)) == 2) +#elif (LZO_MM_MEDIUM || LZO_MM_LARGE || LZO_MM_HUGE) + LZOCHK_ASSERT(sizeof(void (*)(void)) == 4) +#endif +#if (LZO_ABI_ILP32) + LZOCHK_ASSERT(sizeof(int) == 4) + LZOCHK_ASSERT(sizeof(long) == 4) + LZOCHK_ASSERT(sizeof(void*) == 4) + LZOCHK_ASSERT(sizeof(ptrdiff_t) == sizeof(void*)) + LZOCHK_ASSERT(sizeof(size_t) == sizeof(void*)) + LZOCHK_ASSERT(sizeof(lzo_intptr_t) == sizeof(void *)) +#endif +#if (LZO_ABI_ILP64) + LZOCHK_ASSERT(sizeof(int) == 8) + LZOCHK_ASSERT(sizeof(long) == 8) + LZOCHK_ASSERT(sizeof(void*) == 8) + LZOCHK_ASSERT(sizeof(ptrdiff_t) == sizeof(void*)) + LZOCHK_ASSERT(sizeof(size_t) == sizeof(void*)) + LZOCHK_ASSERT(sizeof(lzo_intptr_t) == sizeof(void *)) +#endif +#if (LZO_ABI_IP32L64) + LZOCHK_ASSERT(sizeof(int) == 4) + LZOCHK_ASSERT(sizeof(long) == 8) + LZOCHK_ASSERT(sizeof(void*) == 4) + LZOCHK_ASSERT(sizeof(ptrdiff_t) == sizeof(void*)) + LZOCHK_ASSERT(sizeof(size_t) == sizeof(void*)) + LZOCHK_ASSERT(sizeof(lzo_intptr_t) == sizeof(void *)) +#endif +#if (LZO_ABI_LLP64) + LZOCHK_ASSERT(sizeof(int) == 4) + LZOCHK_ASSERT(sizeof(long) == 4) + LZOCHK_ASSERT(sizeof(void*) == 8) + LZOCHK_ASSERT(sizeof(ptrdiff_t) == sizeof(void*)) + LZOCHK_ASSERT(sizeof(size_t) == sizeof(void*)) + LZOCHK_ASSERT(sizeof(lzo_intptr_t) == sizeof(void *)) +#endif +#if (LZO_ABI_LP32) + LZOCHK_ASSERT(sizeof(int) == 2) + LZOCHK_ASSERT(sizeof(long) == 4) + LZOCHK_ASSERT(sizeof(void*) == 4) + LZOCHK_ASSERT(sizeof(lzo_intptr_t) == sizeof(void *)) +#endif +#if (LZO_ABI_LP64) + LZOCHK_ASSERT(sizeof(int) == 4) + LZOCHK_ASSERT(sizeof(long) == 8) + LZOCHK_ASSERT(sizeof(void*) == 8) + LZOCHK_ASSERT(sizeof(ptrdiff_t) == sizeof(void*)) + LZOCHK_ASSERT(sizeof(size_t) == sizeof(void*)) + LZOCHK_ASSERT(sizeof(lzo_intptr_t) == sizeof(void *)) +#endif +#if (LZO_ABI_IP32W64) + LZOCHK_ASSERT(sizeof(int) == 4) + LZOCHK_ASSERT(sizeof(void*) == 4) + LZOCHK_ASSERT(sizeof(ptrdiff_t) == sizeof(void*)) + LZOCHK_ASSERT(sizeof(size_t) == sizeof(void*)) + LZOCHK_ASSERT(sizeof(lzo_intptr_t) == sizeof(void *)) + LZOCHK_ASSERT(LZO_WORDSIZE == 8) +#endif +#if (LZO_ARCH_I086) + LZOCHK_ASSERT(sizeof(size_t) == 2) + LZOCHK_ASSERT(sizeof(lzo_intptr_t) == sizeof(void *)) +#elif (LZO_ARCH_I386 || LZO_ARCH_M68K) + LZOCHK_ASSERT(sizeof(size_t) == 4) + LZOCHK_ASSERT(sizeof(ptrdiff_t) == 4) + LZOCHK_ASSERT(sizeof(lzo_intptr_t) == sizeof(void *)) +#endif +#if (LZO_OS_DOS32 || LZO_OS_OS2 || LZO_OS_WIN32) + LZOCHK_ASSERT(sizeof(size_t) == 4) + LZOCHK_ASSERT(sizeof(ptrdiff_t) == 4) + LZOCHK_ASSERT(sizeof(void (*)(void)) == 4) +#elif (LZO_OS_WIN64) + LZOCHK_ASSERT(sizeof(size_t) == 8) + LZOCHK_ASSERT(sizeof(ptrdiff_t) == 8) + LZOCHK_ASSERT(sizeof(void (*)(void)) == 8) +#endif +#if (LZO_CC_NDPC) +#elif (LZO_SIZEOF_INT > 1) + LZOCHK_ASSERT( LZO_STATIC_CAST(int, LZO_STATIC_CAST(unsigned char, LZO_STATIC_CAST(signed char, -1))) == 255) +#endif +#if defined(LZOCHK_CFG_PEDANTIC) +#if (LZO_CC_KEILC) +#elif (LZO_CC_NDPC) +#elif !(LZO_BROKEN_INTEGRAL_PROMOTION) && (LZO_SIZEOF_INT > 1) + LZOCHK_ASSERT( ((LZO_STATIC_CAST(unsigned char, 128)) << LZO_STATIC_CAST(int, (8*sizeof(int)-8))) < 0) +#endif +#endif +#if defined(LZOCHK_CFG_PEDANTIC) +#if (LZO_CC_BORLANDC && (__BORLANDC__ >= 0x0530) && (__BORLANDC__ < 0x0560)) +# pragma option pop +#endif +#endif +#endif +#if defined(LZO_WANT_ACCLIB_VGET) +# undef LZO_WANT_ACCLIB_VGET +#define __LZOLIB_VGET_CH_INCLUDED 1 +#if !defined(LZOLIB_PUBLIC) +# define LZOLIB_PUBLIC(r,f) r __LZOLIB_FUNCNAME(f) +#endif +#if !defined(LZOLIB_PUBLIC_NOINLINE) +# if !defined(__lzo_noinline) +# define LZOLIB_PUBLIC_NOINLINE(r,f) r __LZOLIB_FUNCNAME(f) +# elif (LZO_CC_CLANG || (LZO_CC_GNUC >= 0x030400ul) || LZO_CC_LLVM) +# define LZOLIB_PUBLIC_NOINLINE(r,f) __lzo_noinline __attribute__((__used__)) r __LZOLIB_FUNCNAME(f) +# else +# define LZOLIB_PUBLIC_NOINLINE(r,f) __lzo_noinline r __LZOLIB_FUNCNAME(f) +# endif +#endif +extern void* volatile lzo_vget_ptr__; +#if (LZO_CC_CLANG || (LZO_CC_GNUC >= 0x030400ul) || LZO_CC_LLVM) +void* volatile __attribute__((__used__)) lzo_vget_ptr__ = LZO_STATIC_CAST(void *, 0); +#else +void* volatile lzo_vget_ptr__ = LZO_STATIC_CAST(void *, 0); +#endif +#ifndef __LZOLIB_VGET_BODY +#define __LZOLIB_VGET_BODY(T) \ + if __lzo_unlikely(lzo_vget_ptr__) { \ + typedef T __lzo_may_alias TT; \ + unsigned char e; expr &= 255; e = LZO_STATIC_CAST(unsigned char, expr); \ + * LZO_STATIC_CAST(TT *, lzo_vget_ptr__) = v; \ + * LZO_STATIC_CAST(unsigned char *, lzo_vget_ptr__) = e; \ + v = * LZO_STATIC_CAST(TT *, lzo_vget_ptr__); \ + } \ + return v; +#endif +LZOLIB_PUBLIC_NOINLINE(short, lzo_vget_short) (short v, int expr) +{ + __LZOLIB_VGET_BODY(short) +} +LZOLIB_PUBLIC_NOINLINE(int, lzo_vget_int) (int v, int expr) +{ + __LZOLIB_VGET_BODY(int) +} +LZOLIB_PUBLIC_NOINLINE(long, lzo_vget_long) (long v, int expr) +{ + __LZOLIB_VGET_BODY(long) +} +#if defined(lzo_int64l_t) +LZOLIB_PUBLIC_NOINLINE(lzo_int64l_t, lzo_vget_lzo_int64l_t) (lzo_int64l_t v, int expr) +{ + __LZOLIB_VGET_BODY(lzo_int64l_t) +} +#endif +LZOLIB_PUBLIC_NOINLINE(lzo_hsize_t, lzo_vget_lzo_hsize_t) (lzo_hsize_t v, int expr) +{ + __LZOLIB_VGET_BODY(lzo_hsize_t) +} +#if !(LZO_CFG_NO_DOUBLE) +LZOLIB_PUBLIC_NOINLINE(double, lzo_vget_double) (double v, int expr) +{ + __LZOLIB_VGET_BODY(double) +} +#endif +LZOLIB_PUBLIC_NOINLINE(lzo_hvoid_p, lzo_vget_lzo_hvoid_p) (lzo_hvoid_p v, int expr) +{ + __LZOLIB_VGET_BODY(lzo_hvoid_p) +} +#if (LZO_ARCH_I086 && LZO_CC_TURBOC && (__TURBOC__ == 0x0295)) && !defined(__cplusplus) +LZOLIB_PUBLIC_NOINLINE(lzo_hvoid_p, lzo_vget_lzo_hvoid_cp) (const lzo_hvoid_p vv, int expr) +{ + lzo_hvoid_p v = (lzo_hvoid_p) vv; + __LZOLIB_VGET_BODY(lzo_hvoid_p) +} +#else +LZOLIB_PUBLIC_NOINLINE(const lzo_hvoid_p, lzo_vget_lzo_hvoid_cp) (const lzo_hvoid_p v, int expr) +{ + __LZOLIB_VGET_BODY(const lzo_hvoid_p) +} +#endif +#endif +#if defined(LZO_WANT_ACCLIB_HMEMCPY) +# undef LZO_WANT_ACCLIB_HMEMCPY +#define __LZOLIB_HMEMCPY_CH_INCLUDED 1 +#if !defined(LZOLIB_PUBLIC) +# define LZOLIB_PUBLIC(r,f) r __LZOLIB_FUNCNAME(f) +#endif +LZOLIB_PUBLIC(int, lzo_hmemcmp) (const lzo_hvoid_p s1, const lzo_hvoid_p s2, lzo_hsize_t len) +{ +#if (LZO_HAVE_MM_HUGE_PTR) || !(HAVE_MEMCMP) + const lzo_hbyte_p p1 = LZO_STATIC_CAST(const lzo_hbyte_p, s1); + const lzo_hbyte_p p2 = LZO_STATIC_CAST(const lzo_hbyte_p, s2); + if __lzo_likely(len > 0) do + { + int d = *p1 - *p2; + if (d != 0) + return d; + p1++; p2++; + } while __lzo_likely(--len > 0); + return 0; +#else + return memcmp(s1, s2, len); +#endif +} +LZOLIB_PUBLIC(lzo_hvoid_p, lzo_hmemcpy) (lzo_hvoid_p dest, const lzo_hvoid_p src, lzo_hsize_t len) +{ +#if (LZO_HAVE_MM_HUGE_PTR) || !(HAVE_MEMCPY) + lzo_hbyte_p p1 = LZO_STATIC_CAST(lzo_hbyte_p, dest); + const lzo_hbyte_p p2 = LZO_STATIC_CAST(const lzo_hbyte_p, src); + if (!(len > 0) || p1 == p2) + return dest; + do + *p1++ = *p2++; + while __lzo_likely(--len > 0); + return dest; +#else + return memcpy(dest, src, len); +#endif +} +LZOLIB_PUBLIC(lzo_hvoid_p, lzo_hmemmove) (lzo_hvoid_p dest, const lzo_hvoid_p src, lzo_hsize_t len) +{ +#if (LZO_HAVE_MM_HUGE_PTR) || !(HAVE_MEMMOVE) + lzo_hbyte_p p1 = LZO_STATIC_CAST(lzo_hbyte_p, dest); + const lzo_hbyte_p p2 = LZO_STATIC_CAST(const lzo_hbyte_p, src); + if (!(len > 0) || p1 == p2) + return dest; + if (p1 < p2) + { + do + *p1++ = *p2++; + while __lzo_likely(--len > 0); + } + else + { + p1 += len; + p2 += len; + do + *--p1 = *--p2; + while __lzo_likely(--len > 0); + } + return dest; +#else + return memmove(dest, src, len); +#endif +} +LZOLIB_PUBLIC(lzo_hvoid_p, lzo_hmemset) (lzo_hvoid_p s, int cc, lzo_hsize_t len) +{ +#if (LZO_HAVE_MM_HUGE_PTR) || !(HAVE_MEMSET) + lzo_hbyte_p p = LZO_STATIC_CAST(lzo_hbyte_p, s); + unsigned char c = LZO_ITRUNC(unsigned char, cc); + if __lzo_likely(len > 0) do + *p++ = c; + while __lzo_likely(--len > 0); + return s; +#else + return memset(s, cc, len); +#endif +} +#endif +#if defined(LZO_WANT_ACCLIB_RAND) +# undef LZO_WANT_ACCLIB_RAND +#define __LZOLIB_RAND_CH_INCLUDED 1 +#if !defined(LZOLIB_PUBLIC) +# define LZOLIB_PUBLIC(r,f) r __LZOLIB_FUNCNAME(f) +#endif +LZOLIB_PUBLIC(void, lzo_srand31) (lzo_rand31_p r, lzo_uint32l_t seed) +{ + r->seed = seed & LZO_UINT32_C(0xffffffff); +} +LZOLIB_PUBLIC(lzo_uint32l_t, lzo_rand31) (lzo_rand31_p r) +{ + r->seed = r->seed * LZO_UINT32_C(1103515245) + 12345; + r->seed &= LZO_UINT32_C(0x7fffffff); + return r->seed; +} +#if defined(lzo_int64l_t) +LZOLIB_PUBLIC(void, lzo_srand48) (lzo_rand48_p r, lzo_uint32l_t seed) +{ + r->seed = seed & LZO_UINT32_C(0xffffffff); + r->seed <<= 16; r->seed |= 0x330e; +} +LZOLIB_PUBLIC(lzo_uint32l_t, lzo_rand48) (lzo_rand48_p r) +{ + lzo_uint64l_t a; + r->seed = r->seed * LZO_UINT64_C(25214903917) + 11; + r->seed &= LZO_UINT64_C(0xffffffffffff); + a = r->seed >> 17; + return LZO_STATIC_CAST(lzo_uint32l_t, a); +} +LZOLIB_PUBLIC(lzo_uint32l_t, lzo_rand48_r32) (lzo_rand48_p r) +{ + lzo_uint64l_t a; + r->seed = r->seed * LZO_UINT64_C(25214903917) + 11; + r->seed &= LZO_UINT64_C(0xffffffffffff); + a = r->seed >> 16; + return LZO_STATIC_CAST(lzo_uint32l_t, a); +} +#endif +#if defined(lzo_int64l_t) +LZOLIB_PUBLIC(void, lzo_srand64) (lzo_rand64_p r, lzo_uint64l_t seed) +{ + r->seed = seed & LZO_UINT64_C(0xffffffffffffffff); +} +LZOLIB_PUBLIC(lzo_uint32l_t, lzo_rand64) (lzo_rand64_p r) +{ + lzo_uint64l_t a; + r->seed = r->seed * LZO_UINT64_C(6364136223846793005) + 1; +#if (LZO_SIZEOF_LZO_INT64L_T > 8) + r->seed &= LZO_UINT64_C(0xffffffffffffffff); +#endif + a = r->seed >> 33; + return LZO_STATIC_CAST(lzo_uint32l_t, a); +} +LZOLIB_PUBLIC(lzo_uint32l_t, lzo_rand64_r32) (lzo_rand64_p r) +{ + lzo_uint64l_t a; + r->seed = r->seed * LZO_UINT64_C(6364136223846793005) + 1; +#if (LZO_SIZEOF_LZO_INT64L_T > 8) + r->seed &= LZO_UINT64_C(0xffffffffffffffff); +#endif + a = r->seed >> 32; + return LZO_STATIC_CAST(lzo_uint32l_t, a); +} +#endif +LZOLIB_PUBLIC(void, lzo_srandmt) (lzo_randmt_p r, lzo_uint32l_t seed) +{ + unsigned i = 0; + do { + r->s[i++] = (seed &= LZO_UINT32_C(0xffffffff)); + seed ^= seed >> 30; + seed = seed * LZO_UINT32_C(0x6c078965) + i; + } while (i != 624); + r->n = i; +} +LZOLIB_PUBLIC(lzo_uint32l_t, lzo_randmt) (lzo_randmt_p r) +{ + return (__LZOLIB_FUNCNAME(lzo_randmt_r32)(r)) >> 1; +} +LZOLIB_PUBLIC(lzo_uint32l_t, lzo_randmt_r32) (lzo_randmt_p r) +{ + lzo_uint32l_t v; + if __lzo_unlikely(r->n == 624) { + unsigned i = 0, j; + r->n = 0; + do { + j = i - 623; if (LZO_STATIC_CAST(int, j) < 0) j += 624; + v = (r->s[i] & LZO_UINT32_C(0x80000000)) ^ (r->s[j] & LZO_UINT32_C(0x7fffffff)); + j = i - 227; if (LZO_STATIC_CAST(int, j) < 0) j += 624; + r->s[i] = r->s[j] ^ (v >> 1); + if (v & 1) r->s[i] ^= LZO_UINT32_C(0x9908b0df); + } while (++i != 624); + } + { unsigned i = r->n++; v = r->s[i]; } + v ^= v >> 11; v ^= (v & LZO_UINT32_C(0x013a58ad)) << 7; + v ^= (v & LZO_UINT32_C(0x0001df8c)) << 15; v ^= v >> 18; + return v; +} +#if defined(lzo_int64l_t) +LZOLIB_PUBLIC(void, lzo_srandmt64) (lzo_randmt64_p r, lzo_uint64l_t seed) +{ + unsigned i = 0; + do { + r->s[i++] = (seed &= LZO_UINT64_C(0xffffffffffffffff)); + seed ^= seed >> 62; + seed = seed * LZO_UINT64_C(0x5851f42d4c957f2d) + i; + } while (i != 312); + r->n = i; +} +#if 0 +LZOLIB_PUBLIC(lzo_uint32l_t, lzo_randmt64) (lzo_randmt64_p r) +{ + lzo_uint64l_t v; + v = (__LZOLIB_FUNCNAME(lzo_randmt64_r64)(r)) >> 33; + return LZO_STATIC_CAST(lzo_uint32l_t, v); +} +#endif +LZOLIB_PUBLIC(lzo_uint64l_t, lzo_randmt64_r64) (lzo_randmt64_p r) +{ + lzo_uint64l_t v; + if __lzo_unlikely(r->n == 312) { + unsigned i = 0, j; + r->n = 0; + do { + j = i - 311; if (LZO_STATIC_CAST(int, j) < 0) j += 312; + v = (r->s[i] & LZO_UINT64_C(0xffffffff80000000)) ^ (r->s[j] & LZO_UINT64_C(0x7fffffff)); + j = i - 156; if (LZO_STATIC_CAST(int, j) < 0) j += 312; + r->s[i] = r->s[j] ^ (v >> 1); + if (v & 1) r->s[i] ^= LZO_UINT64_C(0xb5026f5aa96619e9); + } while (++i != 312); + } + { unsigned i = r->n++; v = r->s[i]; } + v ^= (v & LZO_UINT64_C(0xaaaaaaaaa0000000)) >> 29; + v ^= (v & LZO_UINT64_C(0x38eb3ffff6d3)) << 17; + v ^= (v & LZO_UINT64_C(0x7ffbf77)) << 37; + return v ^ (v >> 43); +} +#endif +#endif +#if defined(LZO_WANT_ACCLIB_RDTSC) +# undef LZO_WANT_ACCLIB_RDTSC +#define __LZOLIB_RDTSC_CH_INCLUDED 1 +#if !defined(LZOLIB_PUBLIC) +# define LZOLIB_PUBLIC(r,f) r __LZOLIB_FUNCNAME(f) +#endif +#if defined(lzo_int32e_t) +#if (LZO_OS_WIN32 && LZO_CC_PELLESC && (__POCC__ >= 290)) +# pragma warn(push) +# pragma warn(disable:2007) +#endif +#if (LZO_ARCH_AMD64 || LZO_ARCH_I386) && (LZO_ASM_SYNTAX_GNUC) +#if (LZO_ARCH_AMD64 && LZO_CC_INTELC) +# define __LZOLIB_RDTSC_REGS : : "c" (t) : "memory", "rax", "rdx" +#elif (LZO_ARCH_AMD64) +# define __LZOLIB_RDTSC_REGS : : "c" (t) : "cc", "memory", "rax", "rdx" +#elif (LZO_ARCH_I386 && LZO_CC_GNUC && (LZO_CC_GNUC < 0x020000ul)) +# define __LZOLIB_RDTSC_REGS : : "c" (t) : "ax", "dx" +#elif (LZO_ARCH_I386 && LZO_CC_INTELC) +# define __LZOLIB_RDTSC_REGS : : "c" (t) : "memory", "eax", "edx" +#else +# define __LZOLIB_RDTSC_REGS : : "c" (t) : "cc", "memory", "eax", "edx" +#endif +#endif +LZOLIB_PUBLIC(int, lzo_tsc_read) (lzo_uint32e_t* t) +{ +#if (LZO_ARCH_AMD64 || LZO_ARCH_I386) && (LZO_ASM_SYNTAX_GNUC) + __asm__ __volatile__( + "clc \n" ".byte 0x0f,0x31\n" + "movl %%eax,(%0)\n" "movl %%edx,4(%0)\n" + __LZOLIB_RDTSC_REGS + ); + return 0; +#elif (LZO_ARCH_I386) && (LZO_ASM_SYNTAX_MSC) + LZO_UNUSED(t); + __asm { + mov ecx, t + clc +# if (LZO_CC_MSC && (_MSC_VER < 1200)) + _emit 0x0f + _emit 0x31 +# else + rdtsc +# endif + mov [ecx], eax + mov [ecx+4], edx + } + return 0; +#else + t[0] = t[1] = 0; return -1; +#endif +} +#if (LZO_OS_WIN32 && LZO_CC_PELLESC && (__POCC__ >= 290)) +# pragma warn(pop) +#endif +#endif +#endif +#if defined(LZO_WANT_ACCLIB_DOSALLOC) +# undef LZO_WANT_ACCLIB_DOSALLOC +#define __LZOLIB_DOSALLOC_CH_INCLUDED 1 +#if !defined(LZOLIB_PUBLIC) +# define LZOLIB_PUBLIC(r,f) r __LZOLIB_FUNCNAME(f) +#endif +#if (LZO_OS_OS216) +LZO_EXTERN_C unsigned short __far __pascal DosAllocHuge(unsigned short, unsigned short, unsigned short __far *, unsigned short, unsigned short); +LZO_EXTERN_C unsigned short __far __pascal DosFreeSeg(unsigned short); +#endif +#if (LZO_OS_DOS16 || LZO_OS_WIN16) +#if !(LZO_CC_AZTECC) +LZOLIB_PUBLIC(void __far*, lzo_dos_alloc) (unsigned long size) +{ + void __far* p = 0; + union REGS ri, ro; + if ((long)size <= 0) + return p; + size = (size + 15) >> 4; + if (size > 0xffffu) + return p; + ri.x.ax = 0x4800; + ri.x.bx = (unsigned short) size; + int86(0x21, &ri, &ro); + if ((ro.x.cflag & 1) == 0) + p = (void __far*) LZO_PTR_MK_FP(ro.x.ax, 0); + return p; +} +LZOLIB_PUBLIC(int, lzo_dos_free) (void __far* p) +{ + union REGS ri, ro; + struct SREGS rs; + if (!p) + return 0; + if (LZO_PTR_FP_OFF(p) != 0) + return -1; + segread(&rs); + ri.x.ax = 0x4900; + rs.es = LZO_PTR_FP_SEG(p); + int86x(0x21, &ri, &ro, &rs); + if (ro.x.cflag & 1) + return -1; + return 0; +} +#endif +#endif +#if (LZO_OS_OS216) +LZOLIB_PUBLIC(void __far*, lzo_dos_alloc) (unsigned long size) +{ + void __far* p = 0; + unsigned short sel = 0; + if ((long)size <= 0) + return p; + if (DosAllocHuge((unsigned short)(size >> 16), (unsigned short)size, &sel, 0, 0) == 0) + p = (void __far*) LZO_PTR_MK_FP(sel, 0); + return p; +} +LZOLIB_PUBLIC(int, lzo_dos_free) (void __far* p) +{ + if (!p) + return 0; + if (LZO_PTR_FP_OFF(p) != 0) + return -1; + if (DosFreeSeg(LZO_PTR_FP_SEG(p)) != 0) + return -1; + return 0; +} +#endif +#endif +#if defined(LZO_WANT_ACCLIB_GETOPT) +# undef LZO_WANT_ACCLIB_GETOPT +#define __LZOLIB_GETOPT_CH_INCLUDED 1 +#if !defined(LZOLIB_PUBLIC) +# define LZOLIB_PUBLIC(r,f) r __LZOLIB_FUNCNAME(f) +#endif +LZOLIB_PUBLIC(void, lzo_getopt_init) (lzo_getopt_p g, + int start_argc, int argc, char** argv) +{ + memset(g, 0, sizeof(*g)); + g->optind = start_argc; + g->argc = argc; g->argv = argv; + g->optopt = -1; +} +static int __LZOLIB_FUNCNAME(lzo_getopt_rotate) (char** p, int first, int middle, int last) +{ + int i = middle, n = middle - first; + if (first >= middle || middle >= last) return 0; + for (;;) + { + char* t = p[first]; p[first] = p[i]; p[i] = t; + if (++first == middle) + { + if (++i == last) break; + middle = i; + } + else if (++i == last) + i = middle; + } + return n; +} +static int __LZOLIB_FUNCNAME(lzo_getopt_perror) (lzo_getopt_p g, int ret, const char* f, ...) +{ + if (g->opterr) + { +#if (HAVE_STDARG_H) + struct { va_list ap; } s; + va_start(s.ap, f); + g->opterr(g, f, &s); + va_end(s.ap); +#else + g->opterr(g, f, NULL); +#endif + } + ++g->errcount; + return ret; +} +LZOLIB_PUBLIC(int, lzo_getopt) (lzo_getopt_p g, + const char* shortopts, + const lzo_getopt_longopt_p longopts, + int* longind) +{ +#define pe __LZOLIB_FUNCNAME(lzo_getopt_perror) + int ordering = LZO_GETOPT_PERMUTE; + int missing_arg_ret = g->bad_option; + char* a; + if (shortopts) + { + if (*shortopts == '-' || *shortopts == '+') + ordering = *shortopts++ == '-' ? LZO_GETOPT_RETURN_IN_ORDER : LZO_GETOPT_REQUIRE_ORDER; + if (*shortopts == ':') + missing_arg_ret = *shortopts++; + } + g->optarg = NULL; + if (g->optopt == -1) + g->optopt = g->bad_option; + if (longind) + *longind = -1; + if (g->eof) + return -1; + if (g->shortpos) + goto lzo_label_next_shortopt; + g->optind -= __LZOLIB_FUNCNAME(lzo_getopt_rotate)(g->argv, g->pending_rotate_first, g->pending_rotate_middle, g->optind); + g->pending_rotate_first = g->pending_rotate_middle = g->optind; + if (ordering == LZO_GETOPT_PERMUTE) + { + while (g->optind < g->argc && !(g->argv[g->optind][0] == '-' && g->argv[g->optind][1])) + ++g->optind; + g->pending_rotate_middle = g->optind; + } + if (g->optind >= g->argc) + { + g->optind = g->pending_rotate_first; + goto lzo_label_eof; + } + a = g->argv[g->optind]; + if (a[0] == '-' && a[1] == '-') + { + size_t l = 0; + const lzo_getopt_longopt_p o; + const lzo_getopt_longopt_p o1 = NULL; + const lzo_getopt_longopt_p o2 = NULL; + int need_exact = 0; + ++g->optind; + if (!a[2]) + goto lzo_label_eof; + for (a += 2; a[l] && a[l] != '=' && a[l] != '#'; ) + ++l; + for (o = longopts; l && o && o->name; ++o) + { + if (strncmp(a, o->name, l) != 0) + continue; + if (!o->name[l]) + goto lzo_label_found_o; + need_exact |= o->has_arg & LZO_GETOPT_EXACT_ARG; + if (o1) o2 = o; + else o1 = o; + } + if (!o1 || need_exact) + return pe(g, g->bad_option, "unrecognized option '--%s'", a); + if (o2) + return pe(g, g->bad_option, "option '--%s' is ambiguous (could be '--%s' or '--%s')", a, o1->name, o2->name); + o = o1; + lzo_label_found_o: + a += l; + switch (o->has_arg & 0x2f) + { + case LZO_GETOPT_OPTIONAL_ARG: + if (a[0]) + g->optarg = a + 1; + break; + case LZO_GETOPT_REQUIRED_ARG: + if (a[0]) + g->optarg = a + 1; + else if (g->optind < g->argc) + g->optarg = g->argv[g->optind++]; + if (!g->optarg) + return pe(g, missing_arg_ret, "option '--%s' requires an argument", o->name); + break; + case LZO_GETOPT_REQUIRED_ARG | 0x20: + if (a[0] && a[1]) + g->optarg = a + 1; + if (!g->optarg) + return pe(g, missing_arg_ret, "option '--%s=' requires an argument", o->name); + break; + default: + if (a[0]) + return pe(g, g->bad_option, "option '--%s' doesn't allow an argument", o->name); + break; + } + if (longind) + *longind = (int) (o - longopts); + if (o->flag) + { + *o->flag = o->val; + return 0; + } + return o->val; + } + if (a[0] == '-' && a[1]) + { + unsigned char c; + const char* s; + lzo_label_next_shortopt: + a = g->argv[g->optind] + ++g->shortpos; + c = (unsigned char) *a++; s = NULL; + if (c != ':' && shortopts) + s = strchr(shortopts, c); + if (!s || s[1] != ':') + { + if (!a[0]) + { ++g->optind; g->shortpos = 0; } + if (!s) + { + g->optopt = c; + return pe(g, g->bad_option, "invalid option '-%c'", c); + } + } + else + { + ++g->optind; g->shortpos = 0; + if (a[0]) + g->optarg = a; + else if (s[2] != ':') + { + if (g->optind < g->argc) + g->optarg = g->argv[g->optind++]; + else + { + g->optopt = c; + return pe(g, missing_arg_ret, "option '-%c' requires an argument", c); + } + } + } + return c; + } + if (ordering == LZO_GETOPT_RETURN_IN_ORDER) + { + ++g->optind; + g->optarg = a; + return 1; + } +lzo_label_eof: + g->optind -= __LZOLIB_FUNCNAME(lzo_getopt_rotate)(g->argv, g->pending_rotate_first, g->pending_rotate_middle, g->optind); + g->pending_rotate_first = g->pending_rotate_middle = g->optind; + g->eof = 1; + return -1; +#undef pe +} +#endif +#if defined(LZO_WANT_ACCLIB_HALLOC) +# undef LZO_WANT_ACCLIB_HALLOC +#define __LZOLIB_HALLOC_CH_INCLUDED 1 +#if !defined(LZOLIB_PUBLIC) +# define LZOLIB_PUBLIC(r,f) r __LZOLIB_FUNCNAME(f) +#endif +#if (LZO_HAVE_MM_HUGE_PTR) +#if 1 && (LZO_OS_DOS16 && defined(BLX286)) +# define __LZOLIB_HALLOC_USE_DAH 1 +#elif 1 && (LZO_OS_DOS16 && defined(DOSX286)) +# define __LZOLIB_HALLOC_USE_DAH 1 +#elif 1 && (LZO_OS_OS216) +# define __LZOLIB_HALLOC_USE_DAH 1 +#elif 1 && (LZO_OS_WIN16) +# define __LZOLIB_HALLOC_USE_GA 1 +#elif 1 && (LZO_OS_DOS16) && (LZO_CC_BORLANDC) && defined(__DPMI16__) +# define __LZOLIB_HALLOC_USE_GA 1 +#endif +#endif +#if (__LZOLIB_HALLOC_USE_DAH) +#if 0 && (LZO_OS_OS216) +#include +#else +LZO_EXTERN_C unsigned short __far __pascal DosAllocHuge(unsigned short, unsigned short, unsigned short __far *, unsigned short, unsigned short); +LZO_EXTERN_C unsigned short __far __pascal DosFreeSeg(unsigned short); +#endif +#endif +#if (__LZOLIB_HALLOC_USE_GA) +#if 0 +#define STRICT 1 +#include +#else +LZO_EXTERN_C const void __near* __far __pascal GlobalAlloc(unsigned, unsigned long); +LZO_EXTERN_C const void __near* __far __pascal GlobalFree(const void __near*); +LZO_EXTERN_C unsigned long __far __pascal GlobalHandle(unsigned); +LZO_EXTERN_C void __far* __far __pascal GlobalLock(const void __near*); +LZO_EXTERN_C int __far __pascal GlobalUnlock(const void __near*); +#endif +#endif +LZOLIB_PUBLIC(lzo_hvoid_p, lzo_halloc) (lzo_hsize_t size) +{ + lzo_hvoid_p p = LZO_STATIC_CAST(lzo_hvoid_p, 0); + if (!(size > 0)) + return p; +#if 0 && defined(__palmos__) + p = MemPtrNew(size); +#elif !(LZO_HAVE_MM_HUGE_PTR) + if (size < LZO_STATIC_CAST(size_t, -1)) + p = malloc(LZO_STATIC_CAST(size_t, size)); +#else + if (LZO_STATIC_CAST(long, size) <= 0) + return p; +{ +#if (__LZOLIB_HALLOC_USE_DAH) + unsigned short sel = 0; + if (DosAllocHuge((unsigned short)(size >> 16), (unsigned short)size, &sel, 0, 0) == 0) + p = (lzo_hvoid_p) LZO_PTR_MK_FP(sel, 0); +#elif (__LZOLIB_HALLOC_USE_GA) + const void __near* h = GlobalAlloc(2, size); + if (h) { + p = GlobalLock(h); + if (p && LZO_PTR_FP_OFF(p) != 0) { + GlobalUnlock(h); + p = 0; + } + if (!p) + GlobalFree(h); + } +#elif (LZO_CC_MSC && (_MSC_VER >= 700)) + p = _halloc(size, 1); +#elif (LZO_CC_MSC || LZO_CC_WATCOMC) + p = halloc(size, 1); +#elif (LZO_CC_DMC || LZO_CC_SYMANTECC || LZO_CC_ZORTECHC) + p = farmalloc(size); +#elif (LZO_CC_BORLANDC || LZO_CC_TURBOC) + p = farmalloc(size); +#elif (LZO_CC_AZTECC) + p = lmalloc(size); +#else + if (size < LZO_STATIC_CAST(size_t, -1)) + p = malloc(LZO_STATIC_CAST(size_t, size)); +#endif +} +#endif + return p; +} +LZOLIB_PUBLIC(void, lzo_hfree) (lzo_hvoid_p p) +{ + if (!p) + return; +#if 0 && defined(__palmos__) + MemPtrFree(p); +#elif !(LZO_HAVE_MM_HUGE_PTR) + free(p); +#else +#if (__LZOLIB_HALLOC_USE_DAH) + if (LZO_PTR_FP_OFF(p) == 0) + DosFreeSeg((unsigned short) LZO_PTR_FP_SEG(p)); +#elif (__LZOLIB_HALLOC_USE_GA) + if (LZO_PTR_FP_OFF(p) == 0) { + const void __near* h = (const void __near*) (unsigned) GlobalHandle(LZO_PTR_FP_SEG(p)); + if (h) { + GlobalUnlock(h); + GlobalFree(h); + } + } +#elif (LZO_CC_MSC && (_MSC_VER >= 700)) + _hfree(p); +#elif (LZO_CC_MSC || LZO_CC_WATCOMC) + hfree(p); +#elif (LZO_CC_DMC || LZO_CC_SYMANTECC || LZO_CC_ZORTECHC) + farfree((void __far*) p); +#elif (LZO_CC_BORLANDC || LZO_CC_TURBOC) + farfree((void __far*) p); +#elif (LZO_CC_AZTECC) + lfree(p); +#else + free(p); +#endif +#endif +} +#endif +#if defined(LZO_WANT_ACCLIB_HFREAD) +# undef LZO_WANT_ACCLIB_HFREAD +#define __LZOLIB_HFREAD_CH_INCLUDED 1 +#if !defined(LZOLIB_PUBLIC) +# define LZOLIB_PUBLIC(r,f) r __LZOLIB_FUNCNAME(f) +#endif +LZOLIB_PUBLIC(lzo_hsize_t, lzo_hfread) (void* vfp, lzo_hvoid_p buf, lzo_hsize_t size) +{ + FILE* fp = LZO_STATIC_CAST(FILE *, vfp); +#if (LZO_HAVE_MM_HUGE_PTR) +#if (LZO_MM_TINY || LZO_MM_SMALL || LZO_MM_MEDIUM) +#define __LZOLIB_REQUIRE_HMEMCPY_CH 1 + unsigned char tmp[512]; + lzo_hsize_t l = 0; + while (l < size) + { + size_t n = size - l > sizeof(tmp) ? sizeof(tmp) : (size_t) (size - l); + n = fread(tmp, 1, n, fp); + if (n == 0) + break; + __LZOLIB_FUNCNAME(lzo_hmemcpy)((lzo_hbyte_p)buf + l, tmp, (lzo_hsize_t)n); + l += n; + } + return l; +#elif (LZO_MM_COMPACT || LZO_MM_LARGE || LZO_MM_HUGE) + lzo_hbyte_p b = (lzo_hbyte_p) buf; + lzo_hsize_t l = 0; + while (l < size) + { + size_t n; + n = LZO_PTR_FP_OFF(b); n = (n <= 1) ? 0x8000u : (0u - n); + if ((lzo_hsize_t) n > size - l) + n = (size_t) (size - l); + n = fread((void __far*)b, 1, n, fp); + if (n == 0) + break; + b += n; l += n; + } + return l; +#else +# error "unknown memory model" +#endif +#else + return fread(buf, 1, size, fp); +#endif +} +LZOLIB_PUBLIC(lzo_hsize_t, lzo_hfwrite) (void* vfp, const lzo_hvoid_p buf, lzo_hsize_t size) +{ + FILE* fp = LZO_STATIC_CAST(FILE *, vfp); +#if (LZO_HAVE_MM_HUGE_PTR) +#if (LZO_MM_TINY || LZO_MM_SMALL || LZO_MM_MEDIUM) +#define __LZOLIB_REQUIRE_HMEMCPY_CH 1 + unsigned char tmp[512]; + lzo_hsize_t l = 0; + while (l < size) + { + size_t n = size - l > sizeof(tmp) ? sizeof(tmp) : (size_t) (size - l); + __LZOLIB_FUNCNAME(lzo_hmemcpy)(tmp, (const lzo_hbyte_p)buf + l, (lzo_hsize_t)n); + n = fwrite(tmp, 1, n, fp); + if (n == 0) + break; + l += n; + } + return l; +#elif (LZO_MM_COMPACT || LZO_MM_LARGE || LZO_MM_HUGE) + const lzo_hbyte_p b = (const lzo_hbyte_p) buf; + lzo_hsize_t l = 0; + while (l < size) + { + size_t n; + n = LZO_PTR_FP_OFF(b); n = (n <= 1) ? 0x8000u : (0u - n); + if ((lzo_hsize_t) n > size - l) + n = (size_t) (size - l); + n = fwrite((void __far*)b, 1, n, fp); + if (n == 0) + break; + b += n; l += n; + } + return l; +#else +# error "unknown memory model" +#endif +#else + return fwrite(buf, 1, size, fp); +#endif +} +#endif +#if defined(LZO_WANT_ACCLIB_HSREAD) +# undef LZO_WANT_ACCLIB_HSREAD +#define __LZOLIB_HSREAD_CH_INCLUDED 1 +#if !defined(LZOLIB_PUBLIC) +# define LZOLIB_PUBLIC(r,f) r __LZOLIB_FUNCNAME(f) +#endif +LZOLIB_PUBLIC(long, lzo_safe_hread) (int fd, lzo_hvoid_p buf, long size) +{ + lzo_hbyte_p b = (lzo_hbyte_p) buf; + long l = 0; + int saved_errno; + saved_errno = errno; + while (l < size) + { + long n = size - l; +#if (LZO_HAVE_MM_HUGE_PTR) +# define __LZOLIB_REQUIRE_HREAD_CH 1 + errno = 0; n = lzo_hread(fd, b, n); +#elif (LZO_OS_DOS32) && defined(__DJGPP__) + errno = 0; n = _read(fd, b, n); +#else + errno = 0; n = read(fd, b, n); +#endif + if (n == 0) + break; + if (n < 0) { +#if defined(EAGAIN) + if (errno == (EAGAIN)) continue; +#endif +#if defined(EINTR) + if (errno == (EINTR)) continue; +#endif + if (errno == 0) errno = 1; + return l; + } + b += n; l += n; + } + errno = saved_errno; + return l; +} +LZOLIB_PUBLIC(long, lzo_safe_hwrite) (int fd, const lzo_hvoid_p buf, long size) +{ + const lzo_hbyte_p b = (const lzo_hbyte_p) buf; + long l = 0; + int saved_errno; + saved_errno = errno; + while (l < size) + { + long n = size - l; +#if (LZO_HAVE_MM_HUGE_PTR) +# define __LZOLIB_REQUIRE_HREAD_CH 1 + errno = 0; n = lzo_hwrite(fd, b, n); +#elif (LZO_OS_DOS32) && defined(__DJGPP__) + errno = 0; n = _write(fd, b, n); +#else + errno = 0; n = write(fd, b, n); +#endif + if (n == 0) + break; + if (n < 0) { +#if defined(EAGAIN) + if (errno == (EAGAIN)) continue; +#endif +#if defined(EINTR) + if (errno == (EINTR)) continue; +#endif + if (errno == 0) errno = 1; + return l; + } + b += n; l += n; + } + errno = saved_errno; + return l; +} +#endif +#if defined(LZO_WANT_ACCLIB_PCLOCK) +# undef LZO_WANT_ACCLIB_PCLOCK +#define __LZOLIB_PCLOCK_CH_INCLUDED 1 +#if !defined(LZOLIB_PUBLIC) +# define LZOLIB_PUBLIC(r,f) r __LZOLIB_FUNCNAME(f) +#endif +#if 1 && (LZO_OS_POSIX_LINUX && LZO_ARCH_AMD64 && LZO_ASM_SYNTAX_GNUC && !LZO_CFG_NO_SYSCALL) +#ifndef lzo_pclock_syscall_clock_gettime +#define lzo_pclock_syscall_clock_gettime lzo_pclock_syscall_clock_gettime +#endif +__lzo_static_noinline long lzo_pclock_syscall_clock_gettime(long clockid, struct timespec *ts) +{ + unsigned long r = 228; + __asm__ __volatile__("syscall\n" : "=a" (r), "=m" (*ts) : "0" (r), "D" (clockid), "S" (ts) __LZO_ASM_CLOBBER_LIST_CC); + return LZO_ICAST(long, r); +} +#endif +#if 1 && (LZO_OS_POSIX_LINUX && LZO_ARCH_I386 && LZO_ASM_SYNTAX_GNUC && !LZO_CFG_NO_SYSCALL) && defined(lzo_int64l_t) +#ifndef lzo_pclock_syscall_clock_gettime +#define lzo_pclock_syscall_clock_gettime lzo_pclock_syscall_clock_gettime +#endif +__lzo_static_noinline long lzo_pclock_syscall_clock_gettime(long clockid, struct timespec *ts) +{ + unsigned long r = 265; + __asm__ __volatile__("pushl %%ebx\n pushl %%edx\n popl %%ebx\n int $0x80\n popl %%ebx\n": "=a" (r), "=m" (*ts) : "0" (r), "d" (clockid), "c" (ts) __LZO_ASM_CLOBBER_LIST_CC); + return LZO_ICAST(long, r); +} +#endif +#if 0 && defined(lzo_pclock_syscall_clock_gettime) +#ifndef lzo_pclock_read_clock_gettime_r_syscall +#define lzo_pclock_read_clock_gettime_r_syscall lzo_pclock_read_clock_gettime_r_syscall +#endif +static int lzo_pclock_read_clock_gettime_r_syscall(lzo_pclock_handle_p h, lzo_pclock_p c) +{ + struct timespec ts; + if (lzo_pclock_syscall_clock_gettime(0, &ts) != 0) + return -1; + c->tv_sec = ts.tv_sec; + c->tv_nsec = LZO_STATIC_CAST(lzo_uint32l_t, ts.tv_nsec); + LZO_UNUSED(h); return 0; +} +#endif +#if (HAVE_GETTIMEOFDAY) +#ifndef lzo_pclock_read_gettimeofday +#define lzo_pclock_read_gettimeofday lzo_pclock_read_gettimeofday +#endif +static int lzo_pclock_read_gettimeofday(lzo_pclock_handle_p h, lzo_pclock_p c) +{ + struct timeval tv; + if (gettimeofday(&tv, NULL) != 0) + return -1; +#if defined(lzo_int64l_t) + c->tv_sec = tv.tv_sec; +#else + c->tv_sec_high = 0; + c->tv_sec_low = tv.tv_sec; +#endif + c->tv_nsec = LZO_STATIC_CAST(lzo_uint32l_t, (tv.tv_usec * 1000u)); + LZO_UNUSED(h); return 0; +} +#endif +#if defined(CLOCKS_PER_SEC) && !(LZO_CFG_NO_DOUBLE) +#ifndef lzo_pclock_read_clock +#define lzo_pclock_read_clock lzo_pclock_read_clock +#endif +static int lzo_pclock_read_clock(lzo_pclock_handle_p h, lzo_pclock_p c) +{ + clock_t ticks; + double secs; +#if defined(lzo_int64l_t) + lzo_uint64l_t nsecs; + ticks = clock(); + secs = LZO_STATIC_CAST(double, ticks) / (CLOCKS_PER_SEC); + nsecs = LZO_STATIC_CAST(lzo_uint64l_t, (secs * 1000000000.0)); + c->tv_sec = LZO_STATIC_CAST(lzo_int64l_t, (nsecs / 1000000000ul)); + nsecs = (nsecs % 1000000000ul); + c->tv_nsec = LZO_STATIC_CAST(lzo_uint32l_t, nsecs); +#else + ticks = clock(); + secs = LZO_STATIC_CAST(double, ticks) / (CLOCKS_PER_SEC); + c->tv_sec_high = 0; + c->tv_sec_low = LZO_STATIC_CAST(lzo_uint32l_t, (secs + 0.5)); + c->tv_nsec = 0; +#endif + LZO_UNUSED(h); return 0; +} +#endif +#if 1 && defined(lzo_pclock_syscall_clock_gettime) +#ifndef lzo_pclock_read_clock_gettime_m_syscall +#define lzo_pclock_read_clock_gettime_m_syscall lzo_pclock_read_clock_gettime_m_syscall +#endif +static int lzo_pclock_read_clock_gettime_m_syscall(lzo_pclock_handle_p h, lzo_pclock_p c) +{ + struct timespec ts; + if (lzo_pclock_syscall_clock_gettime(1, &ts) != 0) + return -1; + c->tv_sec = ts.tv_sec; + c->tv_nsec = LZO_STATIC_CAST(lzo_uint32l_t, ts.tv_nsec); + LZO_UNUSED(h); return 0; +} +#endif +#if (LZO_OS_DOS32 && LZO_CC_GNUC) && defined(__DJGPP__) && defined(UCLOCKS_PER_SEC) && !(LZO_CFG_NO_DOUBLE) +#ifndef lzo_pclock_read_uclock +#define lzo_pclock_read_uclock lzo_pclock_read_uclock +#endif +static int lzo_pclock_read_uclock(lzo_pclock_handle_p h, lzo_pclock_p c) +{ + lzo_uint64l_t ticks; + double secs; + lzo_uint64l_t nsecs; + ticks = uclock(); + secs = LZO_STATIC_CAST(double, ticks) / (UCLOCKS_PER_SEC); + nsecs = LZO_STATIC_CAST(lzo_uint64l_t, (secs * 1000000000.0)); + c->tv_sec = nsecs / 1000000000ul; + c->tv_nsec = LZO_STATIC_CAST(lzo_uint32l_t, (nsecs % 1000000000ul)); + LZO_UNUSED(h); return 0; +} +#endif +#if 1 && (HAVE_CLOCK_GETTIME) && defined(CLOCK_PROCESS_CPUTIME_ID) && defined(lzo_int64l_t) +#ifndef lzo_pclock_read_clock_gettime_p_libc +#define lzo_pclock_read_clock_gettime_p_libc lzo_pclock_read_clock_gettime_p_libc +#endif +static int lzo_pclock_read_clock_gettime_p_libc(lzo_pclock_handle_p h, lzo_pclock_p c) +{ + struct timespec ts; + if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts) != 0) + return -1; + c->tv_sec = ts.tv_sec; + c->tv_nsec = LZO_STATIC_CAST(lzo_uint32l_t, ts.tv_nsec); + LZO_UNUSED(h); return 0; +} +#endif +#if 1 && defined(lzo_pclock_syscall_clock_gettime) +#ifndef lzo_pclock_read_clock_gettime_p_syscall +#define lzo_pclock_read_clock_gettime_p_syscall lzo_pclock_read_clock_gettime_p_syscall +#endif +static int lzo_pclock_read_clock_gettime_p_syscall(lzo_pclock_handle_p h, lzo_pclock_p c) +{ + struct timespec ts; + if (lzo_pclock_syscall_clock_gettime(2, &ts) != 0) + return -1; + c->tv_sec = ts.tv_sec; + c->tv_nsec = LZO_STATIC_CAST(lzo_uint32l_t, ts.tv_nsec); + LZO_UNUSED(h); return 0; +} +#endif +#if (LZO_OS_CYGWIN || LZO_OS_WIN32 || LZO_OS_WIN64) && (LZO_HAVE_WINDOWS_H) && defined(lzo_int64l_t) +#ifndef lzo_pclock_read_getprocesstimes +#define lzo_pclock_read_getprocesstimes lzo_pclock_read_getprocesstimes +#endif +static int lzo_pclock_read_getprocesstimes(lzo_pclock_handle_p h, lzo_pclock_p c) +{ + FILETIME ct, et, kt, ut; + lzo_uint64l_t ticks; + if (GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut) == 0) + return -1; + ticks = (LZO_STATIC_CAST(lzo_uint64l_t, ut.dwHighDateTime) << 32) | ut.dwLowDateTime; + if __lzo_unlikely(h->ticks_base == 0) + h->ticks_base = ticks; + else + ticks -= h->ticks_base; + c->tv_sec = LZO_STATIC_CAST(lzo_int64l_t, (ticks / 10000000ul)); + ticks = (ticks % 10000000ul) * 100u; + c->tv_nsec = LZO_STATIC_CAST(lzo_uint32l_t, ticks); + LZO_UNUSED(h); return 0; +} +#endif +#if (HAVE_GETRUSAGE) && defined(RUSAGE_SELF) +#ifndef lzo_pclock_read_getrusage +#define lzo_pclock_read_getrusage lzo_pclock_read_getrusage +#endif +static int lzo_pclock_read_getrusage(lzo_pclock_handle_p h, lzo_pclock_p c) +{ + struct rusage ru; + if (getrusage(RUSAGE_SELF, &ru) != 0) + return -1; +#if defined(lzo_int64l_t) + c->tv_sec = ru.ru_utime.tv_sec; +#else + c->tv_sec_high = 0; + c->tv_sec_low = ru.ru_utime.tv_sec; +#endif + c->tv_nsec = LZO_STATIC_CAST(lzo_uint32l_t, (ru.ru_utime.tv_usec * 1000u)); + LZO_UNUSED(h); return 0; +} +#endif +#if 1 && (HAVE_CLOCK_GETTIME) && defined(CLOCK_THREAD_CPUTIME_ID) && defined(lzo_int64l_t) +#ifndef lzo_pclock_read_clock_gettime_t_libc +#define lzo_pclock_read_clock_gettime_t_libc lzo_pclock_read_clock_gettime_t_libc +#endif +static int lzo_pclock_read_clock_gettime_t_libc(lzo_pclock_handle_p h, lzo_pclock_p c) +{ + struct timespec ts; + if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) != 0) + return -1; + c->tv_sec = ts.tv_sec; + c->tv_nsec = (lzo_uint32l_t) ts.tv_nsec; + LZO_UNUSED(h); return 0; +} +#endif +#if 1 && defined(lzo_pclock_syscall_clock_gettime) +#ifndef lzo_pclock_read_clock_gettime_t_syscall +#define lzo_pclock_read_clock_gettime_t_syscall lzo_pclock_read_clock_gettime_t_syscall +#endif +static int lzo_pclock_read_clock_gettime_t_syscall(lzo_pclock_handle_p h, lzo_pclock_p c) +{ + struct timespec ts; + if (lzo_pclock_syscall_clock_gettime(3, &ts) != 0) + return -1; + c->tv_sec = ts.tv_sec; + c->tv_nsec = LZO_STATIC_CAST(lzo_uint32l_t, ts.tv_nsec); + LZO_UNUSED(h); return 0; +} +#endif +#if (LZO_OS_CYGWIN || LZO_OS_WIN32 || LZO_OS_WIN64) && (LZO_HAVE_WINDOWS_H) && defined(lzo_int64l_t) +#ifndef lzo_pclock_read_getthreadtimes +#define lzo_pclock_read_getthreadtimes lzo_pclock_read_getthreadtimes +#endif +static int lzo_pclock_read_getthreadtimes(lzo_pclock_handle_p h, lzo_pclock_p c) +{ + FILETIME ct, et, kt, ut; + lzo_uint64l_t ticks; + if (GetThreadTimes(GetCurrentThread(), &ct, &et, &kt, &ut) == 0) + return -1; + ticks = (LZO_STATIC_CAST(lzo_uint64l_t, ut.dwHighDateTime) << 32) | ut.dwLowDateTime; + if __lzo_unlikely(h->ticks_base == 0) + h->ticks_base = ticks; + else + ticks -= h->ticks_base; + c->tv_sec = LZO_STATIC_CAST(lzo_int64l_t, (ticks / 10000000ul)); + ticks = (ticks % 10000000ul) * 100u; + c->tv_nsec = LZO_STATIC_CAST(lzo_uint32l_t, ticks); + LZO_UNUSED(h); return 0; +} +#endif +LZOLIB_PUBLIC(int, lzo_pclock_open) (lzo_pclock_handle_p h, int mode) +{ + lzo_pclock_t c; + int i; + h->h = LZO_STATIC_CAST(lzolib_handle_t, 0); + h->mode = -1; + h->read_error = 2; + h->name = NULL; + h->gettime = LZO_STATIC_CAST(lzo_pclock_gettime_t, 0); +#if defined(lzo_int64l_t) + h->ticks_base = 0; +#endif + switch (mode) + { + case LZO_PCLOCK_REALTIME: +# if defined(lzo_pclock_read_clock_gettime_r_syscall) + if (lzo_pclock_read_clock_gettime_r_syscall(h, &c) == 0) { + h->gettime = lzo_pclock_read_clock_gettime_r_syscall; + h->name = "CLOCK_REALTIME/syscall"; + break; + } +# endif +# if defined(lzo_pclock_read_gettimeofday) + if (lzo_pclock_read_gettimeofday(h, &c) == 0) { + h->gettime = lzo_pclock_read_gettimeofday; + h->name = "gettimeofday"; + break; + } +# endif + break; + case LZO_PCLOCK_MONOTONIC: +# if defined(lzo_pclock_read_clock_gettime_m_syscall) + if (lzo_pclock_read_clock_gettime_m_syscall(h, &c) == 0) { + h->gettime = lzo_pclock_read_clock_gettime_m_syscall; + h->name = "CLOCK_MONOTONIC/syscall"; + break; + } +# endif +# if defined(lzo_pclock_read_uclock) + if (lzo_pclock_read_uclock(h, &c) == 0) { + h->gettime = lzo_pclock_read_uclock; + h->name = "uclock"; + break; + } +# endif +# if defined(lzo_pclock_read_clock) + if (lzo_pclock_read_clock(h, &c) == 0) { + h->gettime = lzo_pclock_read_clock; + h->name = "clock"; + break; + } +# endif + break; + case LZO_PCLOCK_PROCESS_CPUTIME_ID: +# if defined(lzo_pclock_read_getprocesstimes) + if (lzo_pclock_read_getprocesstimes(h, &c) == 0) { + h->gettime = lzo_pclock_read_getprocesstimes; + h->name = "GetProcessTimes"; + break; + } +# endif +# if defined(lzo_pclock_read_clock_gettime_p_syscall) + if (lzo_pclock_read_clock_gettime_p_syscall(h, &c) == 0) { + h->gettime = lzo_pclock_read_clock_gettime_p_syscall; + h->name = "CLOCK_PROCESS_CPUTIME_ID/syscall"; + break; + } +# endif +# if defined(lzo_pclock_read_clock_gettime_p_libc) + if (lzo_pclock_read_clock_gettime_p_libc(h, &c) == 0) { + h->gettime = lzo_pclock_read_clock_gettime_p_libc; + h->name = "CLOCK_PROCESS_CPUTIME_ID/libc"; + break; + } +# endif +# if defined(lzo_pclock_read_getrusage) + if (lzo_pclock_read_getrusage(h, &c) == 0) { + h->gettime = lzo_pclock_read_getrusage; + h->name = "getrusage"; + break; + } +# endif + break; + case LZO_PCLOCK_THREAD_CPUTIME_ID: +# if defined(lzo_pclock_read_getthreadtimes) + if (lzo_pclock_read_getthreadtimes(h, &c) == 0) { + h->gettime = lzo_pclock_read_getthreadtimes; + h->name = "GetThreadTimes"; + } +# endif +# if defined(lzo_pclock_read_clock_gettime_t_syscall) + if (lzo_pclock_read_clock_gettime_t_syscall(h, &c) == 0) { + h->gettime = lzo_pclock_read_clock_gettime_t_syscall; + h->name = "CLOCK_THREAD_CPUTIME_ID/syscall"; + break; + } +# endif +# if defined(lzo_pclock_read_clock_gettime_t_libc) + if (lzo_pclock_read_clock_gettime_t_libc(h, &c) == 0) { + h->gettime = lzo_pclock_read_clock_gettime_t_libc; + h->name = "CLOCK_THREAD_CPUTIME_ID/libc"; + break; + } +# endif + break; + } + if (!h->gettime) + return -1; + if (!h->h) + h->h = LZO_STATIC_CAST(lzolib_handle_t, 1); + h->mode = mode; + h->read_error = 0; + if (!h->name) + h->name = "unknown"; + for (i = 0; i < 10; i++) { + __LZOLIB_FUNCNAME(lzo_pclock_read)(h, &c); + } + return 0; +} +LZOLIB_PUBLIC(int, lzo_pclock_open_default) (lzo_pclock_handle_p h) +{ + if (__LZOLIB_FUNCNAME(lzo_pclock_open)(h, LZO_PCLOCK_PROCESS_CPUTIME_ID) == 0) + return 0; + if (__LZOLIB_FUNCNAME(lzo_pclock_open)(h, LZO_PCLOCK_MONOTONIC) == 0) + return 0; + if (__LZOLIB_FUNCNAME(lzo_pclock_open)(h, LZO_PCLOCK_REALTIME) == 0) + return 0; + if (__LZOLIB_FUNCNAME(lzo_pclock_open)(h, LZO_PCLOCK_THREAD_CPUTIME_ID) == 0) + return 0; + return -1; +} +LZOLIB_PUBLIC(int, lzo_pclock_close) (lzo_pclock_handle_p h) +{ + h->h = LZO_STATIC_CAST(lzolib_handle_t, 0); + h->mode = -1; + h->name = NULL; + h->gettime = LZO_STATIC_CAST(lzo_pclock_gettime_t, 0); + return 0; +} +LZOLIB_PUBLIC(void, lzo_pclock_read) (lzo_pclock_handle_p h, lzo_pclock_p c) +{ + if (h->gettime) { + if (h->gettime(h, c) == 0) + return; + } + h->read_error = 1; +#if defined(lzo_int64l_t) + c->tv_sec = 0; +#else + c->tv_sec_high = 0; + c->tv_sec_low = 0; +#endif + c->tv_nsec = 0; +} +#if !(LZO_CFG_NO_DOUBLE) +LZOLIB_PUBLIC(double, lzo_pclock_get_elapsed) (lzo_pclock_handle_p h, const lzo_pclock_p start, const lzo_pclock_p stop) +{ + if (!h->h) { h->mode = -1; return 0.0; } + { +#if 1 && (LZO_ARCH_I386 && LZO_CC_GNUC) && defined(__STRICT_ALIGNMENT__) + float tstop, tstart; + tstop = LZO_STATIC_CAST(float, (stop->tv_sec + stop->tv_nsec / 1000000000.0)); + tstart = LZO_STATIC_CAST(float, (start->tv_sec + start->tv_nsec / 1000000000.0)); +#elif defined(lzo_int64l_t) + double tstop, tstart; +#if 1 && (LZO_CC_INTELC) + { lzo_int64l_t a = stop->tv_sec; lzo_uint32l_t b = stop->tv_nsec; + tstop = a + b / 1000000000.0; } + { lzo_int64l_t a = start->tv_sec; lzo_uint32l_t b = start->tv_nsec; + tstart = a + b / 1000000000.0; } +#else + tstop = stop->tv_sec + stop->tv_nsec / 1000000000.0; + tstart = start->tv_sec + start->tv_nsec / 1000000000.0; +#endif +#else + double tstop, tstart; + tstop = stop->tv_sec_low + stop->tv_nsec / 1000000000.0; + tstart = start->tv_sec_low + start->tv_nsec / 1000000000.0; +#endif + return tstop - tstart; + } +} +#endif +LZOLIB_PUBLIC(int, lzo_pclock_flush_cpu_cache) (lzo_pclock_handle_p h, unsigned flags) +{ + LZO_UNUSED(h); LZO_UNUSED(flags); + return -1; +} +#endif +#if defined(LZO_WANT_ACCLIB_MISC) +# undef LZO_WANT_ACCLIB_MISC +#define __LZOLIB_MISC_CH_INCLUDED 1 +#if !defined(LZOLIB_PUBLIC) +# define LZOLIB_PUBLIC(r,f) r __LZOLIB_FUNCNAME(f) +#endif +#if !defined(LZOLIB_PUBLIC_NOINLINE) +# if !defined(__lzo_noinline) +# define LZOLIB_PUBLIC_NOINLINE(r,f) r __LZOLIB_FUNCNAME(f) +# elif (LZO_CC_CLANG || (LZO_CC_GNUC >= 0x030400ul) || LZO_CC_LLVM) +# define LZOLIB_PUBLIC_NOINLINE(r,f) __lzo_noinline __attribute__((__used__)) r __LZOLIB_FUNCNAME(f) +# else +# define LZOLIB_PUBLIC_NOINLINE(r,f) __lzo_noinline r __LZOLIB_FUNCNAME(f) +# endif +#endif +#if (LZO_OS_WIN32 && LZO_CC_PELLESC && (__POCC__ >= 290)) +# pragma warn(push) +# pragma warn(disable:2007) +#endif +LZOLIB_PUBLIC(const char *, lzo_getenv) (const char *s) +{ +#if (HAVE_GETENV) + return getenv(s); +#else + LZO_UNUSED(s); return LZO_STATIC_CAST(const char *, 0); +#endif +} +LZOLIB_PUBLIC(lzo_intptr_t, lzo_get_osfhandle) (int fd) +{ + if (fd < 0) + return -1; +#if (LZO_OS_CYGWIN) + return get_osfhandle(fd); +#elif (LZO_OS_EMX && defined(__RSXNT__)) + return -1; +#elif (LZO_OS_WIN32 && LZO_CC_GNUC) && defined(__PW32__) + return -1; +#elif (LZO_OS_WIN32 || LZO_OS_WIN64) +# if (LZO_CC_PELLESC && (__POCC__ < 280)) + return -1; +# elif (LZO_CC_WATCOMC && (__WATCOMC__ < 1000)) + return -1; +# elif (LZO_CC_WATCOMC && (__WATCOMC__ < 1100)) + return _os_handle(fd); +# else + return _get_osfhandle(fd); +# endif +#else + return fd; +#endif +} +LZOLIB_PUBLIC(int, lzo_set_binmode) (int fd, int binary) +{ +#if (LZO_ARCH_M68K && LZO_OS_TOS && LZO_CC_GNUC) && defined(__MINT__) + FILE* fp; int old_binary; + if (fd == STDIN_FILENO) fp = stdin; + else if (fd == STDOUT_FILENO) fp = stdout; + else if (fd == STDERR_FILENO) fp = stderr; + else return -1; + old_binary = fp->__mode.__binary; + __set_binmode(fp, binary ? 1 : 0); + return old_binary ? 1 : 0; +#elif (LZO_ARCH_M68K && LZO_OS_TOS) + LZO_UNUSED(fd); LZO_UNUSED(binary); + return -1; +#elif (LZO_OS_DOS16 && (LZO_CC_AZTECC || LZO_CC_PACIFICC)) + LZO_UNUSED(fd); LZO_UNUSED(binary); + return -1; +#elif (LZO_OS_DOS32 && LZO_CC_GNUC) && defined(__DJGPP__) + int r; unsigned old_flags = __djgpp_hwint_flags; + LZO_COMPILE_TIME_ASSERT(O_BINARY > 0) + LZO_COMPILE_TIME_ASSERT(O_TEXT > 0) + if (fd < 0) return -1; + r = setmode(fd, binary ? O_BINARY : O_TEXT); + if ((old_flags & 1u) != (__djgpp_hwint_flags & 1u)) + __djgpp_set_ctrl_c(!(old_flags & 1)); + if (r == -1) return -1; + return (r & O_TEXT) ? 0 : 1; +#elif (LZO_OS_WIN32 && LZO_CC_GNUC) && defined(__PW32__) + if (fd < 0) return -1; + LZO_UNUSED(binary); + return 1; +#elif (LZO_OS_DOS32 && LZO_CC_HIGHC) + FILE* fp; int r; + if (fd == fileno(stdin)) fp = stdin; + else if (fd == fileno(stdout)) fp = stdout; + else if (fd == fileno(stderr)) fp = stderr; + else return -1; + r = _setmode(fp, binary ? _BINARY : _TEXT); + if (r == -1) return -1; + return (r & _BINARY) ? 1 : 0; +#elif (LZO_OS_WIN32 && LZO_CC_MWERKS) && defined(__MSL__) + LZO_UNUSED(fd); LZO_UNUSED(binary); + return -1; +#elif (LZO_OS_CYGWIN && (LZO_CC_GNUC < 0x025a00ul)) + LZO_UNUSED(fd); LZO_UNUSED(binary); + return -1; +#elif (LZO_OS_CYGWIN || LZO_OS_DOS16 || LZO_OS_DOS32 || LZO_OS_EMX || LZO_OS_OS2 || LZO_OS_OS216 || LZO_OS_WIN16 || LZO_OS_WIN32 || LZO_OS_WIN64) + int r; +#if !(LZO_CC_ZORTECHC) + LZO_COMPILE_TIME_ASSERT(O_BINARY > 0) +#endif + LZO_COMPILE_TIME_ASSERT(O_TEXT > 0) + if (fd < 0) return -1; + r = setmode(fd, binary ? O_BINARY : O_TEXT); + if (r == -1) return -1; + return (r & O_TEXT) ? 0 : 1; +#else + if (fd < 0) return -1; + LZO_UNUSED(binary); + return 1; +#endif +} +LZOLIB_PUBLIC(int, lzo_isatty) (int fd) +{ + if (fd < 0) + return 0; +#if (LZO_OS_DOS16 && !(LZO_CC_AZTECC)) + { + union REGS ri, ro; + ri.x.ax = 0x4400; ri.x.bx = fd; + int86(0x21, &ri, &ro); + if ((ro.x.cflag & 1) == 0) + if ((ro.x.ax & 0x83) != 0x83) + return 0; + } +#elif (LZO_OS_DOS32 && LZO_CC_WATCOMC) + { + union REGS ri, ro; + ri.w.ax = 0x4400; ri.w.bx = LZO_STATIC_CAST(unsigned short, fd); + int386(0x21, &ri, &ro); + if ((ro.w.cflag & 1) == 0) + if ((ro.w.ax & 0x83) != 0x83) + return 0; + } +#elif (LZO_HAVE_WINDOWS_H) + { + lzo_intptr_t h = __LZOLIB_FUNCNAME(lzo_get_osfhandle)(fd); + LZO_COMPILE_TIME_ASSERT(sizeof(h) == sizeof(HANDLE)) + if (h != -1) + { + DWORD d = 0; + if (GetConsoleMode(LZO_REINTERPRET_CAST(HANDLE, h), &d) == 0) + return 0; + } + } +#endif +#if (HAVE_ISATTY) + return (isatty(fd)) ? 1 : 0; +#else + return 0; +#endif +} +LZOLIB_PUBLIC(int, lzo_mkdir) (const char* name, unsigned mode) +{ +#if !(HAVE_MKDIR) + LZO_UNUSED(name); LZO_UNUSED(mode); + return -1; +#elif (LZO_ARCH_M68K && LZO_OS_TOS && (LZO_CC_PUREC || LZO_CC_TURBOC)) + LZO_UNUSED(mode); + return Dcreate(name); +#elif (LZO_OS_DOS32 && LZO_CC_GNUC) && defined(__DJGPP__) + return mkdir(name, mode); +#elif (LZO_OS_WIN32 && LZO_CC_GNUC) && defined(__PW32__) + return mkdir(name, mode); +#elif ((LZO_OS_DOS16 || LZO_OS_DOS32) && (LZO_CC_HIGHC || LZO_CC_PACIFICC)) + LZO_UNUSED(mode); + return mkdir(LZO_UNCONST_CAST(char *, name)); +#elif (LZO_OS_DOS16 || LZO_OS_DOS32 || LZO_OS_OS2 || LZO_OS_OS216 || LZO_OS_WIN16 || LZO_OS_WIN32 || LZO_OS_WIN64) + LZO_UNUSED(mode); + return mkdir(name); +#elif (LZO_CC_WATCOMC) + return mkdir(name, LZO_STATIC_CAST(mode_t, mode)); +#else + return mkdir(name, mode); +#endif +} +LZOLIB_PUBLIC(int, lzo_rmdir) (const char* name) +{ +#if !(HAVE_RMDIR) + LZO_UNUSED(name); + return -1; +#elif ((LZO_OS_DOS16 || LZO_OS_DOS32) && (LZO_CC_HIGHC || LZO_CC_PACIFICC)) + return rmdir(LZO_UNCONST_CAST(char *, name)); +#else + return rmdir(name); +#endif +} +#if defined(lzo_int32e_t) +LZOLIB_PUBLIC(lzo_int32e_t, lzo_muldiv32s) (lzo_int32e_t a, lzo_int32e_t b, lzo_int32e_t x) +{ + lzo_int32e_t r = 0; + if __lzo_likely(x != 0) + { +#if defined(lzo_int64l_t) + lzo_int64l_t rr = (LZO_ICONV(lzo_int64l_t, a) * b) / x; + r = LZO_ITRUNC(lzo_int32e_t, rr); +#else + LZO_UNUSED(a); LZO_UNUSED(b); +#endif + } + return r; +} +LZOLIB_PUBLIC(lzo_uint32e_t, lzo_muldiv32u) (lzo_uint32e_t a, lzo_uint32e_t b, lzo_uint32e_t x) +{ + lzo_uint32e_t r = 0; + if __lzo_likely(x != 0) + { +#if defined(lzo_int64l_t) + lzo_uint64l_t rr = (LZO_ICONV(lzo_uint64l_t, a) * b) / x; + r = LZO_ITRUNC(lzo_uint32e_t, rr); +#else + LZO_UNUSED(a); LZO_UNUSED(b); +#endif + } + return r; +} +#endif +#if (LZO_OS_WIN16) +LZO_EXTERN_C void __far __pascal DebugBreak(void); +#endif +LZOLIB_PUBLIC_NOINLINE(void, lzo_debug_break) (void) +{ +#if (LZO_OS_WIN16) + DebugBreak(); +#elif (LZO_ARCH_I086) +#elif (LZO_OS_WIN64) && (LZO_HAVE_WINDOWS_H) + DebugBreak(); +#elif (LZO_ARCH_AMD64 || LZO_ARCH_I386) && (LZO_ASM_SYNTAX_GNUC) + __asm__ __volatile__("int $3\n" : : __LZO_ASM_CLOBBER_LIST_CC_MEMORY); +#elif (LZO_ARCH_I386) && (LZO_ASM_SYNTAX_MSC) + __asm { int 3 } +#elif (LZO_OS_WIN32) && (LZO_HAVE_WINDOWS_H) + DebugBreak(); +#else + volatile lzo_intptr_t a = -1; + * LZO_STATIC_CAST(volatile unsigned long *, LZO_REINTERPRET_CAST(volatile void *, a)) = ~0ul; +#endif +} +LZOLIB_PUBLIC_NOINLINE(void, lzo_debug_nop) (void) +{ +} +LZOLIB_PUBLIC_NOINLINE(int, lzo_debug_align_check_query) (void) +{ +#if (LZO_ARCH_AMD64 || LZO_ARCH_I386) && (LZO_ASM_SYNTAX_GNUC) +# if (LZO_ARCH_AMD64) + lzo_uint64e_t r = 0; +# else + size_t r = 0; +# endif + __asm__ __volatile__("pushf\n pop %0\n" : "=a" (r) : __LZO_ASM_CLOBBER_LIST_CC_MEMORY); + return LZO_ICONV(int, (r >> 18) & 1); +#elif (LZO_ARCH_I386) && (LZO_ASM_SYNTAX_MSC) + unsigned long r; + __asm { + pushf + pop eax + mov r,eax + } + return LZO_ICONV(int, (r >> 18) & 1); +#else + return -1; +#endif +} +LZOLIB_PUBLIC_NOINLINE(int, lzo_debug_align_check_enable) (int v) +{ +#if (LZO_ARCH_AMD64) && (LZO_ASM_SYNTAX_GNUC) + if (v) { + __asm__ __volatile__("pushf\n orl $262144,(%%rsp)\n popf\n" : : __LZO_ASM_CLOBBER_LIST_CC_MEMORY); + } else { + __asm__ __volatile__("pushf\n andl $-262145,(%%rsp)\n popf\n" : : __LZO_ASM_CLOBBER_LIST_CC_MEMORY); + } + return 0; +#elif (LZO_ARCH_I386) && (LZO_ASM_SYNTAX_GNUC) + if (v) { + __asm__ __volatile__("pushf\n orl $262144,(%%esp)\n popf\n" : : __LZO_ASM_CLOBBER_LIST_CC_MEMORY); + } else { + __asm__ __volatile__("pushf\n andl $-262145,(%%esp)\n popf\n" : : __LZO_ASM_CLOBBER_LIST_CC_MEMORY); + } + return 0; +#elif (LZO_ARCH_I386) && (LZO_ASM_SYNTAX_MSC) + if (v) { __asm { + pushf + or dword ptr [esp],262144 + popf + }} else { __asm { + pushf + and dword ptr [esp],-262145 + popf + }} + return 0; +#else + LZO_UNUSED(v); return -1; +#endif +} +LZOLIB_PUBLIC_NOINLINE(unsigned, lzo_debug_running_on_qemu) (void) +{ + unsigned r = 0; +#if (LZO_OS_POSIX_LINUX || LZO_OS_WIN32 || LZO_OS_WIN64) + const char* p; + p = __LZOLIB_FUNCNAME(lzo_getenv)(LZO_PP_STRINGIZE(LZO_ENV_RUNNING_ON_QEMU)); + if (p) { + if (p[0] == 0) r = 0; + else if ((p[0] >= '0' && p[0] <= '9') && p[1] == 0) r = LZO_ICAST(unsigned, p[0]) - '0'; + else r = 1; + } +#endif + return r; +} +LZOLIB_PUBLIC_NOINLINE(unsigned, lzo_debug_running_on_valgrind) (void) +{ +#if (LZO_ARCH_AMD64 && LZO_ABI_ILP32) + return 0; +#elif (LZO_ARCH_AMD64 || LZO_ARCH_I386) && (LZO_ASM_SYNTAX_GNUC) + volatile size_t a[6]; + size_t r = 0; + a[0] = 0x1001; a[1] = 0; a[2] = 0; a[3] = 0; a[4] = 0; a[5] = 0; +# if (LZO_ARCH_AMD64) + __asm__ __volatile__(".byte 0x48,0xc1,0xc7,0x03,0x48,0xc1,0xc7,0x0d,0x48,0xc1,0xc7,0x3d,0x48,0xc1,0xc7,0x33,0x48,0x87,0xdb\n" : "=d" (r) : "a" (&a[0]), "d" (r) __LZO_ASM_CLOBBER_LIST_CC_MEMORY); +# elif (LZO_ARCH_I386) + __asm__ __volatile__(".byte 0xc1,0xc7,0x03,0xc1,0xc7,0x0d,0xc1,0xc7,0x1d,0xc1,0xc7,0x13,0x87,0xdb\n" : "=d" (r) : "a" (&a[0]), "d" (r) __LZO_ASM_CLOBBER_LIST_CC_MEMORY); +# endif + return LZO_ITRUNC(unsigned, r); +#else + return 0; +#endif +} +#if (LZO_OS_WIN32 && LZO_CC_PELLESC && (__POCC__ >= 290)) +# pragma warn(pop) +#endif +#endif +#if defined(LZO_WANT_ACCLIB_WILDARGV) +# undef LZO_WANT_ACCLIB_WILDARGV +#define __LZOLIB_WILDARGV_CH_INCLUDED 1 +#if !defined(LZOLIB_PUBLIC) +# define LZOLIB_PUBLIC(r,f) r __LZOLIB_FUNCNAME(f) +#endif +#if (LZO_OS_DOS16 || LZO_OS_OS216 || LZO_OS_WIN16) +#if 0 && (LZO_CC_MSC) +LZO_EXTERN_C int __lzo_cdecl __setargv(void); +LZO_EXTERN_C int __lzo_cdecl _setargv(void); +LZO_EXTERN_C int __lzo_cdecl _setargv(void) { return __setargv(); } +#endif +#endif +#if (LZO_OS_WIN32 || LZO_OS_WIN64) +#if (LZO_CC_MSC && (_MSC_VER >= 1900)) +#elif (LZO_CC_INTELC || LZO_CC_MSC) +LZO_EXTERN_C int __lzo_cdecl __setargv(void); +LZO_EXTERN_C int __lzo_cdecl _setargv(void); +LZO_EXTERN_C int __lzo_cdecl _setargv(void) { return __setargv(); } +#endif +#endif +#if (LZO_OS_EMX) +#define __LZOLIB_HAVE_LZO_WILDARGV 1 +LZOLIB_PUBLIC(void, lzo_wildargv) (int* argc, char*** argv) +{ + if (argc && argv) { + _response(argc, argv); + _wildcard(argc, argv); + } +} +#endif +#if (LZO_OS_CONSOLE_PSP) && defined(__PSPSDK_DEBUG__) +#define __LZOLIB_HAVE_LZO_WILDARGV 1 +LZO_EXTERN_C int lzo_psp_init_module(int*, char***, int); +LZOLIB_PUBLIC(void, lzo_wildargv) (int* argc, char*** argv) +{ + lzo_psp_init_module(argc, argv, -1); +} +#endif +#if !(__LZOLIB_HAVE_LZO_WILDARGV) +#define __LZOLIB_HAVE_LZO_WILDARGV 1 +LZOLIB_PUBLIC(void, lzo_wildargv) (int* argc, char*** argv) +{ +#if 1 && (LZO_ARCH_I086PM) + if (LZO_MM_AHSHIFT != 3) { exit(1); } +#elif 1 && (LZO_ARCH_M68K && LZO_OS_TOS && LZO_CC_GNUC) && defined(__MINT__) + __binmode(1); + if (isatty(1)) __set_binmode(stdout, 0); + if (isatty(2)) __set_binmode(stderr, 0); +#endif + LZO_UNUSED(argc); LZO_UNUSED(argv); +} +#endif +#endif + +/* vim:set ts=4 sw=4 et: */ From afe2c2876aae7637af0238695dc6069626dff5bd Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Mon, 7 Sep 2026 11:34:58 +0200 Subject: [PATCH 019/179] Decompress through the vendored LZO with bounds checking --- code/CMakeLists.txt | 1 + code/lzo.h | 49 ------ code/lzo1x.h | 123 -------------- code/lzo1x_c.cpp | 388 -------------------------------------------- code/lzo1x_d.cpp | 236 --------------------------- code/lzo_conf.h | 298 ---------------------------------- code/lzoconf.h | 230 -------------------------- code/lzopipe.cpp | 25 +-- code/lzostraw.cpp | 13 +- code/startup.cpp | 5 + 10 files changed, 29 insertions(+), 1339 deletions(-) delete mode 100644 code/lzo.h delete mode 100644 code/lzo1x.h delete mode 100644 code/lzo1x_c.cpp delete mode 100644 code/lzo1x_d.cpp delete mode 100644 code/lzo_conf.h delete mode 100644 code/lzoconf.h diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index fa2d5ea20..aa6f43837 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -204,6 +204,7 @@ target_link_libraries(OpenTS PRIVATE bx bimg miniaudio + lzo comctl32 dbghelp iphlpapi diff --git a/code/lzo.h b/code/lzo.h deleted file mode 100644 index e25a722f6..000000000 --- a/code/lzo.h +++ /dev/null @@ -1,49 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2025 Electronic Arts Inc. - * Copyright 2026 OpenTS contributors - * - * Contains material derived from Electronic Arts source code. - * Modified by OpenTS contributors, 2026. - * EA's GPLv3 Section 7 additional terms and supplemental warranty - * disclaimers apply; see LICENSE.md. - ******************************************************************************/ - -/*********************************************************************************************** - *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** - *********************************************************************************************** - * * - * Project Name : Command & Conquer * - * * - * $Archive:: /Commando/Code/wwlib/lzo.h $* - * * - * $Author:: Greg_h $* - * * - * $Modtime:: 7/19/99 3:35p $* - * * - * $Revision:: 3 $* - * * - *---------------------------------------------------------------------------------------------* - * Functions: * - * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - -#pragma once - -#include "lzoconf.h" -#include "lzo1x.h" - - -int lzo1x_1_compress ( const lzo_byte *in, - lzo_uint in_len, - lzo_byte *out, - lzo_uint *out_len, - lzo_voidp wrkmem); - - -int lzo1x_decompress ( const lzo_byte *in, - lzo_uint in_len, - lzo_byte *out, - lzo_uint *out_len, - lzo_voidp); diff --git a/code/lzo1x.h b/code/lzo1x.h deleted file mode 100644 index d07ae6769..000000000 --- a/code/lzo1x.h +++ /dev/null @@ -1,123 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2025 Electronic Arts Inc. - * Copyright 2026 OpenTS contributors - * - * Contains material derived from Electronic Arts source code. - * Modified by OpenTS contributors, 2026. - * EA's GPLv3 Section 7 additional terms and supplemental warranty - * disclaimers apply; see LICENSE.md. - ******************************************************************************/ - -/*********************************************************************************************** - *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** - *********************************************************************************************** - * * - * Project Name : Command & Conquer * - * * - * $Archive:: /Commando/Library/lzo1x.h $* - * * - * $Author:: Greg_h $* - * * - * $Modtime:: 7/22/97 11:37a $* - * * - * $Revision:: 1 $* - * * - *---------------------------------------------------------------------------------------------* - * Functions: * - * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - -/* lzo1x.h -- public interface of the LZO1X compression algorithm - - This file is part of the LZO real-time data compression library. - - Copyright (C) 1996 Markus Franz Xaver Johannes Oberhumer - - The LZO library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - The LZO library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with the LZO library; see the file COPYING.LIB. - If not, write to the Free Software Foundation, Inc., - 675 Mass Ave, Cambridge, MA 02139, USA. - - Markus F.X.J. Oberhumer - markus.oberhumer@jk.uni-linz.ac.at - */ - - -#ifndef __LZO1X_H -#define __LZO1X_H - -#include "lzoconf.h" - -//#ifdef __cplusplus -//extern "C" { -//#endif - - -/*********************************************************************** -// -************************************************************************/ - -/* Memory required for the wrkmem parameter. - * When the required size is 0, you can also pass a NULL pointer. - */ - -#define LZO1X_MEM_COMPRESS ((lzo_uint) (16384L * sizeof(lzo_byte *))) -#define LZO1X_MEM_DECOMPRESS (0) - - -/* fast decompression */ -LZO_EXTERN(int) -lzo1x_decompress ( const lzo_byte *src, lzo_uint src_len, - lzo_byte *dst, lzo_uint *dst_len, - lzo_voidp wrkmem /* NOT USED */ ); - -/* safe decompression with overrun testing */ -LZO_EXTERN(int) -lzo1x_decompress_x ( const lzo_byte *src, lzo_uint src_len, - lzo_byte *dst, lzo_uint *dst_len, - lzo_voidp wrkmem /* NOT USED */ ); - - -/*********************************************************************** -// -************************************************************************/ - -LZO_EXTERN(int) -lzo1x_1_compress ( const lzo_byte *src, lzo_uint src_len, - lzo_byte *dst, lzo_uint *dst_len, - lzo_voidp wrkmem ); - - -/*********************************************************************** -// better compression ratio at the cost of more memory and time -************************************************************************/ - -#define LZO1X_999_MEM_COMPRESS ((lzo_uint) (14 * 16384L * sizeof(short))) - -LZO_EXTERN(int) -lzo1x_999_compress ( const lzo_byte *src, lzo_uint src_len, - lzo_byte *dst, lzo_uint *dst_len, - lzo_voidp wrkmem ); - - -//#ifdef __cplusplus -//} /* extern "C" */ -//#endif - -#endif /* already included */ - -/* -vi:ts=4 -*/ diff --git a/code/lzo1x_c.cpp b/code/lzo1x_c.cpp deleted file mode 100644 index d612da89a..000000000 --- a/code/lzo1x_c.cpp +++ /dev/null @@ -1,388 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2025 Electronic Arts Inc. - * Copyright 2026 OpenTS contributors - * - * Contains material derived from Electronic Arts source code. - * Modified by OpenTS contributors, 2026. - * EA's GPLv3 Section 7 additional terms and supplemental warranty - * disclaimers apply; see LICENSE.md. - ******************************************************************************/ - -/*********************************************************************************************** - *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** - *********************************************************************************************** - * * - * Project Name : Command & Conquer * - * * - * $Archive:: /Commando/Code/wwlib/lzo1x_c.cpp $* - * * - * $Author:: Jani_p $* - * * - * $Modtime:: 6/28/00 10:13a $* - * * - * $Revision:: 2 $* - * * - *---------------------------------------------------------------------------------------------* - * Functions: * - * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - -/* $Header: /Commando/Code/wwlib/lzo1x_c.cpp 2 7/05/00 6:26p Jani_p $ */ -/* lzo1x_c.c -- standalone LZO1X-1 compressor - - This file is part of the LZO real-time data compression library. - - Copyright (C) 1996 Markus Franz Xaver Johannes Oberhumer - - The LZO library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - The LZO library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with the LZO library; see the file COPYING.LIB. - If not, write to the Free Software Foundation, Inc., - 675 Mass Ave, Cambridge, MA 02139, USA. - - Markus F.X.J. Oberhumer - markus.oberhumer@jk.uni-linz.ac.at - */ - - -#include "always.h" - -#include "lzo1x.h" -#include "lzo_conf.h" - -#include - -#if !defined(LZO1X) && !defined(LZO1Y) -# define LZO1X -#endif - - -/*********************************************************************** -// -************************************************************************/ - -#define M1_MAX_OFFSET 0x0400 -#if defined(LZO1X) -#define M2_MAX_OFFSET 0x0800 -#elif defined(LZO1Y) -#define M2_MAX_OFFSET 0x0400 -#endif -#define M3_MAX_OFFSET 0x4000 -#define M4_MAX_OFFSET 0xbfff - -#define MX_MAX_OFFSET (M1_MAX_OFFSET + M2_MAX_OFFSET) - -#define M1_MARKER 0 -#define M2_MARKER 64 -#define M3_MARKER 32 -#define M4_MARKER 16 - - -#define _DV2(p,shift1,shift2) \ - (((( (lzo_uint)(p[2]) << shift1) ^ p[1]) << shift2) ^ p[0]) -#define DVAL_NEXT(dv,p) \ - dv ^= p[-1]; dv = (((dv) >> 5) ^ ((lzo_uint)(p[2]) << (2*5))) -#define _DV(p,shift) _DV2(p,shift,shift) -#define DVAL_FIRST(dv,p) dv = _DV((p),5) -#define _DINDEX(dv,p) ((40799u * (dv)) >> 5) -#define DINDEX(dv,p) (((_DINDEX(dv,p)) & 0x3fff) << 0) -#define UPDATE_D(dict,cycle,dv,p) dict[ DINDEX(dv,p) ] = (p) -#define UPDATE_I(dict,cycle,index,p) dict[index] = (p) - - -/*********************************************************************** -// compress a block of data. -************************************************************************/ - -/// -/// Compresses a block of data with the LZO1X-1 algorithm. -/// This is the low level compressor that the public entry point hands a block to once it -/// is long enough to be worth searching for matches. Literal runs and back references are -/// written to the destination as they are found. -/// -/// Set to the number of compressed bytes written. -/// Work memory used to hold the compressor's match dictionary. -/// Returns with an LZO error code; LZO_E_OK if the block was compressed. -/// The work memory must be at least LZO1X_MEM_COMPRESS bytes long, and the -/// destination buffer must be big enough to hold data that fails to compress. -static int do_compress(const lzo_byte * in, lzo_uint in_len, - lzo_byte *out, lzo_uint *out_len, - lzo_voidp wrkmem ) -{ - - const lzo_byte *ip; - lzo_uint dv; - lzo_byte *op; - const lzo_byte * const in_end = in + in_len; - const lzo_byte * const ip_end = in + in_len - 9 - 4; - const lzo_byte *ii; - const lzo_bytepp const dict = (const lzo_bytepp) wrkmem; - - op = out; - ip = in; - ii = ip; - - DVAL_FIRST(dv,ip); UPDATE_D(dict,cycle,dv,ip); ip++; - DVAL_NEXT(dv,ip); UPDATE_D(dict,cycle,dv,ip); ip++; - DVAL_NEXT(dv,ip); UPDATE_D(dict,cycle,dv,ip); ip++; - DVAL_NEXT(dv,ip); UPDATE_D(dict,cycle,dv,ip); ip++; - - for (;;) { - const lzo_byte *m_pos; - lzo_uint m_len; - lzo_ptrdiff_t m_off; - lzo_uint lit; - - lzo_uint dindex = DINDEX(dv,ip); - m_pos = dict[dindex]; - UPDATE_I(dict,cycle,dindex,ip); - - - if (LZO_CHECK_MPOS_NON_DET(m_pos,m_off,in,ip,M4_MAX_OFFSET)) { - } -#if defined(LZO_UNALIGNED_OK_2) - else - if (* (unsigned short *) m_pos != * (unsigned short *) ip) -#else - else - if (m_pos[0] != ip[0] || m_pos[1] != ip[1]) -#endif - { - } else { - if (m_pos[2] == ip[2]) { - lit = ip - ii; - m_pos += 3; - if (m_off <= M2_MAX_OFFSET) - goto match; - - /* better compression, but slower */ - if (lit == 3) { - assert(op - 2 > out); op[-2] |= LZO_BYTE(3); - *op++ = *ii++; *op++ = *ii++; *op++ = *ii++; - goto code_match; - } - - if (*m_pos == ip[3]) { - goto match; - } - } else { - /* still need a better way for finding M1 matches */ - } - } - - - /* a literal */ - ++ip; - if (ip >= ip_end) { - break; - } - DVAL_NEXT(dv,ip); - continue; - - - /* a match */ - -match: - - /* store current literal run */ - if (lit > 0) { - lzo_uint t = lit; - - if (t <= 3) { - assert(op - 2 > out); - op[-2] |= LZO_BYTE(t); - } else { - if (t <= 18) { - *op++ = LZO_BYTE(t - 3); - } else { - lzo_uint tt = t - 18; - - *op++ = 0; - while (tt > 255) { - tt -= 255; - *op++ = 0; - } - assert(tt > 0); - *op++ = LZO_BYTE(tt); - } - } - - do { - *op++ = *ii++; - } while (--t > 0); - } - - - /* code the match */ -code_match: - assert(ii == ip); - ip += 3; - if (*m_pos++ != *ip++ || *m_pos++ != *ip++ || *m_pos++ != *ip++ || - *m_pos++ != *ip++ || *m_pos++ != *ip++ || *m_pos++ != *ip++) - { - --ip; - m_len = ip - ii; - assert(m_len >= 3); assert(m_len <= 8); - - if (m_off <= M2_MAX_OFFSET) { - m_off -= 1; - *op++ = LZO_BYTE(((m_len - 1) << 5) | ((m_off & 7) << 2)); - *op++ = LZO_BYTE(m_off >> 3); - } else { - if (m_off <= M3_MAX_OFFSET) { - m_off -= 1; - *op++ = LZO_BYTE(M3_MARKER | (m_len - 2)); - goto m3_m4_offset; - } else { - m_off -= 0x4000; - assert(m_off > 0); assert(m_off <= 0x7fff); - *op++ = LZO_BYTE(M4_MARKER | - ((m_off & 0x4000) >> 11) | (m_len - 2)); - goto m3_m4_offset; - } - } - } else { - const lzo_byte *end; - end = in_end; - while (ip < end && *m_pos == *ip) { - m_pos++; - ip++; - } - m_len = (ip - ii); - assert(m_len >= 3); - - if (m_off <= M3_MAX_OFFSET) { - m_off -= 1; - if (m_len <= 33) { - *op++ = LZO_BYTE(M3_MARKER | (m_len - 2)); - } else { - m_len -= 33; - *op++ = M3_MARKER | 0; - goto m3_m4_len; - } - } else { - m_off -= 0x4000; - assert(m_off > 0); assert(m_off <= 0x7fff); - if (m_len <= 9) { - *op++ = LZO_BYTE(M4_MARKER | - ((m_off & 0x4000) >> 11) | (m_len - 2)); - } else { - m_len -= 9; - *op++ = LZO_BYTE(M4_MARKER | ((m_off & 0x4000) >> 11)); -m3_m4_len: - while (m_len > 255) { - m_len -= 255; - *op++ = 0; - } - assert(m_len > 0); - *op++ = LZO_BYTE(m_len); - } - } - -m3_m4_offset: - *op++ = LZO_BYTE((m_off & 63) << 2); - *op++ = LZO_BYTE(m_off >> 6); - } - - ii = ip; - if (ip >= ip_end) { - break; - } - DVAL_FIRST(dv,ip); - } - - /* store final literal run */ - if (in_end - ii > 0) { - lzo_uint t = in_end - ii; - - if (op == out && t <= 238) { - *op++ = LZO_BYTE(17 + t); - } else { - if (t <= 3) { - op[-2] |= LZO_BYTE(t); - } else { - if (t <= 18) { - *op++ = LZO_BYTE(t - 3); - } else { - lzo_uint tt = t - 18; - - *op++ = 0; - while (tt > 255) { - tt -= 255; - *op++ = 0; - } - assert(tt > 0); - *op++ = LZO_BYTE(tt); - } - } - } - do { - *op++ = *ii++; - } while (--t > 0); - } - - *out_len = op - out; - return(LZO_E_OK); -} - - -/*********************************************************************** -// public entry point -************************************************************************/ - -/// -/// Compresses a block of data into the LZO1X-1 stream format. -/// This is the public entry point to the compressor. A block too short to be worth -/// matching is copied through as a single literal run, and every stream is finished off -/// with an end of stream marker so that the decompressor knows where to stop. -/// -/// Set to the number of compressed bytes written. -/// Work memory used to hold the compressor's match dictionary. -/// Returns with an LZO error code; LZO_E_OK if the block was compressed. -/// The work memory must be at least LZO1X_MEM_COMPRESS bytes long. Be sure that -/// the destination buffer is big enough to hold data that fails to compress. -int lzo1x_1_compress ( const lzo_byte * in, lzo_uint in_len, - lzo_byte * out, lzo_uint *out_len, - lzo_voidp wrkmem ) -{ - lzo_byte *op = out; - int r = LZO_E_OK; - - if (in_len <= 0) { - *out_len = 0; - } else { - if (in_len <= 9 + 4) { - *op++ = LZO_BYTE(17 + in_len); - do *op++ = *in++; while (--in_len > 0); - *out_len = op - out; - } else { - r = do_compress(in,in_len,out,out_len,wrkmem); - } - } - - if (r == LZO_E_OK) { - op = out + *out_len; - *op++ = M4_MARKER | 1; - *op++ = 0; - *op = 0; - *out_len += 3; - } - - return(r); -} - - -/* -vi:ts=4 -*/ diff --git a/code/lzo1x_d.cpp b/code/lzo1x_d.cpp deleted file mode 100644 index d25d39654..000000000 --- a/code/lzo1x_d.cpp +++ /dev/null @@ -1,236 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2025 Electronic Arts Inc. - * Copyright 2026 OpenTS contributors - * - * Contains material derived from Electronic Arts source code. - * Modified by OpenTS contributors, 2026. - * EA's GPLv3 Section 7 additional terms and supplemental warranty - * disclaimers apply; see LICENSE.md. - ******************************************************************************/ - -/*********************************************************************************************** - *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** - *********************************************************************************************** - * * - * Project Name : Command & Conquer * - * * - * $Archive:: /Commando/Library/LZO1X_D.CPP $* - * * - * $Author:: Greg_h $* - * * - * $Modtime:: 7/22/97 11:37a $* - * * - * $Revision:: 1 $* - * * - *---------------------------------------------------------------------------------------------* - * Functions: * - * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - -/* $Header: /Commando/Library/LZO1X_D.CPP 1 7/22/97 12:00p Greg_h $ */ -/* lzo1x_d.c -- standalone LZO1X decompressor - - This file is part of the LZO real-time data compression library. - - Copyright (C) 1996 Markus Franz Xaver Johannes Oberhumer - - The LZO library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - The LZO library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with the LZO library; see the file COPYING.LIB. - If not, write to the Free Software Foundation, Inc., - 675 Mass Ave, Cambridge, MA 02139, USA. - - Markus F.X.J. Oberhumer - markus.oberhumer@jk.uni-linz.ac.at - */ - - -#include "always.h" - -#include "lzo1x.h" -#include - -#if !defined(LZO1X) && !defined(LZO1Y) -# define LZO1X -#endif - -#if 1 -# define TEST_IP 1 -#else -# define TEST_IP (ip < ip_end) -#endif - - -/*********************************************************************** -// decompress a block of data. -************************************************************************/ - -/// -/// Expands a block of LZO1X compressed data. -/// This is the low level decompression routine that the LZO straw, the LZO pipe, and the -/// compressed stream reader all funnel through. The compressed block carries its own end -/// marker, so the length of the expanded data is discovered as the block is unpacked rather -/// than being told to this routine. -/// -/// Pointer to the compressed data to expand. -/// Length of the compressed data, in bytes. -/// Buffer that the expanded data is written into. -/// Set to the number of bytes that were expanded into the buffer. -/// Returns with LZO_E_OK if the block expanded cleanly, otherwise one of the -/// LZO_E_ error codes. -/// Be sure that the destination buffer is big enough to hold the expanded data, -/// since this routine performs no bounds checking upon it. -int lzo1x_decompress ( const lzo_byte * in, lzo_uint in_len, - lzo_byte * out, lzo_uint * out_len, - lzo_voidp ) -{ - lzo_byte *op; - const lzo_byte *ip; - lzo_uint t; - const lzo_byte *m_pos; - const lzo_byte * const ip_end = in + in_len; - - *out_len = 0; - - op = out; - ip = in; - - if (*ip > 17) { - t = *ip++ - 17; - goto first_literal_run; - } - - for (;;) { -// while (TEST_IP) { - t = *ip++; - if (t >= 16) - goto match; - /* a literal run */ - if (t == 0) { - t = 15; - while (*ip == 0) { - t += 255, ip++; - } - t += *ip++; - } - /* copy literals */ - *op++ = *ip++; *op++ = *ip++; *op++ = *ip++; -first_literal_run: - do *op++ = *ip++; while (--t > 0); - - - t = *ip++; - - if (t >= 16) { - goto match; - } -#if defined(LZO1X) - m_pos = op - 1 - 0x800; -#elif defined(LZO1Y) - m_pos = op - 1 - 0x400; -#endif - m_pos -= t >> 2; - m_pos -= *ip++ << 2; - *op++ = *m_pos++; - *op++ = *m_pos++; - *op++ = *m_pos; -// *op++ = *m_pos++; - goto match_done; - - - /* handle matches */ - for (;;) { -// while (TEST_IP) { - if (t < 16) { /* a M1 match */ - m_pos = op - 1; - m_pos -= t >> 2; - m_pos -= *ip++ << 2; - *op++ = *m_pos++; - *op++ = *m_pos; -// *op++ = *m_pos++; - } else { -match: - if (t >= 64) { /* a M2 match */ - m_pos = op - 1; -#if defined(LZO1X) - m_pos -= (t >> 2) & 7; - m_pos -= *ip++ << 3; - t = (t >> 5) - 1; -#elif defined(LZO1Y) - m_pos -= (t >> 2) & 3; - m_pos -= *ip++ << 2; - t = (t >> 4) - 3; -#endif - } else { - if (t >= 32) { /* a M3 match */ - t &= 31; - if (t == 0) { - t = 31; - while (*ip == 0) { - t += 255, ip++; - } - t += *ip++; - } - m_pos = op - 1; - m_pos -= *ip++ >> 2; - m_pos -= *ip++ << 6; - } else { /* a M4 match */ - m_pos = op; - m_pos -= (t & 8) << 11; - t &= 7; - if (t == 0) { - t = 7; - while (*ip == 0) { - t += 255, ip++; - } - t += *ip++; - } - m_pos -= *ip++ >> 2; - m_pos -= *ip++ << 6; - if (m_pos == op) { - goto eof_found; - } - m_pos -= 0x4000; - } - } - *op++ = *m_pos++; *op++ = *m_pos++; - do *op++ = *m_pos++; while (--t > 0); - } - -match_done: - t = ip[-2] & 3; - if (t == 0) - break; - /* copy literals */ - do *op++ = *ip++; while (--t > 0); - t = *ip++; - } - } - - /* ip == ip_end and no EOF code was found */ - - //Unreachable - ST 9/5/96 5:07PM - //*out_len = op - out; - //return (ip == ip_end ? LZO_E_EOF_NOT_FOUND : LZO_E_ERROR); - -eof_found: - assert(t == 1); - *out_len = op - out; - return (ip == ip_end ? LZO_E_OK : LZO_E_ERROR); -} - - -/* -vi:ts=4 -*/ diff --git a/code/lzo_conf.h b/code/lzo_conf.h deleted file mode 100644 index c8bf56810..000000000 --- a/code/lzo_conf.h +++ /dev/null @@ -1,298 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2025 Electronic Arts Inc. - * Copyright 2026 OpenTS contributors - * - * Contains material derived from Electronic Arts source code. - * Modified by OpenTS contributors, 2026. - * EA's GPLv3 Section 7 additional terms and supplemental warranty - * disclaimers apply; see LICENSE.md. - ******************************************************************************/ - -/*********************************************************************************************** - *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** - *********************************************************************************************** - * * - * Project Name : Command & Conquer * - * * - * $Archive:: /Commando/Library/lzo_conf.h $* - * * - * $Author:: Greg_h $* - * * - * $Modtime:: 7/22/97 11:37a $* - * * - * $Revision:: 1 $* - * * - *---------------------------------------------------------------------------------------------* - * Functions: * - * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - -/* lzo_conf.h -- main internal configuration file for the the LZO library - - This file is part of the LZO real-time data compression library. - - Copyright (C) 1996 Markus Franz Xaver Johannes Oberhumer - - The LZO library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - The LZO library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with the LZO library; see the file COPYING.LIB. - If not, write to the Free Software Foundation, Inc., - 675 Mass Ave, Cambridge, MA 02139, USA. - - Markus F.X.J. Oberhumer - markus.oberhumer@jk.uni-linz.ac.at - */ - - -/* WARNING: this file should *not* be used by applications. It is - part of the implementation of the library and is subject - to change. - */ - - -#ifndef __LZO_CONF_H -#define __LZO_CONF_H - -#ifndef __LZOCONF_H -# include -#endif - - -/*********************************************************************** -// compiler specific defines -************************************************************************/ - -/* need Borland C 4.0 or above because of huge-pointer bugs */ -#if defined(__LZO_MSDOS16) && defined(__TURBOC__) -# if (__TURBOC__ < 0x452) -# error You need a newer compiler version -# endif -#endif - -#if defined(__LZO_MSDOS) || defined(__i386__) || defined(__386__) -# if !defined(__LZO_i386) -# define __LZO_i386 -# endif -#endif - - -/*********************************************************************** -// -************************************************************************/ - -#include /* ptrdiff_t, size_t */ -#include /* memcpy, memmove, memcmp, memset */ - -#if 0 && !defined(assert) -# error not included -#endif - -#if defined(__BOUNDS_CHECKING_ON) -# include -#else -# define BOUNDS_CHECKING_OFF_DURING(stmt) stmt -# define BOUNDS_CHECKING_OFF_IN_EXPR(expr) (expr) -#endif - -/* ptrdiff_t */ -#if (UINT_MAX >= 0xffffffffL) - typedef ptrdiff_t lzo_ptrdiff_t; -#else - typedef long lzo_ptrdiff_t; -#endif - - -#ifdef __cplusplus -# define LZO_UNUSED(parm) -#else -# define LZO_UNUSED(parm) parm -#endif - - -#if !defined(__inline__) && !defined(__GNUC__) -# if defined(__cplusplus) -# define __inline__ inline -# else -# define __inline__ /* nothing */ -# endif -#endif - - -/*********************************************************************** -// compiler and architecture specific stuff -************************************************************************/ - -/* Some defines that indicate if memory can be accessed at unaligned - * addresses. You should also test that this is actually faster if - * it is allowed by your system. - */ - -#if 1 && defined(__LZO_i386) -# if !defined(LZO_UNALIGNED_OK_2) -# define LZO_UNALIGNED_OK_2 -# endif -# if !defined(LZO_UNALIGNED_OK_4) -# define LZO_UNALIGNED_OK_4 -# endif -#endif - - -#if defined(LZO_UNALIGNED_OK_2) || defined(LZO_UNALIGNED_OK_4) -# if !defined(LZO_UNALIGNED_OK) -# define LZO_UNALIGNED_OK -# endif -#endif - - -/* Definitions for byte order, according to significance of bytes, from low - * addresses to high addresses. The value is what you get by putting '4' - * in the most significant byte, '3' in the second most significant byte, - * '2' in the second least significant byte, and '1' in the least - * significant byte. - */ - -#define LZO_LITTLE_ENDIAN 1234 -#define LZO_BIG_ENDIAN 4321 -#define LZO_PDP_ENDIAN 3412 - -/* The byte order is only needed if we use LZO_UNALIGNED_OK */ -#if !defined(LZO_BYTE_ORDER) -# if defined(__LZO_i386) -# define LZO_BYTE_ORDER LZO_LITTLE_ENDIAN -# elif defined(__mc68000__) -# define LZO_BYTE_ORDER LZO_BIG_ENDIAN -# elif defined(__BYTE_ORDER) -# define LZO_BYTE_ORDER __BYTE_ORDER -# endif -#endif - -#if defined(LZO_UNALIGNED_OK) -# if !defined(LZO_BYTE_ORDER) -# error LZO_BYTE_ORDER is not defined -# elif (LZO_BYTE_ORDER != LZO_LITTLE_ENDIAN) && \ - (LZO_BYTE_ORDER != LZO_BIG_ENDIAN) -# error invalid LZO_BYTE_ORDER -# endif -#endif - - -/*********************************************************************** -// optimization -************************************************************************/ - -/* gcc 2.6.3 and gcc 2.7.2 have a bug */ -#define LZO_OPTIMIZE_GNUC_i386_IS_BUGGY - -/* Help the optimizer with register allocation. - * Don't activate this macro for a fair comparision with other algorithms. - */ -#if 1 && defined(NDEBUG) && !defined(__BOUNDS_CHECKING_ON) -# if defined(__GNUC__) && defined(__i386__) -# if !defined(LZO_OPTIMIZE_GNUC_i386_IS_BUGGY) -# define LZO_OPTIMIZE_GNUC_i386 -# endif -# endif -#endif - - -/*********************************************************************** -// -************************************************************************/ - -#define LZO_BYTE(x) ((unsigned char) (x)) - -#define LZO_MAX(a,b) ((a) >= (b) ? (a) : (b)) -#define LZO_MIN(a,b) ((a) <= (b) ? (a) : (b)) - -#define lzo_sizeof(x) ((lzo_uint) (sizeof(x))) - -#define LZO_HIGH(x) ((lzo_uint) (sizeof(x)/sizeof(*(x)))) - -/* this always fits into 16 bits */ -#define LZO_SIZE(bits) (1u << (bits)) -#define LZO_MASK(bits) (LZO_SIZE(bits) - 1) - -#define LZO_LSIZE(bits) (1ul << (bits)) -#define LZO_LMASK(bits) (LZO_LSIZE(bits) - 1) - -#define LZO_USIZE(bits) ((lzo_uint) 1 << (bits)) -#define LZO_UMASK(bits) (LZO_USIZE(bits) - 1) - - -/*********************************************************************** -// ANSI C preprocessor macros -************************************************************************/ - -#define _LZO_STRINGIZE(x) #x -#define _LZO_MEXPAND(x) _LZO_STRINGIZE(x) - -/* concatenate */ -#define _LZO_CONCAT2(a,b) a ## b -#define _LZO_CONCAT3(a,b,c) a ## b ## c -#define _LZO_CONCAT4(a,b,c,d) a ## b ## c ## d -#define _LZO_CONCAT5(a,b,c,d,e) a ## b ## c ## d ## e - -/* expand and concatenate (by using one level of indirection) */ -#define _LZO_ECONCAT2(a,b) _LZO_CONCAT2(a,b) -#define _LZO_ECONCAT3(a,b,c) _LZO_CONCAT3(a,b,c) -#define _LZO_ECONCAT4(a,b,c,d) _LZO_CONCAT4(a,b,c,d) -#define _LZO_ECONCAT5(a,b,c,d,e) _LZO_CONCAT5(a,b,c,d,e) - - -/*********************************************************************** -// -************************************************************************/ - -/* Generate compressed data in a deterministic way. - * This is fully portable, and compression can be faster as well. - * A reason NOT to be deterministic is when the block size is - * very small (e.g. 8kB) or the dictionary is big, because - * then the initialization of the dictionary becomes a relevant - * magnitude for compression speed. - */ -#define LZO_DETERMINISTIC - - -/*********************************************************************** -// -************************************************************************/ - -#if 0 -/* This line causes problems on some architectures */ -#define LZO_CHECK_MPOS_DET(m_pos,m_off,in,ip,max_offset) \ - (BOUNDS_CHECKING_OFF_IN_EXPR( \ - (m_off = ip - m_pos) > max_offset )) - -#else -/* This is the safe (but slower) version */ -#define LZO_CHECK_MPOS_DET(m_pos,m_off,in,ip,max_offset) \ - (m_pos == NULL || (m_off = ip - m_pos) > max_offset) -#endif - - -/* m_pos may point anywhere... - * This marco is probably a good candidate for architecture specific problems. - * Try casting the pointers to lzo_ptr_t before comparing them. - */ -#define LZO_CHECK_MPOS_NON_DET(m_pos,m_off,in,ip,max_offset) \ - (BOUNDS_CHECKING_OFF_IN_EXPR( \ - (m_pos < in || (m_off = ip - m_pos) <= 0 || m_off > max_offset) )) - - - -#endif /* already included */ - -/* -vi:ts=4 -*/ diff --git a/code/lzoconf.h b/code/lzoconf.h deleted file mode 100644 index 407124f02..000000000 --- a/code/lzoconf.h +++ /dev/null @@ -1,230 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2025 Electronic Arts Inc. - * Copyright 2026 OpenTS contributors - * - * Contains material derived from Electronic Arts source code. - * Modified by OpenTS contributors, 2026. - * EA's GPLv3 Section 7 additional terms and supplemental warranty - * disclaimers apply; see LICENSE.md. - ******************************************************************************/ - -/*********************************************************************************************** - *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** - *********************************************************************************************** - * * - * Project Name : Command & Conquer * - * * - * $Archive:: /Commando/Library/lzoconf.h $* - * * - * $Author:: Greg_h $* - * * - * $Modtime:: 7/22/97 11:37a $* - * * - * $Revision:: 1 $* - * * - *---------------------------------------------------------------------------------------------* - * Functions: * - * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - -/* lzoconf.h -- configuration for the LZO real-time data compression library - - This file is part of the LZO real-time data compression library. - - Copyright (C) 1996 Markus Franz Xaver Johannes Oberhumer - - The LZO library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - The LZO library 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 - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with the LZO library; see the file COPYING.LIB. - If not, write to the Free Software Foundation, Inc., - 675 Mass Ave, Cambridge, MA 02139, USA. - - Markus F.X.J. Oberhumer - markus.oberhumer@jk.uni-linz.ac.at - */ - - -#ifndef __LZOCONF_H -#define __LZOCONF_H - -#define LZO_VERSION 0x0200 -#define LZO_VERSION_STRING "0.20" -#define LZO_VERSION_DATE "11 Aug 1996" - - -#include /* CHAR_BIT, UINT_MAX, ULONG_MAX */ -#if !defined(CHAR_BIT) || (CHAR_BIT != 8) -# error invalid CHAR_BIT -#endif - -//#ifdef __cplusplus -//extern "C" { -//#endif - - -/*********************************************************************** -// defines -************************************************************************/ - -#if defined(__MSDOS__) || defined(MSDOS) -# define __LZO_MSDOS -# if (UINT_MAX < 0xffffffffL) -# define __LZO_MSDOS16 -# endif -#endif - - -/*********************************************************************** -// integral and pointer types -************************************************************************/ - -/* Unsigned type with 32 bits or more */ -#if (UINT_MAX >= 0xffffffffL) - typedef unsigned int lzo_uint; - typedef int lzo_int; -# define LZO_UINT_MAX UINT_MAX -#elif (ULONG_MAX >= 0xffffffffL) - typedef unsigned long lzo_uint; - typedef long lzo_int; -# define LZO_UINT_MAX ULONG_MAX -#else -# error lzo_uint -#endif - - -/* Memory model that allows to access memory at offsets of lzo_uint. - * Huge pointers (16 bit MSDOS) are somewhat slow, but they work - * fine and I really don't care about 16 bit compiler - * optimizations nowadays. - */ -#if (LZO_UINT_MAX <= UINT_MAX) -# define __LZO_MMODEL -#elif defined(__LZO_MSDOS16) -# define __LZO_MMODEL huge -# define __LZO_ENTRY __cdecl -#else -# error __LZO_MMODEL -#endif - - -/* no typedef here because of const-pointer issues */ -#define lzo_byte unsigned char __LZO_MMODEL -#define lzo_voidp void __LZO_MMODEL * -#define lzo_bytep unsigned char __LZO_MMODEL * -#define lzo_uintp lzo_uint __LZO_MMODEL * -#define lzo_intp lzo_int __LZO_MMODEL * -#define lzo_voidpp lzo_voidp __LZO_MMODEL * -#define lzo_bytepp lzo_bytep __LZO_MMODEL * - - -/* Unsigned type that can store all bits of a lzo_voidp */ -typedef unsigned long lzo_ptr_t; - -/* Align a pointer on a boundary that is a multiple of 'size' */ -#define LZO_ALIGN(ptr,size) \ - ((lzo_voidp) (((lzo_ptr_t)(ptr) + (size)-1) & ~((lzo_ptr_t)((size)-1)))) - - -/*********************************************************************** -// function types -************************************************************************/ - -//#ifdef __cplusplus -//# define LZO_EXTERN_C extern "C" -//#else -# define LZO_EXTERN_C extern -//#endif - - -#if !defined(__LZO_ENTRY) /* calling convention */ -# define __LZO_ENTRY -#endif -#if !defined(__LZO_EXPORT) /* DLL export (and maybe size) information */ -# define __LZO_EXPORT -#endif - -#if !defined(LZO_EXTERN) -# define LZO_EXTERN(_rettype) LZO_EXTERN_C _rettype __LZO_ENTRY __LZO_EXPORT -#endif - - -typedef int __LZO_ENTRY -(__LZO_EXPORT *lzo_compress_t) ( const lzo_byte *src, lzo_uint src_len, - lzo_byte *dst, lzo_uint *dst_len, - lzo_voidp wrkmem ); - -typedef int __LZO_ENTRY -(__LZO_EXPORT *lzo_decompress_t)( const lzo_byte *src, lzo_uint src_len, - lzo_byte *dst, lzo_uint *dst_len, - lzo_voidp wrkmem ); - - -/* a progress indicator callback function */ -typedef void __LZO_ENTRY -(__LZO_EXPORT *lzo_progress_callback_t)(lzo_uint,lzo_uint); - - -/*********************************************************************** -// error codes and prototypes -************************************************************************/ - -/* Error codes for the compression/decompression functions. Negative - * values are errors, positive values will be used for special but - * normal events. - */ -#define LZO_E_OK 0 -#define LZO_E_ERROR (-1) -#define LZO_E_NOT_COMPRESSIBLE (-2) /* not used right now */ -#define LZO_E_EOF_NOT_FOUND (-3) -#define LZO_E_INPUT_OVERRUN (-4) -#define LZO_E_OUTPUT_OVERRUN (-5) -#define LZO_E_LOOKBEHIND_OVERRUN (-6) -#define LZO_E_OUT_OF_MEMORY (-7) /* not used right now */ - - -/* this should be the first function you call. Check the return code ! */ -LZO_EXTERN(int) lzo_init(void); - -/* version functions (useful for shared libraries) */ -LZO_EXTERN(unsigned) lzo_version(void); -LZO_EXTERN(const char *) lzo_version_string(void); - -/* string functions */ -LZO_EXTERN(int) -lzo_memcmp(const lzo_voidp _s1, const lzo_voidp _s2, lzo_uint _len); -LZO_EXTERN(lzo_voidp) -lzo_memcpy(lzo_voidp _dest, const lzo_voidp _src, lzo_uint _len); -LZO_EXTERN(lzo_voidp) -lzo_memmove(lzo_voidp _dest, const lzo_voidp _src, lzo_uint _len); -LZO_EXTERN(lzo_voidp) -lzo_memset(lzo_voidp _s, int _c, lzo_uint _len); - -/* checksum functions */ -LZO_EXTERN(lzo_uint) -lzo_adler32(lzo_uint _adler, const lzo_byte *_buf, lzo_uint _len); - -/* misc. */ -LZO_EXTERN(int) lzo_assert(int _expr); -LZO_EXTERN(int) _lzo_config_check(void); - - -//#ifdef __cplusplus -//} /* extern "C" */ -//#endif - -#endif /* already included */ - -/* -vi:ts=4 -*/ diff --git a/code/lzopipe.cpp b/code/lzopipe.cpp index 283ee4b36..5913a0e58 100644 --- a/code/lzopipe.cpp +++ b/code/lzopipe.cpp @@ -37,7 +37,7 @@ #include "lzopipe.h" -#include "lzo.h" +#include #include #include @@ -172,9 +172,14 @@ int LZOPipe::Put(void const * source, int slen) ** through the pipe. */ if (Counter == BlockHeader.CompCount) { - unsigned int length = sizeof (Buffer2); - lzo1x_decompress ((unsigned char*)Buffer, BlockHeader.CompCount, (unsigned char*)Buffer2, &length, NULL); - total += BASECLASS::Put(Buffer2, BlockHeader.UncompCount); + // The block header was read from the stream, so its counts are only a + // claim; a block that does not expand to exactly what it promises is + // dropped rather than passed on. + lzo_uint length = BlockSize + SafetyMargin; + int const status = lzo1x_decompress_safe((unsigned char*)Buffer, BlockHeader.CompCount, (unsigned char*)Buffer2, &length, NULL); + if (status == LZO_E_OK && length == BlockHeader.UncompCount) { + total += BASECLASS::Put(Buffer2, BlockHeader.UncompCount); + } Counter = 0; BlockHeader.CompCount = 0xFFFF; } @@ -195,8 +200,8 @@ int LZOPipe::Put(void const * source, int slen) Counter += tocopy; if (Counter == BlockSize) { - unsigned int len = sizeof (Buffer2); - char *dictionary = new char [64*1024]; + lzo_uint len = BlockSize + SafetyMargin; + char *dictionary = new char [LZO1X_1_MEM_COMPRESS]; lzo1x_1_compress ((unsigned char*)Buffer, BlockSize, (unsigned char*)Buffer2, &len, dictionary); delete [] dictionary; BlockHeader.CompCount = (unsigned short)len; @@ -212,8 +217,8 @@ int LZOPipe::Put(void const * source, int slen) ** source data left for a whole data block. */ while (slen >= BlockSize) { - unsigned int len = 0;//sizeof (Buffer2); - char *dictionary = new char [64*1024]; + lzo_uint len = BlockSize + SafetyMargin; + char *dictionary = new char [LZO1X_1_MEM_COMPRESS]; lzo1x_1_compress ((unsigned char*)source, BlockSize, (unsigned char*)Buffer2, &len, dictionary); delete [] dictionary; source = ((char *)source) + BlockSize; @@ -299,8 +304,8 @@ int LZOPipe::Flush(void) ** A partial block in the compression process is a normal occurrence. Just ** compress the partial block and output normally. */ - unsigned int len = 0;//sizeof (Buffer2); - char *dictionary = new char [64*1024]; + lzo_uint len = BlockSize + SafetyMargin; + char *dictionary = new char [LZO1X_1_MEM_COMPRESS]; lzo1x_1_compress ((unsigned char*)Buffer, Counter, (unsigned char *)Buffer2, &len, dictionary); delete [] dictionary; BlockHeader.CompCount = (unsigned short)len; diff --git a/code/lzostraw.cpp b/code/lzostraw.cpp index e2490b524..6a46231a3 100644 --- a/code/lzostraw.cpp +++ b/code/lzostraw.cpp @@ -36,7 +36,7 @@ #include "lzostraw.h" -#include "lzo.h" +#include #include #include @@ -164,15 +164,18 @@ int LZOStraw::Get(void * destbuf, int slen) delete [] staging_buffer; break; } - unsigned int length = sizeof(Buffer); - lzo1x_decompress ((unsigned char*)staging_buffer, BlockHeader.CompCount, (unsigned char*)Buffer, &length, NULL); + // The block header was read from the stream, so its counts are only a claim; a + // block that does not expand to exactly what it promises ends the straw. + lzo_uint length = BlockSize + SafetyMargin; + int const status = lzo1x_decompress_safe((unsigned char*)staging_buffer, BlockHeader.CompCount, (unsigned char*)Buffer, &length, NULL); delete [] staging_buffer; + if (status != LZO_E_OK || length != BlockHeader.UncompCount) break; Counter = BlockHeader.UncompCount; } else { BlockHeader.UncompCount = (unsigned short)BASECLASS::Get(Buffer, BlockSize); if (BlockHeader.UncompCount == 0) break; - char *dictionary = new char [64*1024]; - unsigned int length = sizeof (Buffer2) - sizeof (BlockHeader); + char *dictionary = new char [LZO1X_1_MEM_COMPRESS]; + lzo_uint length = (BlockSize + SafetyMargin) - sizeof(BlockHeader); lzo1x_1_compress ((unsigned char*)Buffer, BlockHeader.UncompCount, (unsigned char*)(&Buffer2[sizeof(BlockHeader)]), &length, dictionary); BlockHeader.CompCount = (unsigned short)length; delete [] dictionary; diff --git a/code/startup.cpp b/code/startup.cpp index d028eaa66..03692a6ff 100644 --- a/code/startup.cpp +++ b/code/startup.cpp @@ -158,6 +158,8 @@ #include "wwmouse.h" #include "zbuffer.h" +#include + #include #include @@ -431,6 +433,9 @@ int CALLBACK WinMain ( HINSTANCE instance , HINSTANCE , char * , int command_sho ProgramInstance = instance; + // Refuses a build whose type sizes do not match the ones LZO was compiled against. + if (lzo_init() != LZO_E_OK) return(1); + Debug_Init(); // Handed over now because the exception path may not ask the logger for anything: the From fef9fed4d7114a7aa0c161ddb223f982a770f4de Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Sun, 6 Sep 2026 22:32:45 +0200 Subject: [PATCH 020/179] Add a saved game container that does not depend on COM --- code/savefile.cpp | 550 ++++++++++++++++++++++++++++++++++++++ code/savefile.h | 78 ++++++ tests/CMakeLists.txt | 1 + tests/save/CMakeLists.txt | 29 ++ tests/save/savetest.cpp | 496 ++++++++++++++++++++++++++++++++++ 5 files changed, 1154 insertions(+) create mode 100644 code/savefile.cpp create mode 100644 code/savefile.h create mode 100644 tests/save/CMakeLists.txt create mode 100644 tests/save/savetest.cpp diff --git a/code/savefile.cpp b/code/savefile.cpp new file mode 100644 index 000000000..baaf6ac98 --- /dev/null +++ b/code/savefile.cpp @@ -0,0 +1,550 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#include "savefile.h" + +#include + +#include +#include +#include + +namespace { + +unsigned char const Signature[4] = { 'O', 'T', 'S', 'V' }; + +constexpr unsigned int FLAG_LZO = 0x0001; +constexpr unsigned int FIELD_HEADER_SIZE = 8; +constexpr unsigned int MAX_FIELD_LENGTH = 0x10000; +// No game state comes near this, and a header asking for more is asking for memory. +constexpr unsigned int MAX_CONTENT_LENGTH = 0x10000000; +// A listing is a dozen short fields; a table beyond this is not one. +constexpr unsigned int MAX_TABLE_LENGTH = 0x100000; + + +unsigned int Get_U16(unsigned char const * from) +{ + return((unsigned int)from[0] | ((unsigned int)from[1] << 8)); +} + + +unsigned int Get_U32(unsigned char const * from) +{ + return((unsigned int)from[0] | ((unsigned int)from[1] << 8) + | ((unsigned int)from[2] << 16) | ((unsigned int)from[3] << 24)); +} + + +void Put_U16(unsigned char * into, unsigned int value) +{ + into[0] = (unsigned char)(value & 0xFF); + into[1] = (unsigned char)((value >> 8) & 0xFF); +} + + +void Put_U32(unsigned char * into, unsigned int value) +{ + into[0] = (unsigned char)(value & 0xFF); + into[1] = (unsigned char)((value >> 8) & 0xFF); + into[2] = (unsigned char)((value >> 16) & 0xFF); + into[3] = (unsigned char)((value >> 24) & 0xFF); +} + + +void Append(std::vector & into, void const * data, unsigned int length) +{ + unsigned char const * bytes = (unsigned char const *)data; + into.insert(into.end(), bytes, bytes + length); +} + + +// Sizes a buffer the header asked for, and says so rather than throw when the process +// cannot hold it. +bool Reserve(std::vector & buffer, std::size_t length) +{ + try { + buffer.resize(length); + } catch (std::bad_alloc const &) { + buffer.clear(); + return(false); + } + return(true); +} + + +bool Read_Range(HANDLE file, void * into, unsigned int length) +{ + unsigned char * cursor = (unsigned char *)into; + + while (length > 0) { + DWORD got = 0; + if (!ReadFile(file, cursor, length, &got, NULL) || got == 0) return(false); + cursor += got; + length -= got; + } + + return(true); +} + + +bool Write_Range(HANDLE file, void const * data, unsigned int length) +{ + unsigned char const * cursor = (unsigned char const *)data; + + while (length > 0) { + DWORD const block = (length > 0x100000) ? 0x100000 : length; + DWORD written = 0; + if (!WriteFile(file, cursor, block, &written, NULL) || written != block) return(false); + cursor += written; + length -= written; + } + + return(true); +} + + +struct HeaderType { + unsigned int Version; + unsigned int Flags; + unsigned int TableLength; + unsigned int ContentOffset; + unsigned int StoredLength; + unsigned int ContentLength; + unsigned int ContentCRC; + unsigned int HeaderCRC; +}; + + +// The header checksum continues over the field table, so a listing can verify what it +// reads without touching the content. +unsigned int Header_CRC(unsigned char const * header, unsigned char const * table, unsigned int length) +{ + return(SaveFileClass::Checksum(table, length, SaveFileClass::Checksum(header, SaveFileClass::HEADER_SIZE - 4))); +} + + +// Decides everything the first 32 bytes can decide, in the order a caller wants to +// hear about it: not ours, a version we do not read, or damage. +SaveFileClass::ResultType Parse_Header(unsigned char const * bytes, unsigned int available, HeaderType & header) +{ + if (available < sizeof(Signature) || memcmp(bytes, Signature, sizeof(Signature)) != 0) { + return(SaveFileClass::RESULT_NOT_A_SAVE); + } + if (available < SaveFileClass::HEADER_SIZE) { + return(SaveFileClass::RESULT_CORRUPT); + } + + header.Version = Get_U16(bytes + 4); + header.Flags = Get_U16(bytes + 6); + header.TableLength = Get_U32(bytes + 8); + header.ContentOffset = Get_U32(bytes + 12); + header.StoredLength = Get_U32(bytes + 16); + header.ContentLength = Get_U32(bytes + 20); + header.ContentCRC = Get_U32(bytes + 24); + header.HeaderCRC = Get_U32(bytes + 28); + + if (header.Version == 0 || header.Version > SaveFileClass::FORMAT_VERSION) { + return(SaveFileClass::RESULT_UNSUPPORTED_VERSION); + } + if ((header.Flags & ~FLAG_LZO) != 0) { + return(SaveFileClass::RESULT_UNSUPPORTED_VERSION); + } + if (header.TableLength > MAX_TABLE_LENGTH) { + return(SaveFileClass::RESULT_CORRUPT); + } + if (header.ContentOffset != SaveFileClass::HEADER_SIZE + header.TableLength) { + return(SaveFileClass::RESULT_CORRUPT); + } + if (header.StoredLength > MAX_CONTENT_LENGTH || header.ContentLength > MAX_CONTENT_LENGTH) { + return(SaveFileClass::RESULT_CORRUPT); + } + + return(SaveFileClass::RESULT_OK); +} + +} // namespace + + +SaveFileClass::SaveFileClass(void) +{ +} + + +unsigned int SaveFileClass::Checksum(unsigned char const * data, unsigned int length, unsigned int seed) +{ + static unsigned int table[256]; + static bool ready = false; + + if (!ready) { + for (unsigned int index = 0; index < 256; index++) { + unsigned int value = index; + for (int bit = 0; bit < 8; bit++) { + value = (value & 1) ? (0xEDB88320u ^ (value >> 1)) : (value >> 1); + } + table[index] = value; + } + ready = true; + } + + unsigned int crc = ~seed; + for (unsigned int index = 0; index < length; index++) { + crc = table[(crc ^ data[index]) & 0xFF] ^ (crc >> 8); + } + + return(~crc); +} + + +char const * SaveFileClass::Result_Text(ResultType result) +{ + switch (result) { + case RESULT_OK: return("ok"); + case RESULT_MISSING: return("the file is missing"); + case RESULT_NOT_A_SAVE: return("the file is not a saved game"); + case RESULT_UNSUPPORTED_VERSION: return("the file uses a format version this build does not read"); + case RESULT_CORRUPT: return("the file is damaged"); + case RESULT_WRITE_FAILED: return("the file could not be written"); + case RESULT_NO_MEMORY: return("there is not enough memory to read the file"); + case RESULT_TOO_LARGE: return("the game state is larger than a saved game can hold"); + } + return("unknown"); +} + + +SaveFileClass::FieldType const * SaveFileClass::Find(int id, int kind) const +{ + for (FieldType const & field : Fields) { + if (field.ID == id && field.Kind == kind) return(&field); + } + return(NULL); +} + + +void SaveFileClass::Set(int id, int kind, void const * data, unsigned int length) +{ + for (FieldType & field : Fields) { + if (field.ID == id && field.Kind == kind) { + field.Bytes.assign((unsigned char const *)data, (unsigned char const *)data + length); + return; + } + } + + FieldType field; + field.ID = id; + field.Kind = kind; + field.Bytes.assign((unsigned char const *)data, (unsigned char const *)data + length); + Fields.push_back(field); +} + + +void SaveFileClass::Set_String(int id, char const * text) +{ + if (text == NULL) text = ""; + Set(id, FIELD_STRING, text, (unsigned int)strlen(text)); +} + + +void SaveFileClass::Set_Int(int id, int value) +{ + unsigned char bytes[4]; + Put_U32(bytes, (unsigned int)value); + Set(id, FIELD_INT, bytes, sizeof(bytes)); +} + + +void SaveFileClass::Set_Time(int id, FILETIME const & time) +{ + unsigned char bytes[8]; + Put_U32(bytes, time.dwLowDateTime); + Put_U32(bytes + 4, time.dwHighDateTime); + Set(id, FIELD_TIME, bytes, sizeof(bytes)); +} + + +// A string that does not fit is truncated to what does; the result is always terminated. +bool SaveFileClass::Get_String(int id, char * text, int size) const +{ + if (text == NULL || size <= 0) return(false); + + FieldType const * const field = Find(id, FIELD_STRING); + if (field == NULL) { + text[0] = '\0'; + return(false); + } + + unsigned int length = (unsigned int)field->Bytes.size(); + if (length > (unsigned int)(size - 1)) { + // A cut never splits a UTF-8 sequence, so a shortened description stays text. + length = (unsigned int)(size - 1); + while (length > 0 && (field->Bytes[length] & 0xC0) == 0x80) length--; + } + memcpy(text, field->Bytes.data(), length); + text[length] = '\0'; + + return(true); +} + + +bool SaveFileClass::Get_Int(int id, int * value) const +{ + FieldType const * const field = Find(id, FIELD_INT); + if (field == NULL || field->Bytes.size() != 4) return(false); + + if (value != NULL) *value = (int)Get_U32(field->Bytes.data()); + return(true); +} + + +bool SaveFileClass::Get_Time(int id, FILETIME * time) const +{ + FieldType const * const field = Find(id, FIELD_TIME); + if (field == NULL || field->Bytes.size() != 8) return(false); + + if (time != NULL) { + time->dwLowDateTime = Get_U32(field->Bytes.data()); + time->dwHighDateTime = Get_U32(field->Bytes.data() + 4); + } + return(true); +} + + +void SaveFileClass::Clear_Fields(void) +{ + Fields.clear(); +} + + +void SaveFileClass::Serialize_Fields(std::vector & table) const +{ + table.clear(); + + for (FieldType const & field : Fields) { + unsigned char head[FIELD_HEADER_SIZE]; + Put_U16(head, (unsigned int)field.ID); + Put_U16(head + 2, (unsigned int)field.Kind); + Put_U32(head + 4, (unsigned int)field.Bytes.size()); + Append(table, head, sizeof(head)); + Append(table, field.Bytes.data(), (unsigned int)field.Bytes.size()); + } +} + + +SaveFileClass::ResultType SaveFileClass::Parse_Fields(unsigned char const * table, unsigned int length) +{ + Fields.clear(); + + unsigned int offset = 0; + while (offset < length) { + if (length - offset < FIELD_HEADER_SIZE) return(RESULT_CORRUPT); + + FieldType field; + field.ID = (int)Get_U16(table + offset); + field.Kind = (int)Get_U16(table + offset + 2); + unsigned int const bytes = Get_U32(table + offset + 4); + offset += FIELD_HEADER_SIZE; + + if (bytes > MAX_FIELD_LENGTH || bytes > length - offset) return(RESULT_CORRUPT); + field.Bytes.assign(table + offset, table + offset + bytes); + offset += bytes; + + Fields.push_back(field); + } + + return(RESULT_OK); +} + + +// The file lands under its final name only once every byte is on disk, so a save +// interrupted at any point leaves the previous file untouched. +SaveFileClass::ResultType SaveFileClass::Write(char const * path) const +{ + if (path == NULL) return(RESULT_WRITE_FAILED); + + // The reader's limits bind the writer too, so a save this build writes is one it reads, + // and one it cannot write leaves the file on disk alone. + if (Content.size() > MAX_CONTENT_LENGTH) return(RESULT_TOO_LARGE); + for (FieldType const & field : Fields) { + if (field.Bytes.size() > MAX_FIELD_LENGTH) return(RESULT_TOO_LARGE); + } + + std::vector table; + Serialize_Fields(table); + if (table.size() > MAX_TABLE_LENGTH) return(RESULT_TOO_LARGE); + + std::vector stored; + unsigned int flags = 0; + + if (!Content.empty()) { + std::vector work(LZO1X_MEM_COMPRESS); + stored.resize(Content.size() + Content.size() / 16 + 64 + 3); + + lzo_uint packed = 0; + int const status = lzo1x_1_compress(Content.data(), (lzo_uint)Content.size(), + stored.data(), &packed, work.data()); + + if (status == LZO_E_OK && packed < Content.size()) { + stored.resize((std::size_t)packed); + flags |= FLAG_LZO; + } else { + stored = Content; + } + } + + std::vector image(HEADER_SIZE); + unsigned char * const header = image.data(); + memcpy(header, Signature, sizeof(Signature)); + Put_U16(header + 4, FORMAT_VERSION); + Put_U16(header + 6, flags); + Put_U32(header + 8, (unsigned int)table.size()); + Put_U32(header + 12, HEADER_SIZE + (unsigned int)table.size()); + Put_U32(header + 16, (unsigned int)stored.size()); + Put_U32(header + 20, (unsigned int)Content.size()); + Put_U32(header + 24, Checksum(stored.data(), (unsigned int)stored.size())); + Put_U32(header + 28, Header_CRC(header, table.data(), (unsigned int)table.size())); + + image.insert(image.end(), table.begin(), table.end()); + image.insert(image.end(), stored.begin(), stored.end()); + + std::string const temporary = std::string(path) + ".tmp"; + + HANDLE const file = CreateFileA(temporary.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) return(RESULT_WRITE_FAILED); + + bool ok = Write_Range(file, image.data(), (unsigned int)image.size()); + if (ok) ok = (FlushFileBuffers(file) != FALSE); + if (!CloseHandle(file)) ok = false; + + if (ok) ok = (MoveFileExA(temporary.c_str(), path, MOVEFILE_REPLACE_EXISTING) != FALSE); + + if (!ok) { + DeleteFileA(temporary.c_str()); + return(RESULT_WRITE_FAILED); + } + + return(RESULT_OK); +} + + +SaveFileClass::ResultType SaveFileClass::Read(char const * path) +{ + Fields.clear(); + Content.clear(); + + if (path == NULL) return(RESULT_MISSING); + + HANDLE const file = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) return(RESULT_MISSING); + + // The header is judged before anything the file's size could ask for is allocated. + unsigned char head[HEADER_SIZE]; + DWORD got = 0; + bool const ok = (ReadFile(file, head, HEADER_SIZE, &got, NULL) != FALSE); + + HeaderType header; + ResultType result = ok ? Parse_Header(head, got, header) : RESULT_CORRUPT; + + std::vector image; + if (result == RESULT_OK) { + DWORD const size = GetFileSize(file, NULL); + if (size == INVALID_FILE_SIZE || size != header.ContentOffset + header.StoredLength) { + result = RESULT_CORRUPT; + } else if (!Reserve(image, size)) { + result = RESULT_NO_MEMORY; + } else { + memcpy(image.data(), head, HEADER_SIZE); + if (!Read_Range(file, image.data() + HEADER_SIZE, size - HEADER_SIZE)) result = RESULT_CORRUPT; + } + } + CloseHandle(file); + if (result != RESULT_OK) return(result); + + if (Header_CRC(image.data(), image.data() + HEADER_SIZE, header.TableLength) != header.HeaderCRC) { + return(RESULT_CORRUPT); + } + + result = Parse_Fields(image.data() + HEADER_SIZE, header.TableLength); + if (result != RESULT_OK) return(result); + + unsigned char const * const stored = image.data() + header.ContentOffset; + if (Checksum(stored, header.StoredLength) != header.ContentCRC) { + Fields.clear(); + return(RESULT_CORRUPT); + } + + if ((header.Flags & FLAG_LZO) != 0) { + if (!Reserve(Content, header.ContentLength)) { + Fields.clear(); + return(RESULT_NO_MEMORY); + } + + lzo_uint unpacked = (lzo_uint)Content.size(); + int const status = lzo1x_decompress_safe(stored, (lzo_uint)header.StoredLength, + Content.data(), &unpacked, NULL); + + if (status != LZO_E_OK || unpacked != header.ContentLength) { + Fields.clear(); + Content.clear(); + return(RESULT_CORRUPT); + } + } else { + if (header.StoredLength != header.ContentLength) { + Fields.clear(); + return(RESULT_CORRUPT); + } + if (!Reserve(Content, header.StoredLength)) { + Fields.clear(); + return(RESULT_NO_MEMORY); + } + memcpy(Content.data(), stored, header.StoredLength); + } + + return(RESULT_OK); +} + + +// Reads the header and the field table only, so listing a folder of saves touches a +// few hundred bytes of each file. +SaveFileClass::ResultType SaveFileClass::Read_Fields(char const * path) +{ + Fields.clear(); + Content.clear(); + + if (path == NULL) return(RESULT_MISSING); + + HANDLE const file = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) return(RESULT_MISSING); + + unsigned char head[HEADER_SIZE]; + DWORD got = 0; + bool ok = (ReadFile(file, head, HEADER_SIZE, &got, NULL) != FALSE); + + HeaderType header; + ResultType result = ok ? Parse_Header(head, got, header) : RESULT_CORRUPT; + + std::vector table; + if (result == RESULT_OK && header.TableLength > 0) { + DWORD const size = GetFileSize(file, NULL); + if (size == INVALID_FILE_SIZE || header.TableLength > size - HEADER_SIZE) { + result = RESULT_CORRUPT; + } else if (!Reserve(table, header.TableLength)) { + result = RESULT_NO_MEMORY; + } else { + if (!Read_Range(file, table.data(), header.TableLength)) result = RESULT_CORRUPT; + } + } + CloseHandle(file); + + if (result != RESULT_OK) return(result); + if (Header_CRC(head, table.data(), (unsigned int)table.size()) != header.HeaderCRC) return(RESULT_CORRUPT); + + return(Parse_Fields(table.data(), (unsigned int)table.size())); +} diff --git a/code/savefile.h b/code/savefile.h new file mode 100644 index 000000000..f6b3afa69 --- /dev/null +++ b/code/savefile.h @@ -0,0 +1,78 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#pragma once + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +#include + +// The file a saved game is kept in: a fixed header, a table of listing fields, and one +// compressed block of game state. docs/SAVE-FORMAT.md records the layout. +class SaveFileClass +{ + public: + enum ResultType { + RESULT_OK, + RESULT_MISSING, // No file under that name. + RESULT_NOT_A_SAVE, // The file does not begin with the signature. + RESULT_UNSUPPORTED_VERSION, // A format version, or a header flag, this build does not read. + RESULT_CORRUPT, // A length, checksum or block that does not add up. + RESULT_WRITE_FAILED, // The file could not be written or moved into place. + RESULT_NO_MEMORY, // The file is within its limits but the process cannot hold it. + RESULT_TOO_LARGE, // The content or a listing field is more than a save can hold. + }; + + enum { + FORMAT_VERSION = 1, + HEADER_SIZE = 32, + }; + + SaveFileClass(void); + + void Set_String(int id, char const * text); + void Set_Int(int id, int value); + void Set_Time(int id, FILETIME const & time); + bool Get_String(int id, char * text, int size) const; + bool Get_Int(int id, int * value) const; + bool Get_Time(int id, FILETIME * time) const; + void Clear_Fields(void); + + ResultType Write(char const * path) const; + ResultType Read(char const * path); + ResultType Read_Fields(char const * path); + + static char const * Result_Text(ResultType result); + static unsigned int Checksum(unsigned char const * data, unsigned int length, unsigned int seed = 0); + + std::vector Content; + + private: + enum FieldKind { + FIELD_STRING = 1, + FIELD_INT = 2, + FIELD_TIME = 3, + }; + + struct FieldType { + int ID; + int Kind; + std::vector Bytes; + }; + + FieldType const * Find(int id, int kind) const; + void Set(int id, int kind, void const * data, unsigned int length); + void Serialize_Fields(std::vector & table) const; + ResultType Parse_Fields(unsigned char const * table, unsigned int length); + + std::vector Fields; +}; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 69f46e4cc..e8f1b9274 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -32,3 +32,4 @@ add_subdirectory(deploymentconfig) add_subdirectory(tutorial) add_subdirectory(utf8) add_subdirectory(shapefacing) +add_subdirectory(save) diff --git a/tests/save/CMakeLists.txt b/tests/save/CMakeLists.txt new file mode 100644 index 000000000..556327111 --- /dev/null +++ b/tests/save/CMakeLists.txt @@ -0,0 +1,29 @@ +# The harness drives the file a saved game is kept in: savefile.cpp's writer and reader over +# the field table, the compressed content, and every refusal the reader makes. It links the +# same LZO the engine does. +add_executable(SaveTest + "${CMAKE_CURRENT_SOURCE_DIR}/savetest.cpp" + "${CMAKE_SOURCE_DIR}/code/savefile.cpp" +) + +target_compile_features(SaveTest PRIVATE cxx_std_20) + +target_include_directories(SaveTest PRIVATE + "${CMAKE_SOURCE_DIR}/code" +) + +target_compile_definitions(SaveTest PRIVATE WIN32 _WINDOWS _MBCS) + +target_compile_options(SaveTest PRIVATE + $<$:/MTd /EHsc /Zc:__cplusplus> + $<$:/MT /EHsc /Zc:__cplusplus> +) + +target_link_libraries(SaveTest PRIVATE kernel32 lzo) + +set_target_properties(SaveTest PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" +) + +# The harness writes the files it reads back, so it is given a directory of its own. +add_test(NAME save COMMAND SaveTest "${CMAKE_CURRENT_BINARY_DIR}") diff --git a/tests/save/savetest.cpp b/tests/save/savetest.cpp new file mode 100644 index 000000000..04fae38a3 --- /dev/null +++ b/tests/save/savetest.cpp @@ -0,0 +1,496 @@ +// Exercises the file a saved game is kept in: the field table the load dialog lists from, +// the compressed content block, and every way the reader refuses a file that is not a +// whole, intact save of a version it knows. +// +// Every file it touches it creates itself, in a scratch directory named by the first +// argument, so it reads no game data and leaves nothing behind. + +#include "savefile.h" + +#include + +#include +#include +#include +#include + +static int Failures = 0; +static int Checks = 0; +static std::string Scratch; + + +static void Check(char const * name, bool condition) +{ + Checks++; + if (condition) return; + Failures++; + printf("FAIL %s\n", name); +} + + +static void Check_Result(char const * name, SaveFileClass::ResultType actual, SaveFileClass::ResultType expected) +{ + Checks++; + if (actual == expected) return; + Failures++; + printf("FAIL %s: got \"%s\", expected \"%s\"\n", name, + SaveFileClass::Result_Text(actual), SaveFileClass::Result_Text(expected)); +} + + +static std::string Scratch_Path(char const * name) +{ + return(Scratch + "\\" + name); +} + + +static std::vector Noise(std::size_t length, unsigned int seed) +{ + std::vector data(length); + unsigned int state = seed * 2654435761u + 1u; + for (std::size_t index = 0; index < length; index++) { + state = state * 1103515245u + 12345u; + data[index] = (unsigned char)((state >> 16) & 0xFF); + } + return(data); +} + + +static std::vector Prose(std::size_t length) +{ + static char const text[] = "The quick brown fox jumps over the lazy dog. "; + std::vector data; + while (data.size() < length) { + data.push_back((unsigned char)text[data.size() % (sizeof(text) - 1)]); + } + return(data); +} + + +static std::vector Read_Whole_File(char const * path) +{ + std::vector data; + HANDLE const file = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) return(data); + DWORD const size = GetFileSize(file, NULL); + if (size != INVALID_FILE_SIZE && size > 0) { + data.resize(size); + DWORD got = 0; + if (!ReadFile(file, data.data(), size, &got, NULL) || got != size) data.clear(); + } + CloseHandle(file); + return(data); +} + + +static bool Write_Whole_File(char const * path, std::vector const & data) +{ + HANDLE const file = CreateFileA(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) return(false); + DWORD written = 0; + bool ok = true; + if (!data.empty()) { + ok = WriteFile(file, data.data(), (DWORD)data.size(), &written, NULL) && written == data.size(); + } + CloseHandle(file); + return(ok); +} + + +static bool File_Exists(char const * path) +{ + HANDLE const file = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) return(false); + CloseHandle(file); + return(true); +} + + +enum { + FIELD_TITLE = 2, + FIELD_HOUSE = 3, + FIELD_VERSION = 16, + FIELD_WHEN = 13, + FIELD_MISSING = 77, +}; + + +static void Fill(SaveFileClass & save, std::vector const & content) +{ + FILETIME when; + when.dwLowDateTime = 0x12345678u; + when.dwHighDateTime = 0x01D2C3B4u; + + save.Set_String(FIELD_TITLE, "GDI 04: Eviction Notice"); + save.Set_String(FIELD_HOUSE, "GDI"); + save.Set_Int(FIELD_VERSION, 0x00010203); + save.Set_Time(FIELD_WHEN, when); + save.Content = content; +} + + +static void Check_Fields(char const * prefix, SaveFileClass const & save) +{ + char text[64]; + int value = 0; + FILETIME when = {}; + + Check((std::string(prefix) + ": title present").c_str(), save.Get_String(FIELD_TITLE, text, sizeof(text))); + Check((std::string(prefix) + ": title text").c_str(), strcmp(text, "GDI 04: Eviction Notice") == 0); + Check((std::string(prefix) + ": house present").c_str(), save.Get_String(FIELD_HOUSE, text, sizeof(text))); + Check((std::string(prefix) + ": house text").c_str(), strcmp(text, "GDI") == 0); + Check((std::string(prefix) + ": version present").c_str(), save.Get_Int(FIELD_VERSION, &value)); + Check((std::string(prefix) + ": version value").c_str(), value == 0x00010203); + Check((std::string(prefix) + ": time present").c_str(), save.Get_Time(FIELD_WHEN, &when)); + Check((std::string(prefix) + ": time value").c_str(), + when.dwLowDateTime == 0x12345678u && when.dwHighDateTime == 0x01D2C3B4u); + Check((std::string(prefix) + ": a missing field is absent").c_str(), !save.Get_String(FIELD_MISSING, text, sizeof(text))); + Check((std::string(prefix) + ": a field is not found under another kind").c_str(), !save.Get_Int(FIELD_TITLE, &value)); + + Check((std::string(prefix) + ": a short buffer is clipped").c_str(), + save.Get_String(FIELD_TITLE, text, 4) && strcmp(text, "GDI") == 0); +} + + +static void Test_Round_Trip(void) +{ + std::string const path = Scratch_Path("ROUNDTRIP.SAV"); + std::vector const content = Prose(300000); + + SaveFileClass written; + Fill(written, content); + Check_Result("round trip: write", written.Write(path.c_str()), SaveFileClass::RESULT_OK); + Check("round trip: no temporary file is left behind", !File_Exists((path + ".tmp").c_str())); + + std::vector const image = Read_Whole_File(path.c_str()); + Check("round trip: the prose was compressed", !image.empty() && image.size() < content.size() / 4); + + SaveFileClass read; + Check_Result("round trip: read", read.Read(path.c_str()), SaveFileClass::RESULT_OK); + Check_Fields("round trip", read); + Check("round trip: content reads back whole", read.Content == content); + + SaveFileClass listed; + Check_Result("round trip: fields alone", listed.Read_Fields(path.c_str()), SaveFileClass::RESULT_OK); + Check_Fields("fields alone", listed); + Check("fields alone: no content is read", listed.Content.empty()); +} + + +static void Test_Cuts(void) +{ + SaveFileClass save; + save.Set_String(FIELD_TITLE, "ab\xC3\xA9" "cd"); + + char text[8]; + Check("cuts: a cut never splits a character", save.Get_String(FIELD_TITLE, text, 4) && strcmp(text, "ab") == 0); + Check("cuts: a cut after a character keeps it whole", save.Get_String(FIELD_TITLE, text, 5) && strcmp(text, "ab\xC3\xA9") == 0); + Check("cuts: a buffer that fits keeps everything", save.Get_String(FIELD_TITLE, text, 8) && strcmp(text, "ab\xC3\xA9" "cd") == 0); +} + + +static void Test_Incompressible(void) +{ + std::string const path = Scratch_Path("NOISE.SAV"); + std::vector const content = Noise(70000, 7); + + SaveFileClass written; + Fill(written, content); + Check_Result("noise: write", written.Write(path.c_str()), SaveFileClass::RESULT_OK); + + std::vector const image = Read_Whole_File(path.c_str()); + Check("noise: stored as it is when compression does not pay", image.size() >= content.size() + SaveFileClass::HEADER_SIZE); + + SaveFileClass read; + Check_Result("noise: read", read.Read(path.c_str()), SaveFileClass::RESULT_OK); + Check("noise: content reads back whole", read.Content == content); +} + + +static void Test_Empty(void) +{ + std::string const path = Scratch_Path("EMPTY.SAV"); + + SaveFileClass written; + Check_Result("empty: write", written.Write(path.c_str()), SaveFileClass::RESULT_OK); + + std::vector const image = Read_Whole_File(path.c_str()); + Check("empty: a header alone", image.size() == SaveFileClass::HEADER_SIZE); + + SaveFileClass read; + Check_Result("empty: read", read.Read(path.c_str()), SaveFileClass::RESULT_OK); + Check("empty: no content", read.Content.empty()); + char text[8]; + Check("empty: no fields", !read.Get_String(FIELD_TITLE, text, sizeof(text))); +} + + +static void Test_Overwrite(void) +{ + std::string const path = Scratch_Path("REPLACE.SAV"); + + SaveFileClass first; + Fill(first, Prose(5000)); + first.Set_String(FIELD_TITLE, "the earlier save"); + Check_Result("replace: first write", first.Write(path.c_str()), SaveFileClass::RESULT_OK); + + Check("replace: a stale temporary is planted", Write_Whole_File((path + ".tmp").c_str(), Noise(100, 3))); + + SaveFileClass second; + Fill(second, Noise(20000, 11)); + second.Set_String(FIELD_TITLE, "the later save"); + Check_Result("replace: second write", second.Write(path.c_str()), SaveFileClass::RESULT_OK); + Check("replace: the stale temporary is gone", !File_Exists((path + ".tmp").c_str())); + + SaveFileClass read; + Check_Result("replace: read", read.Read(path.c_str()), SaveFileClass::RESULT_OK); + char text[64]; + Check("replace: the later save is the one on disk", + read.Get_String(FIELD_TITLE, text, sizeof(text)) && strcmp(text, "the later save") == 0); + Check("replace: the later content is the one on disk", read.Content == second.Content); + + SaveFileClass rewritten; + rewritten.Set_String(FIELD_TITLE, "overwritten field"); + rewritten.Set_String(FIELD_TITLE, "final field"); + Check_Result("replace: field rewrite", rewritten.Write(path.c_str()), SaveFileClass::RESULT_OK); + Check_Result("replace: field rewrite read", read.Read_Fields(path.c_str()), SaveFileClass::RESULT_OK); + Check("replace: a field set twice keeps the last value", + read.Get_String(FIELD_TITLE, text, sizeof(text)) && strcmp(text, "final field") == 0); +} + + +static void Put_U32(std::vector & image, std::size_t at, unsigned int value) +{ + image[at] = (unsigned char)(value & 0xFF); + image[at + 1] = (unsigned char)((value >> 8) & 0xFF); + image[at + 2] = (unsigned char)((value >> 16) & 0xFF); + image[at + 3] = (unsigned char)((value >> 24) & 0xFF); +} + + +static void Test_Limits(void) +{ + std::string const path = Scratch_Path("LIMITS.SAV"); + + SaveFileClass kept; + Fill(kept, Prose(3000)); + kept.Set_String(FIELD_TITLE, "the save that stays"); + Check_Result("limits: the save that stays", kept.Write(path.c_str()), SaveFileClass::RESULT_OK); + + SaveFileClass wide; + Fill(wide, Prose(3000)); + wide.Set_String(FIELD_TITLE, std::string(0x10001, 'x').c_str()); + Check_Result("limits: a field beyond its limit is refused", wide.Write(path.c_str()), SaveFileClass::RESULT_TOO_LARGE); + + SaveFileClass many; + Fill(many, Prose(3000)); + for (int id = 100; id < 117; id++) { + many.Set_String(id, std::string(0x10000, 'y').c_str()); + } + Check_Result("limits: a table beyond its limit is refused", many.Write(path.c_str()), SaveFileClass::RESULT_TOO_LARGE); + + SaveFileClass huge; + huge.Content.resize(0x10000001); + Check_Result("limits: content beyond its limit is refused", huge.Write(path.c_str()), SaveFileClass::RESULT_TOO_LARGE); + + Check("limits: no temporary is left behind", !File_Exists((path + ".tmp").c_str())); + SaveFileClass read; + Check_Result("limits: the earlier save still reads", read.Read(path.c_str()), SaveFileClass::RESULT_OK); + char text[64]; + Check("limits: the earlier save is the one on disk", + read.Get_String(FIELD_TITLE, text, sizeof(text)) && strcmp(text, "the save that stays") == 0); +} + + +// Recomputes the header checksum after a test has changed a header byte on purpose. +static void Reseal_Header(std::vector & image, unsigned int table) +{ + unsigned int crc = SaveFileClass::Checksum(image.data(), SaveFileClass::HEADER_SIZE - 4); + crc = SaveFileClass::Checksum(image.data() + SaveFileClass::HEADER_SIZE, table, crc); + Put_U32(image, 28, crc); +} + + +// Rebuilds a save image around a field table of the test's own making, with the content +// kept and every checksum made good. +static std::vector Forge_Table(std::vector const & image, unsigned int table, + std::vector const & newtable) +{ + std::vector forged(image.begin(), image.begin() + SaveFileClass::HEADER_SIZE); + forged.insert(forged.end(), newtable.begin(), newtable.end()); + forged.insert(forged.end(), image.begin() + SaveFileClass::HEADER_SIZE + table, image.end()); + Put_U32(forged, 8, (unsigned int)newtable.size()); + Put_U32(forged, 12, SaveFileClass::HEADER_SIZE + (unsigned int)newtable.size()); + Reseal_Header(forged, (unsigned int)newtable.size()); + return(forged); +} + + +// Replaces the content of a save image with a compressed block of the test's own making, +// declared as expanding to the length given, with every checksum made good. +static std::vector Forge_Content(std::vector const & image, unsigned int table, + std::vector const & stored, unsigned int expands_to) +{ + std::vector forged(image.begin(), image.begin() + SaveFileClass::HEADER_SIZE + table); + forged.insert(forged.end(), stored.begin(), stored.end()); + forged[6] |= 0x01; + Put_U32(forged, 12, SaveFileClass::HEADER_SIZE + table); + Put_U32(forged, 16, (unsigned int)stored.size()); + Put_U32(forged, 20, expands_to); + Put_U32(forged, 24, SaveFileClass::Checksum(stored.data(), (unsigned int)stored.size())); + Reseal_Header(forged, table); + return(forged); +} + + +static void Test_Refusals(void) +{ + SaveFileClass read; + + std::string const missing = Scratch_Path("MISSING.SAV"); + Check_Result("refuse: a missing file", read.Read(missing.c_str()), SaveFileClass::RESULT_MISSING); + Check_Result("refuse: a missing file's fields", read.Read_Fields(missing.c_str()), SaveFileClass::RESULT_MISSING); + + std::string const plain = Scratch_Path("PLAIN.SAV"); + std::vector hello = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' }; + Write_Whole_File(plain.c_str(), hello); + Check_Result("refuse: a file that is not a save", read.Read(plain.c_str()), SaveFileClass::RESULT_NOT_A_SAVE); + Check_Result("refuse: its fields", read.Read_Fields(plain.c_str()), SaveFileClass::RESULT_NOT_A_SAVE); + + std::string const good = Scratch_Path("GOOD.SAV"); + SaveFileClass written; + Fill(written, Prose(40000)); + Check_Result("refuse: the reference save", written.Write(good.c_str()), SaveFileClass::RESULT_OK); + std::vector const image = Read_Whole_File(good.c_str()); + Check("refuse: the reference save is readable", !image.empty()); + + std::string const damaged = Scratch_Path("DAMAGED.SAV"); + + unsigned int const table = (unsigned int)image[8] | ((unsigned int)image[9] << 8) + | ((unsigned int)image[10] << 16) | ((unsigned int)image[11] << 24); + + std::vector future = image; + future[4] = 99; + future[5] = 0; + Reseal_Header(future, table); + Write_Whole_File(damaged.c_str(), future); + Check_Result("refuse: a later format version", read.Read(damaged.c_str()), SaveFileClass::RESULT_UNSUPPORTED_VERSION); + Check_Result("refuse: its fields", read.Read_Fields(damaged.c_str()), SaveFileClass::RESULT_UNSUPPORTED_VERSION); + + std::vector flagged = image; + flagged[6] |= 0x02; + Reseal_Header(flagged, table); + Write_Whole_File(damaged.c_str(), flagged); + Check_Result("refuse: a header flag this build does not know", read.Read(damaged.c_str()), SaveFileClass::RESULT_UNSUPPORTED_VERSION); + Check_Result("refuse: its fields", read.Read_Fields(damaged.c_str()), SaveFileClass::RESULT_UNSUPPORTED_VERSION); + + unsigned int const content_offset = (unsigned int)image[12] | ((unsigned int)image[13] << 8) + | ((unsigned int)image[14] << 16) | ((unsigned int)image[15] << 24); + unsigned int const content_length = (unsigned int)image[20] | ((unsigned int)image[21] << 8) + | ((unsigned int)image[22] << 16) | ((unsigned int)image[23] << 24); + Check("refuse: the reference save is compressed", (image[6] & 0x01) != 0); + std::vector const stored(image.begin() + content_offset, image.end()); + + Write_Whole_File(damaged.c_str(), Forge_Content(image, table, stored, content_length - 1)); + Check_Result("refuse: a block that expands past its declared length", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + Write_Whole_File(damaged.c_str(), Forge_Content(image, table, stored, 16)); + Check_Result("refuse: a block that expands far past its declared length", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + Write_Whole_File(damaged.c_str(), Forge_Content(image, table, stored, content_length + 1)); + Check_Result("refuse: a block that ends before its declared length", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + + std::vector const reaching_back = { 18, 'A', 4, 0 }; + Write_Whole_File(damaged.c_str(), Forge_Content(image, table, reaching_back, 3)); + Check_Result("refuse: a block whose match reaches before the start", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + + std::vector const unfinished = { 18, 'A' }; + Write_Whole_File(damaged.c_str(), Forge_Content(image, table, unfinished, 1)); + Check_Result("refuse: a block with no end marker", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + + Write_Whole_File(damaged.c_str(), Forge_Content(image, table, stored, 0x10000001)); + Check_Result("refuse: a block declared larger than any save", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + Check_Result("refuse: its fields", read.Read_Fields(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + + std::vector const oversized(0x100001, 0); + Write_Whole_File(damaged.c_str(), Forge_Table(image, table, oversized)); + Check_Result("refuse: a field table longer than any listing", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + Check_Result("refuse: its fields", read.Read_Fields(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + + std::vector gapped = image; + gapped.insert(gapped.begin() + content_offset, 8, 0); + Put_U32(gapped, 12, content_offset + 8); + Reseal_Header(gapped, table); + Check("refuse: the gapped image is longer", gapped.size() == image.size() + 8); + Write_Whole_File(damaged.c_str(), gapped); + Check_Result("refuse: a gap between the table and the content", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + Check_Result("refuse: its fields", read.Read_Fields(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + + std::vector header_hit = image; + header_hit[9] ^= 0x01; + Write_Whole_File(damaged.c_str(), header_hit); + Check_Result("refuse: a header byte flipped", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + Check_Result("refuse: its fields", read.Read_Fields(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + + std::vector table_hit = image; + table_hit[SaveFileClass::HEADER_SIZE + 10] ^= 0x20; + Write_Whole_File(damaged.c_str(), table_hit); + Check_Result("refuse: a field byte flipped", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + Check_Result("refuse: its fields", read.Read_Fields(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + + std::vector content_hit = image; + content_hit[image.size() - 40] ^= 0x80; + Write_Whole_File(damaged.c_str(), content_hit); + Check_Result("refuse: a content byte flipped", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); + Check_Result("refuse: a flipped content byte still lists", read.Read_Fields(damaged.c_str()), SaveFileClass::RESULT_OK); + + std::size_t const cuts[] = { 3, 12, SaveFileClass::HEADER_SIZE - 1, SaveFileClass::HEADER_SIZE + 5, + SaveFileClass::HEADER_SIZE + table, image.size() / 2, image.size() - 1 }; + for (std::size_t cut : cuts) { + std::vector truncated(image.begin(), image.begin() + cut); + Write_Whole_File(damaged.c_str(), truncated); + char name[80]; + snprintf(name, sizeof(name), "refuse: a file cut at %u bytes", (unsigned int)cut); + SaveFileClass::ResultType const result = read.Read(damaged.c_str()); + Check(name, result == SaveFileClass::RESULT_CORRUPT || (cut < 4 && result == SaveFileClass::RESULT_NOT_A_SAVE)); + } + + std::vector appended = image; + appended.push_back(0); + Write_Whole_File(damaged.c_str(), appended); + Check_Result("refuse: a file with a trailing byte", read.Read(damaged.c_str()), SaveFileClass::RESULT_CORRUPT); +} + + +int main(int argc, char ** argv) +{ + if (argc < 2) { + printf("usage: SaveTest \n"); + return(2); + } + if (lzo_init() != LZO_E_OK) { + printf("lzo_init failed\n"); + return(2); + } + + Scratch = argv[1]; + CreateDirectoryA(Scratch.c_str(), NULL); + + Test_Round_Trip(); + Test_Cuts(); + Test_Incompressible(); + Test_Empty(); + Test_Overwrite(); + Test_Limits(); + Test_Refusals(); + + char const * const names[] = { "ROUNDTRIP.SAV", "NOISE.SAV", "EMPTY.SAV", "REPLACE.SAV", + "PLAIN.SAV", "GOOD.SAV", "DAMAGED.SAV" }; + for (char const * name : names) { + DeleteFileA(Scratch_Path(name).c_str()); + } + + printf("%d checks, %d failures\n", Checks, Failures); + return(Failures == 0 ? 0 : 1); +} From 93613db0d74efd6942f5b00870858ed97ee41da4 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Sun, 6 Sep 2026 22:34:07 +0200 Subject: [PATCH 021/179] Keep saved games in the engine's own container instead of OLE storage --- code/abstract.cpp | 101 +--- code/abstract.h | 16 +- code/aircraft.cpp | 2 +- code/aircraft.h | 2 +- code/base.cpp | 25 - code/base.h | 2 - code/brain.cpp | 26 +- code/brain.h | 4 +- code/building.cpp | 10 +- code/building.h | 2 +- code/cstream.cpp | 531 --------------------- code/cstream.h | 114 ----- code/display.cpp | 4 +- code/display.h | 4 +- code/drive.cpp | 22 +- code/droppod.cpp | 22 +- code/enviro.cpp | 16 +- code/enviro.h | 4 +- code/foot.cpp | 31 +- code/house.cpp | 2 +- code/house.h | 4 +- code/houstype.cpp | 19 +- code/houstype.h | 1 - code/infantry.cpp | 14 +- code/infantry.h | 2 +- code/ion.cpp | 16 +- code/ion.h | 4 +- code/isun.h | 2 - code/isun_i.c | 6 - code/layer.cpp | 22 +- code/layer.h | 4 +- code/loco.cpp | 130 ++---- code/loco.h | 18 +- code/mouse.cpp | 55 ++- code/mouse.h | 4 +- code/particle.cpp | 2 +- code/particle.h | 2 +- code/{ilinkstm.h => persist.h} | 20 +- code/revent.cpp | 24 +- code/revent.h | 6 +- code/rules.cpp | 12 +- code/rules.h | 4 +- code/saveload.cpp | 346 +++++++------- code/saveload.h | 14 +- code/savestream.cpp | 71 +-- code/savestream.h | 53 ++- code/savever.cpp | 817 ++------------------------------- code/savever.h | 27 +- code/scenario.cpp | 12 +- code/scenario.h | 4 +- code/script.cpp | 4 +- code/session.cpp | 22 +- code/session.h | 4 +- code/startup.cpp | 2 - code/terrain.cpp | 4 +- code/terrain.h | 2 +- code/tiberium.cpp | 2 +- code/tiberium.h | 2 +- code/unit.cpp | 14 +- code/unit.h | 2 +- code/vector.h | 6 +- code/vein.cpp | 54 ++- code/vein.h | 4 +- code/walk.cpp | 23 +- 64 files changed, 590 insertions(+), 2210 deletions(-) delete mode 100644 code/cstream.cpp delete mode 100644 code/cstream.h rename code/{ilinkstm.h => persist.h} (54%) diff --git a/code/abstract.cpp b/code/abstract.cpp index 9810f1802..5f9a1d3d3 100644 --- a/code/abstract.cpp +++ b/code/abstract.cpp @@ -110,8 +110,8 @@ void AbstractClass::Create_ID(void) /// /// Fetches a COM interface pointer from this object. /// This is the IUnknown implementation shared by every game object. Abstract -/// objects expose IUnknown, IPersistStream and IPersist; the save game system -/// reaches the whole object hierarchy through them. +/// objects expose IUnknown alone; the save game system reaches them through +/// IPersistent, which needs no identifier. /// /// The identifier of the interface being asked for. /// Receives the interface pointer, or NULL when the @@ -129,13 +129,7 @@ HRESULT STDMETHODCALLTYPE AbstractClass::QueryInterface(REFIID riid, LPVOID * pp *ppvObject = NULL; if (riid == IID_IUnknown) { - *ppvObject = (IUnknown *)(IPersistStream *)this; - } - if (riid == IID_IPersistStream) { - *ppvObject = (IPersistStream *)this; - } - if (riid == IID_IPersist) { - *ppvObject = (IPersist *)this; + *ppvObject = (IUnknown *)(IPersistent *)this; } if (*ppvObject == NULL) { return(E_NOINTERFACE); @@ -175,7 +169,7 @@ ULONG STDMETHODCALLTYPE AbstractClass::Release(void) /// The stream to write to. /// Should the object be marked clean once it has been written? /// Returns with S_OK when the object was written, otherwise a failure code. -HRESULT STDMETHODCALLTYPE AbstractClass::Save(IStream * stream, BOOL cleardirty) +HRESULT AbstractClass::Save(SaveStreamClass & stream, BOOL cleardirty) { return(Save_Members(stream, cleardirty)); } @@ -186,7 +180,7 @@ HRESULT STDMETHODCALLTYPE AbstractClass::Save(IStream * stream, BOOL cleardirty) /// /// The stream to read from. /// Returns with S_OK when the object was read, otherwise a failure code. -HRESULT STDMETHODCALLTYPE AbstractClass::Load(IStream * stream) +HRESULT AbstractClass::Load(SaveStreamClass & stream) { return(Load_Members(stream)); } @@ -200,27 +194,15 @@ HRESULT STDMETHODCALLTYPE AbstractClass::Load(IStream * stream) /// The stream to write to. /// Should the object be marked clean once it has been written? /// Returns with S_OK when the record was written, otherwise a failure code. -HRESULT AbstractClass::Save_Members(IStream * stream, BOOL cleardirty) +HRESULT AbstractClass::Save_Members(SaveStreamClass & stream, BOOL cleardirty) { - if (stream == NULL) { - return(E_POINTER); - } - uintptr_t id = (uintptr_t)this; - - HRESULT result = stream->Write(&id, sizeof(id), NULL); - if (FAILED(result)) { - return(result); + stream.Serialize(id); + Serialize(stream); + if (SUCCEEDED(stream.Result()) && cleardirty) { + Dirty = false; } - - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize(savestream); - - if (SUCCEEDED(savestream.Result()) && cleardirty) { - Dirty = false; - } - - return(savestream.Result()); + return(stream.Result()); } @@ -231,30 +213,26 @@ HRESULT AbstractClass::Save_Members(IStream * stream, BOOL cleardirty) /// /// The stream to read from. /// Returns with S_OK when the record was read, otherwise a failure code. -HRESULT AbstractClass::Load_Members(IStream * stream) +HRESULT AbstractClass::Load_Members(SaveStreamClass & stream) { - if (stream == NULL) { - return(E_POINTER); + uintptr_t id = 0; + stream.Serialize(id); + if (stream.Was_Error()) { + return(stream.Result()); } - - uintptr_t id; - - HRESULT result = stream->Read(&id, sizeof(id), NULL); - if (FAILED(result)) { - return(result); - } - Swizzle_Here_I_Am(id, this); - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context(typeid(*this).name(), id); - Serialize(savestream); + // A nested record borrows the stream, so the owner's context is put back afterwards. + char const * const outertype = stream.Context_Type(); + uintptr_t const outerid = stream.Context_ID(); + stream.Set_Context(typeid(*this).name(), id); + Serialize(stream); + stream.Set_Context(outertype, outerid); - if (SUCCEEDED(savestream.Result())) { + if (SUCCEEDED(stream.Result())) { Post_Load(); } - - return(savestream.Result()); + return(stream.Result()); } @@ -279,20 +257,6 @@ void AbstractClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the number of bytes that Save will write. -/// A record is as long as the members a class names, so the count is not known before -/// the members have been written. Nothing in the game asks for it, so rather than -/// walk the object twice this reports that the size cannot be supplied. -/// -/// Receives the maximum size, in bytes. -/// Returns with E_NOTIMPL. -HRESULT STDMETHODCALLTYPE AbstractClass::GetSizeMax(ULARGE_INTEGER *pcbSize) -{ - return(E_NOTIMPL); -} - - /// /// Folds this object's state into a running CRC. /// The multiplayer sync check walks every object each frame and accumulates its @@ -335,23 +299,6 @@ bool AbstractClass::Is_Techno(void) const } -/// -/// Determines if this object has changed since it was last saved. -/// -/// Returns with S_OK when the object is dirty, or S_FALSE when it is not. -HRESULT AbstractClass::IsDirty(void) -{ - /* - * Per IPersistStream::IsDirty specifications this method returns S_OK to indicate that the object has changed. - * Otherwise, it returns S_FALSE. - */ - if (Dirty) { - return(S_OK); - } - return(S_FALSE); -} - - /// /// Resets this object to its start of scenario state. /// The bare abstract object carries no scenario state, so there is nothing to do. diff --git a/code/abstract.h b/code/abstract.h index 8e9278fe0..1487c6fac 100644 --- a/code/abstract.h +++ b/code/abstract.h @@ -39,7 +39,7 @@ #include "house.hh" #include "rtti.hh" -#include +#include "persist.h" class AbstractTypeClass; class CRCEngine; @@ -62,7 +62,7 @@ class MonoClass; ** This class is the base class for all game objects that have an existence on the ** battlefield. */ -class AbstractClass : public IPersistStream +class AbstractClass : public IPersistent { public: @@ -74,8 +74,8 @@ class AbstractClass : public IPersistStream * the members are read -- dropping a registration keyed by the identity the read * is about to replace, say. */ - HRESULT Save_Members(IStream * stream, BOOL cleardirty); - HRESULT Load_Members(IStream * stream); + HRESULT Save_Members(SaveStreamClass & stream, BOOL cleardirty); + HRESULT Load_Members(SaveStreamClass & stream); public: @@ -96,7 +96,7 @@ class AbstractClass : public IPersistStream /* * If this object has changed since it was last written out, then this flag will be - * true. Save clears it on request and IsDirty reports it, as IPersistStream asks. + * true. Save clears it on request. */ bool Dirty; @@ -110,10 +110,8 @@ class AbstractClass : public IPersistStream virtual ULONG STDMETHODCALLTYPE AddRef(void) override; virtual ULONG STDMETHODCALLTYPE Release(void) override; - virtual HRESULT STDMETHODCALLTYPE IsDirty(void) override; - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream) override; - virtual HRESULT STDMETHODCALLTYPE Save(IStream * stream, BOOL cleardirty) override; - virtual HRESULT STDMETHODCALLTYPE GetSizeMax(ULARGE_INTEGER *pcbSize) override; + virtual HRESULT Load(SaveStreamClass & stream) override; + virtual HRESULT Save(SaveStreamClass & stream, BOOL cleardirty) override; virtual int What_Am_I(void) const; virtual int Fetch_ID(void) const; diff --git a/code/aircraft.cpp b/code/aircraft.cpp index 875a5e5d3..c4a6d3017 100644 --- a/code/aircraft.cpp +++ b/code/aircraft.cpp @@ -3932,7 +3932,7 @@ void AircraftClass::Read_INI(CCINIClass const & ini) /// /// The stream to read this object from. /// Returns with S_OK if the aircraft was loaded successfully. -HRESULT STDMETHODCALLTYPE AircraftClass::Load(IStream * stream) +HRESULT AircraftClass::Load(SaveStreamClass & stream) { TargetTracker.Remove_Index(Fetch_ID()); return(BASECLASS::Load(stream)); diff --git a/code/aircraft.h b/code/aircraft.h index 8543f29b7..64d199680 100644 --- a/code/aircraft.h +++ b/code/aircraft.h @@ -58,7 +58,7 @@ class AircraftClass : public FootClass, public IFlyControl virtual ~AircraftClass(void) override; virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream) override; + virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/base.cpp b/code/base.cpp index b4344eb5a..c3d566cbb 100644 --- a/code/base.cpp +++ b/code/base.cpp @@ -547,31 +547,6 @@ void BaseClass::Write_INI(CCINIClass & ini, char const * hname) /// Reads the base back in from a save game. /// /// Returns with the result reported by the stream read. -HRESULT STDMETHODCALLTYPE BaseClass::Load(IStream *stream) -{ - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("BaseClass"); - Serialize(savestream); - return(savestream.Result()); -} - - -/// -/// Writes the base out to a save game. -/// -/// Returns with the result reported by the stream write. -HRESULT STDMETHODCALLTYPE BaseClass::Save(IStream * stream) -{ - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize(savestream); - return(savestream.Result()); -} - - -/// -/// Lists the members the base plan holds. -/// -/// The stream carrying the members. void BaseClass::Serialize(SaveStreamClass & stream) { stream.Serialize(Nodes); diff --git a/code/base.h b/code/base.h index 352064766..c69d62662 100644 --- a/code/base.h +++ b/code/base.h @@ -103,8 +103,6 @@ class BaseClass */ void Read_INI(CCINIClass const & ini, char const * hname); void Write_INI(CCINIClass & ini, char const * hname); - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream); - virtual HRESULT STDMETHODCALLTYPE Save(IStream * stream); void Serialize(SaveStreamClass & stream); virtual void Compute_CRC(CRCEngine &) const; diff --git a/code/brain.cpp b/code/brain.cpp index b09549d86..ccbcfc272 100644 --- a/code/brain.cpp +++ b/code/brain.cpp @@ -159,15 +159,11 @@ bool BrainClass::Add_Neuron(NeuronClass *neuron) /// Returns with S_OK when the brain was written, E_POINTER when no stream was supplied, /// or the stream's own failure code. /// -HRESULT BrainClass::Save(IStream * stream, BOOL cleardirty) +HRESULT BrainClass::Save(SaveStreamClass & stream, BOOL cleardirty) { - if (stream == NULL) { - return(E_POINTER); - } - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize(savestream, cleardirty); - return(savestream.Result()); + Serialize(stream, cleardirty); + return(stream.Result()); } @@ -180,16 +176,12 @@ HRESULT BrainClass::Save(IStream * stream, BOOL cleardirty) /// Returns with S_OK when the brain was read, E_POINTER when no stream was supplied, or /// the stream's own failure code. /// -HRESULT BrainClass::Load(IStream * stream) +HRESULT BrainClass::Load(SaveStreamClass & stream) { - if (stream == NULL) { - return(E_POINTER); - } - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("BrainClass"); - Serialize(savestream); - return(savestream.Result()); + stream.Set_Context("BrainClass"); + Serialize(stream); + return(stream.Result()); } @@ -212,10 +204,10 @@ void BrainClass::Serialize(SaveStreamClass & stream, BOOL cleardirty) for (int i = 0; i < count && !stream.Was_Error(); i++) { if (stream.Is_Loading()) { NeuronClass * neuron = new NeuronClass; - neuron->Load(stream.Get_Stream()); + neuron->Load(stream); Add_Neuron(neuron); } else { - Neurons[i]->Save(stream.Get_Stream(), cleardirty); + Neurons[i]->Save(stream, cleardirty); } } // MinCount -- the limits a brain was prepared with rather than anything it accumulated. diff --git a/code/brain.h b/code/brain.h index 9287f630c..9ae55f0df 100644 --- a/code/brain.h +++ b/code/brain.h @@ -62,8 +62,8 @@ class BrainClass void Init(int min, int max); bool Add_Neuron(NeuronClass *neuron); - HRESULT Load(IStream * stream); - HRESULT Save(IStream * stream, BOOL cleardirty); + HRESULT Load(SaveStreamClass & stream); + HRESULT Save(SaveStreamClass & stream, BOOL cleardirty); void Serialize(SaveStreamClass & stream, BOOL cleardirty = FALSE); diff --git a/code/building.cpp b/code/building.cpp index 43f3b94c1..11f5ea579 100644 --- a/code/building.cpp +++ b/code/building.cpp @@ -5516,9 +5516,7 @@ int BuildingClass::Do_MISSION_REPAIR(void) ** distance check. Fixed-wing aircraft are very inaccurate with ** their landings. */ - IPersistPtr persist(tech->Locomotion); - CLSID clsid; - persist->GetClassID(&clsid); + CLSID const clsid = Locomotion_Class_ID(tech->Locomotion); bool hover = (clsid == CLSID_HoverLocomotion) != 0; if (hover) { distance = 0x96; @@ -6236,9 +6234,7 @@ int BuildingClass::Do_MISSION_UNLOAD(void) if (unit) { unit->Assign_Mission(MISSION_MOVE); - IPersistPtr persist(unit->Locomotion); - CLSID clsid; - persist->GetClassID(&clsid); + CLSID const clsid = Locomotion_Class_ID(unit->Locomotion); if (clsid == CLSID_TunnelLocomotion) { IPiggybackPtr piggy(unit->Locomotion); @@ -8747,7 +8743,7 @@ void BuildingClass::Clear_Occupy_Bit(Coord const & coord) /// /// Returns with S_OK if the building was read, or the failure code from the /// underlying stream. -HRESULT STDMETHODCALLTYPE BuildingClass::Load(IStream *stream) +HRESULT BuildingClass::Load(SaveStreamClass & stream) { TargetTracker.Remove_Index(Fetch_ID()); return(BASECLASS::Load(stream)); diff --git a/code/building.h b/code/building.h index 55f1f1bca..31b5e40d1 100644 --- a/code/building.h +++ b/code/building.h @@ -348,7 +348,7 @@ class BuildingClass : public TechnoClass virtual ~BuildingClass(void) override; virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream) override; + virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/cstream.cpp b/code/cstream.cpp deleted file mode 100644 index f0429c6c5..000000000 --- a/code/cstream.cpp +++ /dev/null @@ -1,531 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#include "always.h" - -#include "cstream.h" - -#include "lzo.h" - -extern ULONG COMRefCount; - -/// -/// Creates a compressing stream object. -/// This routine prepares the working buffers that the LZO codec needs. The object starts -/// out with no storage stream of its own to compress through. -/// -/// Call Link_Stream to attach a storage stream before reading or writing. -CStreamClass::CStreamClass(void) : - StreamPtr(NULL), - RefCount(0), - IsReading(false), - IsWriting(false), - CurOffset(0), - DataBuffer(new unsigned char[BUFFER_SIZE]), - StreamBuffer(new unsigned char[BUFFER_SIZE]), - LZODictionary(new unsigned char[BUFFER_SIZE]) -{ - BlockHead.CompSize = BUFFER_SIZE - 1; -} - - -/// -/// Destroys the compressing stream object. -/// Any storage stream still attached is unlinked first, so that whatever is left in the -/// work buffer is compressed out rather than lost. -/// -CStreamClass::~CStreamClass(void) -{ - IUnknown **unk = NULL; - if (StreamPtr) { - Unlink_Stream(unk); - } - - delete [] LZODictionary; - LZODictionary = NULL; - delete [] DataBuffer; - DataBuffer = NULL; - delete [] StreamBuffer; - StreamBuffer = NULL; -} - - -/// -/// Takes out a reference on this stream object. -/// -/// Returns with the new reference count. -ULONG CStreamClass::AddRef(void) -{ - COMRefCount++; - return(InterlockedIncrement(&RefCount)); -} - - -/// -/// Releases a reference to this stream object. -/// This routine will destroy the object once the last outstanding reference has been -/// given up. -/// -/// Returns with the number of references that remain. -ULONG CStreamClass::Release(void) -{ - COMRefCount--; - ULONG i = InterlockedDecrement(&RefCount); - - if (i == 0) { - delete this; - } - - return(i); -} - - -/// -/// Fetches an alternate interface to this stream object. -/// The IUnknown, IStream and ILinkStream interfaces are the ones supported. A successful -/// query takes out a reference on this object for the caller. -/// -/// The identifier of the interface being asked for. -/// Pointer to the interface pointer to fill in. -/// Returns with S_OK, or E_NOINTERFACE if the interface is not supported. -LONG CStreamClass::QueryInterface(REFIID riid, LPVOID * ppvObject) -{ - if (ppvObject == NULL) { - return(E_POINTER); - } - - *ppvObject = NULL; - if (riid == IID_IUnknown) { - *ppvObject = this; - } - if (riid == IID_IStream) { - *ppvObject = (IStream *)this; - } - if (riid == IID_ILinkStream) { - *ppvObject = (ILinkStream *)this; - } - if (*ppvObject == NULL) { - return(E_NOINTERFACE); - } - //reinterpret_cast(*ppvObject)->AddRef(); - this->AddRef(); - return(S_OK); -} - - -/// -/// Attaches the storage stream this object compresses through. -/// Use this routine to bind the compressor to the real stream that the compressed blocks -/// will be written to or read back from. Only one stream may be attached at a time. -/// -/// Pointer to the object to fetch the storage stream from. -/// Returns with S_OK, or E_FAIL if a stream is already attached. -HRESULT CStreamClass::Link_Stream(IUnknown *stream) -{ - if (stream == NULL) { - return(E_POINTER); - } - - if (StreamPtr != NULL) { - return(E_FAIL); - } - - HRESULT hr = stream->QueryInterface(__uuidof(IStream), (void **)&stream); - if (FAILED(hr)) { - /// &StreamPtr; - StreamPtr.Attach(NULL, false); - } else { - StreamPtr.Attach((IStream *)stream, false); - } - - if (FAILED(hr) && (hr != E_NOINTERFACE)) { - _com_issue_error(hr); - } - - return(S_OK); -} - - -/// -/// Detaches the underlying storage stream from this object. -/// Anything still sitting in the work buffer is compressed out first and the storage -/// stream is committed, so the caller gets back a complete stream. -/// -/// Pointer to fill in with the released stream, or NULL if the caller -/// does not want it. -/// Returns with S_OK, or E_FAIL if no stream was attached. A commit failure is -/// returned as it stands. -HRESULT CStreamClass::Unlink_Stream(IUnknown **stream) -{ - Compress(); - - if (StreamPtr == NULL) { - return(E_FAIL); - } - - if (stream != NULL) { - StreamPtr->AddRef(); - *stream = StreamPtr; - } - - HRESULT hr = StreamPtr->Commit(0); - if (SUCCEEDED(hr)) { - StreamPtr.Release(); - } else { - return(hr); - } - - return(S_OK); -} - - -/// -/// Reads and decompresses data from the underlying stream. -/// This routine pulls whole compressed blocks out of the storage stream and doles out -/// pieces of the decompressed result until the caller's request has been satisfied. -/// -/// Pointer to the buffer to fill with the data read. -/// The number of bytes to read. -/// Pointer to fill in with the number of bytes read, or NULL if the -/// count is not wanted. -/// Returns with S_OK, or an error code if the data could not be read. -/// A stream that is being written cannot also be read. -HRESULT CStreamClass::Read(void *pv, ULONG cb, ULONG *pcbRead) -{ - int read_size; - int left; - - read_size = cb; - left = cb; - - if (pv == NULL) { - return(E_POINTER); - } - - if (read_size < 0) { - return(E_INVALIDARG); - } - - if (StreamPtr == NULL) { - return(E_FAIL); - } - - if (IsWriting) { - return(E_FAIL); - } - - IsReading = true; - - if (pcbRead != NULL) { - *pcbRead = 0; - } - - while (left > 0) { - - int offset = CurOffset; - if (offset > 0) { - int len = left; - if (left >= offset) { - len = CurOffset; - } - memmove(pv, (char *)DataBuffer + BlockHead.UncompSize - offset, len); - pv = (char *)pv + len; - left -= len; - CurOffset -= len; - } - - if (left == 0) { - break; - } - - ULONG read = 0; - - HRESULT hr = StreamPtr->Read(&BlockHead, sizeof(BlockHead), &read); - if (FAILED(hr)) { - return(hr); - } - - if (read != sizeof(BlockHead)) { - return(E_FAIL); - } - - hr = StreamPtr->Read(StreamBuffer, BlockHead.CompSize, &read); - if (FAILED(hr)) { - return(hr); - } - - unsigned int inlen = BlockHead.CompSize; - if (read != inlen) { - return(E_FAIL); - } - lzo_byte *out = (lzo_byte *)DataBuffer; - lzo_byte *in = (lzo_byte *)StreamBuffer; - unsigned int out_len = BUFFER_SIZE; - lzo1x_decompress(in, inlen, out, &out_len, 0); - CurOffset = BlockHead.UncompSize; - } - - if (pcbRead != NULL) { - *pcbRead = read_size; - } - - return(S_OK); -} - - -/// -/// Compresses data out to the underlying stream. -/// This routine gathers the caller's data into a work buffer and hands it to the -/// compressor a block at a time, so what reaches the storage stream is a run of -/// compressed blocks rather than the raw bytes. -/// -/// Pointer to the data to write. -/// The number of bytes to write. -/// Pointer to fill in with the number of bytes accepted, or NULL -/// if the count is not wanted. -/// Returns with S_OK, or an error code if the data could not be written. -/// A stream that is being read cannot also be written. The trailing partial -/// block does not reach the storage stream until the object is flushed or unlinked. -HRESULT CStreamClass::Write(const void *pv, ULONG cb, ULONG *pcbWritten) -{ - unsigned char *ptr; - int write_size; - int left; - int result; - int temp_size; - - ptr = (unsigned char *)pv; - write_size = cb; - left = cb; - - if (pv == NULL) { - return(E_POINTER); - } - - if (write_size < 0) { - return(E_INVALIDARG); - } - - if (StreamPtr == NULL) { - return(E_FAIL); - } - - if (IsReading) { - return(E_FAIL); - } - - IsWriting = true; - - if (cb != 0) { - if (pcbWritten != NULL) { - *pcbWritten = 0; - } - - if (CurOffset > 0) { - if (write_size >= BUFFER_SIZE - CurOffset) { - write_size = BUFFER_SIZE - CurOffset; - } - - memmove((unsigned char *)DataBuffer + CurOffset, ptr, write_size); - temp_size = write_size + CurOffset; - - ptr += write_size; - left -= write_size; - CurOffset = temp_size; - - if (CurOffset == BUFFER_SIZE) { - result = Compress(DataBuffer, CurOffset); - if (result < 0) { - return(result); - } - CurOffset = 0; - } - - write_size = cb; - } - while (left >= BUFFER_SIZE) { - result = Compress(ptr, BUFFER_SIZE); - if (result < 0) { - return(result); - } - left -= BUFFER_SIZE; - ptr += BUFFER_SIZE; - } - - if (left > 0) { - memmove((unsigned char *)DataBuffer, ptr, left); - write_size = cb; - CurOffset = left; - } - - if (pcbWritten) { - *pcbWritten = write_size; - } - } - - return(S_OK); -} - - -/// -/// Moves the file pointer of the underlying stream. -/// -/// Returns with S_OK, or E_FAIL if a transfer is already under way. -/// Seeking is refused once reading or writing has begun, since the compressor -/// keeps state that a seek would invalidate. -HRESULT CStreamClass::Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition) -{ - if (IsReading || IsWriting) { - return(E_FAIL); - } - - return(StreamPtr->Seek(dlibMove, dwOrigin, plibNewPosition)); -} - - -/// -/// Sets the size of the underlying stream. -/// -/// Returns with S_OK, or E_FAIL if a transfer is already under way. -/// Resizing is refused once reading or writing has begun. -HRESULT CStreamClass::SetSize(ULARGE_INTEGER libNewSize) -{ - if (IsReading || IsWriting) { - return(E_FAIL); - } - - return(StreamPtr->SetSize(libNewSize)); -} - - -/// -/// Copies data from this stream over to another stream. -/// The request is handed straight to the underlying stream, so it is the compressed -/// bytes that get copied rather than the data they stand for. -/// -/// Returns with the result of the underlying stream's copy request. -HRESULT CStreamClass::CopyTo(IStream *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten) -{ - return(StreamPtr->CopyTo(pstm, cb, pcbRead, pcbWritten)); -} - - -/// -/// Commits any pending changes to the underlying stream. -/// -/// Returns with the result of the underlying stream's commit request. -HRESULT CStreamClass::Commit(DWORD grfCommitFlags) -{ - return(StreamPtr->Commit(grfCommitFlags)); -} - -/// -/// Discards any uncommitted changes to the stream. -/// -/// Returns with the result of the underlying stream's revert request. -HRESULT CStreamClass::Revert(void) -{ - return(StreamPtr->Revert()); -} - - -/// -/// Locks a byte range of the underlying stream. -/// -/// Returns with the result of the underlying stream's lock request. -HRESULT CStreamClass::LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) -{ - return(StreamPtr->LockRegion(libOffset, cb, dwLockType)); -} - - -/// -/// Releases a lock on a byte range of the underlying stream. -/// -/// Returns with the result of the underlying stream's unlock request. -HRESULT CStreamClass::UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) -{ - return(StreamPtr->UnlockRegion(libOffset, cb, dwLockType)); -} - - -/// -/// Fetches the statistics of the underlying stream. -/// -/// Returns with the result of the underlying stream's stat request. -HRESULT CStreamClass::Stat(STATSTG *pstatstg, DWORD grfStatFlag) -{ - return(StreamPtr->Stat(pstatstg, grfStatFlag)); -} - - -/// -/// Creates a second stream object over the same storage. -/// The clone is made by the underlying stream, so it is a plain stream rather than a -/// compressing one. -/// -/// Returns with the result of the underlying stream's clone request. -HRESULT CStreamClass::Clone(IStream **ppstm) -{ - return(StreamPtr->Clone(ppstm)); -} - - -/// -/// Compresses a buffer out as a single stream block. -/// This is the low level routine that runs the buffer through the LZO compressor and -/// writes the block header and the compressed bytes to the underlying stream. -/// -/// Pointer to the data to compress. -/// The number of bytes to compress. -/// Returns with S_OK, or an error code if the block could not be written. -HRESULT CStreamClass::Compress(void *in_buffer, ULONG length) -{ - HRESULT hr; - unsigned int out_len = length; - lzo1x_1_compress((lzo_byte *)in_buffer, length, (lzo_byte *)StreamBuffer, &out_len, (lzo_byte *)LZODictionary); - BlockHead.UncompSize = BUFFER_SIZE; - length = 0; - BlockHead.CompSize = out_len; - - hr = StreamPtr->Write(&BlockHead, sizeof(BlockHead), &length); - - if (SUCCEEDED(hr)) { - if (length != sizeof(BlockHead)) { - return(E_FAIL); - } - - hr = StreamPtr->Write(StreamBuffer, out_len, &length); - if (SUCCEEDED(hr)) { - hr = length != out_len ? (unsigned int)E_FAIL : 0; - } - } - - return(hr); -} - - -/// -/// Flushes any buffered data out as a compressed block. -/// Use this routine to make sure the tail end of a write actually reaches the stream. -/// It does nothing if there is nothing left over to flush. -/// -/// Returns with S_OK, or an error code if the block could not be written. -HRESULT CStreamClass::Compress(void) -{ - if (IsWriting && CurOffset > 0) { - if (StreamPtr == NULL) { - return(E_FAIL); - } - if (CurOffset > 0) { - return(Compress(DataBuffer, CurOffset)); - } - } - return(S_OK); -} diff --git a/code/cstream.h b/code/cstream.h deleted file mode 100644 index 33e1b48b6..000000000 --- a/code/cstream.h +++ /dev/null @@ -1,114 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#pragma once - -#include "ilinkstm.h" - -#include -#include - -class CStreamClass : public IStream, public ILinkStream -{ - public: - virtual LONG STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID * ppvObject) override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) override; - virtual ULONG STDMETHODCALLTYPE Release(void) override; - - virtual HRESULT STDMETHODCALLTYPE Read(void *pv, ULONG cb, ULONG *pcbRead) override; - virtual HRESULT STDMETHODCALLTYPE Write(const void *pv, ULONG cb, ULONG *pcbWritten) override; - - virtual HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition) override; - virtual HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER libNewSize) override; - virtual HRESULT STDMETHODCALLTYPE CopyTo(IStream *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten) override; - virtual HRESULT STDMETHODCALLTYPE Commit(DWORD grfCommitFlags) override; - virtual HRESULT STDMETHODCALLTYPE Revert() override; - virtual HRESULT STDMETHODCALLTYPE LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) override; - virtual HRESULT STDMETHODCALLTYPE UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) override; - virtual HRESULT STDMETHODCALLTYPE Stat(STATSTG *pstatstg, DWORD grfStatFlag) override; - virtual HRESULT STDMETHODCALLTYPE Clone(IStream **ppstm) override; - - virtual HRESULT STDMETHODCALLTYPE Link_Stream(IUnknown *stream) override; - virtual HRESULT STDMETHODCALLTYPE Unlink_Stream(IUnknown **stream) override; - - public: - CStreamClass(void); - virtual ~CStreamClass(void); - - HRESULT Compress(void *in_buffer, ULONG length); - HRESULT Compress(void); - - enum { - BUFFER_SIZE = 64*1024, - }; - - private: - /* - * This points to the stream the compressed data actually travels over. Nothing can be - * read or written until one has been linked in, and the link is broken again once the - * stream has been committed. - */ - IStreamPtr StreamPtr; - - /* - * This is the COM reference count for this object. The stream destroys itself once - * the last reference to it has been released. - */ - LONG RefCount; - - /* - * These flags record which direction the stream has been committed to. The first read - * or write sets one of them, and from that point on the opposite operation is - * refused, as are seeking and resizing. - */ - bool IsReading; - bool IsWriting; - - /* - * This is how much of the data buffer is currently in play, expressed in bytes. While - * reading it counts down the part of the decompressed block not yet handed out; while - * writing it counts up the bytes waiting to be compressed. - */ - int CurOffset; - - /* - * This is the working buffer that holds the data in its uncompressed form, one - * block's worth at a time. - */ - void *DataBuffer; - - /* - * This is the working buffer that holds a block in its compressed form, on its way to - * or from the linked stream. - */ - void *StreamBuffer; - - /* - * This is the scratch memory the LZO compressor keeps its dictionary in. It is of no - * interest outside the compression call itself. - */ - void *LZODictionary; - - /* - * This is the header of the block currently being read or written. Every block on the - * stream is preceded by one, so the reader knows how much compressed data to pull in - * and how far it will expand. - */ - struct BlockHeader { - /* - * This is the number of bytes the block occupies on the stream, compressed. - */ - unsigned int CompSize; - - /* - * This is the number of bytes the block expands to once decompressed. - */ - unsigned int UncompSize; - } BlockHead; -}; diff --git a/code/display.cpp b/code/display.cpp index 29798a650..baba6de9c 100644 --- a/code/display.cpp +++ b/code/display.cpp @@ -3848,7 +3848,7 @@ LRESULT DisplayClass::Windows_Message_Proc(HWND hWnd, UINT Msg, WPARAM wParam, L /// The stream to read the layers from. /// Returns with S_OK if every layer was read, otherwise the failure code of the /// layer that could not be read. -HRESULT DisplayClass::Load(IStream * stream) +HRESULT DisplayClass::Load(SaveStreamClass & stream) { HRESULT result = S_OK; for (LayerType layer = LAYER_FIRST; layer < LAYER_COUNT; layer++) { @@ -3865,7 +3865,7 @@ HRESULT DisplayClass::Load(IStream * stream) /// The stream to write the layers to. /// Returns with S_OK if every layer was written, otherwise the failure code of the /// layer that could not be written. -HRESULT DisplayClass::Save(IStream * stream) +HRESULT DisplayClass::Save(SaveStreamClass & stream) { HRESULT result = S_OK; for (LayerType layer = LAYER_FIRST; layer < LAYER_COUNT; layer++) { diff --git a/code/display.h b/code/display.h index b805c93e8..d9eaf694b 100644 --- a/code/display.h +++ b/code/display.h @@ -65,8 +65,8 @@ class DisplayClass: public MapClass friend class Tactical; public: - virtual HRESULT Load(IStream * stream); - virtual HRESULT Save(IStream * stream); + virtual HRESULT Load(SaveStreamClass & stream); + virtual HRESULT Save(SaveStreamClass & stream); virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/drive.cpp b/code/drive.cpp index 538c71bc9..0de192a7b 100644 --- a/code/drive.cpp +++ b/code/drive.cpp @@ -68,6 +68,7 @@ #include "inline.h" #include "overtype.h" #include "rules.h" +#include "saveload.h" #include "savestream.h" #include "tube.h" #include "unit.h" @@ -146,18 +147,10 @@ HRESULT DriveLocomotionClass::Piggyback_CLSID(CLSID * classid) } if (Piggybacker != NULL) { - IPersistPtr ptr(Piggybacker); - if (ptr == NULL) { - return(E_FAIL); - } - return(ptr->GetClassID(classid)); - } - - IPersistPtr ptr(this); - if (ptr == NULL) { - return(E_FAIL); + *classid = Locomotion_Class_ID(Piggybacker); + return(S_OK); } - return(ptr->GetClassID(classid)); + return(GetClassID(classid)); } @@ -192,7 +185,7 @@ HRESULT STDMETHODCALLTYPE DriveLocomotionClass::QueryInterface(REFIID riid, LPVO /// /// Lists the members this driver carries. /// A locomotor riding along on this one is a separate persistent object rather than a -/// member, so it still travels framed by OLE and is recreated as the class it was saved as. +/// member, so it travels as a record of its own and is recreated as the class it was saved as. /// /// The stream carrying the members. void DriveLocomotionClass::Serialize(SaveStreamClass & stream) @@ -220,10 +213,9 @@ void DriveLocomotionClass::Serialize(SaveStreamClass & stream) if (haspiggy) { if (stream.Is_Saving()) { - IPersistStreamPtr persist(Piggybacker); - OleSaveToStream(persist, stream.Get_Stream()); + Save_Object(stream, (ILocomotion *)Piggybacker); } else { - OleLoadFromStream(stream.Get_Stream(), IID_ILocomotion, (LPVOID *)&Piggybacker); + Load_Object(stream, IID_ILocomotion, (LPVOID *)&Piggybacker); } } // TrackControl -- constant tables shared by every driver. diff --git a/code/droppod.cpp b/code/droppod.cpp index 0e74e2fc8..fece78c5e 100644 --- a/code/droppod.cpp +++ b/code/droppod.cpp @@ -23,6 +23,7 @@ #include "house.h" #include "map.h" #include "rules.h" +#include "saveload.h" #include "savestream.h" #include "sun.h" #include "weapon.h" @@ -228,7 +229,7 @@ HRESULT STDMETHODCALLTYPE DropPodLocomotionClass::GetClassID(CLSID * retval) /// /// Lists the members this drop pod locomotor carries. /// The locomotor set aside while the pod descends is a separate persistent object rather -/// than a member, so it still travels framed by OLE and is recreated as the class it was +/// than a member, so it travels as a record of its own and is recreated as the class it was /// saved as. /// /// The stream carrying the members. @@ -244,10 +245,9 @@ void DropPodLocomotionClass::Serialize(SaveStreamClass & stream) if (haspiggy) { if (stream.Is_Saving()) { - IPersistStreamPtr persist(Piggybacker); - OleSaveToStream(persist, stream.Get_Stream()); + Save_Object(stream, (ILocomotion *)Piggybacker); } else { - OleLoadFromStream(stream.Get_Stream(), IID_ILocomotion, (LPVOID *)&Piggybacker); + Load_Object(stream, IID_ILocomotion, (LPVOID *)&Piggybacker); } } } @@ -369,18 +369,10 @@ HRESULT STDMETHODCALLTYPE DropPodLocomotionClass::Piggyback_CLSID(GUID * classid } if (Piggybacker != NULL) { - IPersistPtr ptr(Piggybacker); - if (ptr == NULL) { - return(E_FAIL); - } - return(ptr->GetClassID(classid)); - } - - IPersistPtr ptr(this); - if (ptr == NULL) { - return(E_FAIL); + *classid = Locomotion_Class_ID(Piggybacker); + return(S_OK); } - return(ptr->GetClassID(classid)); + return(GetClassID(classid)); } diff --git a/code/enviro.cpp b/code/enviro.cpp index 2f6cfe9aa..492f51679 100644 --- a/code/enviro.cpp +++ b/code/enviro.cpp @@ -110,12 +110,11 @@ void EnvironmentClass::Restore(void) /// restored, before the scenario itself is brought back. /// /// Returns with the result reported by the stream read. -HRESULT EnvironmentClass::Load(IStream * stream) +HRESULT EnvironmentClass::Load(SaveStreamClass & stream) { - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("EnvironmentClass"); - Serialize(savestream); - return(savestream.Result()); + stream.Set_Context("EnvironmentClass"); + Serialize(stream); + return(stream.Result()); } @@ -123,11 +122,10 @@ HRESULT EnvironmentClass::Load(IStream * stream) /// Writes the carry over environment out to a save game. /// /// Returns with the result reported by the stream write. -HRESULT EnvironmentClass::Save(IStream * stream) +HRESULT EnvironmentClass::Save(SaveStreamClass & stream) { - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize(savestream); - return(savestream.Result()); + Serialize(stream); + return(stream.Result()); } diff --git a/code/enviro.h b/code/enviro.h index 01cf06eec..8599da2f4 100644 --- a/code/enviro.h +++ b/code/enviro.h @@ -26,8 +26,8 @@ class EnvironmentClass void Store(void); void Restore(void); - HRESULT Load(IStream * stream); - HRESULT Save(IStream * stream); + HRESULT Load(SaveStreamClass & stream); + HRESULT Save(SaveStreamClass & stream); void Serialize(SaveStreamClass & stream); diff --git a/code/foot.cpp b/code/foot.cpp index 05382daf0..2f50a4676 100644 --- a/code/foot.cpp +++ b/code/foot.cpp @@ -115,6 +115,7 @@ #include "partsys.h" #include "revent.h" #include "rules.h" +#include "saveload.h" #include "savestream.h" #include "session.h" #include "swizzle.h" @@ -604,7 +605,6 @@ void FootClass::Advance_Path(int count) } - /*********************************************************************************************** * FootClass::Mission_Move -- AI process for moving a vehicle to its destination. * * * @@ -1132,9 +1132,7 @@ void FootClass::Approach_Target(void) */ bool flyer = (RTTI == RTTI_AIRCRAFT); - CLSID clsid; - IPersistPtr persist(Locomotion); - persist->GetClassID(&clsid); + CLSID const clsid = Locomotion_Class_ID(Locomotion); if (clsid == CLSID_JumpjetLocomotion) { flyer = true; } @@ -2387,9 +2385,7 @@ void FootClass::Assign_Destination(AbstractClass * target, bool) ParticleSystems[ATTACHED_PARTICLE_FIRE] = NULL; } - CLSID locoid; - IPersistPtr persist(Locomotion); - persist->GetClassID(&locoid); + CLSID const locoid = Locomotion_Class_ID(Locomotion); if (locoid == CLSID_HoverLocomotion && PathDelay == 0) { PathDelay = 1; @@ -3519,19 +3515,18 @@ void FootClass::Serialize(SaveStreamClass & stream) stream.Serialize(BlockagePathDelay); /* - * The locomotor is a COM sub-object rather than a member, so it persists itself onto - * the raw stream through OLE. The one being replaced is released first, since loading - * hands back a fresh interface pointer rather than filling this one in. + * The locomotor is a sub-object rather than a member, so it travels as a record of + * its own. The one being replaced is released first, since loading hands back a fresh + * interface pointer rather than filling this one in. */ if (stream.Is_Saving()) { - IPersistStreamPtr persist(Locomotion); - OleSaveToStream(persist, stream.Get_Stream()); + Save_Object(stream, (ILocomotion *)Locomotion); } else { if (Locomotion != NULL) { ((ILocomotion *)Locomotion)->Release(); } Locomotion.Detach(); - OleLoadFromStream(stream.Get_Stream(), IID_ILocomotion, (LPVOID *)&Locomotion); + Load_Object(stream, IID_ILocomotion, (LPVOID *)&Locomotion); } stream.Serialize(HeadToCoord); @@ -4729,10 +4724,7 @@ void FootClass::Delete_Me(void) /// bool; Is the object in the air? bool FootClass::In_Air(void) const { - IPersistPtr loco(Locomotion); - - CLSID clsid; - loco->GetClassID(&clsid); + CLSID const clsid = Locomotion_Class_ID(Locomotion); if (clsid == CLSID_HoverLocomotion) { return(false); @@ -4753,10 +4745,7 @@ bool FootClass::On_Ground(void) const if (BASECLASS::On_Ground()) { return(true); } - IPersistPtr loco(Locomotion); - - CLSID clsid; - loco->GetClassID(&clsid); + CLSID const clsid = Locomotion_Class_ID(Locomotion); return(IsDown && clsid == CLSID_HoverLocomotion); } diff --git a/code/house.cpp b/code/house.cpp index 89914c629..7c2f6ff11 100644 --- a/code/house.cpp +++ b/code/house.cpp @@ -6433,7 +6433,7 @@ void HouseClass::Compute_CRC(CRCEngine & crc) const /// /// The stream to read the house from. /// Returns with S_OK, or the failure code reported by the stream. -HRESULT STDMETHODCALLTYPE HouseClass::Load(IStream *stream) +HRESULT HouseClass::Load(SaveStreamClass & stream) { while (SuperWeapon.Count()) { delete SuperWeapon[0]; diff --git a/code/house.h b/code/house.h index 7f4e50526..f2fd31f51 100644 --- a/code/house.h +++ b/code/house.h @@ -736,7 +736,7 @@ class HouseClass : public AbstractClass virtual ~HouseClass(void) override; virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream) override; + virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; @@ -1060,8 +1060,6 @@ class HouseClass : public AbstractClass BuildChoiceClass(UrgencyType urgency=URGENCY_NONE, StructType structure=STRUCT_NONE) : Urgency(urgency), Structure(structure) {}; bool operator==(BuildChoiceClass const & ) const {return(false);} bool operator!=(BuildChoiceClass const & ) const {return(true);} - HRESULT Save(IStream *) const {return(S_OK);}; - HRESULT Load(IStream *) {return(S_OK);}; }; static DynamicVectorClass BuildChoice; diff --git a/code/houstype.cpp b/code/houstype.cpp index 593e2be76..8a7be00cc 100644 --- a/code/houstype.cpp +++ b/code/houstype.cpp @@ -231,17 +231,6 @@ void HouseTypeClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Determines if this house type has been changed since it was last saved. -/// House types are written out wholesale rather than on demand, so the answer never varies. -/// -/// Returns with S_OK. -HRESULT STDMETHODCALLTYPE HouseTypeClass::IsDirty(void) -{ - return(false); -} - - /// /// Lists the members this house type carries. /// @@ -285,13 +274,7 @@ HRESULT STDMETHODCALLTYPE HouseTypeClass::QueryInterface(REFIID riid, LPVOID * p *ppvObject = NULL; if (riid == IID_IUnknown) { - *ppvObject = (IUnknown *)(IPersistStream *)this; - } - if (riid == IID_IPersist) { - *ppvObject = (IPersistStream *)this; - } - if (riid == IID_IPersistStream) { - *ppvObject = (IPersist *)this; + *ppvObject = (IUnknown *)(IPersistent *)this; } if (*ppvObject == NULL) { return(E_NOINTERFACE); diff --git a/code/houstype.h b/code/houstype.h index ef3cb3d95..e5c401a47 100644 --- a/code/houstype.h +++ b/code/houstype.h @@ -103,7 +103,6 @@ class HouseTypeClass : public AbstractTypeClass virtual ~HouseTypeClass() override; virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE IsDirty(void) override; virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID * ppvObject) override; virtual ULONG STDMETHODCALLTYPE AddRef(void) override; diff --git a/code/infantry.cpp b/code/infantry.cpp index 3f9c03c8d..e0bff5b21 100644 --- a/code/infantry.cpp +++ b/code/infantry.cpp @@ -632,9 +632,7 @@ void InfantryClass::Draw_It(Point2D const & xpoint, Rect const & cliprect) const Cell cell = Get_Target_Cell(); if (CurrentTube == -1) { - IPersistPtr persist = Locomotion; - CLSID clsid; - persist->GetClassID(&clsid); + CLSID const clsid = Locomotion_Class_ID(Locomotion); if (HeightAGL > 0 && clsid == CLSID_BallisticLocomotion) { ShapeSet const * shapefile = (ShapeSet const *)MFCD::Retrieve("POD.SHP"); @@ -1174,9 +1172,7 @@ void InfantryClass::Assign_Destination(AbstractClass * target, bool immediate) } if (target != NULL && Class->IsJumpJet && Locomotion->Is_Moving()) { - IPersistPtr persist(Locomotion); - CLSID clsid; - persist->GetClassID(&clsid); + CLSID const clsid = Locomotion_Class_ID(Locomotion); if (clsid == CLSID_WalkLocomotion) { NavQueue.Add_Head(target); target = Get_Target_Cell_Ptr(); @@ -3973,7 +3969,7 @@ void InfantryClass::Clear_Occupy_Bit(Coord const & coord) /// again once that identity has arrived. /// /// Returns with S_OK if the object was read successfully. -HRESULT STDMETHODCALLTYPE InfantryClass::Load(IStream * stream) +HRESULT InfantryClass::Load(SaveStreamClass & stream) { TargetTracker.Remove_Index(Fetch_ID()); return(BASECLASS::Load(stream)); @@ -4226,9 +4222,7 @@ bool InfantryClass::Is_JumpJet(void) const return(false); } - IPersistPtr persist(Locomotion); - CLSID clsid; - persist->GetClassID(&clsid); + CLSID const clsid = Locomotion_Class_ID(Locomotion); return((clsid == CLSID_JumpjetLocomotion) ? true : false); } diff --git a/code/infantry.h b/code/infantry.h index fc6183008..2369c581c 100644 --- a/code/infantry.h +++ b/code/infantry.h @@ -127,7 +127,7 @@ class InfantryClass : public FootClass virtual ~InfantryClass(void) override; virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream) override; + virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/ion.cpp b/code/ion.cpp index c4ce335a1..d769131b0 100644 --- a/code/ion.cpp +++ b/code/ion.cpp @@ -78,11 +78,10 @@ void IonStormClass::Init(void) /// Saves the ion storm state to the save game stream. /// /// Returns with the result reported by the stream write. -HRESULT IonStormClass::Save(IStream * stream) +HRESULT IonStormClass::Save(SaveStreamClass & stream) { - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize(savestream); - return(savestream.Result()); + Serialize(stream); + return(stream.Result()); } @@ -92,12 +91,11 @@ HRESULT IonStormClass::Save(IStream * stream) /// Returns with the result reported by the stream read. /// Only the bookkeeping is restored here. Post_Load_Game must still call /// Apply_Secondary_Effect to put the world back into its storm bound state. -HRESULT IonStormClass::Load(IStream * stream) +HRESULT IonStormClass::Load(SaveStreamClass & stream) { - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("IonStormClass"); - Serialize(savestream); - return(savestream.Result()); + stream.Set_Context("IonStormClass"); + Serialize(stream); + return(stream.Result()); } diff --git a/code/ion.h b/code/ion.h index b3dab97f2..2479b14ea 100644 --- a/code/ion.h +++ b/code/ion.h @@ -22,8 +22,8 @@ class IonStormClass { public: static void Init(void); - static HRESULT Save(IStream * stream); - static HRESULT Load(IStream * stream); + static HRESULT Save(SaveStreamClass & stream); + static HRESULT Load(SaveStreamClass & stream); static void Serialize(SaveStreamClass & stream); diff --git a/code/isun.h b/code/isun.h index c46215471..ea17f62b3 100644 --- a/code/isun.h +++ b/code/isun.h @@ -15,8 +15,6 @@ #define GAME_VERNAME TEXT("Tiberian Sun") -EXTERN_C const IID IID_ILinkStream; -EXTERN_C const CLSID CLSID_CompressStream; EXTERN_C const CLSID CLSID_HouseClass; EXTERN_C const CLSID CLSID_SuperWeaponTypeClass; EXTERN_C const CLSID CLSID_SuperWeaponClass; diff --git a/code/isun_i.c b/code/isun_i.c index d667c8d25..2f35f17e8 100644 --- a/code/isun_i.c +++ b/code/isun_i.c @@ -44,12 +44,6 @@ typedef struct _IID typedef IID CLSID; #endif // CLSID_DEFINED -const IID IID_ILinkStream = {0x0D5CD78E,0x6470,0x11D2,{0x9B,0x74,0x00,0x10,0x4B,0x97,0x2F,0xE8}}; - - -const CLSID CLSID_CompressStream = {0xB48FA168,0x646F,0x11D2,{0x9B,0x74,0x00,0x10,0x4B,0x97,0x2F,0xE8}}; - - const CLSID CLSID_HouseClass = {0xD9D4A910,0x87C6,0x11D1,{0xB7,0x07,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; diff --git a/code/layer.cpp b/code/layer.cpp index 13ef01533..0053d4be5 100644 --- a/code/layer.cpp +++ b/code/layer.cpp @@ -153,15 +153,11 @@ int LayerClass::Sorted_Add(ObjectClass const * const object) /// /// Returns with S_OK if the layer was written. Otherwise, the failure code from /// the stream is returned. -HRESULT LayerClass::Save(IStream * stream) +HRESULT LayerClass::Save(SaveStreamClass & stream) { - if (stream == NULL) { - return(E_POINTER); - } - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - DynamicVectorClass::Serialize(savestream); - return(savestream.Result()); + DynamicVectorClass::Serialize(stream); + return(stream.Result()); } @@ -173,14 +169,10 @@ HRESULT LayerClass::Save(IStream * stream) /// /// Returns with S_OK if the layer was read. Otherwise, the failure code from the /// stream is returned. -HRESULT LayerClass::Load(IStream * stream) +HRESULT LayerClass::Load(SaveStreamClass & stream) { - if (stream == NULL) { - return(E_POINTER); - } - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("LayerClass"); - DynamicVectorClass::Serialize(savestream); - return(savestream.Result()); + stream.Set_Context("LayerClass"); + DynamicVectorClass::Serialize(stream); + return(stream.Result()); } diff --git a/code/layer.h b/code/layer.h index ffa129bc1..7d317e368 100644 --- a/code/layer.h +++ b/code/layer.h @@ -40,8 +40,8 @@ class ObjectClass; class LayerClass : public DynamicVectorClass { public: - HRESULT Load(IStream * stream); - HRESULT Save(IStream * stream); + HRESULT Load(SaveStreamClass & stream); + HRESULT Save(SaveStreamClass & stream); public: diff --git a/code/loco.cpp b/code/loco.cpp index b012a46cd..e956b29e1 100644 --- a/code/loco.cpp +++ b/code/loco.cpp @@ -216,8 +216,8 @@ ULONG STDMETHODCALLTYPE LocomotionClass::Release(void) /// /// Fetches one of the interfaces this locomotor implements. -/// A locomotor answers to IUnknown, IPersist, IPersistStream, and ILocomotion. Any other -/// interface asked for is refused. +/// A locomotor answers to IUnknown and ILocomotion. Any other interface asked for is +/// refused. /// /// The identifier of the interface being asked for. /// Pointer to the location to store the interface pointer in. @@ -235,15 +235,9 @@ LONG STDMETHODCALLTYPE LocomotionClass::QueryInterface(REFIID riid, LPVOID *ppvO if (riid == IID_IUnknown) { *ppvObject = (IUnknown *)(ILocomotion *)this; } - if (riid == IID_IPersistStream) { - *ppvObject = (IPersistStream *)this; - } if (riid == IID_ILocomotion) { *ppvObject = (ILocomotion *)this; } - if (riid == IID_IPersist) { - *ppvObject = (IPersist *)this; - } if (*ppvObject == NULL) { return(E_NOINTERFACE); } @@ -253,113 +247,71 @@ LONG STDMETHODCALLTYPE LocomotionClass::QueryInterface(REFIID riid, LPVOID *ppvO } +CLSID Locomotion_Class_ID(ILocomotion * locomotion) +{ + CLSID classid = CLSID_NULL; + IPersistent * const persist = dynamic_cast(locomotion); + if (persist != NULL) { + persist->GetClassID(&classid); + } + return(classid); +} + + /// /// Saves the locomotor out to a save game stream. /// The locomotor's address is written ahead of its data, which is what lets the swizzle /// manager remap every pointer to it when the game is loaded again. /// /// Should the locomotor be marked as no longer needing a save? -/// Returns with the result of the write, or E_POINTER if no stream was supplied. -HRESULT STDMETHODCALLTYPE LocomotionClass::Save(IStream * stream, BOOL cleardirty) +/// Returns with the result of the write. +HRESULT LocomotionClass::Save(SaveStreamClass & stream, BOOL cleardirty) { - if (stream == NULL) { - return(E_POINTER); /// E_INVALIDARG - } - return(Save_Members(stream, cleardirty)); } -/// -/// Loads the locomotor back from a save game stream. -/// The locomotor announces its new address to the swizzle manager before its data is -/// read in, so that every saved pointer to it can be remapped and its link back to the -/// object it drives can be restored. The reference count belongs to the running session -/// rather than to the saved state, so it survives the load untouched. -/// -/// The stream to read the locomotor back from. -/// Returns with the result of the read, or E_POINTER if no stream was supplied. -HRESULT STDMETHODCALLTYPE LocomotionClass::Load(IStream * stream) +HRESULT LocomotionClass::Load(SaveStreamClass & stream) { - if (stream == NULL) { - return(E_POINTER); /// E_INVALIDARG - } - return(Load_Members(stream)); } -/// -/// Writes the members this locomotor describes out to the save stream. -/// The locomotor's address goes out first as its swizzle identity, and the members follow -/// in the order Serialize names them. -/// -/// The stream to write to. -/// Should the locomotor be marked clean once it has been written? -/// Returns with S_OK when the record was written, otherwise a failure code. -HRESULT LocomotionClass::Save_Members(IStream * stream, BOOL cleardirty) +HRESULT LocomotionClass::Save_Members(SaveStreamClass & stream, BOOL cleardirty) { - if (stream == NULL) { - return(E_POINTER); - } - - uintptr_t id = (uintptr_t)(this); - - HRESULT result = stream->Write(&id, sizeof(id), NULL); - if (FAILED(result)) { - return(result); - } - - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize(savestream); - - if (SUCCEEDED(savestream.Result()) && cleardirty) { + uintptr_t id = (uintptr_t)this; + stream.Serialize(id); + Serialize(stream); + if (SUCCEEDED(stream.Result()) && cleardirty) { Dirty = false; } - - return(savestream.Result()); + return(stream.Result()); } -/// -/// Reads the members this locomotor describes back from the save stream. -/// The saved address is handed to the swizzle system so that pointers elsewhere in the -/// save game can be remapped onto this locomotor, and the members follow. -/// -/// The stream to read from. -/// Returns with S_OK when the record was read, otherwise a failure code. -HRESULT LocomotionClass::Load_Members(IStream * stream) +HRESULT LocomotionClass::Load_Members(SaveStreamClass & stream) { - if (stream == NULL) { - return(E_POINTER); + uintptr_t id = 0; + stream.Serialize(id); + if (stream.Was_Error()) { + return(stream.Result()); } - - uintptr_t id; - - HRESULT result = stream->Read(&id, sizeof(id), NULL); - if (FAILED(result)) { - return(result); - } - assert(id != 0); Swizzle_Here_I_Am(id, this); - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context(typeid(*this).name(), id); - Serialize(savestream); + char const * const outertype = stream.Context_Type(); + uintptr_t const outerid = stream.Context_ID(); + stream.Set_Context(typeid(*this).name(), id); + Serialize(stream); + stream.Set_Context(outertype, outerid); - if (SUCCEEDED(savestream.Result())) { + if (SUCCEEDED(stream.Result())) { Post_Load(); } - - return(savestream.Result()); + return(stream.Result()); } -/// -/// Lists the members every locomotor carries. -/// -/// The stream carrying the members. void LocomotionClass::Serialize(SaveStreamClass & stream) { stream.Serialize(LinkedTo); @@ -379,20 +331,6 @@ void LocomotionClass::Post_Load(void) } -/// -/// Fetches the number of bytes needed to save this locomotor. -/// A record is as long as the members a class names, so the count is not known before -/// the members have been written. Nothing in the game asks for it, so rather than -/// walk the locomotor twice this reports that the size cannot be supplied. -/// -/// Pointer to the value to fill in with the required byte count. -/// Returns with E_NOTIMPL. -LONG STDMETHODCALLTYPE LocomotionClass::GetSizeMax(ULARGE_INTEGER *pcbSize) -{ - return(E_NOTIMPL); -} - - /// /// Asks the object to step out of the way in the direction specified. /// This routine is used when another object needs the cell this one happens to be diff --git a/code/loco.h b/code/loco.h index ba9f5f627..8f0006661 100644 --- a/code/loco.h +++ b/code/loco.h @@ -11,11 +11,17 @@ #include "coord.h" #include "ilocos.h" +#include "persist.h" class FootClass; class SaveStreamClass; -class LocomotionClass : public IPersistStream, public ILocomotion +// The class identifier of a locomotor reached through its locomotion interface, or +// CLSID_NULL when it is not one of ours. +CLSID Locomotion_Class_ID(ILocomotion * locomotion); + + +class LocomotionClass : public IPersistent, public ILocomotion { public: LocomotionClass(void); @@ -25,10 +31,8 @@ class LocomotionClass : public IPersistStream, public ILocomotion virtual ULONG STDMETHODCALLTYPE AddRef() override; virtual ULONG STDMETHODCALLTYPE Release() override; - virtual LONG STDMETHODCALLTYPE IsDirty(void) override {return(Dirty ? S_OK : S_FALSE);} - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream) override; - virtual HRESULT STDMETHODCALLTYPE Save(IStream * stream, BOOL cleardirty) override; - virtual LONG STDMETHODCALLTYPE GetSizeMax(ULARGE_INTEGER *pcbSize) override; + virtual HRESULT Load(SaveStreamClass & stream) override; + virtual HRESULT Save(SaveStreamClass & stream, BOOL cleardirty) override; virtual HRESULT STDMETHODCALLTYPE Link_To_Object(void *object) override; virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; @@ -98,8 +102,8 @@ class LocomotionClass : public IPersistStream, public ILocomotion * from its Load and Save; the record is the swizzle identity followed by whatever * members the class names. */ - HRESULT Save_Members(IStream * stream, BOOL cleardirty); - HRESULT Load_Members(IStream * stream); + HRESULT Save_Members(SaveStreamClass & stream, BOOL cleardirty); + HRESULT Load_Members(SaveStreamClass & stream); protected: /* diff --git a/code/mouse.cpp b/code/mouse.cpp index ea7bdc390..f13ec8b91 100644 --- a/code/mouse.cpp +++ b/code/mouse.cpp @@ -52,6 +52,7 @@ #include "mixfile.h" #include "overtype.h" #include "rawfile.h" +#include "saveload.h" #include "savestream.h" #include "scenario.h" #include "shapeset.h" @@ -393,14 +394,15 @@ void MouseClass::Init_Clear(void) /// manager, and the theater specific type data is reinitialized to match the scenario. /// /// Returns with S_OK if the map was loaded, otherwise the stream error. -HRESULT MouseClass::Load(IStream * stream) +HRESULT MouseClass::Load(SaveStreamClass & stream) { int i; HRESULT result = BASECLASS::Load(stream); if (SUCCEEDED(result)) { int theater; - result = stream->Read(&theater, sizeof(theater), NULL); + stream.Serialize(theater); + result = stream.Result(); if (FAILED(result)) { return(result); } @@ -435,10 +437,9 @@ HRESULT MouseClass::Load(IStream * stream) Array.Clear(); - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("MouseClass"); - Serialize(savestream); - result = savestream.Result(); + stream.Set_Context("MouseClass"); + Serialize(stream); + result = stream.Result(); if (FAILED(result)) { return(result); } @@ -478,21 +479,23 @@ HRESULT MouseClass::Load(IStream * stream) SubzoneConnectionHashTable[i] = new SUBZONE_CONNECTION_HASH_SET(20, 256, SubzoneHash); } - result = stream->Read(CellZones, sizeof(*CellZones) * CellZoneCount, NULL); + stream.Serialize_Bytes(CellZones, (int)(sizeof(*CellZones) * CellZoneCount)); + result = stream.Result(); if (FAILED(result)) { return(result); } for (i = 0; i < MZONE_COUNT; i++) { Zones[i] = new unsigned short[ZoneCount]; - result = stream->Read(Zones[i], sizeof(unsigned short) * ZoneCount, NULL); + stream.Serialize_Bytes(Zones[i], (int)(sizeof(unsigned short) * ZoneCount)); + result = stream.Result(); if (FAILED(result)) { return(result); } } - savestream.Serialize(ZoneConnections); - result = savestream.Result(); + stream.Serialize(ZoneConnections); + result = stream.Result(); if (FAILED(result)) { return(result); } @@ -502,13 +505,14 @@ HRESULT MouseClass::Load(IStream * stream) Array[i] = NULL; } int count; - result = stream->Read(&count, sizeof(count), NULL); + stream.Serialize(count); + result = stream.Result(); if (FAILED(result)) { return(result); } for (i = 0; i < count; i++) { LPVOID ptr; - OleLoadFromStream(stream, IID_IUnknown, &ptr); + Load_Object(stream, IID_IUnknown, &ptr); } TerrainTypeClass::Init(Scen->Theater); @@ -535,10 +539,10 @@ HRESULT MouseClass::Load(IStream * stream) /// Saves the map layer to a save game stream. /// This routine writes the theater, the members of the whole display chain, the zone tables /// and zone connections, and then every valid cell, in the order that Load expects to find -/// them. The cells persist themselves through OLE, so each one writes its own contents. +/// them. Each cell writes its own contents as a record of its own. /// /// Returns with S_OK if the map was written, otherwise the stream error. -HRESULT MouseClass::Save(IStream * stream) +HRESULT MouseClass::Save(SaveStreamClass & stream) { int i; int count; @@ -546,32 +550,34 @@ HRESULT MouseClass::Save(IStream * stream) HRESULT result = BASECLASS::Save(stream); if (SUCCEEDED(result)) { int theater = Scen->Theater; - result = stream->Write(&theater, sizeof(theater), NULL); + stream.Serialize(theater); + result = stream.Result(); if (FAILED(result)) { return(result); } - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize(savestream); - result = savestream.Result(); + Serialize(stream); + result = stream.Result(); if (FAILED(result)) { return(result); } - result = stream->Write(CellZones, sizeof(*CellZones) * CellZoneCount, NULL); + stream.Serialize_Bytes(CellZones, (int)(sizeof(*CellZones) * CellZoneCount)); + result = stream.Result(); if (FAILED(result)) { return(result); } for (i = 0; i < MZONE_COUNT; i++) { - result = stream->Write(Zones[i], sizeof(unsigned short) * ZoneCount, NULL); + stream.Serialize_Bytes(Zones[i], (int)(sizeof(unsigned short) * ZoneCount)); + result = stream.Result(); if (FAILED(result)) { return(result); } } - savestream.Serialize(ZoneConnections); - result = savestream.Result(); + stream.Serialize(ZoneConnections); + result = stream.Result(); if (FAILED(result)) { return(result); } @@ -586,7 +592,8 @@ HRESULT MouseClass::Save(IStream * stream) } cptr = Iterate(); } - result = stream->Write(&count, sizeof(count), NULL); + stream.Serialize(count); + result = stream.Result(); if (FAILED(result)) { return(result); } @@ -595,7 +602,7 @@ HRESULT MouseClass::Save(IStream * stream) while (cptr != NULL) { Cell cell = cptr->CellID; if (Is_Valid(cell)) { - OleSaveToStream(cptr, stream); + Save_Object(stream, cptr); count--; } cptr = Iterate(); diff --git a/code/mouse.h b/code/mouse.h index 9a2da1d17..a3888c1ea 100644 --- a/code/mouse.h +++ b/code/mouse.h @@ -42,8 +42,8 @@ class MouseClass: public ScrollClass typedef ScrollClass BASECLASS; public: - virtual HRESULT Load(IStream * stream) override; - virtual HRESULT Save(IStream * stream) override; + virtual HRESULT Load(SaveStreamClass & stream) override; + virtual HRESULT Save(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/particle.cpp b/code/particle.cpp index d9c689f6b..5a66b8402 100644 --- a/code/particle.cpp +++ b/code/particle.cpp @@ -930,7 +930,7 @@ void ParticleClass::Serialize(SaveStreamClass & stream) /// The stream to write this particle to. /// Should the modified flag be cleared once written? /// Returns with S_OK if the particle was written successfully. -HRESULT STDMETHODCALLTYPE ParticleClass::Save(IStream * stream, BOOL cleardirty) +HRESULT ParticleClass::Save(SaveStreamClass & stream, BOOL cleardirty) { HRESULT result = BASECLASS::Save(stream, cleardirty); WasSaved = true; diff --git a/code/particle.h b/code/particle.h index b7b32a9c9..bbbe5f55a 100644 --- a/code/particle.h +++ b/code/particle.h @@ -32,7 +32,7 @@ class ParticleClass : public ObjectClass virtual ~ParticleClass(void) override; virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE Save(IStream * stream, BOOL cleardirty) override; + virtual HRESULT Save(SaveStreamClass & stream, BOOL cleardirty) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/ilinkstm.h b/code/persist.h similarity index 54% rename from code/ilinkstm.h rename to code/persist.h index 055b91573..e2cb97908 100644 --- a/code/ilinkstm.h +++ b/code/persist.h @@ -11,19 +11,13 @@ #include -/// Names and comments from TLBs +class SaveStreamClass; -EXTERN_C const IID IID_ILinkStream; - -MIDL_INTERFACE("0D5CD78E-6470-11D2-9B74-00104B972FE8") -ILinkStream : public IUnknown +// Not a COM interface: it has no identifier, and the loader reaches it by dynamic_cast +// from the IUnknown a class factory hands out. +struct IPersistent : public IUnknown { -public: - virtual HRESULT STDMETHODCALLTYPE Link_Stream(IUnknown *stream) = 0; - virtual HRESULT STDMETHODCALLTYPE Unlink_Stream(IUnknown **stream) = 0; + virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * classid) = 0; + virtual HRESULT Load(SaveStreamClass & stream) = 0; + virtual HRESULT Save(SaveStreamClass & stream, BOOL cleardirty) = 0; }; - -/* - * ILinkStream com smart pointer declaration. - */ -//_COM_SMARTPTR_TYPEDEF(ILinkStream, __uuidof(ILinkStream)); diff --git a/code/revent.cpp b/code/revent.cpp index 8d0a51dee..e2406955c 100644 --- a/code/revent.cpp +++ b/code/revent.cpp @@ -366,20 +366,19 @@ void RadarEventClass::Get_Event_Rect(Point2D (& event_rect)[4]) const /// /// The stream to write the radar events to. /// bool; Were the events written successfully? -bool RadarEventClass::Save(IStream * stream) +bool RadarEventClass::Save(SaveStreamClass & stream) { - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); int count = RadarEvents.Count(); - savestream.Serialize(count); + stream.Serialize(count); for (int index = 0; index < count; index++) { - RadarEvents[index]->Serialize(savestream); + RadarEvents[index]->Serialize(stream); } - savestream.Serialize(LastRadarEventCell); + stream.Serialize(LastRadarEventCell); - return(SUCCEEDED(savestream.Result())); + return(SUCCEEDED(stream.Result())); } @@ -390,27 +389,26 @@ bool RadarEventClass::Save(IStream * stream) /// /// The stream to read the radar events from. /// bool; Were the events read successfully? -bool RadarEventClass::Load(IStream * stream) +bool RadarEventClass::Load(SaveStreamClass & stream) { for (int i = RadarEvents.Count() - 1; i >= 0; i--) { delete RadarEvents[i]; RadarEvents.Delete_Index(i); } - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("RadarEventClass"); + stream.Set_Context("RadarEventClass"); int count = 0; - savestream.Serialize(count); + stream.Serialize(count); for (int index = 0; index < count; index++) { RadarEventClass * event = new RadarEventClass(RADAREVENT_NONE, Cell(0, 0)); - event->Serialize(savestream); + event->Serialize(stream); } - savestream.Serialize(LastRadarEventCell); + stream.Serialize(LastRadarEventCell); - return(SUCCEEDED(savestream.Result())); + return(SUCCEEDED(stream.Result())); } diff --git a/code/revent.h b/code/revent.h index 4c3264376..7f1aee6e5 100644 --- a/code/revent.h +++ b/code/revent.h @@ -18,15 +18,15 @@ #include "revent.hh" -struct IStream; +class SaveStreamClass; class SaveStreamClass; template class DynamicVectorClass; class RadarEventClass { public: - static bool Save(IStream * stream); - static bool Load(IStream * stream); + static bool Save(SaveStreamClass & stream); + static bool Load(SaveStreamClass & stream); public: RadarEventClass(RadarEventType event, Cell cell); diff --git a/code/rules.cpp b/code/rules.cpp index 3c2cb6d8e..efa582fb7 100644 --- a/code/rules.cpp +++ b/code/rules.cpp @@ -2061,10 +2061,9 @@ bool RulesClass::Do_Movies(CCINIClass const & ini) /// /// Writes the rule data out to a save game stream. /// -void RulesClass::Save(IStream * stream) +void RulesClass::Save(SaveStreamClass & stream) { - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize(savestream); + Serialize(stream); } @@ -2073,11 +2072,10 @@ void RulesClass::Save(IStream * stream) /// /// Be sure the object heaps have been loaded before calling this routine, since /// the pointer swizzle needs them. -void RulesClass::Load(IStream * stream) +void RulesClass::Load(SaveStreamClass & stream) { - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("RulesClass"); - Serialize(savestream); + stream.Set_Context("RulesClass"); + Serialize(stream); } diff --git a/code/rules.h b/code/rules.h index a4e708736..727ba1815 100644 --- a/code/rules.h +++ b/code/rules.h @@ -137,8 +137,8 @@ class RulesClass bool Do_Movies(CCINIClass const & ini); bool Objects(CCINIClass const & ini); - void Save(IStream * stream); - void Load(IStream * stream); + void Save(SaveStreamClass & stream); + void Load(SaveStreamClass & stream); void Serialize(SaveStreamClass & stream); diff --git a/code/saveload.cpp b/code/saveload.cpp index 0b6b98991..43acedce6 100644 --- a/code/saveload.cpp +++ b/code/saveload.cpp @@ -77,7 +77,6 @@ #include "globals.h" #include "goptions.h" #include "houstype.h" -#include "ilinkstm.h" #include "infantry.h" #include "infatype.h" #include "init.h" @@ -91,10 +90,12 @@ #include "ovrlight.h" #include "particle.h" #include "partsys.h" +#include "persist.h" #include "psystype.h" #include "ptype.h" #include "revent.h" #include "rules.h" +#include "savefile.h" #include "savemgr.h" #include "savestream.h" #include "savever.h" @@ -152,28 +153,133 @@ */ unsigned int ExpectedGameVersion = LoadOptionsClass::GAMEVER_OPENTS; -_COM_SMARTPTR_TYPEDEF(ILinkStream, __uuidof(ILinkStream)); + +/// +/// Writes one object to the save stream as a record of its own. +/// The record is the class identifier, the length of what follows, and whatever the +/// object's Save writes; a reader that does not consume exactly that length has read a +/// record of a different shape than was written. +/// +/// Returns with S_OK, or the failure code of the write that went wrong. +HRESULT Save_Object(SaveStreamClass & stream, IPersistent * persist) +{ + if (persist == NULL) { + return(E_POINTER); + } + + CLSID classid; + HRESULT result = persist->GetClassID(&classid); + if (FAILED(result)) { + return(result); + } + + stream.Serialize_Bytes(&classid, sizeof(classid)); + unsigned int const lengthat = stream.Offset(); + unsigned int length = 0; + stream.Serialize(length); + unsigned int const start = stream.Offset(); + + result = persist->Save(stream, TRUE); + if (FAILED(result)) { + return(result); + } + + length = stream.Offset() - start; + stream.Overwrite_Bytes(lengthat, &length, sizeof(length)); + return(stream.Result()); +} + + +HRESULT Save_Object(SaveStreamClass & stream, ILocomotion * locomotion) +{ + IPersistent * const persist = dynamic_cast(locomotion); + if (persist == NULL) { + return(E_NOINTERFACE); + } + return(Save_Object(stream, persist)); +} /// -/// Loads a vector of persistent objects from the save game stream. -/// This routine reads the element count and then recreates each object through OLE. The -/// objects are not handed back -- each one reattaches itself to its own heap as it is -/// constructed, which is what refills the game's vectors. +/// Recreates one object from the save stream. +/// The object is created through the class factory registered for the identifier the +/// record carries, and reattaches itself to its own heap as it is constructed. /// -/// Returns with S_OK, or the failure code of the read that went wrong. -__forceinline HRESULT Load_Vector(IStream * stream) +/// The interface to hand back, or IID_IUnknown when the caller +/// only needs the object to exist. +/// Receives the interface, or NULL on failure. +/// Returns with S_OK, or the failure code of what went wrong: an identifier no +/// class answers to, a record the object could not read, or one whose length does not +/// match what the object consumed. +HRESULT Load_Object(SaveStreamClass & stream, REFIID riid, void ** object) { - int count; - int index; - LPVOID obj; + if (object != NULL) { + *object = NULL; + } + + CLSID classid; + unsigned int length = 0; + stream.Serialize_Bytes(&classid, sizeof(classid)); + stream.Serialize(length); + if (stream.Was_Error()) { + return(stream.Result()); + } + + unsigned int const start = stream.Offset(); + if (length > stream.Size() - start) { + DebugString("Save record at %u claims %u bytes, past the end of the save\n", start, length); + return(E_FAIL); + } - HRESULT result = stream->Read(&count, sizeof(count), NULL); + IUnknown * unknown = NULL; + HRESULT result = CoCreateInstance(classid, NULL, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, + IID_IUnknown, (LPVOID *)&unknown); if (FAILED(result)) { + DebugString("Save record at %u names a class this build does not register\n", start); return(result); } - for (index = 0; index < count; index++) { - result = OleLoadFromStream(stream, IID_IUnknown, &obj); + + IPersistent * const persist = dynamic_cast(unknown); + if (persist == NULL) { + unknown->Release(); + return(E_NOINTERFACE); + } + + result = persist->Load(stream); + if (SUCCEEDED(result) && stream.Offset() != start + length) { + DebugString("Save record of %s at %u is %u bytes but %u were read\n", + typeid(*unknown).name(), start, length, stream.Offset() - start); + result = E_FAIL; + } + if (SUCCEEDED(result) && object != NULL) { + result = unknown->QueryInterface(riid, object); + } + + unknown->Release(); + return(result); +} + + +/// +/// Loads a vector of persistent objects from the save game stream. +/// The objects are not handed back -- each one reattaches itself to its own heap as it is +/// constructed, which is what refills the game's vectors. +/// +/// Returns with S_OK, or the failure code of the read that went wrong. +static HRESULT Load_Vector(SaveStreamClass & stream) +{ + int count = 0; + stream.Serialize(count); + if (stream.Was_Error()) { + return(stream.Result()); + } + if (count < 0) { + return(E_FAIL); + } + + for (int index = 0; index < count; index++) { + LPVOID obj; + HRESULT const result = Load_Object(stream, IID_IUnknown, &obj); if (FAILED(result)) { return(result); } @@ -184,36 +290,25 @@ __forceinline HRESULT Load_Vector(IStream * stream) /// /// Saves a vector of persistent objects to the save game stream. -/// This routine writes the element count and then streams out each object in turn through -/// its IPersistStream interface. /// /// Returns with S_OK, or the failure code of the first object that refused to /// save. template -__forceinline HRESULT Save_Vector(IStream * stream, const DynamicVectorClass &list) +static HRESULT Save_Vector(SaveStreamClass & stream, const DynamicVectorClass &list) { int count = list.Count(); - HRESULT result = stream->Write(&count, sizeof(count), NULL); - if (SUCCEEDED(result)) { - for (int index = 0; index < count; index++) { - LPPERSISTSTREAM lpPS = NULL; - result = list[index]->QueryInterface(IID_IPersistStream, (LPVOID *)&lpPS); - if (FAILED(result)) { - return(result); - } - result = OleSaveToStream(lpPS, stream); - if (FAILED(result)) { - return(result); - } - result = lpPS->Release(); - if (FAILED(result)) { - return(result); - } + stream.Serialize(count); + + for (int index = 0; index < count; index++) { + HRESULT const result = Save_Object(stream, list[index]); + if (FAILED(result)) { + return(result); } - result = S_OK; } - return(result); + return(stream.Result()); } + + /// /// Builds a checksum over the whole of the game object state. /// This routine walks the scenario and every object and type heap, folding each one's own @@ -299,7 +394,7 @@ void Print_Heap_CRCs(FILE * fp) * HISTORY: * * 07/08/1996 JLB : Created. * *=============================================================================================*/ -static bool Put_All(IStream *stream, int save_net) +static bool Put_All(SaveStreamClass & stream, int save_net) { /* ** Save the scenario global information. @@ -348,7 +443,7 @@ static bool Put_All(IStream *stream, int save_net) } DebugString("Saving TacticalMap\n"); - if (FAILED(OleSaveToStream(TacticalMap, stream))) { + if (FAILED(Save_Object(stream, TacticalMap))) { DebugString("\t***** FAILED!\n"); return(false); } @@ -626,7 +721,7 @@ static bool Put_All(IStream *stream, int save_net) } } - return(true); + return(!stream.Was_Error()); } @@ -638,7 +733,7 @@ static bool Put_All(IStream *stream, int save_net) /// order they were written out. /// /// bool; Was the game state restored? -static bool Get_All(IStream *stream, bool save_net) +static bool Get_All(SaveStreamClass & stream, bool save_net) { Clear_Scenario(); Scen->Load(stream); @@ -705,7 +800,7 @@ static bool Get_All(IStream *stream, bool save_net) TacticalMap = NULL; } Tactical * old_tactical; - if (FAILED(OleLoadFromStream(stream, IID_IUnknown, (LPVOID *)&old_tactical))) { + if (FAILED(Load_Object(stream, IID_IUnknown, (LPVOID *)&old_tactical))) { return(false); } @@ -873,7 +968,7 @@ static bool Get_All(IStream *stream, bool save_net) Map.Flag_To_Redraw(GS_REDRAW_ALL); - return(true); + return(!stream.Was_Error()); } /*************************************************************************** @@ -917,32 +1012,8 @@ static bool Get_All(IStream *stream, bool save_net) *=========================================================================*/ bool Save_Game(const char *file_name, char const * descr) { - WCHAR name[MAX_PATH]; - DebugString("\nSAVING GAME [%s - %s]\n", file_name, descr); - MultiByteToWideChar(0,0, Saved_Game_Name(file_name).c_str(), -1, name, sizeof(name)/sizeof(WCHAR)); - - /* - ** Open the file - */ - DebugString("Creating DocFile\n"); - IStoragePtr storage; - if (FAILED(StgCreateDocfile(name, STGM_CREATE|STGM_SHARE_EXCLUSIVE|STGM_READWRITE, 0, &storage))) { - DebugString("\t***** FAILED!\n"); - return(false); - } - - - /* - ** Save the description, scenario #, and house - ** (scenario # & house are saved separately from the actual Scenario & - ** PlayerPtr globals for convenience; we can quickly find out which - ** house & scenario this save-game file is for by reading these values. - ** Also, PlayerPtr is stored in a coded form in Save_Misc_Values(), - ** which may or may not be a HousesType number; so, saving 'house' - ** here ensures we can always pull out the house for this file.) - */ SaveVersionInfo info; info.Set_Internal_Version(ExpectedGameVersion); info.Set_Scenario_Description(descr); @@ -952,66 +1023,37 @@ bool Save_Game(const char *file_name, char const * descr) info.Set_Scenario_Number(Scen->Scenario); info.Set_Executable_Name("SUN.EXE"); info.Set_Game_Type(Session.Type); + FILETIME FileTime; CoFileTimeNow(&FileTime); info.Set_Last_Time(FileTime); info.Set_Start_Time(FileTime); info.Set_Play_Time(FileTime); - /* - ** Save the save-game version, for loading verification - */ - DebugString("Saving version information\n"); - if (FAILED(info.Save(storage))) { - DebugString("\t***** FAILED!\n"); - return(false); - } - - DebugString("Creating content stream\n"); - IStreamPtr content; - if (FAILED(storage->CreateStream(L"CONTENTS", STGM_CREATE|STGM_SHARE_EXCLUSIVE|STGM_READWRITE, 0, 0, &content))) { - DebugString("\t***** FAILED!\n"); - return(false); - } - - DebugString("Linking content stream to compressor\n"); - ILinkStreamPtr link; - link.CreateInstance(CLSID_CompressStream, NULL, CLSCTX_INPROC|CLSCTX_LOCAL_SERVER); - if (FAILED(link->Link_Stream(content))) { - DebugString("\t***** FAILED!\n"); - return(false); - } - IStreamPtr stream(link); + SaveFileClass file; + info.Save(file); - /* - ** Dump the save game data to the file. The data is compressed - ** and then encrypted. The message digest is calculated in the - ** process by using the data just as it is written to disk. - */ DebugString("Calling Put_All()\n"); - bool res = Put_All(stream,0); - - DebugString("Unlinking content stream from compressor\n"); - if (FAILED(link->Unlink_Stream(NULL))) { - DebugString("\t***** FAILED!\n"); - return(false); + SaveStreamClass stream(file.Content, SaveStreamClass::MODE_SAVE); + bool res = Put_All(stream, 0); + if (!res) { + DebugString("\t***** FAILED! (0x%08lx)\n", (unsigned long)stream.Result()); } - DebugString("Releasing content stream\n"); - content.Release(); - - DebugString("Closing DocFile\n"); - if (FAILED(storage->Commit(0))) { - DebugString("\t***** FAILED!\n"); - return(false); + if (res) { + DebugString("Writing %s\n", file_name); + SaveFileClass::ResultType const result = file.Write(Saved_Game_Name(file_name).c_str()); + if (result != SaveFileClass::RESULT_OK) { + DebugString("\t***** FAILED! (%s)\n", SaveFileClass::Result_Text(result)); + res = false; + } } - DebugString("SAVING GAME [%s - %s] - Complete\n\n", file_name, descr); + DebugString("SAVING GAME [%s - %s] - %s\n\n", file_name, descr, res ? "Complete" : "Failed"); if (res) { SaveManager.Autosave.Schedule(Frame); } - return(res); } @@ -1058,62 +1100,39 @@ bool Save_Game(const char *file_name, char const * descr) *=========================================================================*/ bool Load_Game(const char *file_name) { - WCHAR name[MAX_PATH]; - DebugString("\nLOADING GAME [%s]\n", file_name); - /* - ** Read & discard the save-game's header info - */ SaveVersionInfo info; if (!Get_Savefile_Info(file_name, &info)) { return(false); } - - /* - * The load dialog screens the saves it lists, but a network save reaches this routine - * without passing through it, so the stamp is checked here as well. - */ if (info.Get_Internal_Version() != ExpectedGameVersion) { return(false); } - LoadedSaveVersion = info.Get_Internal_Version(); - Session.Type = (GameType)info.Get_Game_Type(); - Swizzler.Discard(); - - /* - ** Open the file - */ - IStoragePtr storage; - - // Structured storage goes straight to Windows, so the saved game is named in full first. - MultiByteToWideChar(0,0,Saved_Game_Name(file_name).c_str(), -1, name, (sizeof(name)/sizeof(WCHAR))); - - if (FAILED(StgOpenStorage(name, 0, STGM_SHARE_DENY_WRITE, 0, 0, &storage))) { + // The whole file is checked before the running game is torn down, so a damaged + // save costs nothing. + SaveFileClass file; + SaveFileClass::ResultType const result = file.Read(Saved_Game_Name(file_name).c_str()); + if (result != SaveFileClass::RESULT_OK) { + DebugString("\t***** FAILED! (%s)\n", SaveFileClass::Result_Text(result)); return(false); } - IStreamPtr content; - if (FAILED(storage->OpenStream(L"CONTENTS", 0, STGM_SHARE_EXCLUSIVE, 0, &content))) { - return(false); - } + LoadedSaveVersion = info.Get_Internal_Version(); + Session.Type = (GameType)info.Get_Game_Type(); - IUnknown *pUnknown = NULL; - ILinkStreamPtr link; - link.CreateInstance(CLSID_CompressStream, pUnknown,CLSCTX_INPROC|CLSCTX_LOCAL_SERVER); - if (FAILED(link->Link_Stream(content))) { - return(false); - } - IStreamPtr stream(link); + Swizzler.Discard(); + SaveStreamClass stream(file.Content, SaveStreamClass::MODE_LOAD); bool res = Get_All(stream, false); - - link->Unlink_Stream(NULL); - if (!res) { + DebugString("\t***** FAILED! (0x%08lx at %u of %u bytes)\n", (unsigned long)stream.Result(), stream.Offset(), stream.Size()); return(false); } + if (stream.Offset() != stream.Size()) { + DebugString("Save carries %u bytes past its last record\n", stream.Size() - stream.Offset()); + } Swizzler.Resolve(); @@ -1207,11 +1226,10 @@ static void Serialize_Misc_Values(SaveStreamClass & stream) } -int Save_Misc_Values(IStream * stream) +int Save_Misc_Values(SaveStreamClass & stream) { - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize_Misc_Values(savestream); - return(savestream.Result()); + Serialize_Misc_Values(stream); + return(stream.Result()); } @@ -1228,12 +1246,11 @@ int Save_Misc_Values(IStream * stream) * 06/24/1995 BRR : Created. * * 03/12/1996 JLB : Simplified. * *=============================================================================================*/ -int Load_Misc_Values(IStream * stream) +int Load_Misc_Values(SaveStreamClass & stream) { - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("Load_Misc_Values"); - Serialize_Misc_Values(savestream); - return(savestream.Result()); + stream.Set_Context("Load_Misc_Values"); + Serialize_Misc_Values(stream); + return(stream.Result()); } @@ -1257,23 +1274,20 @@ int Load_Misc_Values(IStream * stream) *=========================================================================*/ bool Get_Savefile_Info(char const * name, SaveVersionInfo * info) { - IStoragePtr storage; - WCHAR wname[MAX_PATH]; - - // Structured storage goes straight to Windows, so the saved game is named in full first. - MultiByteToWideChar(0, 0, Saved_Game_Name(name).c_str(), -1, wname, sizeof(wname) / sizeof(WCHAR)); - - HRESULT result = StgOpenStorage(wname, NULL, STGM_SHARE_EXCLUSIVE|STGM_READWRITE, NULL, 0, &storage); - if (FAILED(result)) { + if (name == NULL || info == NULL) { return(false); } - result = info->Load(storage); - if (FAILED(result)) { + SaveFileClass file; + SaveFileClass::ResultType const result = file.Read_Fields(Saved_Game_Name(name).c_str()); + if (result != SaveFileClass::RESULT_OK) { + if (result != SaveFileClass::RESULT_MISSING) { + DebugString("Saved game %s: %s\n", name, SaveFileClass::Result_Text(result)); + } return(false); } - return(true); + return(info->Load(file)); } diff --git a/code/saveload.h b/code/saveload.h index c92bc5df8..b9b686118 100644 --- a/code/saveload.h +++ b/code/saveload.h @@ -13,16 +13,24 @@ #pragma once +#include "persist.h" + #include -struct IStream; +class SaveStreamClass; class SaveVersionInfo; +struct ILocomotion; /* ** SAVELOAD.CPP */ -int Load_Misc_Values(IStream * stream); -int Save_Misc_Values(IStream * stream); +int Load_Misc_Values(SaveStreamClass & stream); +int Save_Misc_Values(SaveStreamClass & stream); + +// An object travels as its class identifier, the length of its record, and the record. +HRESULT Save_Object(SaveStreamClass & stream, IPersistent * object); +HRESULT Save_Object(SaveStreamClass & stream, ILocomotion * locomotion); +HRESULT Load_Object(SaveStreamClass & stream, REFIID riid, void ** object); bool Get_Savefile_Info(char const * name, SaveVersionInfo * info); bool Save_Game(const char *file_name, char const * descr); bool Load_Game(const char *file_name); diff --git a/code/savestream.cpp b/code/savestream.cpp index fd15e72f6..c6f9e5915 100644 --- a/code/savestream.cpp +++ b/code/savestream.cpp @@ -14,19 +14,23 @@ #include "saveload.h" +#include + unsigned int LoadedSaveVersion = 0; /// -/// Builds a save stream over the stream given. +/// Builds a save stream over the buffer given, appending to it when saving and reading +/// it from the start when loading. /// -/// The stream the members are to be read from or written to. +/// The bytes of the saved game, which must outlive this stream. /// Is this stream saving or loading? -SaveStreamClass::SaveStreamClass(IStream * stream, ModeType mode) : - Stream(stream), +SaveStreamClass::SaveStreamClass(std::vector & buffer, ModeType mode) : + Buffer(&buffer), + Cursor(mode == MODE_SAVE ? (unsigned int)buffer.size() : 0), Mode(mode), - ErrorCode(stream != NULL ? S_OK : E_POINTER), + ErrorCode(S_OK), FormatVersion(mode == MODE_LOAD ? LoadedSaveVersion : ExpectedGameVersion), OwnerType(NULL), OwnerID(0) @@ -34,12 +38,6 @@ SaveStreamClass::SaveStreamClass(IStream * stream, ModeType mode) : } -/// -/// Stops the pass, as though the stream itself had failed. -/// This is for a record that reads back as something no save could hold -- a length that -/// is negative, or one that does not fit the object waiting for it. An earlier failure is -/// left in place, since it is the one that explains the rest. -/// void SaveStreamClass::Fail(void) { if (SUCCEEDED(ErrorCode)) { @@ -49,36 +47,49 @@ void SaveStreamClass::Fail(void) /// -/// Moves a block of bytes between the object and the stream. -/// Every other Serialize reaches the stream through this one. Once something has gone -/// wrong the block is left alone and the failure is kept, so the rest of the pass runs -/// harmlessly and the caller finds out at the end. +/// Moves the bytes of one value between the caller and the stream. +/// A load that runs out of stream in the middle of a value fails the stream rather than +/// hand back a partly read value, and every later call is ignored. A negative length is +/// a count that wrapped, and fails the same way. /// -/// The bytes to write, or the place to read them back into. -/// The number of bytes to move. void SaveStreamClass::Serialize_Bytes(void * data, int length) { if (FAILED(ErrorCode)) { return; } + if (length < 0) { + ErrorCode = E_FAIL; + return; + } + if (length == 0) { + return; + } - ULONG moved = 0; - HRESULT result; + unsigned char * const bytes = (unsigned char *)data; if (Mode == MODE_SAVE) { - result = Stream->Write(data, length, &moved); + Buffer->insert(Buffer->end(), bytes, bytes + length); + Cursor = (unsigned int)Buffer->size(); } else { - result = Stream->Read(data, length, &moved); + if ((unsigned int)length > Buffer->size() - Cursor) { + ErrorCode = E_FAIL; + return; + } + memcpy(bytes, Buffer->data() + Cursor, (std::size_t)length); + Cursor += (unsigned int)length; } +} - /* - * A stream that stops early has run out in the middle of an object, which leaves - * the rest of the members holding whatever they held before. Treat it as a failure - * rather than let a half-read object reach the game. - */ - if (SUCCEEDED(result) && moved != (ULONG)length) { - result = E_FAIL; - } - ErrorCode = result; +// A saver patches a length it could not know until the record was written. +void SaveStreamClass::Overwrite_Bytes(unsigned int offset, void const * data, int length) +{ + if (FAILED(ErrorCode) || Mode != MODE_SAVE || length <= 0) { + return; + } + if (offset > Buffer->size() || (unsigned int)length > Buffer->size() - offset) { + ErrorCode = E_FAIL; + return; + } + memcpy(Buffer->data() + offset, data, (std::size_t)length); } diff --git a/code/savestream.h b/code/savestream.h index 735a12db9..0ffba91aa 100644 --- a/code/savestream.h +++ b/code/savestream.h @@ -73,7 +73,7 @@ class SaveStreamClass MODE_LOAD }; - SaveStreamClass(IStream * stream, ModeType mode); + SaveStreamClass(std::vector & buffer, ModeType mode); bool Is_Saving(void) const {return(Mode == MODE_SAVE);} bool Is_Loading(void) const {return(Mode == MODE_LOAD);} @@ -99,11 +99,6 @@ class SaveStreamClass */ unsigned int Version(void) const {return(FormatVersion);} - /* - * The stream underneath, for the sub-objects that are still framed by OLE. - */ - IStream * Get_Stream(void) const {return(Stream);} - /* * Names the record this stream is carrying, so that a pointer which nothing * answers for can be reported against the object that asked for it. @@ -113,9 +108,35 @@ class SaveStreamClass OwnerType = ownertype; OwnerID = ownerid; } + char const * Context_Type(void) const {return(OwnerType);} + uintptr_t Context_ID(void) const {return(OwnerID);} + + /* + * Where the next byte goes or comes from, so a record can be framed by its length. + */ + unsigned int Offset(void) const {return(Cursor);} + unsigned int Size(void) const {return((unsigned int)Buffer->size());} + void Overwrite_Bytes(unsigned int offset, void const * data, int length); void Serialize_Bytes(void * data, int length); + /* + * Refuses a count that the bytes left in the stream could not hold, so a damaged + * count fails the load before anything is allocated for it. Nothing serializes + * an element in less than a byte. + */ + bool Fits(int count, std::size_t each) + { + if (Is_Loading()) { + std::size_t const room = (std::size_t)(Buffer->size() - Cursor) / (each > 0 ? each : 1); + if (count < 0 || (std::size_t)count > room) { + Fail(); + return(false); + } + } + return(true); + } + /* * Numbers and enumerations travel as their declared width. */ @@ -202,8 +223,7 @@ class SaveStreamClass Serialize(count); if (Is_Loading()) { - if (count < 0) { - Fail(); + if (!Fits(count, (std::is_arithmetic_v || std::is_enum_v) ? sizeof(T) : 1)) { return; } value.clear(); @@ -253,8 +273,7 @@ class SaveStreamClass Serialize(count); if (Is_Loading()) { - if (count < 0) { - Fail(); + if (!Fits(count, 1)) { return; } value.clear(); @@ -277,8 +296,7 @@ class SaveStreamClass Serialize(count); if (Is_Loading()) { - if (count < 0) { - Fail(); + if (!Fits(count, 1)) { return; } value.assign((std::size_t)count, false); @@ -300,8 +318,7 @@ class SaveStreamClass Serialize(count); if (Is_Loading()) { - if (count < 0) { - Fail(); + if (!Fits(count, 1)) { return; } value.resize(count); @@ -337,7 +354,8 @@ class SaveStreamClass } } - IStream * Stream; + std::vector * Buffer; + unsigned int Cursor; ModeType Mode; HRESULT ErrorCode; unsigned int FormatVersion; @@ -364,8 +382,7 @@ class SaveStreamClass /* - * The version stamp of the save game currently being read. Each object builds its own - * stream inside IPersistStream::Load, which has no way to be told, so the value is left - * here by the load as a whole. + * The version stamp of the save game currently being read, left here by the load as a + * whole so every stream built during it reports the same version. */ extern unsigned int LoadedSaveVersion; diff --git a/code/savever.cpp b/code/savever.cpp index 414bb7ce6..50d67725e 100644 --- a/code/savever.cpp +++ b/code/savever.cpp @@ -11,13 +11,11 @@ #include "savever.h" -#include "utf8.h" +#include "savefile.h" #include "dbgprint.h" #include "session.h" -#include - /// /// Creates an empty save file information block. @@ -320,796 +318,43 @@ int SaveVersionInfo::Get_Game_Type(void) /// -/// Saves the version information into a save file. -/// This routine is called while a save game is being written. It records every value into -/// the summary information property set and then again as one stream per value, so that a -/// reader which knows only the older layout can still identify the save. -/// -/// Returns with S_OK once every value has been written, otherwise the failure code -/// from the storage layer. -HRESULT SaveVersionInfo::Save(IStorage *storage) -{ - if (storage == NULL) { - return(E_POINTER); - } - - DebugString("Attempting to obtain PropertySetStorage interface\n"); - - IPropertySetStoragePtr storageset; - HRESULT res; - - res = storage->QueryInterface(IID_IPropertySetStorage, (void **)&storageset); - if (SUCCEEDED(res)) { - - DebugString("Saving version information the NEW way.\n"); - - res = Save_String_Set(storageset, PIDSI_SCEN_DESCRIP, ScenarioDescription); - if (FAILED(res)) { - return(res); - } - - res = Save_String_Set(storageset, PIDSI_PLAYER_HOUSE, PlayerHouse); - if (FAILED(res)) { - return(res); - } - - res = Save_Int_Set(storageset, PIDSI_G_VERSION, Version); - if (FAILED(res)) { - return(res); - } - - res = Save_Int_Set(storageset, PIDSI_INTERNAL_VER, InternalVersion); - if (FAILED(res)) { - return(res); - } - - res = Save_Time_Set(storageset, PIDSI_G_START_TIME, &StartTime); - if (FAILED(res)) { - return(res); - } - - res = Save_Time_Set(storageset, PIDSI_LAST_SAVE_TIME, &LastSaveTime); - if (FAILED(res)) { - return(res); - } - - res = Save_Time_Set(storageset, PIDSI_G_PLAY_TIME, &PlayTime); - if (FAILED(res)) { - return(res); - } - - res = Save_String_Set(storageset, PIDSI_EXEC_NAME, ExecutableName); - if (FAILED(res)) { - return(res); - } - - res = Save_String_Set(storageset, PIDSI_PLAYER_NAME1, PlayerName); - if (FAILED(res)) { - return(res); - } - - res = Save_String_Set(storageset, PIDSI_PLAYER_NAME2, PlayerName); - if (FAILED(res)) { - return(res); - } - - res = Save_Int_Set(storageset, PIDSI_SCENARIO_NUM, ScenarioNumber); - if (FAILED(res)) { - return(res); - } - - res = Save_Int_Set(storageset, PIDSI_CAMPAIGN_NUM, CampaignNumber); - if (FAILED(res)) { - return(res); - } - - res = Save_Int_Set(storageset, PIDSI_GAME_TYPE, GameType); - if (FAILED(res)) { - return(res); - } - - } else { - DebugString("\t***** FAILED!\n"); - } - - DebugString("Saving version information the old way.\n"); - - res = Save_String(storage, PIDSI_SCEN_DESCRIP, ScenarioDescription); - if (FAILED(res)) { - return(res); - } - - res = Save_String(storage, PIDSI_PLAYER_HOUSE, PlayerHouse); - if (FAILED(res)) { - return(res); - } - - res = Save_Int(storage, PIDSI_G_VERSION, Version); - if (FAILED(res)) { - return(res); - } - - res = Save_Int(storage, PIDSI_INTERNAL_VER, InternalVersion); - if (FAILED(res)) { - return(res); - } - - res = Save_Time(storage, PIDSI_G_START_TIME, &StartTime); - if (FAILED(res)) { - return(res); - } - - res = Save_Time(storage, PIDSI_LAST_SAVE_TIME, &LastSaveTime); - if (FAILED(res)) { - return(res); - } - - res = Save_Time(storage, PIDSI_G_PLAY_TIME, &PlayTime); - if (FAILED(res)) { - return(res); - } - - res = Save_String(storage, PIDSI_EXEC_NAME, ExecutableName); - if (FAILED(res)) { - return(res); - } - - res = Save_String(storage, PIDSI_PLAYER_NAME1, PlayerName); - if (FAILED(res)) { - return(res); - } - - res = Save_String(storage, PIDSI_PLAYER_NAME2, PlayerName); - if (FAILED(res)) { - return(res); - } - - res = Save_Int(storage, PIDSI_SCENARIO_NUM, ScenarioNumber); - if (FAILED(res)) { - return(res); - } - - res = Save_Int(storage, PIDSI_CAMPAIGN_NUM, CampaignNumber); - if (FAILED(res)) { - return(res); - } - - res = Save_Int(storage, PIDSI_GAME_TYPE, GameType); - if (FAILED(res)) { - return(res); - } - - return(S_OK); -} - - -/// -/// Loads the version information out of a save file. -/// This routine is called when a save game is scanned or restored. It prefers the property -/// set that the current game writes and falls back to the one stream per value layout that -/// older save files use, so that both generations of save file stay readable. -/// -/// Returns with S_OK once every value has been recovered, otherwise the failure -/// code from the storage layer. -HRESULT SaveVersionInfo::Load(IStorage *storage) -{ - if (storage == NULL) { - return(E_POINTER); - } - - IPropertySetStoragePtr storageset; - HRESULT res; - - if (SUCCEEDED(storage->QueryInterface(IID_IPropertySetStorage, (void **)&storageset)) - && SUCCEEDED(Load_String_Set(storageset, PIDSI_SCEN_DESCRIP, ScenarioDescription, sizeof(ScenarioDescription)))) { - - - res = Load_String_Set(storageset, PIDSI_PLAYER_HOUSE, PlayerHouse, sizeof(PlayerHouse)); - if (FAILED(res)) { - return(res); - } - - res = Load_Int_Set(storageset, PIDSI_G_VERSION, &Version); - if (FAILED(res)) { - return(res); - } - - res = Load_Int_Set(storageset, PIDSI_INTERNAL_VER, &InternalVersion); - if (FAILED(res)) { - return(res); - } - - res = Load_Time_Set(storageset, PIDSI_G_START_TIME, &StartTime); - if (FAILED(res)) { - return(res); - } - - res = Load_Time_Set(storageset, PIDSI_LAST_SAVE_TIME, &LastSaveTime); - if (FAILED(res)) { - return(res); - } - - res = Load_Time_Set(storageset, PIDSI_G_PLAY_TIME, &PlayTime); - if (FAILED(res)) { - return(res); - } - - res = Load_String_Set(storageset, PIDSI_EXEC_NAME, ExecutableName, sizeof(ExecutableName)); - if (FAILED(res)) { - return(res); - } - - res = Load_String_Set(storageset, PIDSI_PLAYER_NAME1, PlayerName, sizeof(PlayerName)); - if (FAILED(res)) { - return(res); - } - - res = Load_Int_Set(storageset, PIDSI_SCENARIO_NUM, &ScenarioNumber); - if (FAILED(res)) { - return(res); - } - - res = Load_Int_Set(storageset, PIDSI_CAMPAIGN_NUM, &CampaignNumber); - if (FAILED(res)) { - return(res); - } - - res = Load_Int_Set(storageset, PIDSI_GAME_TYPE, &GameType); - if (FAILED(res)) { - return(res); - } - - } else { - - res = Load_String(storage, PIDSI_SCEN_DESCRIP, ScenarioDescription, sizeof(ScenarioDescription)); - if (FAILED(res)) { - return(res); - } - - - res = Load_String(storage, PIDSI_PLAYER_HOUSE, PlayerHouse, sizeof(PlayerHouse)); - if (FAILED(res)) { - return(res); - } - - - res = Load_Int(storage, PIDSI_G_VERSION, &Version); - if (FAILED(res)) { - return(res); - } - - res = Load_Int(storage, PIDSI_INTERNAL_VER, &InternalVersion); - if (FAILED(res)) { - return(res); - } - - res = Load_Time(storage, PIDSI_G_START_TIME, &StartTime); - if (FAILED(res)) { - return(res); - } - - res = Load_Time(storage, PIDSI_LAST_SAVE_TIME, &LastSaveTime); - if (FAILED(res)) { - return(res); - } - - res = Load_Time(storage, PIDSI_G_PLAY_TIME, &PlayTime); - if (FAILED(res)) { - return(res); - } - - res = Load_String(storage, PIDSI_EXEC_NAME, ExecutableName, sizeof(ExecutableName)); - if (FAILED(res)) { - return(res); - } - - res = Load_String(storage, PIDSI_PLAYER_NAME1, PlayerName, sizeof(PlayerName)); - if (FAILED(res)) { - return(res); - } - - res = Load_Int(storage, PIDSI_SCENARIO_NUM, &ScenarioNumber); - if (FAILED(res)) { - return(res); - } - - res = Load_Int(storage, PIDSI_CAMPAIGN_NUM, &CampaignNumber); - if (FAILED(res)) { - return(res); - } - - res = Load_Int(storage, PIDSI_GAME_TYPE, &GameType); - if (FAILED(res)) { - return(res); - } - } - - return(S_OK); -} - - -/// -/// Reads a string from a stream of its own. -/// The wide text held in the stream is narrowed into the caller's buffer, which is emptied -/// first. This is the old style counterpart of Load_String_Set, used for save files written -/// before the version information moved into a property set. -/// -/// The property identifier naming the stream to open. -/// Returns with the result of the read. A failure means the stream is absent or -/// ended before the text was terminated. -/// The capacity of string; longer text is cut on a character boundary. -HRESULT SaveVersionInfo::Load_String(IStorage *storage, int id, char *string, int size) -{ - *string = '\0'; - - HRESULT res; - IStreamPtr stm; - - res = storage->OpenStream(Stream_Name_From_ID(id), NULL, STGM_SHARE_EXCLUSIVE, 0, &stm); - if (FAILED(res)) { - return(res); - } - - WCHAR buf[128]; - ULONG count; - - int i = 0; - for (; i < ARRAY_SIZE(buf); i++) { - res = stm->Read(&buf[i], sizeof(buf[i]), &count); - if (FAILED(res)) { - return(res); - } - if (res != S_OK || count != sizeof(buf[i])) { - return(E_FAIL); - } - if (buf[i] == '\0') { - break; - } - } - - if (i == ARRAY_SIZE(buf)) { - return(E_FAIL); - } - - char text[512]; - if (WideCharToMultiByte(CP_ACP, 0, buf, -1, text, sizeof(text), 0, 0) == 0) { - text[0] = '\0'; - } - UTF8::Copy(string, size, text); - - return(S_OK); -} - - -/// -/// Reads a string from the save file's property set. -/// The wide text held in the property is narrowed back into the caller's buffer. That buffer -/// is emptied before the read is attempted, so a missing property yields an empty string. -/// -/// The summary information property identifier to read. -/// Returns with the result of the read. A failure means the property set could not -/// be opened. -/// The capacity of string; longer text is cut on a character boundary. -HRESULT SaveVersionInfo::Load_String_Set(IPropertySetStorage *storageset, int id, char *string, int size) -{ - *string = '\0'; - - HRESULT res; - IPropertyStoragePtr storage; - - res = storageset->Open(FMTID_SummaryInformation, STGM_SHARE_EXCLUSIVE|STGM_READWRITE, &storage); - if (FAILED(res)) { - return(res); - } - - PROPSPEC propsec; - propsec.ulKind = PRSPEC_PROPID; - propsec.propid = id; - PROPVARIANT propvar; - - res = storage->ReadMultiple(1, &propsec, &propvar); - if (FAILED(res)) { - return(res); - } - - if (propvar.vt == VT_LPWSTR) { - char text[1024]; - if (WideCharToMultiByte(CP_ACP, 0, propvar.pwszVal, -1, text, sizeof(text), 0, 0) == 0) { - text[0] = '\0'; - } - UTF8::Copy(string, size, text); - } - - return(res); -} - - -/// -/// Reads an integer from a stream of its own. -/// This is the old style counterpart of Load_Int_Set, used for save files written before -/// the version information moved into a property set. The value is cleared first. -/// -/// The property identifier naming the stream to open. -/// Returns with the result of the read. A failure means the stream is absent. -HRESULT SaveVersionInfo::Load_Int(IStorage *storage, int id, int *integer) -{ - *integer = 0; - - HRESULT res; - IStreamPtr stm; - - res = storage->OpenStream(Stream_Name_From_ID(id), NULL, STGM_SHARE_EXCLUSIVE, 0, &stm); - if (FAILED(res)) { - return(res); - } - - res = stm->Read(integer, sizeof(*integer), NULL); - if (FAILED(res)) { - return(res); - } - - return(res); -} - - -/// -/// Reads an integer from the save file's property set. -/// The value is cleared before the read is attempted, so a save file that does not carry -/// the property leaves the caller with zero. -/// -/// The summary information property identifier to read. -/// Returns with the result of the read. A failure means the property set could not -/// be opened. -HRESULT SaveVersionInfo::Load_Int_Set(IPropertySetStorage *storageset, int id, int *integer) -{ - *integer = 0; - - HRESULT res; - IPropertyStoragePtr storage; - - res = storageset->Open(FMTID_SummaryInformation, STGM_SHARE_EXCLUSIVE|STGM_READWRITE, &storage); - if (FAILED(res)) { - return(res); - } - - PROPSPEC propsec; - propsec.ulKind = PRSPEC_PROPID; - propsec.propid = id; - PROPVARIANT propvar; - - res = storage->ReadMultiple(1, &propsec, &propvar); - if (FAILED(res)) { - return(res); - } - - if (propvar.vt == VT_I4) { - *integer = propvar.lVal; - } - - return(res); -} - - -/// -/// Writes a string to a stream of its own. -/// The text is widened before it is written. This is the old style counterpart of -/// Save_String_Set, kept for readers that do not understand property sets. -/// -/// The property identifier naming the stream to create. -/// Returns with the result of the write. A failure means the stream was never -/// committed. -HRESULT SaveVersionInfo::Save_String(IStorage *storage, int id, char *string) -{ - WCHAR buf[260]; - - if (MultiByteToWideChar(CP_ACP, 0, string, -1, buf, ARRAY_SIZE(buf)) == 0) { - buf[0] = L'\0'; - } - - IStreamPtr stm(NULL); - - HRESULT res = storage->CreateStream(Stream_Name_From_ID(id), STGM_SHARE_EXCLUSIVE|STGM_READWRITE, 0, 0, &stm); - if (FAILED(res)) { - return(res); - } - - res = stm->Write(buf, sizeof(WCHAR) * wcslen(buf) + 2, NULL); - if (FAILED(res)) { - return(res); - } - res = stm->Commit(0); - if (FAILED(res)) { - return(res); - } - - return(res); -} - - -/// -/// Writes a string into the save file's property set. -/// The text is widened before it is stored, since the summary information properties are -/// held as wide characters. The property set is created if the save file has none yet. -/// -/// The summary information property identifier to write. -/// Returns with the result of the write. A failure means the property set could -/// neither be opened nor created. -HRESULT SaveVersionInfo::Save_String_Set(IPropertySetStorage *storageset, int id, const char *string) -{ - WCHAR buf[260]; - - if (MultiByteToWideChar(CP_ACP, 0, string, -1, buf, ARRAY_SIZE(buf)) == 0) { - buf[0] = L'\0'; - } - - HRESULT res; - IPropertyStoragePtr storage; - - res = storageset->Open(FMTID_SummaryInformation, STGM_SHARE_EXCLUSIVE|STGM_READWRITE, &storage); - if (FAILED(res)) { - res = storageset->Create(FMTID_SummaryInformation, NULL, PROPSETFLAG_DEFAULT, STGM_SHARE_EXCLUSIVE|STGM_READWRITE|STGM_CREATE, &storage); - if (FAILED(res)) { - return(res); - } - } - - PROPSPEC propsec; - propsec.ulKind = PRSPEC_PROPID; - propsec.propid = id; - PROPVARIANT propvar; - - propvar.vt = VT_LPWSTR; - propvar.pwszVal = buf; - - res = storage->WriteMultiple(1, &propsec, &propvar, PID_FIRST_USABLE); - if (FAILED(res)) { - return(res); - } - - return(res); -} - - -/// -/// Writes an integer to a stream of its own. -/// This is the old style counterpart of Save_Int_Set, kept so that a reader which does not -/// understand property sets can still recover the value. -/// -/// The property identifier naming the stream to create. -/// Returns with the result of the write. A failure means the stream was never -/// committed. -HRESULT SaveVersionInfo::Save_Int(IStorage *storage, int id, int integer) -{ - IStreamPtr stm(NULL); - - HRESULT res = storage->CreateStream(Stream_Name_From_ID(id), STGM_SHARE_EXCLUSIVE|STGM_READWRITE, 0, 0, &stm); - if (FAILED(res)) { - return(res); - } - - res = stm->Write(&integer, sizeof(integer), NULL); - if (FAILED(res)) { - return(res); - } - res = stm->Commit(STGM_READ); - if (FAILED(res)) { - return(res); - } - - return(res); -} - - -/// -/// Writes an integer into the save file's property set. -/// This routine stores the value as a summary information property, creating the property -/// set first if the save file does not carry one yet. +/// Writes every listing field into the file's field table. /// -/// The summary information property identifier to write. -/// Returns with the result of the write. A failure means the property set could -/// neither be opened nor created. -HRESULT SaveVersionInfo::Save_Int_Set(IPropertySetStorage *storageset, int id, int integer) +void SaveVersionInfo::Save(SaveFileClass & file) const { - HRESULT res; - IPropertyStoragePtr storage; - - res = storageset->Open(FMTID_SummaryInformation, STGM_SHARE_EXCLUSIVE|STGM_READWRITE, &storage); - if (FAILED(res)) { - res = storageset->Create(FMTID_SummaryInformation, NULL, PROPSETFLAG_DEFAULT, STGM_SHARE_EXCLUSIVE|STGM_READWRITE|STGM_CREATE, &storage); - if (FAILED(res)) { - return(res); - } - } - - PROPSPEC propsec; - propsec.ulKind = PRSPEC_PROPID; - propsec.propid = id; - PROPVARIANT propvar; - - propvar.vt = VT_I4; - propvar.lVal = integer; - - res = storage->WriteMultiple(1, &propsec, &propvar, PID_FIRST_USABLE); - if (FAILED(res)) { - return(res); - } - - return(res); + file.Set_String(PIDSI_SCEN_DESCRIP, ScenarioDescription); + file.Set_String(PIDSI_PLAYER_HOUSE, PlayerHouse); + file.Set_Int(PIDSI_G_VERSION, Version); + file.Set_Int(PIDSI_INTERNAL_VER, InternalVersion); + file.Set_Time(PIDSI_G_START_TIME, StartTime); + file.Set_Time(PIDSI_LAST_SAVE_TIME, LastSaveTime); + file.Set_Time(PIDSI_G_PLAY_TIME, PlayTime); + file.Set_String(PIDSI_EXEC_NAME, ExecutableName); + file.Set_String(PIDSI_PLAYER_NAME1, PlayerName); + file.Set_String(PIDSI_PLAYER_NAME2, PlayerName); + file.Set_Int(PIDSI_SCENARIO_NUM, ScenarioNumber); + file.Set_Int(PIDSI_CAMPAIGN_NUM, CampaignNumber); + file.Set_Int(PIDSI_GAME_TYPE, GameType); } /// -/// Reads a time stamp from a stream of its own. -/// This is the old style counterpart of Load_Time_Set, used for save files written before -/// the version information moved into a property set. The time is cleared first. +/// Reads the listing fields the file carries; a field the file lacks keeps its default. /// -/// The property identifier naming the stream to open. -/// Returns with the result of the read. A failure means the stream is absent. -HRESULT SaveVersionInfo::Load_Time(IStorage *storage, int id, FILETIME *time) +/// bool; Does the file record an internal version at all? +bool SaveVersionInfo::Load(SaveFileClass const & file) { - time->dwLowDateTime = 0; - time->dwHighDateTime = 0; - - HRESULT res; - IStreamPtr stm; - - res = storage->OpenStream(Stream_Name_From_ID(id), NULL, STGM_SHARE_EXCLUSIVE, 0, &stm); - if (FAILED(res)) { - return(res); - } - - res = stm->Read(time, sizeof(*time), NULL); - if (FAILED(res)) { - return(res); - } - - return(res); -} - + file.Get_String(PIDSI_SCEN_DESCRIP, ScenarioDescription, sizeof(ScenarioDescription)); + file.Get_String(PIDSI_PLAYER_HOUSE, PlayerHouse, sizeof(PlayerHouse)); + file.Get_Int(PIDSI_G_VERSION, &Version); + file.Get_Time(PIDSI_G_START_TIME, &StartTime); + file.Get_Time(PIDSI_LAST_SAVE_TIME, &LastSaveTime); + file.Get_Time(PIDSI_G_PLAY_TIME, &PlayTime); + file.Get_String(PIDSI_EXEC_NAME, ExecutableName, sizeof(ExecutableName)); + file.Get_String(PIDSI_PLAYER_NAME1, PlayerName, sizeof(PlayerName)); + file.Get_Int(PIDSI_SCENARIO_NUM, &ScenarioNumber); + file.Get_Int(PIDSI_CAMPAIGN_NUM, &CampaignNumber); + file.Get_Int(PIDSI_GAME_TYPE, &GameType); -/// -/// Reads a time stamp from the save file's property set. -/// The time is cleared before the read is attempted, so a save file that does not carry the -/// property leaves the caller with a zero time rather than with garbage. -/// -/// The summary information property identifier to read. -/// Returns with the result of the read. A failure means the property set could not -/// be opened. -HRESULT SaveVersionInfo::Load_Time_Set(IPropertySetStorage *storageset, int id, FILETIME *time) -{ - time->dwLowDateTime = 0; - time->dwHighDateTime = 0; - - HRESULT res; - IPropertyStoragePtr storage; - - res = storageset->Open(FMTID_SummaryInformation, STGM_SHARE_EXCLUSIVE|STGM_READWRITE, &storage); - if (FAILED(res)) { - return(res); - } - - PROPSPEC propsec; - propsec.ulKind = PRSPEC_PROPID; - propsec.propid = id; - PROPVARIANT propvar; - - res = storage->ReadMultiple(1, &propsec, &propvar); - if (FAILED(res)) { - return(res); - } - - if (propvar.vt == VT_FILETIME) { - *time = propvar.filetime; - } - - return(res); -} - - -/// -/// Writes a time stamp to a stream of its own. -/// This is the old style counterpart of Save_Time_Set. The save routine records every value -/// this way as well, so that a reader which does not understand property sets can still -/// recover it. -/// -/// The property identifier naming the stream to create. -/// Returns with the result of the write. A failure means the stream was never -/// committed. -HRESULT SaveVersionInfo::Save_Time(IStorage *storage, int id, FILETIME *time) -{ - IStreamPtr stm(NULL); - - HRESULT res = storage->CreateStream(Stream_Name_From_ID(id), STGM_SHARE_EXCLUSIVE|STGM_READWRITE, 0, 0, &stm); - if (FAILED(res)) { - return(res); - } - - res = stm->Write(time, sizeof(*time), NULL); - if (FAILED(res)) { - return(res); - } - res = stm->Commit(STGM_READ); - if (FAILED(res)) { - return(res); - } - - return(res); -} - - -/// -/// Writes a time stamp into the save file's property set. -/// This routine stores the time as a summary information property, creating the property -/// set first if the save file does not carry one yet. -/// -/// The summary information property identifier to write. -/// Returns with the result of the write. A failure means the property set could -/// neither be opened nor created. -HRESULT SaveVersionInfo::Save_Time_Set(IPropertySetStorage *storageset, int id, FILETIME *time) -{ - HRESULT res; - IPropertyStoragePtr storage; - - res = storageset->Open(FMTID_SummaryInformation, STGM_SHARE_EXCLUSIVE|STGM_READWRITE, &storage); - if (FAILED(res)) { - res = storageset->Create(FMTID_SummaryInformation, NULL, PROPSETFLAG_DEFAULT, STGM_SHARE_EXCLUSIVE|STGM_READWRITE|STGM_CREATE, &storage); - if (FAILED(res)) { - return(res); - } - } - - PROPSPEC propsec; - propsec.ulKind = PRSPEC_PROPID; - propsec.propid = id; - PROPVARIANT propvar; - - propvar.vt = VT_FILETIME; - propvar.filetime = *time; - - res = storage->WriteMultiple(1, &propsec, &propvar, PID_FIRST_USABLE); - if (FAILED(res)) { - return(res); - } - - return(res); -} - - -/// -/// Fetches the stream name that a save version property is stored under. -/// This routine maps the summary information property identifiers onto the wide names used -/// by the old style save format, where every value lives in a stream of its own. The stream -/// based save and load helpers call it to name the stream they are about to open. -/// -/// The summary information property identifier to look up. -/// Returns with the stream name for the property, or NULL if the identifier is not -/// one of the recorded version properties. -const WCHAR *Stream_Name_From_ID(int id) -{ - static struct pidsiStruct { - int ID; - WCHAR const *Name; - } _ids[] = { - {PIDSI_SCEN_DESCRIP, L"Scenario Description"}, - {PIDSI_PLAYER_HOUSE, L"Player House"}, - {PIDSI_G_VERSION, L"Version"}, - {PIDSI_INTERNAL_VER, L"Internal Version"}, - {PIDSI_G_START_TIME, L"Start Time"}, - {PIDSI_LAST_SAVE_TIME, L"Last Save Time"}, - {PIDSI_G_PLAY_TIME, L"Play Time"}, - {PIDSI_EXEC_NAME, L"Executable Name"}, - {PIDSI_PLAYER_NAME1, L"Player Name"}, - {PIDSI_PLAYER_NAME2, L"Player Name2"}, - {PIDSI_SCENARIO_NUM, L"Scenario Number"}, - {PIDSI_CAMPAIGN_NUM, L"Campaign"}, - {PIDSI_GAME_TYPE, L"GameType"}, - }; - - for (int i = 0; i < ARRAY_SIZE(_ids); i++) { - if (_ids[i].ID == id) { - return(_ids[i].Name); - } - } - - return(NULL); + return(file.Get_Int(PIDSI_INTERNAL_VER, &InternalVersion)); } diff --git a/code/savever.h b/code/savever.h index 291dd7bde..e6b923015 100644 --- a/code/savever.h +++ b/code/savever.h @@ -11,8 +11,7 @@ #include "win.h" -struct IStorage; -struct IPropertySetStorage; +class SaveFileClass; enum { PIDSI_SCEN_DESCRIP = 2, @@ -75,27 +74,8 @@ class SaveVersionInfo void Set_Game_Type(int id); int Get_Game_Type(void); - HRESULT Save(IStorage *storage); - HRESULT Load(IStorage *storage); - - private: - HRESULT Load_String(IStorage *storage, int id, char *string, int size); - HRESULT Load_String_Set(IPropertySetStorage *storageset, int id, char *string, int size); - - HRESULT Load_Int(IStorage *storage, int id, int *integer); - HRESULT Load_Int_Set(IPropertySetStorage *storageset, int id, int *integer); - - HRESULT Save_String(IStorage *storage, int id, char *string); - HRESULT Save_String_Set(IPropertySetStorage *storageset, int id, const char *string); - - HRESULT Save_Int(IStorage *storage, int id, int integer); - HRESULT Save_Int_Set(IPropertySetStorage *storageset, int id, int integer); - - HRESULT Load_Time(IStorage *storage, int id, FILETIME *time); - HRESULT Load_Time_Set(IPropertySetStorage *storageset, int id, FILETIME *time); - - HRESULT Save_Time(IStorage *storage, int id, FILETIME *time); - HRESULT Save_Time_Set(IPropertySetStorage *storageset, int id, FILETIME *time); + void Save(SaveFileClass & file) const; + bool Load(SaveFileClass const & file); private: /* @@ -160,4 +140,3 @@ class SaveVersionInfo int GameType; }; -const WCHAR *Stream_Name_From_ID(int id); diff --git a/code/scenario.cpp b/code/scenario.cpp index 7e0ab0479..17981714f 100644 --- a/code/scenario.cpp +++ b/code/scenario.cpp @@ -3310,18 +3310,17 @@ static Cell const Clip_Move(Cell const & cell, FacingType facing, int dist) /// The elapsed mission clock is halted across the write so that the time recorded is the /// one the player will be given back when the game is resumed. /// -void ScenarioClass::Save(IStream * stream) const +void ScenarioClass::Save(SaveStreamClass & stream) const { DebugString("Scenario Save: ElapsedTimer = %d\n", (int)ElapsedTimer); ElapsedTimer.Stop(); - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); /* * One member list serves both directions, so it cannot be declared const even though * writing changes nothing. */ - const_cast(this)->Serialize(savestream); + const_cast(this)->Serialize(stream); ElapsedTimer.Start(); } @@ -3332,13 +3331,12 @@ void ScenarioClass::Save(IStream * stream) const /// The elapsed mission clock is halted across the read for the same reason it is halted /// across the write, so that it does not advance over the value coming back in. /// -void ScenarioClass::Load(IStream * stream) +void ScenarioClass::Load(SaveStreamClass & stream) { ElapsedTimer.Stop(); - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("ScenarioClass"); - Serialize(savestream); + stream.Set_Context("ScenarioClass"); + Serialize(stream); ElapsedTimer.Start(); DebugString("Scenario Load: ElapsedTimer = %d\n", (int)ElapsedTimer); diff --git a/code/scenario.h b/code/scenario.h index 8a8230723..3607198dd 100644 --- a/code/scenario.h +++ b/code/scenario.h @@ -101,8 +101,8 @@ class ScenarioClass { bool Read_INI(CCINIClass const & ini); bool Write_INI(CCINIClass & ini, bool mplayer=false) const; - void Save(IStream * stream) const; - void Load(IStream * stream); + void Save(SaveStreamClass & stream) const; + void Load(SaveStreamClass & stream); void Serialize(SaveStreamClass & stream); diff --git a/code/script.cpp b/code/script.cpp index 977dad004..0e9a4869f 100644 --- a/code/script.cpp +++ b/code/script.cpp @@ -153,7 +153,7 @@ bool ScriptClass::Has_Missions_Remaining(void) /// /// Fetches the class identifier used to persist this script. -/// This routine is part of the IPersistStream contract that the save game system relies +/// This routine is part of the IPersistent contract that the save game system relies /// on to recreate objects when a game is loaded. /// /// Pointer to the identifier to fill in. @@ -370,7 +370,7 @@ ScriptTypeClass * ScriptTypeClass::Find_Or_Make(char const * name) /// /// Fetches the class identifier used to persist this script type. -/// This routine is part of the IPersistStream contract that the save game system relies +/// This routine is part of the IPersistent contract that the save game system relies /// on to recreate objects when a game is loaded. /// /// Pointer to the identifier to fill in. diff --git a/code/session.cpp b/code/session.cpp index 510e60faa..14f619969 100644 --- a/code/session.cpp +++ b/code/session.cpp @@ -1329,15 +1329,11 @@ void SessionClass::Init_Fixed_Alliances(void) /// Saves the game options to a save game. /// /// bool; Were the options written successfully? -bool GameOptionsType::Save(IStream * stream) +bool GameOptionsType::Save(SaveStreamClass & stream) { - if (stream == NULL) { - return(false); - } - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - Serialize(savestream); - return(SUCCEEDED(savestream.Result())); + Serialize(stream); + return(SUCCEEDED(stream.Result())); } @@ -1347,17 +1343,13 @@ bool GameOptionsType::Save(IStream * stream) /// scenario with it. /// /// bool; Were the options read back successfully? -bool GameOptionsType::Load(IStream * stream) +bool GameOptionsType::Load(SaveStreamClass & stream) { - if (stream == NULL) { - return(false); - } - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context("GameOptionsType"); - Serialize(savestream); + stream.Set_Context("GameOptionsType"); + Serialize(stream); ScenarioIndex = -1; - return(SUCCEEDED(savestream.Result())); + return(SUCCEEDED(stream.Result())); } diff --git a/code/session.h b/code/session.h index f027ea863..f8f4eb6e5 100644 --- a/code/session.h +++ b/code/session.h @@ -461,8 +461,8 @@ struct GameOptionsType { bool AttackNeutralUnits; // A target scan considers a neutral house's objects. char ScenarioDescription [DESCRIP_MAX]; //Used on client machines only - bool Save(IStream * stream); - bool Load(IStream * stream); + bool Save(SaveStreamClass & stream); + bool Load(SaveStreamClass & stream); void Serialize(SaveStreamClass & stream); }; diff --git a/code/startup.cpp b/code/startup.cpp index 03692a6ff..b6539179e 100644 --- a/code/startup.cpp +++ b/code/startup.cpp @@ -64,7 +64,6 @@ #include "classfactory.h" #include "command.h" #include "conquer.h" -#include "cstream.h" #include "data.h" #include "dbgprint.h" #include "deploymentconfig.h" @@ -284,7 +283,6 @@ static bool RegisterClasses(void) RegisteredClasses.Add(dwRegister); \ } \ - REGISTER_CLASS(CStreamClass, CLSID_CompressStream); REGISTER_CLASS(WaveClass, CLSID_WaveClass); REGISTER_CLASS(TerrainTypeClass, CLSID_TerrainTypeClass); REGISTER_CLASS(TerrainClass, CLSID_TerrainClass); diff --git a/code/terrain.cpp b/code/terrain.cpp index 78039fecb..f1e6561f7 100644 --- a/code/terrain.cpp +++ b/code/terrain.cpp @@ -915,7 +915,7 @@ bool TerrainClass::Render(Rect & cliprect, bool forced, bool extras_only) const /// /// The stream to read the object from. /// Returns with S_OK if the object was read successfully. -HRESULT STDMETHODCALLTYPE TerrainClass::Load(IStream * stream) +HRESULT TerrainClass::Load(SaveStreamClass & stream) { TargetTracker.Remove_Index(Fetch_ID()); @@ -1092,7 +1092,7 @@ RTTIType TerrainClass::Fetch_RTTI(void) const /// /// Fetches the class identifier for this object. -/// This routine is part of the IPersistStream implementation. The save system records +/// This routine is part of the IPersistent implementation. The save system records /// the identifier so that it knows what to recreate when the game is loaded back in. /// /// Pointer to the identifier to fill in. diff --git a/code/terrain.h b/code/terrain.h index 537160275..ade05210a 100644 --- a/code/terrain.h +++ b/code/terrain.h @@ -60,7 +60,7 @@ class TerrainClass : public ObjectClass, public StageClass virtual ~TerrainClass(void) override; virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream) override; + virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/tiberium.cpp b/code/tiberium.cpp index 6bfb58dae..edc633665 100644 --- a/code/tiberium.cpp +++ b/code/tiberium.cpp @@ -216,7 +216,7 @@ HRESULT STDMETHODCALLTYPE TiberiumClass::GetClassID(CLSID * retval) /// Returns with S_OK if the tiberium type was loaded. /// The spread and growth systems are not saved, so they come back empty. They /// must be rebuilt once the game has finished loading. -HRESULT STDMETHODCALLTYPE TiberiumClass::Load(IStream * stream) +HRESULT TiberiumClass::Load(SaveStreamClass & stream) { Clear_Spread(); Clear_Growth(); diff --git a/code/tiberium.h b/code/tiberium.h index 650aa07ee..155f34e94 100644 --- a/code/tiberium.h +++ b/code/tiberium.h @@ -38,7 +38,7 @@ class TiberiumClass : public AbstractTypeClass virtual ~TiberiumClass() override; virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream) override; + virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/unit.cpp b/code/unit.cpp index 3980f005d..30776fb03 100644 --- a/code/unit.cpp +++ b/code/unit.cpp @@ -2106,9 +2106,7 @@ void UnitClass::Per_Cell_Process(PCPType why) Cell center = Center_Coord(); Cell whom_center = whom->Center_Coord(); if (Center_Coord().As_Cell() == whom->Center_Coord().As_Cell() && whom->RTTI == RTTI_BUILDING) { - IPersistPtr persist(Locomotion); - CLSID clsid; - persist->GetClassID(&clsid); + CLSID const clsid = Locomotion_Class_ID(Locomotion); if (clsid == CLSID_HoverLocomotion && static_cast(whom)->Class->IsCanUnitRepair && NavCom == NULL) { NavCom = whom; } @@ -5222,9 +5220,7 @@ void UnitClass::Assign_Destination(AbstractClass * target, bool immediate) * re-target the nearest reachable cell when driving rather than burrowing. */ if (target != NULL && Class->IsSubterranean && Locomotion->Is_Moving()) { - IPersistPtr persist(Locomotion); - CLSID clsid; - persist->GetClassID(&clsid); + CLSID const clsid = Locomotion_Class_ID(Locomotion); if (clsid == CLSID_DriveLocomotion) { NavQueue.Add_Head(target); RouteQueue.Clear(); @@ -5316,9 +5312,7 @@ void UnitClass::Assign_Destination(AbstractClass * target, bool immediate) * (Mirrors BuildingClass weapons-factory exit, building.cpp:6236-6251.) */ if (target != NULL && !Locomotion->Is_Moving()) { - IPersistPtr persist(Locomotion); - CLSID clsid; - persist->GetClassID(&clsid); + CLSID const clsid = Locomotion_Class_ID(Locomotion); if (clsid == CLSID_TunnelLocomotion && Get_Height_AGL() == 0) { Coord tc = target->Center_Coord(); int gl = Map.Get_Height_GL(tc); @@ -6016,7 +6010,7 @@ bool UnitClass::Ready_To_Commence(void) /// /// The stream to read this unit from. /// Returns with S_OK if the unit was read successfully. -HRESULT STDMETHODCALLTYPE UnitClass::Load(IStream *stream) +HRESULT UnitClass::Load(SaveStreamClass & stream) { TargetTracker.Remove_Index(Fetch_ID()); return(BASECLASS::Load(stream)); diff --git a/code/unit.h b/code/unit.h index b1721e3e0..20373e578 100644 --- a/code/unit.h +++ b/code/unit.h @@ -135,7 +135,7 @@ class UnitClass : public FootClass virtual ~UnitClass(void) override; virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE Load(IStream * stream) override; + virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/vector.h b/code/vector.h index f608fa4c0..7466a1fae 100644 --- a/code/vector.h +++ b/code/vector.h @@ -123,8 +123,7 @@ class VectorClass stream.Serialize(count); if (stream.Is_Loading()) { - if (count < 0) { - stream.Fail(); + if (!stream.Fits(count, (std::is_arithmetic_v || std::is_enum_v) ? sizeof(T) : 1)) { return; } Clear(); @@ -518,8 +517,7 @@ class DynamicVectorClass : public VectorClass stream.Serialize(count); if (stream.Is_Loading()) { - if (count < 0) { - stream.Fail(); + if (!stream.Fits(count, (std::is_arithmetic_v || std::is_enum_v) ? sizeof(T) : 1)) { return; } Clear(); diff --git a/code/vein.cpp b/code/vein.cpp index 7f3130628..aaaf65728 100644 --- a/code/vein.cpp +++ b/code/vein.cpp @@ -881,19 +881,21 @@ void VeinholeMonsterClass::Remove_Dead(void) /// growth records and handed to the swizzler and the target tracker. /// /// bool; Were all the monsters read successfully? -bool VeinholeMonsterClass::Load_All(IStream * stream) +bool VeinholeMonsterClass::Load_All(SaveStreamClass & stream) { Reset(); int cell_count = Map_Cell_Count(); int monster_count; - if (FAILED(stream->Read(&monster_count, sizeof(monster_count), NULL))) { + stream.Serialize(monster_count); + if (stream.Was_Error()) { return(false); } GlobalGrowthState = new bool[cell_count]; - if (FAILED(stream->Read(GlobalGrowthState, cell_count, NULL))) { + stream.Serialize_Bytes(GlobalGrowthState, (int)(cell_count)); + if (stream.Was_Error()) { return(false); } @@ -906,29 +908,31 @@ bool VeinholeMonsterClass::Load_All(IStream * stream) VeinholeMonsterClass * monster = new VeinholeMonsterClass(); uintptr_t id; - if (FAILED(stream->Read(&id, sizeof(id), NULL))) { + stream.Serialize(id); + if (stream.Was_Error()) { return(false); } Swizzler.Here_I_Am(id, monster); - SaveStreamClass savestream(stream, SaveStreamClass::MODE_LOAD); - savestream.Set_Context(typeid(*monster).name(), id); - monster->Serialize(savestream); - if (FAILED(savestream.Result())) { + stream.Set_Context(typeid(*monster).name(), id); + monster->Serialize(stream); + if (FAILED(stream.Result())) { return(false); } - if (FAILED(stream->Read(monster->GrowthState, cell_count, NULL))) { + stream.Serialize_Bytes(monster->GrowthState, (int)(cell_count)); + if (stream.Was_Error()) { return(false); } - if (FAILED(stream->Read(monster->GrowthNodes, sizeof(CellNode) * Rule->MaxVeinholeGrowth, NULL))) { + stream.Serialize_Bytes(monster->GrowthNodes, (int)(sizeof(CellNode) * Rule->MaxVeinholeGrowth)); + if (stream.Was_Error()) { return(false); } - monster->GrowthQueue->Serialize(savestream, monster->GrowthNodes); - if (FAILED(savestream.Result())) { + monster->GrowthQueue->Serialize(stream, monster->GrowthNodes); + if (FAILED(stream.Result())) { return(false); } @@ -972,40 +976,44 @@ void VeinholeMonsterClass::Serialize(SaveStreamClass & stream) /// with its vein growth records so that growth can pick up where it left off. /// /// bool; Were all the monsters written successfully? -bool VeinholeMonsterClass::Save_All(IStream * stream) +bool VeinholeMonsterClass::Save_All(SaveStreamClass & stream) { int monster_count = VeinholeMonsters.Count(); - if (FAILED(stream->Write(&monster_count, sizeof(monster_count), NULL))) { + stream.Serialize(monster_count); + if (stream.Was_Error()) { return(false); } int cell_count = Map_Cell_Count(); - if (FAILED(stream->Write(GlobalGrowthState, cell_count, NULL))) { + stream.Serialize_Bytes(GlobalGrowthState, (int)(cell_count)); + if (stream.Was_Error()) { return(false); } for (int i = 0; i < monster_count; i++) { LONG id = (LONG)VeinholeMonsters[i]; - if (FAILED(stream->Write(&id, sizeof(id), NULL))) { + stream.Serialize(id); + if (stream.Was_Error()) { return(false); } - SaveStreamClass savestream(stream, SaveStreamClass::MODE_SAVE); - VeinholeMonsters[i]->Serialize(savestream); - if (FAILED(savestream.Result())) { + VeinholeMonsters[i]->Serialize(stream); + if (FAILED(stream.Result())) { return(false); } - if (FAILED(stream->Write(VeinholeMonsters[i]->GrowthState, cell_count, NULL))) { + stream.Serialize_Bytes(VeinholeMonsters[i]->GrowthState, (int)(cell_count)); + if (stream.Was_Error()) { return(false); } - if (FAILED(stream->Write(VeinholeMonsters[i]->GrowthNodes, sizeof(CellNode) * Rule->MaxVeinholeGrowth, NULL))) { + stream.Serialize_Bytes(VeinholeMonsters[i]->GrowthNodes, (int)(sizeof(CellNode) * Rule->MaxVeinholeGrowth)); + if (stream.Was_Error()) { return(false); } - VeinholeMonsters[i]->GrowthQueue->Serialize(savestream, VeinholeMonsters[i]->GrowthNodes); - if (FAILED(savestream.Result())) { + VeinholeMonsters[i]->GrowthQueue->Serialize(stream, VeinholeMonsters[i]->GrowthNodes); + if (FAILED(stream.Result())) { return(false); } } diff --git a/code/vein.h b/code/vein.h index 92106eb5c..07eee9d52 100644 --- a/code/vein.h +++ b/code/vein.h @@ -61,8 +61,8 @@ class VeinholeMonsterClass : public ObjectClass void Clear_Growth(void); void Destroy_Monster(void); static void Remove_Dead(void); - static bool Load_All(IStream * stream); - static bool Save_All(IStream * stream); + static bool Load_All(SaveStreamClass & stream); + static bool Save_All(SaveStreamClass & stream); void Reduce_Veins_At(CellClass * cellptr); virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/walk.cpp b/code/walk.cpp index d3996e10b..8ad9bdd7e 100644 --- a/code/walk.cpp +++ b/code/walk.cpp @@ -27,6 +27,7 @@ #include "inline.h" #include "overtype.h" #include "rules.h" +#include "saveload.h" #include "savestream.h" #include "tactical.h" #include "tube.h" @@ -35,7 +36,6 @@ #include "layer.hh" - /// /// Constructs a walking locomotor. /// This is the locomotor used by infantry, who travel on foot between the sub-cell @@ -626,7 +626,7 @@ HRESULT STDMETHODCALLTYPE WalkLocomotionClass::GetClassID(CLSID * retval) /// /// Lists the members this walk locomotor carries. /// The locomotor this one was stacked on top of is a separate persistent object rather -/// than a member, so it still travels framed by OLE and is recreated as the class it was +/// than a member, so it travels as a record of its own and is recreated as the class it was /// saved as. /// /// The stream carrying the members. @@ -645,10 +645,9 @@ void WalkLocomotionClass::Serialize(SaveStreamClass & stream) if (haspiggy) { if (stream.Is_Saving()) { - IPersistStreamPtr persist(Piggybacker); - OleSaveToStream(persist, stream.Get_Stream()); + Save_Object(stream, (ILocomotion *)Piggybacker); } else { - OleLoadFromStream(stream.Get_Stream(), IID_ILocomotion, (LPVOID *)&Piggybacker); + Load_Object(stream, IID_ILocomotion, (LPVOID *)&Piggybacker); } } } @@ -761,18 +760,10 @@ HRESULT STDMETHODCALLTYPE WalkLocomotionClass::Piggyback_CLSID(GUID * classid) } if (Piggybacker != NULL) { - IPersistPtr ptr(Piggybacker); - if (ptr == NULL) { - return(E_FAIL); - } - return(ptr->GetClassID(classid)); - } - - IPersistPtr ptr(this); - if (ptr == NULL) { - return(E_FAIL); + *classid = Locomotion_Class_ID(Piggybacker); + return(S_OK); } - return(ptr->GetClassID(classid)); + return(GetClassID(classid)); } From 0349e014fca7a68dae3ecc725ba3f850760ef168 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Sun, 6 Sep 2026 22:34:39 +0200 Subject: [PATCH 022/179] Document the saved game format --- docs/README.md | 2 + docs/SAVE-FORMAT.md | 147 +++++++++++++++++++++++++ manual/changes/save-file-format.md | 13 +++ manual/content/formats/save-games.md | 23 ++-- manual/content/internals/locomotion.md | 2 +- 5 files changed, 176 insertions(+), 11 deletions(-) create mode 100644 docs/SAVE-FORMAT.md create mode 100644 manual/changes/save-file-format.md diff --git a/docs/README.md b/docs/README.md index d983f12aa..963715287 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,6 +11,8 @@ The developer guides are split by subject: - [Project direction](DIRECTION.md) — long-term architecture. - [UI system design](UI_DESIGN.md) — proposed RmlUi and ImGui integration, screen-level interchangeable views, and the migration from OwnerDraw. +- [The saved game format](SAVE-FORMAT.md) — the layout of a `.SAV` file: its + header, listing fields, compressed content, and object records. See [CONTRIBUTING.md](../CONTRIBUTING.md) for contribution and review rules. Player and modder documentation is under [manual/](../manual/README.md). When a diff --git a/docs/SAVE-FORMAT.md b/docs/SAVE-FORMAT.md new file mode 100644 index 000000000..e2bc42b43 --- /dev/null +++ b/docs/SAVE-FORMAT.md @@ -0,0 +1,147 @@ +# The saved game format + +A saved game is one `.SAV` file written by `code/savefile.cpp` and read back by +it. This document owns the layout. Where the files live, how they are named, +and when they are written is on the manual's +[save games page](../manual/content/formats/save-games.md). + +Every integer is little-endian. Offsets are from the start of the file. + +## Header + +| Offset | Size | Field | +| --- | --- | --- | +| 0 | 4 | Signature, the bytes `OTSV` | +| 4 | 2 | Format version, currently 1 | +| 6 | 2 | Flags; bit 0 set when the content is LZO-compressed | +| 8 | 4 | Length of the field table | +| 12 | 4 | Offset of the content | +| 16 | 4 | Stored length of the content | +| 20 | 4 | Uncompressed length of the content | +| 24 | 4 | CRC-32 of the stored content | +| 28 | 4 | CRC-32 of the first 28 bytes of the header, continued over the field table | + +The header is 32 bytes, the field table follows it directly, and the content +follows the table directly. The content offset is recorded rather than assumed +so a later format version can put something between the two; this version +refuses a file whose offset says otherwise. A field table is refused above +1 MiB, since a listing is a dozen short fields. + +Both checksums are the CRC-32 of IEEE 802.3, polynomial `0xEDB88320` +reflected, initial value and final complement of all ones, as PNG and gzip +use it. The header checksum continues over the field table so a listing can +verify what it shows without reading the content. + +## Field table + +The fields are what the load dialog lists a save by. Each is: + +| Size | Field | +| --- | --- | +| 2 | Identifier | +| 2 | Kind: 1 string, 2 integer, 3 file time | +| 4 | Length of the value | +| | The value: string bytes without a terminator, a 4-byte integer, or an 8-byte `FILETIME` | + +The identifiers are the `PIDSI_` values in `code/savever.h`, the same ones the +compound-document property set carried before this format. A field holds at +most 64 KiB. A reader takes the +first field that matches both identifier and kind and ignores the rest, so a +field it does not know costs nothing. A string longer than the buffer it is +read into is cut on a character boundary, so a shortened description stays +UTF-8. `SaveVersionInfo` in `code/savever.cpp` is the only writer and reader. + +## Content + +The content is the game state: the bytes `Put_All` in `code/saveload.cpp` +writes through `SaveStreamClass`, compressed as one block with LZO1X-1 when +that makes it smaller, and stored as it is otherwise. The reader checks the +stored length and checksum before decompressing, and refuses a block that does +not expand to exactly the recorded length. The decompressor checks every read +and write against its buffers, so a block forged to overrun either is refused +like a damaged one. An uncompressed length above 256 MiB is refused before +anything is allocated for it. + +### Object records + +The state is a sequence of values and object records in the order `Put_All` +names them. An object record is: + +| Size | Field | +| --- | --- | +| 16 | The class identifier of the object | +| 4 | Length of the record body | +| | The body: the swizzle identity, then the members the class's `Serialize` names | + +The class identifier is the `CLSID` the object's `GetClassID` reports, the +same one registered with the class factory in `code/startup.cpp` and, for a +locomotor, named by the `Locomotor=` key. The reader creates the object +through `CoCreateInstance`, hands it the stream, and then checks that it +consumed exactly the recorded length. A record that comes up short or long +fails the load with the object's type and offset in the debug log, which is +what a member added to one build and not the other looks like. A vector of +objects is a 4-byte count followed by that many records, and a locomotor +nested inside a unit's record is a record of its own. A count that the bytes +remaining in the content could not hold fails the load before anything is +allocated for it. + +The body is what each class's `Serialize` produces, member by member, in host +byte order. It is not described here; the classes are the description. + +## Versions + +Two numbers gate a save. The format version in the header says how to parse +the file, and a reader refuses a version above its own. The header flags are +gated the same way: a reader refuses a file with a flag bit it does not know, +so a later version can mark content it stores differently without moving the +format version. The internal version in the field table, +`PIDSI_INTERNAL_VER`, is `ExpectedGameVersion`, the packed project version, +and a save whose value differs from the running build's is not offered to the +player. The format version moves only when the layout in this document +changes; the internal version moves with every release. + +## What the reader refuses + +`SaveFileClass::Read` and `Read_Fields` answer one of: + +| Result | When | +| --- | --- | +| `RESULT_MISSING` | No file under that name | +| `RESULT_NOT_A_SAVE` | The first bytes are not the signature | +| `RESULT_UNSUPPORTED_VERSION` | A format version above the reader's, or a header flag it does not know | +| `RESULT_CORRUPT` | A length, checksum or compressed block that does not add up, including a truncated file, a forged block, a field table above 1 MiB, a content offset that does not follow the table, or a content length above 256 MiB | +| `RESULT_NO_MEMORY` | A file within those limits that the process cannot hold | + +`Read` judges the header before it reads or allocates anything else, so a file +of any size costs the reader no more than the limits above allow, and +`Read_Fields` reads the header and the table only, so listing a folder never +allocates for a file's content. + +`Load_Game` reads and checks the whole file before it tears down the running +game, so a refused file costs nothing. + +A save written before this format is an OLE compound document, which begins +with a signature of its own, so the reader answers `RESULT_NOT_A_SAVE` and the +load dialog leaves the file out of its list. Nothing converts those files. + +## Writing + +`SaveFileClass::Write` builds the whole image in memory, writes it to the +target name with `.tmp` appended, flushes and closes it, and then moves it over +the target with `MoveFileExA` and `MOVEFILE_REPLACE_EXISTING`. A save +interrupted at any point leaves the previous file untouched under its name, +and at most a `.tmp` beside it, which the next successful save replaces. +The reader's limits bind the writer too: content above 256 MiB, a field above +64 KiB or a table above 1 MiB is refused with `RESULT_TOO_LARGE` before +anything is written, so a save this build writes is one it reads, and the +file on disk is left as it was. + +## Checks + +`tests/save` builds `code/savefile.cpp` against the LZO library on every target +covers the round trip, the fields-only read, replacement of an existing file +and of a stale `.tmp`, and each refusal above, including a later version, an +unknown flag, a file cut at every boundary, a byte flipped in the header, the +table and the content, a field table above its limit, a gap before the +content, compressed blocks forged to overrun the reader, and a write above +each limit that leaves the earlier save in place. It reads no game data. diff --git a/manual/changes/save-file-format.md b/manual/changes/save-file-format.md new file mode 100644 index 000000000..3f7bb20d0 --- /dev/null +++ b/manual/changes/save-file-format.md @@ -0,0 +1,13 @@ +--- +title: Keep saved games in a file of the engine's own +category: feature +release: 0.2.0 +targets: +- type: format + id: save-games + effect: changed +credit: +- Gunnar Beutner +--- + +A saved game is now a file of the engine's own format rather than an OLE compound document: a header, the listing details, and the game state as one compressed block, written under a temporary name and moved into place once complete. The load dialog reads only the header of each file, and a damaged or truncated file is refused before the running game is disturbed. Saved games written before this change are not read and no longer appear in the load dialog; there is no conversion. diff --git a/manual/content/formats/save-games.md b/manual/content/formats/save-games.md index 0e375f94e..f3f3fe50e 100644 --- a/manual/content/formats/save-games.md +++ b/manual/content/formats/save-games.md @@ -1,7 +1,7 @@ --- format_id: save-games title: Save games -summary: Stores versioned OpenTS game state in `.SAV` compound-document files. +summary: Stores versioned OpenTS game state in `.SAV` files of the engine's own format. kind: binary extensions: - .SAV @@ -18,6 +18,7 @@ source_files: - code/mainloop.cpp - code/mpload.cpp - code/netdlg.cpp + - code/savefile.cpp - code/saveload.cpp - code/savemgr.cpp - code/savestream.cpp @@ -30,7 +31,7 @@ source_files: - code/voc.cpp --- -The save dialog creates `.SAV` files. Each file is an OLE compound document: the listing details live in the document's own property set, and the game state goes into a single `CONTENTS` stream that is compressed as it is written. +The save dialog creates `.SAV` files. Each file begins with a fixed header and a table of the details the load dialog lists a save by, followed by the game state as one compressed block. The listing is read from the header and table alone, and a file that is truncated, damaged, or written by a later format version is refused before anything is loaded. A save is written under a temporary name and moved into place once complete, so an interrupted save leaves the previous file intact. ## Where the files are @@ -60,7 +61,7 @@ Timed saves in a game against other machines run only when a launch file set the The [`QuickSave`](/commands/quicksave/) command writes a campaign to `QUICKSAVE.SAV` and a skirmish to `QUICKSAVE_SKIRMISH.SAV`, replacing the previous file of that kind, so a skirmish never writes over a campaign. The save is written at the frame boundary after the key was pressed, once the frame has retired its dead objects, behind the saving box a menu save shows; the message list then reports `Game saved.` or that the game could not be saved. Each file is described as `Quick Save` and the scenario's description, and the load dialog lists it like any other save. A quick save starts the automatic-save interval over like any completed save. -[`QuickLoad`](/commands/quickload/) restores the file for the kind of game being played. It first reads the file's property set and refuses, with a line in the message list, when the file is missing or carries another version's stamp; otherwise the load runs when the frame ends, in the place the options menu runs, and the mission clock resumes in the restored game. A load that fails partway through the restore shows the same error box as the load dialog and leaves the player in the options menu. +[`QuickLoad`](/commands/quickload/) restores the file for the kind of game being played. It first reads the file's listing fields and refuses, with a line in the message list, when the file is missing or carries another version's stamp; otherwise the load runs when the frame ends, in the place the options menu runs, and the mission clock resumes in the restored game. A load that fails partway through the restore shows the same error box as the load dialog and leaves the player in the options menu. Both commands are refused in a game against other machines, during playback, while a scripted sequence has locked input, and once the game is being won or lost. Both arrive unbound. @@ -74,19 +75,21 @@ In a game against other machines the master can load one of the match's saved ga ## What the file holds -The property set carries the description shown in the list, the player's name and house, the campaign and scenario numbers, the game type, three timestamps, the name of the program that wrote the save, and two version stamps — the save format's own version and the build version of the game that wrote it. +The field table carries the description shown in the list, the player's name and house, the campaign and scenario numbers, the game type, three timestamps, the name of the program that wrote the save, and the build version of the game that wrote it. The header carries the format's own version. -The `CONTENTS` stream is a fixed sequence of records — the scenario, the environment, the rules, the map, the loose global values, and every list of type definitions and runtime objects — written and read back in the same order. Among the loose values are the looping sounds left at waypoints by [Play Sound Effect At](/mapping/actions/taction-play-sound-at/) and the sounds attached to objects, each as the sound and its place or object; the playing sound itself is not saved and starts again on the first sound tick after the load. Each list stores its own length ahead of its members, and each member writes out the members its class declares, in the order that class lists them. What a save holds is therefore a field-by-field record of each object rather than a copy of the bytes it occupied in memory. Type definitions travel with the save, so a save carries the rules types it was made with rather than looking them up again on load. Artwork does not travel with it: once a restored type's members have been read, its shape and voxel pointers are released and fetched from the archives again, so a save loaded against a changed set of files gets the current artwork. One piece does not come back. A UnitType drawn from shapes is given a [voxel turret](/formats/vxl-hva/) when the rules are read, by a routine no restore calls; the restore takes the ordinary voxel path instead, which releases that turret along with the body model it could not find. Its voxel barrel is fetched back, and the barrel is what the shape path draws. +The game state is a fixed sequence of records — the scenario, the environment, the rules, the map, the loose global values, and every list of type definitions and runtime objects — written and read back in the same order. Among the loose values are the looping sounds left at waypoints by [Play Sound Effect At](/mapping/actions/taction-play-sound-at/) and the sounds attached to objects, each as the sound and its place or object; the playing sound itself is not saved and starts again on the first sound tick after the load. Each list stores its own length ahead of its members, and each member writes out the members its class declares, in the order that class lists them. What a save holds is therefore a field-by-field record of each object rather than a copy of the bytes it occupied in memory. Type definitions travel with the save, so a save carries the rules types it was made with rather than looking them up again on load. Artwork does not travel with it: once a restored type's members have been read, its shape and voxel pointers are released and fetched from the archives again, so a save loaded against a changed set of files gets the current artwork. One piece does not come back. A UnitType drawn from shapes is given a [voxel turret](/formats/vxl-hva/) when the rules are read, by a routine no restore calls; the restore takes the ordinary voxel path instead, which releases that turret along with the body model it could not find. Its voxel barrel is fetched back, and the barrel is what the shape path draws. The scenario record also holds the scenario file itself, name and bytes, where the deployment's [`CarryScenarioFile`](/formats/opents-ini/#what-a-save-carries) asks for it; the record is written either way, empty when nothing is carried. A [restart or replay](/systems/campaign-progression/#losing-and-restarting) after a load reads that copy, not the file on disk, which a client resuming the save may have replaced. A random map holds no file. ## What is checked The project-version stamp decides whether a file is offered at all, and only -the running version's stamp is accepted. The load dialog reads the property set -of every `.SAV` in the saved-games folder and skips every file stamped by -anything else, including the Tiberian Sun release and another OpenTS -release-cycle version. A save that reaches the engine without passing through +the running version's stamp is accepted. The load dialog reads the header and +field table of every `.SAV` in the saved-games folder and skips every file +stamped by anything else, including another OpenTS release-cycle version. A +file in the compound-document layout that earlier OpenTS releases and Tiberian +Sun wrote is not a saved game to this reader and is skipped as well; there is +no conversion. A save that reaches the engine without passing through the dialog, as a network save or one resumed from a [launch file](/formats/spawn-ini/) does, is checked the same way and refused. Development snapshots within one cycle share the stamp, and their save layouts @@ -95,4 +98,4 @@ leading `*`. Beyond that stamp and the add-on the scenario declares, nothing about a save is measured against the game it is being loaded into. A save made under one set of rules and loaded under another is not detected, and the type definitions stored in the file are simply restored over the ones the rules built. -Reading `CONTENTS` clears the scenario before it starts and gives up at the first record it cannot restore. A load that stops there fails, rather than carrying on into a scenario that was cleared and never refilled. +The file is read and checked in full, checksums included, before the running game is touched, so a truncated or damaged file is refused at no cost. Restoring the game state then clears the scenario before it starts and gives up at the first record it cannot restore. A load that stops there fails, rather than carrying on into a scenario that was cleared and never refilled. diff --git a/manual/content/internals/locomotion.md b/manual/content/internals/locomotion.md index 2ac0434cb..696b2ce34 100644 --- a/manual/content/internals/locomotion.md +++ b/manual/content/internals/locomotion.md @@ -38,6 +38,6 @@ Callers that perform opportunistic restoration first consult `Is_Ok_To_End`. The ## Persistence identity -`FootClass::Serialize` writes the active locomotor through `IPersistStream` when saving and restores it through `OleLoadFromStream` when loading. A piggyback-capable locomotor writes whether it carries another locomotor and serializes that nested COM object when present. A save made during a temporary movement state therefore retains both the active locomotor and the one to restore. +`FootClass::Serialize` writes the active locomotor as a record of its own, headed by its class identifier, and recreates it from that identifier when loading. A piggyback-capable locomotor writes whether it carries another locomotor and serializes that nested locomotor when present. A save made during a temporary movement state therefore retains both the active locomotor and the one to restore. `GetClassID` identifies the active locomotor implementation. `Piggyback_CLSID` returns the carried locomotor's `GetClassID` while piggybacking and the active locomotor's ID otherwise. These identities are distinct while a temporary locomotor is in control. From f43aae06c40d3b68416af06911c21086003cbd25 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Sun, 6 Sep 2026 23:29:38 +0200 Subject: [PATCH 023/179] Create persistent objects from a class table instead of COM --- code/CMakeLists.txt | 5 -- code/abstract.cpp | 3 -- code/abstract.h | 6 +-- code/aircraft.cpp | 5 +- code/blowfish.cpp | 28 ----------- code/blowfish.h | 16 ------ code/building.cpp | 2 +- code/bullet.cpp | 9 ++-- code/classfactory.cpp | 67 +++++++++++++++++++++++++ code/classfactory.h | 113 ++++-------------------------------------- code/drive.cpp | 2 +- code/droppod.cpp | 2 +- code/foot.cpp | 11 ++-- code/globals.cpp | 4 -- code/house.cpp | 9 +++- code/iblockci.h | 27 ---------- code/iblockci_i.c | 52 ------------------- code/iblowfish.h | 17 ------- code/iblowfish_i.c | 55 -------------------- code/infantry.cpp | 6 +-- code/loco.cpp | 33 ++++++++++-- code/loco.h | 14 ++++-- code/mouse.cpp | 5 +- code/persist.h | 3 ++ code/saveload.cpp | 73 ++++++++++++--------------- code/saveload.h | 3 +- code/startup.cpp | 85 +++---------------------------- code/swizzle.cpp | 15 ++++++ code/swizzle.h | 12 +++++ code/unit.cpp | 4 +- code/walk.cpp | 2 +- docs/SAVE-FORMAT.md | 27 ++++++---- 32 files changed, 240 insertions(+), 475 deletions(-) create mode 100644 code/classfactory.cpp delete mode 100644 code/iblockci.h delete mode 100644 code/iblockci_i.c delete mode 100644 code/iblowfish.h delete mode 100644 code/iblowfish_i.c diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index aa6f43837..c6a793542 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -173,11 +173,6 @@ target_compile_definitions(OpenTS PRIVATE WIN32 _WINDOWS NOMINMAX - - # Compiles Blowfish into the binary instead of reaching it through the COM object in - # blowfish.dll. It decides the layout of BlowfishEngine, so every translation unit has - # to agree on it and it belongs on the compile line rather than in a header. - NO_BLOWFISH_DLL ) # diff --git a/code/abstract.cpp b/code/abstract.cpp index 5f9a1d3d3..c078c9f38 100644 --- a/code/abstract.cpp +++ b/code/abstract.cpp @@ -229,9 +229,6 @@ HRESULT AbstractClass::Load_Members(SaveStreamClass & stream) Serialize(stream); stream.Set_Context(outertype, outerid); - if (SUCCEEDED(stream.Result())) { - Post_Load(); - } return(stream.Result()); } diff --git a/code/abstract.h b/code/abstract.h index 1487c6fac..3d8685b93 100644 --- a/code/abstract.h +++ b/code/abstract.h @@ -135,9 +135,9 @@ class AbstractClass : public IPersistent /* * Restores whatever the record could not carry -- artwork fetched by name, tables * shared with other objects, registrations that depend on the loaded identity. - * Load_Members calls this once the members are in place, so a base class fixup - * runs even when the load was entered through a derived class. An implementation - * chains to its base first and never touches the stream. + * Load_Object calls this once the record has been checked, so an object never takes + * its place in the map or a side table while its record is still in doubt. An + * implementation chains to its base first and never touches the stream. */ virtual void Post_Load(void); diff --git a/code/aircraft.cpp b/code/aircraft.cpp index c4a6d3017..3d4251718 100644 --- a/code/aircraft.cpp +++ b/code/aircraft.cpp @@ -221,7 +221,7 @@ AircraftClass::AircraftClass(AircraftTypeClass const * type, HouseClass * house) Create_ID(); if (Class != NULL) { - Locomotion.CreateInstance(Class->Locomotor); + Locomotion = Create_Locomotor(Class->Locomotor); Locomotion->Link_To_Object(this); } @@ -1397,8 +1397,7 @@ void AircraftClass::Drop_Off_Cargo(void) unit->IsOnBridge = false; } - unit->Locomotion.Release(); - unit->Locomotion = ILocomotionPtr(unit->TClass->Locomotor); + unit->Locomotion = Create_Locomotor(unit->TClass->Locomotor); unit->Locomotion->Link_To_Object(unit); if (!unit->Unlimbo(coord)) { diff --git a/code/blowfish.cpp b/code/blowfish.cpp index cc9f746f1..91ec2c67e 100644 --- a/code/blowfish.cpp +++ b/code/blowfish.cpp @@ -39,15 +39,11 @@ #include "blowfish.h" -#ifndef NO_BLOWFISH_DLL -#include "iblowfish.h" -#endif #include -#ifdef NO_BLOWFISH_DLL /* ** Byte order controlled long integer. This integer is constructed ** so that character 0 (C0) is the most significant byte of the @@ -63,7 +59,6 @@ typedef union { unsigned char C0; } Char; } Int; -#endif /// @@ -73,11 +68,7 @@ typedef union { /// /// You must submit the key before calling the encrypt or decrypt routines. BlowfishEngine::BlowfishEngine(void) : -#ifndef NO_BLOWFISH_DLL - BlockCypher(CLSID_BlowfishObject) -#else IsKeyed(false) -#endif { } @@ -99,11 +90,9 @@ BlowfishEngine::BlowfishEngine(void) : *=============================================================================================*/ BlowfishEngine::~BlowfishEngine(void) { -#ifdef NO_BLOWFISH_DLL if (IsKeyed) { Submit_Key(NULL, 0); } -#endif } @@ -133,10 +122,6 @@ BlowfishEngine::~BlowfishEngine(void) *=============================================================================================*/ void BlowfishEngine::Submit_Key(void const * key, int length) { -#ifndef NO_BLOWFISH_DLL - BlockCypher->Set_Key(length, key); - return; -#else assert(length <= MAX_KEY_LENGTH); /* @@ -210,7 +195,6 @@ void BlowfishEngine::Submit_Key(void const * key, int length) } IsKeyed = true; -#endif } @@ -238,10 +222,6 @@ void BlowfishEngine::Submit_Key(void const * key, int length) *=============================================================================================*/ int BlowfishEngine::Encrypt(void const * plaintext, int length, void * cyphertext) { -#ifndef NO_BLOWFISH_DLL - BlockCypher->Encrypt(length, plaintext, cyphertext); - return(length); -#else if (plaintext == 0 || length == 0) { return(0); } @@ -281,7 +261,6 @@ int BlowfishEngine::Encrypt(void const * plaintext, int length, void * cyphertex memmove(cyphertext, plaintext, length); } return(length); -#endif } @@ -309,10 +288,6 @@ int BlowfishEngine::Encrypt(void const * plaintext, int length, void * cyphertex *=============================================================================================*/ int BlowfishEngine::Decrypt(void const * cyphertext, int length, void * plaintext) { -#ifndef NO_BLOWFISH_DLL - BlockCypher->Decrypt(length, cyphertext, plaintext); - return(length); -#else if (cyphertext == 0 || length == 0) { return(0); } @@ -352,11 +327,9 @@ int BlowfishEngine::Decrypt(void const * cyphertext, int length, void * plaintex memmove(plaintext, cyphertext, length); } return(length); -#endif } -#ifdef NO_BLOWFISH_DLL /*********************************************************************************************** * BlowfishEngine::Process_Block -- Process a block of data using Blowfish algorithm. * * * @@ -631,4 +604,3 @@ unsigned int const BlowfishEngine::S_Init[4][UCHAR_MAX+1] = { 0x90D4F869U,0xA65CDEA0U,0x3F09252DU,0xC208E69FU,0xB74E6132U,0xCE77E25BU,0x578FDFE3U,0x3AC372E6U } }; -#endif diff --git a/code/blowfish.h b/code/blowfish.h index 3cbc2d7c6..41d2e7e2b 100644 --- a/code/blowfish.h +++ b/code/blowfish.h @@ -33,14 +33,7 @@ #include "win.h" -/// Names and comments from TLBs - #include -#ifndef NO_BLOWFISH_DLL -#include "iblockci.h" -#include -_COM_SMARTPTR_TYPEDEF(IBlockCipher, __uuidof(IBlockCipher)); -#endif /* ** This engine will process data blocks by encryption and decryption. @@ -70,14 +63,6 @@ class BlowfishEngine { enum {MAX_KEY_LENGTH=56}; private: -#ifndef NO_BLOWFISH_DLL - /* - * This points to the block cipher object that performs the actual key setup and - * block processing. Where the cipher is available as a component, this engine is - * only a convenience wrapper around it and keeps no tables of its own. - */ - IBlockCipherPtr BlockCypher; -#else bool IsKeyed; void Sub_Key_Encrypt(unsigned int & left, unsigned int & right); @@ -107,5 +92,4 @@ class BlowfishEngine { ** S-Box tables (four). */ unsigned int bf_S[4][UCHAR_MAX+1]; -#endif }; diff --git a/code/building.cpp b/code/building.cpp index 11f5ea579..35fb35cd9 100644 --- a/code/building.cpp +++ b/code/building.cpp @@ -6241,7 +6241,7 @@ int BuildingClass::Do_MISSION_UNLOAD(void) if (piggy != NULL && piggy->Is_Piggybacking()) { piggy->End_Piggyback(&unit->Locomotion); } - ILocomotionPtr walk(CLSID_DriveLocomotion); + ILocomotionPtr walk(Create_Locomotor(CLSID_DriveLocomotion)); walk->Link_To_Object(unit); piggy = IPiggybackPtr(walk); if (piggy != NULL) { diff --git a/code/bullet.cpp b/code/bullet.cpp index fd5ef3660..dfb151bb5 100644 --- a/code/bullet.cpp +++ b/code/bullet.cpp @@ -52,6 +52,8 @@ #include "bullet.h" +#include "classfactory.h" + #include "_convert.h" #include "_map.h" #include "_rules.h" @@ -137,6 +139,8 @@ BulletClass::BulletClass(void) : AnimFrame(0), AnimRate(0) { + // The game holds the one reference a bullet exists under until Release deletes it. + RefCount = 1; Create_ID(); Bullets.Add(this); } @@ -1518,12 +1522,11 @@ bool BulletClass::Is_Homing(void) const /// made. BulletClass * Create_Bullet(BulletTypeClass const *type, AbstractClass *target, TechnoClass *payback, int strength, WarheadTypeClass const *warhead, int max_speed, int range, bool bright) { - LPVOID unk = NULL; - if (FAILED(CoCreateInstance(CLSID_BulletClass, NULL, CLSCTX_INPROC_SERVER|CLSCTX_INPROC_HANDLER|CLSCTX_LOCAL_SERVER, IID_IUnknown, &unk))) { + BulletClass * bullet = dynamic_cast(Create_Object(CLSID_BulletClass)); + if (bullet == NULL) { return(NULL); } - BulletClass * bullet = (BulletClass *)unk; bullet->Set_Bullet_Data(type, target, payback, strength, warhead, max_speed, range, bright); return(bullet); } diff --git a/code/classfactory.cpp b/code/classfactory.cpp new file mode 100644 index 000000000..610107d15 --- /dev/null +++ b/code/classfactory.cpp @@ -0,0 +1,67 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#include "always.h" + +#include "classfactory.h" +#include "dbgprint.h" + +#include + +namespace { + +struct ClassEntryType { + CLSID Class; + ClassCreatorType Creator; +}; + +std::vector Classes; + +} // namespace + + +// A later registration of the same identifier wins, as the last class object +// published did before. +void Register_Class(CLSID const & classid, ClassCreatorType creator) +{ + for (ClassEntryType & entry : Classes) { + if (entry.Class == classid) { + entry.Creator = creator; + return; + } + } + Classes.push_back({ classid, creator }); +} + + +void Unregister_Classes(void) +{ + Classes.clear(); +} + + +/// +/// Creates a new object of the registered class named by the identifier. +/// +/// The object, owned by the caller, or NULL with a debug line naming the +/// identifier when no class was registered for it. +IPersistent * Create_Object(CLSID const & classid) +{ + for (ClassEntryType const & entry : Classes) { + if (entry.Class == classid) { + return(entry.Creator()); + } + } + + DebugString("No class is registered for {%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\n", + (unsigned long)classid.Data1, (unsigned int)classid.Data2, (unsigned int)classid.Data3, + classid.Data4[0], classid.Data4[1], classid.Data4[2], classid.Data4[3], + classid.Data4[4], classid.Data4[5], classid.Data4[6], classid.Data4[7]); + return(NULL); +} diff --git a/code/classfactory.h b/code/classfactory.h index f11d77047..58702df74 100644 --- a/code/classfactory.h +++ b/code/classfactory.h @@ -9,113 +9,18 @@ #pragma once -template -class TClassFactory : public IClassFactory -{ - public: - TClassFactory(void); - - STDMETHOD(QueryInterface)(REFIID riid, void **ppvObj); - STDMETHOD_(ULONG, AddRef)(void); - STDMETHOD_(ULONG, Release)(void); - - STDMETHOD(CreateInstance)(IUnknown *pUnkOuter, REFIID riid, void **ppbObj); - STDMETHOD(LockServer)(BOOL fLock); - - private: - /* - * This is the number of outstanding references to this factory, counting both the - * interface pointers handed out and any server locks taken. The factory deletes - * itself once the count falls back to zero. - */ - LONG RefCount; -}; - - -template -TClassFactory::TClassFactory(void) : - RefCount(0) -{ -} - - -template -STDMETHODIMP TClassFactory::QueryInterface(REFIID riid, void **ppvObj) -{ - if (ppvObj == NULL) { - return(E_POINTER); - } - - *ppvObj = NULL; - - if (riid == IID_IUnknown) { - *ppvObj = (void *)((IClassFactory *)this); - } else if (riid == IID_IClassFactory) { - *ppvObj = (void *)((IClassFactory *)this); - } - - if (*ppvObj == NULL) { - return(E_NOINTERFACE); - } - - ((IClassFactory *)this)->AddRef(); - - return(S_OK); -} +#include "persist.h" +// The classes a saved game or a unit type can name by class identifier. Startup +// registers each one; nothing is created for an identifier nobody registered. +typedef IPersistent * (* ClassCreatorType)(void); -template -ULONG TClassFactory::AddRef(void) -{ - return(InterlockedIncrement(&RefCount)); -} - - -template -ULONG TClassFactory::Release(void) -{ - int count = InterlockedDecrement(&RefCount); - if (count == 0) { - delete this; - } - - return(count); -} - - -template -STDMETHODIMP TClassFactory::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObj) -{ - if (ppvObj == NULL) { - return(E_INVALIDARG); - } - - *ppvObj = NULL; - if (pUnkOuter != NULL) { - return(CLASS_E_NOAGGREGATION); - } - - T *obj = new T(); - if (obj == NULL) { - return(E_OUTOFMEMORY); - } - - HRESULT hr = obj->QueryInterface(riid, ppvObj); - if (FAILED(hr)) { - delete obj; - } - - return(hr); -} - +void Register_Class(CLSID const & classid, ClassCreatorType creator); +void Unregister_Classes(void); +IPersistent * Create_Object(CLSID const & classid); template -HRESULT STDMETHODCALLTYPE TClassFactory::LockServer(BOOL fLock) +void Register_Class(CLSID const & classid) { - if (fLock) { - RefCount++; - } else { - RefCount--; - } - return(S_OK); + Register_Class(classid, []() -> IPersistent * { return(new T); }); } diff --git a/code/drive.cpp b/code/drive.cpp index 0de192a7b..4a06b6d6b 100644 --- a/code/drive.cpp +++ b/code/drive.cpp @@ -215,7 +215,7 @@ void DriveLocomotionClass::Serialize(SaveStreamClass & stream) if (stream.Is_Saving()) { Save_Object(stream, (ILocomotion *)Piggybacker); } else { - Load_Object(stream, IID_ILocomotion, (LPVOID *)&Piggybacker); + Piggybacker = Load_Locomotor(stream); } } // TrackControl -- constant tables shared by every driver. diff --git a/code/droppod.cpp b/code/droppod.cpp index fece78c5e..ecfbbb03a 100644 --- a/code/droppod.cpp +++ b/code/droppod.cpp @@ -247,7 +247,7 @@ void DropPodLocomotionClass::Serialize(SaveStreamClass & stream) if (stream.Is_Saving()) { Save_Object(stream, (ILocomotion *)Piggybacker); } else { - Load_Object(stream, IID_ILocomotion, (LPVOID *)&Piggybacker); + Piggybacker = Load_Locomotor(stream); } } } diff --git a/code/foot.cpp b/code/foot.cpp index 2f50a4676..73746eb1c 100644 --- a/code/foot.cpp +++ b/code/foot.cpp @@ -3516,17 +3516,12 @@ void FootClass::Serialize(SaveStreamClass & stream) /* * The locomotor is a sub-object rather than a member, so it travels as a record of - * its own. The one being replaced is released first, since loading hands back a fresh - * interface pointer rather than filling this one in. + * its own. */ if (stream.Is_Saving()) { Save_Object(stream, (ILocomotion *)Locomotion); } else { - if (Locomotion != NULL) { - ((ILocomotion *)Locomotion)->Release(); - } - Locomotion.Detach(); - Load_Object(stream, IID_ILocomotion, (LPVOID *)&Locomotion); + Locomotion = Load_Locomotor(stream); } stream.Serialize(HeadToCoord); @@ -3591,7 +3586,7 @@ void FootClass::Set_Coord(Coord const & coord) void FootClass::Link_DropPod(void) { ILocomotionPtr locomotion = Locomotion; - ILocomotionPtr ballistic(CLSID_BallisticLocomotion); + ILocomotionPtr ballistic(Create_Locomotor(CLSID_BallisticLocomotion)); ballistic->Link_To_Object(this); IPiggybackPtr piggy(ballistic); piggy->Begin_Piggyback(locomotion); diff --git a/code/globals.cpp b/code/globals.cpp index c214917b1..89eef0178 100644 --- a/code/globals.cpp +++ b/code/globals.cpp @@ -33,16 +33,12 @@ #include "always.h" /// create all com interfaces here -#include "iblowfish.h" -#include "iblowfish_i.c" #include "sun.h" #include "isun_i.c" #include "ilocos.h" #include "ilocos_i.c" #include "ipiggy.h" #include "ipiggy_i.c" -#include "iblockci.h" -#include "iblockci_i.c" #include "iflyctrl.h" #include "iflyctrl_i.c" #undef INCLUDE_COM diff --git a/code/house.cpp b/code/house.cpp index 7c2f6ff11..d06493b0c 100644 --- a/code/house.cpp +++ b/code/house.cpp @@ -690,8 +690,15 @@ HouseClass::~HouseClass (void) } SuperWeapon.Clear(); + // A tag removes itself from this list as it dies; a slot a failed load left empty is + // removed here, or the list would never drain. while (HouseTags.Count() > 0) { - delete HouseTags[0]; + TagClass * const tag = HouseTags[0]; + if (tag == NULL) { + HouseTags.Delete_Index(0); + } else { + delete tag; + } } AbstractTypePtrTracker.Delete(this); diff --git a/code/iblockci.h b/code/iblockci.h deleted file mode 100644 index 0240dffd4..000000000 --- a/code/iblockci.h +++ /dev/null @@ -1,27 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#pragma once - -#include - -/// Names and comments from TLBs - -EXTERN_C const IID IID_IBlockCipher; - -MIDL_INTERFACE("E0113100-6A7C-11D1-B6F9-00A024DDAFD1") -IBlockCipher : public IUnknown -{ -public: - virtual HRESULT STDMETHODCALLTYPE Set_Key(LONG keylength, const void *key) = 0; - virtual HRESULT STDMETHODCALLTYPE get_Max_Key_Length(LONG *length) = 0; - virtual HRESULT STDMETHODCALLTYPE get_Block_Size(LONG *length) = 0; - virtual HRESULT STDMETHODCALLTYPE Encrypt(LONG length, const void *plaintext, void *cyphertext) = 0; - virtual HRESULT STDMETHODCALLTYPE Decrypt(LONG length, const void *cyphertext, void *plaintext) = 0; -}; diff --git a/code/iblockci_i.c b/code/iblockci_i.c deleted file mode 100644 index 750d2597f..000000000 --- a/code/iblockci_i.c +++ /dev/null @@ -1,52 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -/* this file contains the actual definitions of */ -/* the IIDs and CLSIDs */ - -/* link this file in with the server and any clients */ - - -/* File created by MIDL compiler version X.XX.XXXX */ -/* at XXX XXX XX XX:XX:XX XXXX - */ -/* Compiler settings for XXXX.idl: - Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext - error checks: none -*/ -//@@MIDL_FILE_HEADING( ) -#ifdef __cplusplus -extern "C"{ -#endif - - -#ifndef __IID_DEFINED__ -#define __IID_DEFINED__ - -typedef struct _IID -{ - unsigned long x; - unsigned short s1; - unsigned short s2; - unsigned char c[8]; -} IID; - -#endif // __IID_DEFINED__ - -#ifndef CLSID_DEFINED -#define CLSID_DEFINED -typedef IID CLSID; -#endif // CLSID_DEFINED - -const IID IID_IBlockCipher = {0xE0113100,0x6A7C,0x11D1,{0xB6,0xF9,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -#ifdef __cplusplus -} -#endif diff --git a/code/iblowfish.h b/code/iblowfish.h deleted file mode 100644 index 4c01ee694..000000000 --- a/code/iblowfish.h +++ /dev/null @@ -1,17 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#pragma once - -#include - -/// Names and comments from TLBs - -EXTERN_C const IID LIBID_BlowfishLibrary; -EXTERN_C const CLSID CLSID_BlowfishObject; diff --git a/code/iblowfish_i.c b/code/iblowfish_i.c deleted file mode 100644 index 71ad0728b..000000000 --- a/code/iblowfish_i.c +++ /dev/null @@ -1,55 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -/* this file contains the actual definitions of */ -/* the IIDs and CLSIDs */ - -/* link this file in with the server and any clients */ - - -/* File created by MIDL compiler version X.XX.XXXX */ -/* at XXX XXX XX XX:XX:XX XXXX - */ -/* Compiler settings for XXXX.idl: - Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext - error checks: none -*/ -//@@MIDL_FILE_HEADING( ) -#ifdef __cplusplus -extern "C"{ -#endif - - -#ifndef __IID_DEFINED__ -#define __IID_DEFINED__ - -typedef struct _IID -{ - unsigned long x; - unsigned short s1; - unsigned short s2; - unsigned char c[8]; -} IID; - -#endif // __IID_DEFINED__ - -#ifndef CLSID_DEFINED -#define CLSID_DEFINED -typedef IID CLSID; -#endif // CLSID_DEFINED - -const IID LIBID_BlowfishLibrary = {0xE7F91750,0x8861,0x11d1,{0xB7,0x07,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -const CLSID CLSID_BlowfishObject = {0x1440ad10,0x6aa8,0x11d1,{0xb6,0xf9,0x00,0xa0,0x24,0xdd,0xaf,0xd1}}; - - -#ifdef __cplusplus -} -#endif diff --git a/code/infantry.cpp b/code/infantry.cpp index e0bff5b21..ad2a57789 100644 --- a/code/infantry.cpp +++ b/code/infantry.cpp @@ -250,7 +250,7 @@ InfantryClass::InfantryClass(InfantryTypeClass const * type, HouseClass * house) Init(); if (Class != NULL) { - Locomotion.CreateInstance(Class->Locomotor, NULL, CLSCTX_ALL); + Locomotion = Create_Locomotor(Class->Locomotor); Locomotion->Link_To_Object(this); } @@ -1192,7 +1192,7 @@ void InfantryClass::Assign_Destination(AbstractClass * target, bool immediate) piggy->End_Piggyback(&Locomotion); } } - ILocomotionPtr walk(CLSID_WalkLocomotion); + ILocomotionPtr walk(Create_Locomotor(CLSID_WalkLocomotion)); walk->Link_To_Object(this); piggy = IPiggybackPtr(walk); if (piggy != NULL) { @@ -4181,7 +4181,7 @@ bool InfantryClass::JumpJet_To_Walk(void) if (Is_JumpJet()) { IPiggybackPtr piggy(Locomotion); if (piggy != NULL && !piggy->Is_Piggybacking()) { - ILocomotionPtr walk(CLSID_WalkLocomotion); + ILocomotionPtr walk(Create_Locomotor(CLSID_WalkLocomotion)); walk->Link_To_Object(this); piggy = IPiggybackPtr(walk); if (piggy != NULL) { diff --git a/code/loco.cpp b/code/loco.cpp index e956b29e1..62c3b61b3 100644 --- a/code/loco.cpp +++ b/code/loco.cpp @@ -14,10 +14,13 @@ #include "_map.h" #include "_tactica.h" #include "cell.h" +#include "classfactory.h" #include "coord.h" +#include "dbgprint.h" #include "foot.h" #include "globals.h" #include "map.h" +#include "saveload.h" #include "savestream.h" #include "swizzle.h" #include "tactical.h" @@ -28,6 +31,7 @@ #include "zgrad.hh" #include +#include extern ULONG COMRefCount; @@ -247,6 +251,32 @@ LONG STDMETHODCALLTYPE LocomotionClass::QueryInterface(REFIID riid, LPVOID *ppvO } +ILocomotion * Create_Locomotor(CLSID const & classid) +{ + IPersistent * const object = Create_Object(classid); + ILocomotion * const locomotion = dynamic_cast(object); + if (locomotion == NULL) { + delete object; + } + return(locomotion); +} + + +ILocomotion * Load_Locomotor(SaveStreamClass & stream) +{ + SwizzleManagerClass::MarkType const mark = Swizzler.Mark(); + IPersistent * const object = Load_Object(stream); + ILocomotion * const locomotion = dynamic_cast(object); + if (object != NULL && locomotion == NULL) { + DebugString("Save record of %s at %u is not a locomotor\n", typeid(*object).name(), stream.Offset()); + Swizzler.Abandon(mark); + delete object; + stream.Fail(); + } + return(locomotion); +} + + CLSID Locomotion_Class_ID(ILocomotion * locomotion) { CLSID classid = CLSID_NULL; @@ -305,9 +335,6 @@ HRESULT LocomotionClass::Load_Members(SaveStreamClass & stream) Serialize(stream); stream.Set_Context(outertype, outerid); - if (SUCCEEDED(stream.Result())) { - Post_Load(); - } return(stream.Result()); } diff --git a/code/loco.h b/code/loco.h index 8f0006661..42e840d7c 100644 --- a/code/loco.h +++ b/code/loco.h @@ -20,6 +20,14 @@ class SaveStreamClass; // CLSID_NULL when it is not one of ours. CLSID Locomotion_Class_ID(ILocomotion * locomotion); +// A new, unlinked locomotor of the registered class, or NULL when the identifier names +// no locomotor. +ILocomotion * Create_Locomotor(CLSID const & classid); + +// The locomotor whose record is next in the stream, or NULL when the record names +// something that is not one, which fails the stream. +ILocomotion * Load_Locomotor(SaveStreamClass & stream); + class LocomotionClass : public IPersistent, public ILocomotion { @@ -89,9 +97,9 @@ class LocomotionClass : public IPersistent, public ILocomotion virtual void Serialize(SaveStreamClass & stream); /* - * Restores whatever the record could not carry. Load_Members calls this once the - * members are in place, so a base class fixup runs even when the load was entered - * through a derived class. + * Restores whatever the record could not carry. Load_Object calls this once the + * record has been checked, so a locomotor never takes its place while its record + * is still in doubt. */ virtual void Post_Load(void); diff --git a/code/mouse.cpp b/code/mouse.cpp index f13ec8b91..45b1c729f 100644 --- a/code/mouse.cpp +++ b/code/mouse.cpp @@ -511,8 +511,9 @@ HRESULT MouseClass::Load(SaveStreamClass & stream) return(result); } for (i = 0; i < count; i++) { - LPVOID ptr; - Load_Object(stream, IID_IUnknown, &ptr); + if (Load_Object(stream) == NULL) { + return(stream.Result()); + } } TerrainTypeClass::Init(Scen->Theater); diff --git a/code/persist.h b/code/persist.h index e2cb97908..05fed10e6 100644 --- a/code/persist.h +++ b/code/persist.h @@ -19,5 +19,8 @@ struct IPersistent : public IUnknown { virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * classid) = 0; virtual HRESULT Load(SaveStreamClass & stream) = 0; + // Restores what the record could not carry, once the record has been checked; an object + // takes its place in the map or a side table here, never while its record is still in doubt. + virtual void Post_Load(void) {} virtual HRESULT Save(SaveStreamClass & stream, BOOL cleardirty) = 0; }; diff --git a/code/saveload.cpp b/code/saveload.cpp index 43acedce6..a2ff20608 100644 --- a/code/saveload.cpp +++ b/code/saveload.cpp @@ -67,6 +67,7 @@ #include "builtype.h" #include "bullet.h" #include "bullettype.h" +#include "classfactory.h" #include "data.h" #include "dbgprint.h" #include "empulse.h" @@ -142,6 +143,7 @@ #include "objheaps.hh" +#include #include //#define SAVE_BLOCK_SIZE 512 @@ -202,61 +204,51 @@ HRESULT Save_Object(SaveStreamClass & stream, ILocomotion * locomotion) /// /// Recreates one object from the save stream. -/// The object is created through the class factory registered for the identifier the -/// record carries, and reattaches itself to its own heap as it is constructed. +/// The object is created through the class registered for the identifier the record +/// carries, and reattaches itself to its own heap as it is constructed. /// -/// The interface to hand back, or IID_IUnknown when the caller -/// only needs the object to exist. -/// Receives the interface, or NULL on failure. -/// Returns with S_OK, or the failure code of what went wrong: an identifier no -/// class answers to, a record the object could not read, or one whose length does not -/// match what the object consumed. -HRESULT Load_Object(SaveStreamClass & stream, REFIID riid, void ** object) +/// The object, or NULL with the stream failed when the identifier names no +/// registered class, the object could not read its record, or the record's length does +/// not match what the object consumed. +IPersistent * Load_Object(SaveStreamClass & stream) { - if (object != NULL) { - *object = NULL; - } - CLSID classid; unsigned int length = 0; stream.Serialize_Bytes(&classid, sizeof(classid)); stream.Serialize(length); if (stream.Was_Error()) { - return(stream.Result()); + return(NULL); } unsigned int const start = stream.Offset(); if (length > stream.Size() - start) { DebugString("Save record at %u claims %u bytes, past the end of the save\n", start, length); - return(E_FAIL); + stream.Fail(); + return(NULL); } - IUnknown * unknown = NULL; - HRESULT result = CoCreateInstance(classid, NULL, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, - IID_IUnknown, (LPVOID *)&unknown); - if (FAILED(result)) { - DebugString("Save record at %u names a class this build does not register\n", start); - return(result); - } - - IPersistent * const persist = dynamic_cast(unknown); + SwizzleManagerClass::MarkType const mark = Swizzler.Mark(); + std::unique_ptr persist(Create_Object(classid)); if (persist == NULL) { - unknown->Release(); - return(E_NOINTERFACE); + DebugString("Save record at %u names a class this build does not register\n", start); + stream.Fail(); + return(NULL); } - result = persist->Load(stream); - if (SUCCEEDED(result) && stream.Offset() != start + length) { + bool ok = SUCCEEDED(persist->Load(stream)); + if (ok && stream.Offset() != start + length) { DebugString("Save record of %s at %u is %u bytes but %u were read\n", - typeid(*unknown).name(), start, length, stream.Offset() - start); - result = E_FAIL; + typeid(*persist).name(), start, length, stream.Offset() - start); + ok = false; } - if (SUCCEEDED(result) && object != NULL) { - result = unknown->QueryInterface(riid, object); + if (!ok) { + Swizzler.Abandon(mark); + stream.Fail(); + return(NULL); } - unknown->Release(); - return(result); + persist->Post_Load(); + return(persist.release()); } @@ -278,10 +270,8 @@ static HRESULT Load_Vector(SaveStreamClass & stream) } for (int index = 0; index < count; index++) { - LPVOID obj; - HRESULT const result = Load_Object(stream, IID_IUnknown, &obj); - if (FAILED(result)) { - return(result); + if (Load_Object(stream) == NULL) { + return(stream.Result()); } } return(S_OK); @@ -799,8 +789,8 @@ static bool Get_All(SaveStreamClass & stream, bool save_net) delete TacticalMap; TacticalMap = NULL; } - Tactical * old_tactical; - if (FAILED(Load_Object(stream, IID_IUnknown, (LPVOID *)&old_tactical))) { + Tactical * old_tactical = dynamic_cast(Load_Object(stream)); + if (old_tactical == NULL) { return(false); } @@ -1128,6 +1118,9 @@ bool Load_Game(const char *file_name) bool res = Get_All(stream, false); if (!res) { DebugString("\t***** FAILED! (0x%08lx at %u of %u bytes)\n", (unsigned long)stream.Result(), stream.Offset(), stream.Size()); + // What was loaded stays in the heaps until the next teardown, which must not + // follow the identities still sitting in its pointer slots. + Swizzler.Abandon(); return(false); } if (stream.Offset() != stream.Size()) { diff --git a/code/saveload.h b/code/saveload.h index b9b686118..c4db4bb6d 100644 --- a/code/saveload.h +++ b/code/saveload.h @@ -28,9 +28,10 @@ int Load_Misc_Values(SaveStreamClass & stream); int Save_Misc_Values(SaveStreamClass & stream); // An object travels as its class identifier, the length of its record, and the record. +// A locomotor loaded this way is handed back unowned; the caller takes it. HRESULT Save_Object(SaveStreamClass & stream, IPersistent * object); HRESULT Save_Object(SaveStreamClass & stream, ILocomotion * locomotion); -HRESULT Load_Object(SaveStreamClass & stream, REFIID riid, void ** object); +IPersistent * Load_Object(SaveStreamClass & stream); bool Get_Savefile_Info(char const * name, SaveVersionInfo * info); bool Save_Game(const char *file_name, char const * descr); bool Load_Game(const char *file_name); diff --git a/code/startup.cpp b/code/startup.cpp index b6539179e..5ca22da0a 100644 --- a/code/startup.cpp +++ b/code/startup.cpp @@ -81,7 +81,6 @@ #include "house.h" #include "houstype.h" #include "hover.h" -#include "iblowfish.h" #include "infantry.h" #include "infatype.h" #include "init.h" @@ -173,20 +172,9 @@ extern HINSTANCE LanguageResources; #define AUTOPLAY_GUID "b350c6d2-2f36-11d3-a72c-0090272fa661" -#ifndef NO_BLOWFISH_DLL -const struct RegStruct { - const GUID *clsid; - const char *name; -} RegisterTheseDLLs[] = { - { &CLSID_BlowfishObject, "blowfish.dll" } -}; -#endif - HANDLE AppMutex; HANDLE AutoPlayMutex; -DynamicVectorClass RegisteredClasses; - //WinTimerClass * WinTimer; /// @@ -233,55 +221,13 @@ void Reset_Surfaces(void) } /// -/// Registers the game's COM classes with OLE. -/// This routine is called during startup, before anything that lives in the object -/// database can be created. It first ensures the support DLLs are present, asking any -/// that OLE cannot yet instantiate to register themselves, and then publishes a class -/// factory for every persistent game class so that objects can be created by CLSID. The -/// player is told by way of a message box if a support DLL could not be prepared. +/// Registers every class a saved game or a unit type can name by class identifier. +/// This runs during startup, before anything that lives in the object database can be +/// created. /// -/// bool; Did the preparation fail? Note the sense -- true means trouble. -static bool RegisterClasses(void) +static void RegisterClasses(void) { - - bool failed = false; -#ifndef NO_BLOWFISH_DLL - for (int i = 0; i < ARRAY_SIZE(RegisterTheseDLLs); i++) { - IUnknownPtr ptr; - HRESULT result = ptr.CreateInstance(*RegisterTheseDLLs[i].clsid, NULL, CLSCTX_ALL); - failed = FAILED(result); - if (failed) { - failed = false; - HINSTANCE hModule = LoadLibrary(RegisterTheseDLLs[i].name); - if (hModule != NULL) { - FARPROC fprocDllReg = (FARPROC)GetProcAddress(hModule, "DllRegisterServer"); - if (!fprocDllReg || (fprocDllReg(), FAILED(ptr.CreateInstance(*RegisterTheseDLLs[i].clsid, NULL, CLSCTX_ALL)))) { - failed = true; - } - FreeLibrary(hModule); - } else { - failed = true; - } - } - if (failed) { - break; - } - ptr.Release(); - } -#endif - - DWORD dwRegister; - IClassFactory *t; - - /// Handy macros to easily register the class factories. - - /// Register a class-object with OLE. - #define REGISTER_CLASS(_class, _clsid) \ - { \ - t = new TClassFactory<_class>; \ - CoRegisterClassObject(_clsid, t, CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, &dwRegister); \ - RegisteredClasses.Add(dwRegister); \ - } \ + #define REGISTER_CLASS(_class, _clsid) Register_Class<_class>(_clsid); REGISTER_CLASS(WaveClass, CLSID_WaveClass); REGISTER_CLASS(TerrainTypeClass, CLSID_TerrainTypeClass); @@ -349,13 +295,6 @@ static bool RegisterClasses(void) REGISTER_CLASS(NeuronClass, CLSID_NeuronClass); REGISTER_CLASS(FoggedObjectClass, CLSID_FoggedObjectClass); REGISTER_CLASS(AlphaShapeClass, CLSID_AlphaShapeClass); - - if (failed) { - MessageBox(NULL, Fetch_String(TXT_PREPARECOM_FAILED), Fetch_String(TXT_SHORT_TITLE), MB_ICONEXCLAMATION); - } - - return(failed); - } /// @@ -516,11 +455,7 @@ int CALLBACK WinMain ( HINSTANCE instance , HINSTANCE , char * , int command_sho return(EXIT_SUCCESS); } - OleInitialize(NULL); - - if (RegisterClasses()) { - exit(EXIT_FAILURE); - } + RegisterClasses(); /* ** Get the full path to the .EXE @@ -594,7 +529,6 @@ int CALLBACK WinMain ( HINSTANCE instance , HINSTANCE , char * , int command_sho wsprintf (buffer, Fetch_String(TXT_CRITICALLY_LOW), (INIT_FREE_DISK_SPACE) / (1024 * 1024)); int reply = MessageBox(NULL, buffer, Fetch_String(TXT_SHORT_TITLE), MB_ICONQUESTION|MB_YESNO); if (reply == IDNO) { - OleUninitialize(); return(EXIT_FAILURE); } } @@ -725,7 +659,6 @@ int CALLBACK WinMain ( HINSTANCE instance , HINSTANCE , char * , int command_sho Debug_Console_Hold(); } - OleUninitialize(); return(error_code); } @@ -1046,10 +979,7 @@ void __cdecl Prog_End(void) Scen = NULL; } - for (i = 0; i < RegisteredClasses.Count(); i++) { - CoRevokeClassObject((DWORD)RegisteredClasses[i]); - } - RegisteredClasses.Clear(); + Unregister_Classes(); if (LanguageResources) { FreeLibrary(LanguageResources); @@ -1097,7 +1027,6 @@ void Emergency_Exit(void) } } - OleUninitialize(); if (MouseCursor) { MouseCursor->Release_Mouse(); diff --git a/code/swizzle.cpp b/code/swizzle.cpp index c13694f4f..04120ba47 100644 --- a/code/swizzle.cpp +++ b/code/swizzle.cpp @@ -181,6 +181,21 @@ void SwizzleManagerClass::Resolve(void) } +/// +/// Takes back everything registered since the mark, clearing each pointer slot it covers. +/// A slot that was registered still holds the identity read from the file, which is not +/// an address; clearing it lets the object it belongs to be destroyed safely. +/// +void SwizzleManagerClass::Abandon(MarkType const & mark) +{ + for (std::size_t index = mark.Requests; index < RequestTable.size(); index++) { + *(void **)RequestTable[index].Pointer = NULL; + } + RequestTable.resize(mark.Requests); + PointerTable.resize(mark.Pointers); +} + + /// /// Throws away every pending request and announcement. /// The load code calls this routine before it starts reading, so that whatever a load that diff --git a/code/swizzle.h b/code/swizzle.h index 85c16125c..9d11e2910 100644 --- a/code/swizzle.h +++ b/code/swizzle.h @@ -73,6 +73,18 @@ class SwizzleManagerClass void Resolve(void); void Discard(void); + /* + * The tables' extent at some point of a load, so that a record which fails after + * it can take back what it registered before its object is destroyed. + */ + struct MarkType { + std::size_t Requests; + std::size_t Pointers; + }; + MarkType Mark(void) const {return(MarkType{RequestTable.size(), PointerTable.size()});} + void Abandon(MarkType const & mark); + void Abandon(void) {Abandon(MarkType{0, 0});} + private: /* * These are the pointers read back from the save file that still hold a swizzle ID diff --git a/code/unit.cpp b/code/unit.cpp index 30776fb03..5b247ab22 100644 --- a/code/unit.cpp +++ b/code/unit.cpp @@ -223,7 +223,7 @@ UnitClass::UnitClass(UnitTypeClass const * type, HouseClass * house) : SecondaryFacing.Set(PrimaryFacing.Current()); if (Class != NULL) { - Locomotion = ILocomotionPtr(Class->Locomotor, NULL, CLSCTX_ALL); + Locomotion = Create_Locomotor(Class->Locomotor); Locomotion->Link_To_Object(this); } @@ -5338,7 +5338,7 @@ void UnitClass::Assign_Destination(AbstractClass * target, bool immediate) if (piggy != NULL && piggy->Is_Piggybacking()) { piggy->End_Piggyback(&Locomotion); } - ILocomotionPtr walk(CLSID_DriveLocomotion); + ILocomotionPtr walk(Create_Locomotor(CLSID_DriveLocomotion)); walk->Link_To_Object(this); piggy = IPiggybackPtr(walk); if (piggy != NULL) { diff --git a/code/walk.cpp b/code/walk.cpp index 8ad9bdd7e..d91769ad4 100644 --- a/code/walk.cpp +++ b/code/walk.cpp @@ -647,7 +647,7 @@ void WalkLocomotionClass::Serialize(SaveStreamClass & stream) if (stream.Is_Saving()) { Save_Object(stream, (ILocomotion *)Piggybacker); } else { - Load_Object(stream, IID_ILocomotion, (LPVOID *)&Piggybacker); + Piggybacker = Load_Locomotor(stream); } } } diff --git a/docs/SAVE-FORMAT.md b/docs/SAVE-FORMAT.md index e2bc42b43..7fe320c53 100644 --- a/docs/SAVE-FORMAT.md +++ b/docs/SAVE-FORMAT.md @@ -74,16 +74,23 @@ names them. An object record is: | | The body: the swizzle identity, then the members the class's `Serialize` names | The class identifier is the `CLSID` the object's `GetClassID` reports, the -same one registered with the class factory in `code/startup.cpp` and, for a -locomotor, named by the `Locomotor=` key. The reader creates the object -through `CoCreateInstance`, hands it the stream, and then checks that it -consumed exactly the recorded length. A record that comes up short or long -fails the load with the object's type and offset in the debug log, which is -what a member added to one build and not the other looks like. A vector of -objects is a 4-byte count followed by that many records, and a locomotor -nested inside a unit's record is a record of its own. A count that the bytes -remaining in the content could not hold fails the load before anything is -allocated for it. +same one registered in `code/startup.cpp` and, for a locomotor, named by the +`Locomotor=` key. The reader creates the object through that registration, +hands it the stream, checks that it consumed exactly the recorded length, and +only then lets it finish restoring itself, so a refused record never reaches +the map or a side table. A record that comes up short or long fails the load with the object's +type and offset in the debug log, which is what a member added to one build +and not the other looks like. A record read where a locomotor belongs fails +the load the same way when its class is not one. A vector of objects is a +4-byte count followed by that many records, and a locomotor nested inside a +unit's record is a record of its own. A count that the bytes remaining in the +content could not hold fails the load before anything is allocated for it. + +An object whose record fails is destroyed before the load fails. The pointer +slots it had registered are cleared first, since they still hold identities +rather than addresses. The objects loaded before it keep their places in the +heaps and have their slots cleared the same way, so a failed load leaves +nothing that a later teardown cannot delete. The body is what each class's `Serialize` produces, member by member, in host byte order. It is not described here; the classes are the description. From bb2da2af47d19d632c7961e02448572b75e3e4a5 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Mon, 7 Sep 2026 00:10:53 +0200 Subject: [PATCH 024/179] Hold locomotors by unique pointer instead of reference count --- code/building.cpp | 16 +++++----- code/drive.cpp | 70 +++++++++---------------------------------- code/drive.h | 15 +++++----- code/droppod.cpp | 75 ++++++++++++----------------------------------- code/droppod.h | 15 +++++----- code/foot.cpp | 32 ++++++++++---------- code/foot.h | 4 ++- code/iloco.h | 1 + code/infantry.cpp | 32 ++++++++++---------- code/ipiggy.h | 31 ++++++++------------ code/loco.cpp | 8 ++--- code/loco.h | 10 +++---- code/unit.cpp | 18 ++++++------ code/walk.cpp | 68 +++++++++--------------------------------- code/walk.h | 15 +++++----- 15 files changed, 146 insertions(+), 264 deletions(-) diff --git a/code/building.cpp b/code/building.cpp index 35fb35cd9..0a4d296d4 100644 --- a/code/building.cpp +++ b/code/building.cpp @@ -5516,7 +5516,7 @@ int BuildingClass::Do_MISSION_REPAIR(void) ** distance check. Fixed-wing aircraft are very inaccurate with ** their landings. */ - CLSID const clsid = Locomotion_Class_ID(tech->Locomotion); + CLSID const clsid = Locomotion_Class_ID(tech->Locomotion.get()); bool hover = (clsid == CLSID_HoverLocomotion) != 0; if (hover) { distance = 0x96; @@ -6234,19 +6234,19 @@ int BuildingClass::Do_MISSION_UNLOAD(void) if (unit) { unit->Assign_Mission(MISSION_MOVE); - CLSID const clsid = Locomotion_Class_ID(unit->Locomotion); + CLSID const clsid = Locomotion_Class_ID(unit->Locomotion.get()); if (clsid == CLSID_TunnelLocomotion) { - IPiggybackPtr piggy(unit->Locomotion); + IPiggyback * piggy = Piggyback_Of(unit->Locomotion.get()); if (piggy != NULL && piggy->Is_Piggybacking()) { - piggy->End_Piggyback(&unit->Locomotion); + unit->Locomotion = piggy->End_Piggyback(); } - ILocomotionPtr walk(Create_Locomotor(CLSID_DriveLocomotion)); + std::unique_ptr walk = Create_Locomotor(CLSID_DriveLocomotion); walk->Link_To_Object(unit); - piggy = IPiggybackPtr(walk); + piggy = Piggyback_Of(walk.get()); if (piggy != NULL) { - piggy->Begin_Piggyback(unit->Locomotion); - unit->Locomotion = walk; + piggy->Begin_Piggyback(std::move(unit->Locomotion)); + unit->Locomotion = std::move(walk); unit->Locomotion->Force_Track(DriveLocomotionClass::OUT_OF_WEAPON_FACTORY, coord); } else { int damage = unit->Strength; diff --git a/code/drive.cpp b/code/drive.cpp index 4a06b6d6b..23f987ecd 100644 --- a/code/drive.cpp +++ b/code/drive.cpp @@ -118,7 +118,7 @@ DriveLocomotionClass::DriveLocomotionClass(void) : TargetSpeed(0), TrackNumber(-1), TrackIndex(-1), - Piggybacker(NULL) + Piggybacker() { } @@ -147,41 +147,13 @@ HRESULT DriveLocomotionClass::Piggyback_CLSID(CLSID * classid) } if (Piggybacker != NULL) { - *classid = Locomotion_Class_ID(Piggybacker); + *classid = Locomotion_Class_ID(Piggybacker.get()); return(S_OK); } return(GetClassID(classid)); } -/// -/// Fetches an interface supported by this locomotor. -/// The driver answers for the piggyback interface on top of whatever the base locomotor -/// already supports. -/// -/// The identifier of the interface asked for. -/// Pointer to the interface pointer to fill in. -/// Returns with S_OK if the interface was supplied, otherwise -/// E_NOINTERFACE. -HRESULT STDMETHODCALLTYPE DriveLocomotionClass::QueryInterface(REFIID riid, LPVOID * ppvObject) -{ - HRESULT result = BASECLASS::QueryInterface(riid, ppvObject); - - if (result == E_NOINTERFACE) { - if (riid == IID_IPiggyback) { - *ppvObject = (IPiggyback*)this; - } - if (*ppvObject == NULL) { - result = E_NOINTERFACE; - } else { - AddRef(); - result = S_OK; - } - } - return(result); -} - - /// /// Lists the members this driver carries. /// A locomotor riding along on this one is a separate persistent object rather than a @@ -213,7 +185,7 @@ void DriveLocomotionClass::Serialize(SaveStreamClass & stream) if (haspiggy) { if (stream.Is_Saving()) { - Save_Object(stream, (ILocomotion *)Piggybacker); + Save_Object(stream, Piggybacker.get()); } else { Piggybacker = Load_Locomotor(stream); } @@ -229,19 +201,15 @@ void DriveLocomotionClass::Serialize(SaveStreamClass & stream) /// A unit that must travel in some special manner -- through a tunnel, or aboard a /// carrier -- keeps its driver but lets the special locomotor move it for the duration. /// -/// The locomotor that is to take over the unit. -/// Returns with S_OK if the locomotor was taken on, E_FAIL if one is already -/// riding, or E_POINTER if none was supplied. -HRESULT STDMETHODCALLTYPE DriveLocomotionClass::Begin_Piggyback(ILocomotion *pointer) +/// The locomotor that is to take over the unit. +/// bool; Was the locomotor taken on? One already carrying a locomotor refuses. +bool DriveLocomotionClass::Begin_Piggyback(std::unique_ptr carried) { - if (pointer == NULL) { - return(E_POINTER); - } - if (Piggybacker == NULL) { - Piggybacker = pointer; - return(S_OK); + if (carried == NULL || Piggybacker != NULL) { + return(false); } - return(E_FAIL); + Piggybacker = std::move(carried); + return(true); } @@ -250,20 +218,10 @@ HRESULT STDMETHODCALLTYPE DriveLocomotionClass::Begin_Piggyback(ILocomotion *poi /// The riding locomotor is detached and given up, leaving this driver in sole charge of /// the unit once more. /// -/// Pointer to the locomotor pointer to fill in. -/// Returns with S_OK if a locomotor was handed back, S_FALSE if there was none -/// riding, or E_POINTER if no destination was supplied. -HRESULT DriveLocomotionClass::End_Piggyback(ILocomotion **pointer) +/// Returns with the locomotor that was riding, or nothing when none was. +std::unique_ptr DriveLocomotionClass::End_Piggyback(void) { - if (pointer == NULL) { - return(E_POINTER); - } - if (Piggybacker != NULL) { - *pointer = Piggybacker; - Piggybacker.Detach(); - return(S_OK); - } - return(S_FALSE); + return(std::move(Piggybacker)); } @@ -274,7 +232,7 @@ HRESULT DriveLocomotionClass::End_Piggyback(ILocomotion **pointer) /// back only once the unit has settled. /// /// bool; Is it safe to end the piggyback? -boolean DriveLocomotionClass::Is_Ok_To_End(void) +bool DriveLocomotionClass::Is_Ok_To_End(void) { if (!Is_Moving() && (Piggybacker != NULL && IsLocomotorUnlocked)) { return(true); diff --git a/code/drive.h b/code/drive.h index 5011c55ce..5c3d2b587 100644 --- a/code/drive.h +++ b/code/drive.h @@ -40,6 +40,8 @@ #include "matrix3d.h" #include "timer.h" +#include + #include "mark.hh" /**************************************************************************** @@ -62,7 +64,6 @@ class DriveLocomotionClass : public LocomotionClass, public IPiggyback virtual void Serialize(SaveStreamClass & stream) override; - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID * ppvObject) override; virtual ULONG STDMETHODCALLTYPE AddRef(void) override; virtual ULONG STDMETHODCALLTYPE Release(void) override; @@ -90,11 +91,11 @@ class DriveLocomotionClass : public LocomotionClass, public IPiggyback virtual int STDMETHODCALLTYPE Get_Track_Index(void) override; virtual int STDMETHODCALLTYPE Get_Speed_Accum(void) override; - virtual HRESULT STDMETHODCALLTYPE Begin_Piggyback(ILocomotion * pointer) override; - virtual HRESULT STDMETHODCALLTYPE End_Piggyback(ILocomotion ** pointer) override; - virtual boolean STDMETHODCALLTYPE Is_Ok_To_End(void) override; - virtual HRESULT STDMETHODCALLTYPE Piggyback_CLSID(GUID * classid) override; - virtual boolean STDMETHODCALLTYPE Is_Piggybacking(void) override {return(Piggybacker != NULL);} + virtual bool Begin_Piggyback(std::unique_ptr carried) override; + virtual std::unique_ptr End_Piggyback(void) override; + virtual bool Is_Ok_To_End(void) override; + virtual HRESULT Piggyback_CLSID(GUID * classid) override; + virtual bool Is_Piggybacking(void) override {return(Piggybacker != NULL);} /*--------------------------------------------------------------------- ** Member function prototypes. @@ -245,7 +246,7 @@ class DriveLocomotionClass : public LocomotionClass, public IPiggyback * driver answers for it rather than for itself. If NULL, this driver is in sole * charge of the unit. */ - ILocomotionPtr Piggybacker; + std::unique_ptr Piggybacker; /*--------------------------------------------------------------------- ** Member function prototypes. diff --git a/code/droppod.cpp b/code/droppod.cpp index ecfbbb03a..3a08f68e5 100644 --- a/code/droppod.cpp +++ b/code/droppod.cpp @@ -39,7 +39,7 @@ DropPodLocomotionClass::DropPodLocomotionClass(void) : BASECLASS(), Direction(DPOD_DIR_NE), DestinationCoord(COORD_NONE), - Piggybacker(NULL) + Piggybacker() { } @@ -118,8 +118,10 @@ boolean STDMETHODCALLTYPE DropPodLocomotionClass::Process(void) coord = linked->PositionCoord; linked->Limbo(); - AddRef(); - End_Piggyback(&LinkedTo->Locomotion); + // Handing the carried locomotor back makes this pod unowned, so it holds itself + // until the landing is finished and is deleted on return. + std::unique_ptr const self = std::move(LinkedTo->Locomotion); + LinkedTo->Locomotion = End_Piggyback(); if (!linked->Unlimbo(coord, DIR_N)) { Explosion_Damage(coord, 100, LinkedTo, Rule->C4Warhead); @@ -133,7 +135,6 @@ boolean STDMETHODCALLTYPE DropPodLocomotionClass::Process(void) linked->Commence(); linked->Scatter(COORD_NONE); } - Release(); } else { LinkedTo->PositionCoord = coord; WeaponTypeClass const * weapon = Rule->DropPodWeapon; @@ -245,7 +246,7 @@ void DropPodLocomotionClass::Serialize(SaveStreamClass & stream) if (haspiggy) { if (stream.Is_Saving()) { - Save_Object(stream, (ILocomotion *)Piggybacker); + Save_Object(stream, Piggybacker.get()); } else { Piggybacker = Load_Locomotor(stream); } @@ -268,18 +269,15 @@ void STDMETHODCALLTYPE DropPodLocomotionClass::Stop_Moving(void) /// The drop pod holds on to the locomotor it displaces so that the object can be given /// it back when the pod touches down. /// -/// The locomotor to carry. -/// Returns with S_OK, or E_FAIL if something is already being carried. -HRESULT STDMETHODCALLTYPE DropPodLocomotionClass::Begin_Piggyback(ILocomotion * pointer) +/// The locomotor that is to take over the unit. +/// bool; Was the locomotor taken on? One already carrying a locomotor refuses. +bool DropPodLocomotionClass::Begin_Piggyback(std::unique_ptr carried) { - if (pointer == NULL) { - return(E_POINTER); - } - if (Piggybacker == NULL) { - Piggybacker = pointer; - return(S_OK); + if (carried == NULL || Piggybacker != NULL) { + return(false); } - return(E_FAIL); + Piggybacker = std::move(carried); + return(true); } @@ -288,19 +286,10 @@ HRESULT STDMETHODCALLTYPE DropPodLocomotionClass::Begin_Piggyback(ILocomotion * /// The pod gives up its hold without destroying the locomotor, so the object can resume /// using it once the pod has landed. /// -/// Pointer to the location that receives the carried locomotor. -/// Returns with S_OK, or S_FALSE if nothing was being carried. -HRESULT STDMETHODCALLTYPE DropPodLocomotionClass::End_Piggyback(ILocomotion ** pointer) +/// Returns with the locomotor that was riding, or nothing when none was. +std::unique_ptr DropPodLocomotionClass::End_Piggyback(void) { - if (pointer == NULL) { - return(E_POINTER); - } - if (Piggybacker != NULL) { - *pointer = Piggybacker; - Piggybacker.Detach(); - return(S_OK); - } - return(S_FALSE); + return(std::move(Piggybacker)); } @@ -309,7 +298,7 @@ HRESULT STDMETHODCALLTYPE DropPodLocomotionClass::End_Piggyback(ILocomotion ** p /// The carried locomotor may only be given control back once the pod has come to rest. /// /// bool; May the carried locomotor take over again? -boolean STDMETHODCALLTYPE DropPodLocomotionClass::Is_Ok_To_End(void) +bool DropPodLocomotionClass::Is_Ok_To_End(void) { if (!Is_Moving() && Piggybacker != NULL) { return(true); @@ -318,32 +307,6 @@ boolean STDMETHODCALLTYPE DropPodLocomotionClass::Is_Ok_To_End(void) } -/// -/// Fetches an interface pointer from the drop pod locomotor. -/// This routine extends the base locomotor's interface set with IPiggyback, which is how -/// the pod carries the object's real locomotor while it falls. -/// -/// Returns with S_OK, or E_NOINTERFACE if this object does not offer the -/// interface asked for. -HRESULT STDMETHODCALLTYPE DropPodLocomotionClass::QueryInterface(REFIID riid, LPVOID * ppvObject) -{ - HRESULT result = BASECLASS::QueryInterface(riid, ppvObject); - - if (result == E_NOINTERFACE) { - if (riid == IID_IPiggyback) { - *ppvObject = (IPiggyback*)this; - } - if (*ppvObject == NULL) { - result = E_NOINTERFACE; - } else { - AddRef(); - result = S_OK; - } - } - return(result); -} - - /// /// Determines which display layer the pod belongs in. /// A pod is always falling, so it draws along with the other airborne objects right up @@ -362,14 +325,14 @@ LayerType STDMETHODCALLTYPE DropPodLocomotionClass::In_Which_Layer(void) /// /// Returns with S_OK, or an error code if the class ID could not be /// determined. -HRESULT STDMETHODCALLTYPE DropPodLocomotionClass::Piggyback_CLSID(GUID * classid) +HRESULT DropPodLocomotionClass::Piggyback_CLSID(GUID * classid) { if (classid == NULL) { return(E_POINTER); } if (Piggybacker != NULL) { - *classid = Locomotion_Class_ID(Piggybacker); + *classid = Locomotion_Class_ID(Piggybacker.get()); return(S_OK); } return(GetClassID(classid)); diff --git a/code/droppod.h b/code/droppod.h index ecd9da0a7..cdb4c7496 100644 --- a/code/droppod.h +++ b/code/droppod.h @@ -16,6 +16,8 @@ #include "ipiggy.h" #include "loco.h" +#include + class DropPodLocomotionClass : public LocomotionClass, public IPiggyback { @@ -33,7 +35,6 @@ class DropPodLocomotionClass : public LocomotionClass, public IPiggyback virtual void Serialize(SaveStreamClass & stream) override; - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID * ppvObject) override; virtual ULONG STDMETHODCALLTYPE AddRef(void) override {return(BASECLASS::AddRef());} virtual ULONG STDMETHODCALLTYPE Release(void) override {return(BASECLASS::Release());} @@ -45,11 +46,11 @@ class DropPodLocomotionClass : public LocomotionClass, public IPiggyback virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; virtual int STDMETHODCALLTYPE Drawing_Code(void) override; - virtual HRESULT STDMETHODCALLTYPE Begin_Piggyback(ILocomotion * pointer) override; - virtual HRESULT STDMETHODCALLTYPE End_Piggyback(ILocomotion ** pointer) override; - virtual boolean STDMETHODCALLTYPE Is_Ok_To_End(void) override; - virtual HRESULT STDMETHODCALLTYPE Piggyback_CLSID(GUID * classid) override; - virtual boolean STDMETHODCALLTYPE Is_Piggybacking(void) override {return(Piggybacker != NULL);} + virtual bool Begin_Piggyback(std::unique_ptr carried) override; + virtual std::unique_ptr End_Piggyback(void) override; + virtual bool Is_Ok_To_End(void) override; + virtual HRESULT Piggyback_CLSID(GUID * classid) override; + virtual bool Is_Piggybacking(void) override {return(Piggybacker != NULL);} private: enum DropPodDirType { @@ -78,5 +79,5 @@ class DropPodLocomotionClass : public LocomotionClass, public IPiggyback * handed back the moment the pod touches ground, so that the object resumes moving * the way its type normally does. */ - ILocomotionPtr Piggybacker; + std::unique_ptr Piggybacker; }; diff --git a/code/foot.cpp b/code/foot.cpp index 73746eb1c..5fa552428 100644 --- a/code/foot.cpp +++ b/code/foot.cpp @@ -1132,7 +1132,7 @@ void FootClass::Approach_Target(void) */ bool flyer = (RTTI == RTTI_AIRCRAFT); - CLSID const clsid = Locomotion_Class_ID(Locomotion); + CLSID const clsid = Locomotion_Class_ID(Locomotion.get()); if (clsid == CLSID_JumpjetLocomotion) { flyer = true; } @@ -1873,9 +1873,9 @@ bool FootClass::Enter_Idle_Mode(bool, bool resume_waypoint) } bool was_piggybacking = false; - IPiggybackPtr piggy(Locomotion); + IPiggyback * piggy = Piggyback_Of(Locomotion.get()); if (piggy != NULL && piggy->Is_Ok_To_End()) { - piggy->End_Piggyback(&Locomotion); + Locomotion = piggy->End_Piggyback(); was_piggybacking = true; } @@ -2331,9 +2331,9 @@ int FootClass::Do_MISSION_ENTER(void) Enter_Idle_Mode(); } else { if (NavCom == NULL && RouteQueue.Count() > 0 ) { - IPiggybackPtr piggy(Locomotion); + IPiggyback * piggy = Piggyback_Of(Locomotion.get()); if (piggy != NULL && piggy->Is_Ok_To_End()) { - piggy->End_Piggyback(&Locomotion); + Locomotion = piggy->End_Piggyback(); } if (RouteQueue.Count() > 0) { Assign_Destination(RouteQueue[0], false); @@ -2385,7 +2385,7 @@ void FootClass::Assign_Destination(AbstractClass * target, bool) ParticleSystems[ATTACHED_PARTICLE_FIRE] = NULL; } - CLSID const locoid = Locomotion_Class_ID(Locomotion); + CLSID const locoid = Locomotion_Class_ID(Locomotion.get()); if (locoid == CLSID_HoverLocomotion && PathDelay == 0) { PathDelay = 1; @@ -3313,10 +3313,10 @@ void FootClass::AI(void) Scatter(Coord(0,0,0), true); } - IPiggybackPtr piggy(Locomotion); + IPiggyback * piggy = Piggyback_Of(Locomotion.get()); if (piggy != NULL) { if (piggy->Is_Ok_To_End()) { - piggy->End_Piggyback(&Locomotion); + Locomotion = piggy->End_Piggyback(); } } @@ -3519,7 +3519,7 @@ void FootClass::Serialize(SaveStreamClass & stream) * its own. */ if (stream.Is_Saving()) { - Save_Object(stream, (ILocomotion *)Locomotion); + Save_Object(stream, Locomotion.get()); } else { Locomotion = Load_Locomotor(stream); } @@ -3585,12 +3585,12 @@ void FootClass::Set_Coord(Coord const & coord) /// void FootClass::Link_DropPod(void) { - ILocomotionPtr locomotion = Locomotion; - ILocomotionPtr ballistic(Create_Locomotor(CLSID_BallisticLocomotion)); + std::unique_ptr locomotion = std::move(Locomotion); + std::unique_ptr ballistic = Create_Locomotor(CLSID_BallisticLocomotion); ballistic->Link_To_Object(this); - IPiggybackPtr piggy(ballistic); - piggy->Begin_Piggyback(locomotion); - Locomotion = ballistic; + IPiggyback * piggy = Piggyback_Of(ballistic.get()); + piggy->Begin_Piggyback(std::move(locomotion)); + Locomotion = std::move(ballistic); } @@ -4719,7 +4719,7 @@ void FootClass::Delete_Me(void) /// bool; Is the object in the air? bool FootClass::In_Air(void) const { - CLSID const clsid = Locomotion_Class_ID(Locomotion); + CLSID const clsid = Locomotion_Class_ID(Locomotion.get()); if (clsid == CLSID_HoverLocomotion) { return(false); @@ -4740,7 +4740,7 @@ bool FootClass::On_Ground(void) const if (BASECLASS::On_Ground()) { return(true); } - CLSID const clsid = Locomotion_Class_ID(Locomotion); + CLSID const clsid = Locomotion_Class_ID(Locomotion.get()); return(IsDown && clsid == CLSID_HoverLocomotion); } diff --git a/code/foot.h b/code/foot.h index 7ec7d23c1..21e43fee0 100644 --- a/code/foot.h +++ b/code/foot.h @@ -37,6 +37,8 @@ #include "team.h" #include "techno.h" +#include + class UnitClass; class BuildingClass; class WaypointClass; @@ -208,7 +210,7 @@ class FootClass : public TechnoClass * handed to a ballistic locomotor and a unit crossing a tunnel walks -- so all * movement is asked of this interface rather than of the type's setting. */ - ILocomotionPtr Locomotion; + std::unique_ptr Locomotion; /* ** This is the coordinate that the unit is heading to diff --git a/code/iloco.h b/code/iloco.h index 1d43d5e35..15bb5f01e 100644 --- a/code/iloco.h +++ b/code/iloco.h @@ -21,6 +21,7 @@ #include "zgrad.hh" #include +#include /// Names and comments from TLBs diff --git a/code/infantry.cpp b/code/infantry.cpp index ad2a57789..56c02ec49 100644 --- a/code/infantry.cpp +++ b/code/infantry.cpp @@ -632,7 +632,7 @@ void InfantryClass::Draw_It(Point2D const & xpoint, Rect const & cliprect) const Cell cell = Get_Target_Cell(); if (CurrentTube == -1) { - CLSID const clsid = Locomotion_Class_ID(Locomotion); + CLSID const clsid = Locomotion_Class_ID(Locomotion.get()); if (HeightAGL > 0 && clsid == CLSID_BallisticLocomotion) { ShapeSet const * shapefile = (ShapeSet const *)MFCD::Retrieve("POD.SHP"); @@ -1172,7 +1172,7 @@ void InfantryClass::Assign_Destination(AbstractClass * target, bool immediate) } if (target != NULL && Class->IsJumpJet && Locomotion->Is_Moving()) { - CLSID const clsid = Locomotion_Class_ID(Locomotion); + CLSID const clsid = Locomotion_Class_ID(Locomotion.get()); if (clsid == CLSID_WalkLocomotion) { NavQueue.Add_Head(target); target = Get_Target_Cell_Ptr(); @@ -1186,26 +1186,26 @@ void InfantryClass::Assign_Destination(AbstractClass * target, bool immediate) bool should_fly = Should_JumpJet_Fly(Destination_Coord().As_Cell(), target->Center_Coord().As_Cell()); if (Is_JumpJet()) { if (!should_fly) { - IPiggybackPtr piggy(Locomotion); + IPiggyback * piggy = Piggyback_Of(Locomotion.get()); if (piggy != NULL) { if (piggy->Is_Piggybacking() && piggy->Is_Ok_To_End()) { - piggy->End_Piggyback(&Locomotion); + Locomotion = piggy->End_Piggyback(); } } - ILocomotionPtr walk(Create_Locomotor(CLSID_WalkLocomotion)); + std::unique_ptr walk = Create_Locomotor(CLSID_WalkLocomotion); walk->Link_To_Object(this); - piggy = IPiggybackPtr(walk); + piggy = Piggyback_Of(walk.get()); if (piggy != NULL) { - piggy->Begin_Piggyback(Locomotion); - Locomotion = walk; + piggy->Begin_Piggyback(std::move(Locomotion)); + Locomotion = std::move(walk); } } } else { if (should_fly) { - IPiggybackPtr piggy(Locomotion); + IPiggyback * piggy = Piggyback_Of(Locomotion.get()); if (piggy != NULL) { if (piggy->Is_Piggybacking() && piggy->Is_Ok_To_End()) { - piggy->End_Piggyback(&Locomotion); + Locomotion = piggy->End_Piggyback(); } } } @@ -4179,15 +4179,15 @@ bool InfantryClass::JumpJet_To_Walk(void) if (path_length >= 4) return(false); if (Is_JumpJet()) { - IPiggybackPtr piggy(Locomotion); + IPiggyback * piggy = Piggyback_Of(Locomotion.get()); if (piggy != NULL && !piggy->Is_Piggybacking()) { - ILocomotionPtr walk(Create_Locomotor(CLSID_WalkLocomotion)); + std::unique_ptr walk = Create_Locomotor(CLSID_WalkLocomotion); walk->Link_To_Object(this); - piggy = IPiggybackPtr(walk); + piggy = Piggyback_Of(walk.get()); if (piggy != NULL) { Path[0] = FACING_NONE; - piggy->Begin_Piggyback(Locomotion); - Locomotion = walk; + piggy->Begin_Piggyback(std::move(Locomotion)); + Locomotion = std::move(walk); Locomotion->Move_To(NavCom->Center_Coord()); return(true); } @@ -4222,7 +4222,7 @@ bool InfantryClass::Is_JumpJet(void) const return(false); } - CLSID const clsid = Locomotion_Class_ID(Locomotion); + CLSID const clsid = Locomotion_Class_ID(Locomotion.get()); return((clsid == CLSID_JumpjetLocomotion) ? true : false); } diff --git a/code/ipiggy.h b/code/ipiggy.h index 74c1d8216..2f319cb91 100644 --- a/code/ipiggy.h +++ b/code/ipiggy.h @@ -11,43 +11,38 @@ #include "iloco.h" -#include -/// Names and comments from TLBs - -EXTERN_C const IID IID_IPiggyback; - -MIDL_INTERFACE("92FEA800-A184-11D1-B70A-00A024DDAFD1") -IPiggyback : public IUnknown +struct IPiggyback { -public: /* * Piggybacks a locomotor onto this one. */ - virtual HRESULT STDMETHODCALLTYPE Begin_Piggyback(ILocomotion * pointer) = 0; + virtual bool Begin_Piggyback(std::unique_ptr carried) = 0; /* - * End piggyback process and restore locomotor interface pointer. + * Hands the carried locomotor back, or nothing when none is carried. */ - virtual HRESULT STDMETHODCALLTYPE End_Piggyback(ILocomotion ** pointer) = 0; + virtual std::unique_ptr End_Piggyback(void) = 0; /* * Is it ok to end the piggyback process? */ - virtual boolean STDMETHODCALLTYPE Is_Ok_To_End(void) = 0; + virtual bool Is_Ok_To_End(void) = 0; /* * Fetches piggybacked locomotor class ID. */ - virtual HRESULT STDMETHODCALLTYPE Piggyback_CLSID(GUID * classid) = 0; + virtual HRESULT Piggyback_CLSID(GUID * classid) = 0; /* * Is it currently piggy backing another locomotor? */ - virtual boolean STDMETHODCALLTYPE Is_Piggybacking(void) = 0; + virtual bool Is_Piggybacking(void) = 0; }; -/* - * IPiggyback com smart pointer declaration. - */ -_COM_SMARTPTR_TYPEDEF(IPiggyback, __uuidof(IPiggyback)); + +// The piggyback side of a locomotor, or NULL when it cannot carry one. +inline IPiggyback * Piggyback_Of(ILocomotion * locomotion) +{ + return(dynamic_cast(locomotion)); +} diff --git a/code/loco.cpp b/code/loco.cpp index 62c3b61b3..96521e16d 100644 --- a/code/loco.cpp +++ b/code/loco.cpp @@ -251,18 +251,18 @@ LONG STDMETHODCALLTYPE LocomotionClass::QueryInterface(REFIID riid, LPVOID *ppvO } -ILocomotion * Create_Locomotor(CLSID const & classid) +std::unique_ptr Create_Locomotor(CLSID const & classid) { IPersistent * const object = Create_Object(classid); ILocomotion * const locomotion = dynamic_cast(object); if (locomotion == NULL) { delete object; } - return(locomotion); + return(std::unique_ptr(locomotion)); } -ILocomotion * Load_Locomotor(SaveStreamClass & stream) +std::unique_ptr Load_Locomotor(SaveStreamClass & stream) { SwizzleManagerClass::MarkType const mark = Swizzler.Mark(); IPersistent * const object = Load_Object(stream); @@ -273,7 +273,7 @@ ILocomotion * Load_Locomotor(SaveStreamClass & stream) delete object; stream.Fail(); } - return(locomotion); + return(std::unique_ptr(locomotion)); } diff --git a/code/loco.h b/code/loco.h index 42e840d7c..a0eab6096 100644 --- a/code/loco.h +++ b/code/loco.h @@ -20,13 +20,13 @@ class SaveStreamClass; // CLSID_NULL when it is not one of ours. CLSID Locomotion_Class_ID(ILocomotion * locomotion); -// A new, unlinked locomotor of the registered class, or NULL when the identifier names -// no locomotor. -ILocomotion * Create_Locomotor(CLSID const & classid); +// A new, unlinked locomotor of the registered class, or nothing when the identifier +// names no locomotor. +std::unique_ptr Create_Locomotor(CLSID const & classid); -// The locomotor whose record is next in the stream, or NULL when the record names +// The locomotor whose record is next in the stream, or nothing when the record names // something that is not one, which fails the stream. -ILocomotion * Load_Locomotor(SaveStreamClass & stream); +std::unique_ptr Load_Locomotor(SaveStreamClass & stream); class LocomotionClass : public IPersistent, public ILocomotion diff --git a/code/unit.cpp b/code/unit.cpp index 5b247ab22..a4a688a90 100644 --- a/code/unit.cpp +++ b/code/unit.cpp @@ -2106,7 +2106,7 @@ void UnitClass::Per_Cell_Process(PCPType why) Cell center = Center_Coord(); Cell whom_center = whom->Center_Coord(); if (Center_Coord().As_Cell() == whom->Center_Coord().As_Cell() && whom->RTTI == RTTI_BUILDING) { - CLSID const clsid = Locomotion_Class_ID(Locomotion); + CLSID const clsid = Locomotion_Class_ID(Locomotion.get()); if (clsid == CLSID_HoverLocomotion && static_cast(whom)->Class->IsCanUnitRepair && NavCom == NULL) { NavCom = whom; } @@ -5220,7 +5220,7 @@ void UnitClass::Assign_Destination(AbstractClass * target, bool immediate) * re-target the nearest reachable cell when driving rather than burrowing. */ if (target != NULL && Class->IsSubterranean && Locomotion->Is_Moving()) { - CLSID const clsid = Locomotion_Class_ID(Locomotion); + CLSID const clsid = Locomotion_Class_ID(Locomotion.get()); if (clsid == CLSID_DriveLocomotion) { NavQueue.Add_Head(target); RouteQueue.Clear(); @@ -5312,7 +5312,7 @@ void UnitClass::Assign_Destination(AbstractClass * target, bool immediate) * (Mirrors BuildingClass weapons-factory exit, building.cpp:6236-6251.) */ if (target != NULL && !Locomotion->Is_Moving()) { - CLSID const clsid = Locomotion_Class_ID(Locomotion); + CLSID const clsid = Locomotion_Class_ID(Locomotion.get()); if (clsid == CLSID_TunnelLocomotion && Get_Height_AGL() == 0) { Coord tc = target->Center_Coord(); int gl = Map.Get_Height_GL(tc); @@ -5334,16 +5334,16 @@ void UnitClass::Assign_Destination(AbstractClass * target, bool immediate) } if (doswap) { - IPiggybackPtr piggy(Locomotion); + IPiggyback * piggy = Piggyback_Of(Locomotion.get()); if (piggy != NULL && piggy->Is_Piggybacking()) { - piggy->End_Piggyback(&Locomotion); + Locomotion = piggy->End_Piggyback(); } - ILocomotionPtr walk(Create_Locomotor(CLSID_DriveLocomotion)); + std::unique_ptr walk = Create_Locomotor(CLSID_DriveLocomotion); walk->Link_To_Object(this); - piggy = IPiggybackPtr(walk); + piggy = Piggyback_Of(walk.get()); if (piggy != NULL) { - piggy->Begin_Piggyback(Locomotion); - Locomotion = walk; + piggy->Begin_Piggyback(std::move(Locomotion)); + Locomotion = std::move(walk); Locomotion->Force_New_Slope(Map[Get_Coord()].Ramp); } } diff --git a/code/walk.cpp b/code/walk.cpp index d91769ad4..6eb37e4e0 100644 --- a/code/walk.cpp +++ b/code/walk.cpp @@ -645,7 +645,7 @@ void WalkLocomotionClass::Serialize(SaveStreamClass & stream) if (haspiggy) { if (stream.Is_Saving()) { - Save_Object(stream, (ILocomotion *)Piggybacker); + Save_Object(stream, Piggybacker.get()); } else { Piggybacker = Load_Locomotor(stream); } @@ -663,50 +663,20 @@ LayerType STDMETHODCALLTYPE WalkLocomotionClass::In_Which_Layer(void) } -/// -/// Fetches an interface pointer from this locomotor. -/// This routine extends the base locomotor with the piggyback interface. -/// -/// The interface identifier being asked for. -/// Pointer to the interface pointer to fill in. -/// Returns with S_OK if the interface was supplied, otherwise E_NOINTERFACE. -HRESULT STDMETHODCALLTYPE WalkLocomotionClass::QueryInterface(REFIID riid, LPVOID * ppvObject) -{ - HRESULT result = BASECLASS::QueryInterface(riid, ppvObject); - - if (result == E_NOINTERFACE) { - if (riid == IID_IPiggyback) { - *ppvObject = (IPiggyback*)this; - } - if (*ppvObject == NULL) { - result = E_NOINTERFACE; - } else { - AddRef(); - result = S_OK; - } - } - return(result); -} - - /// /// Attaches a piggybacking locomotor to this one. /// This routine is used when some temporary means of travel, such as being carried /// along, must take over from ordinary walking. /// -/// The locomotor that will ride along on this one. -/// Returns with S_OK if the locomotor was attached, or E_FAIL if one is already -/// piggybacking. -HRESULT STDMETHODCALLTYPE WalkLocomotionClass::Begin_Piggyback(ILocomotion * pointer) +/// The locomotor that is to take over the unit. +/// bool; Was the locomotor taken on? One already carrying a locomotor refuses. +bool WalkLocomotionClass::Begin_Piggyback(std::unique_ptr carried) { - if (pointer == NULL) { - return(E_POINTER); + if (carried == NULL || Piggybacker != NULL) { + return(false); } - if (Piggybacker == NULL) { - Piggybacker = pointer; - return(S_OK); - } - return(E_FAIL); + Piggybacker = std::move(carried); + return(true); } @@ -714,20 +684,10 @@ HRESULT STDMETHODCALLTYPE WalkLocomotionClass::Begin_Piggyback(ILocomotion * poi /// Ends the piggyback session and hands back the locomotor that was riding along. /// Ownership of the piggybacking locomotor passes to the caller. /// -/// Pointer to the locomotor pointer to fill in. -/// Returns with S_OK if a piggybacking locomotor was handed back, or S_FALSE if -/// there was none. -HRESULT STDMETHODCALLTYPE WalkLocomotionClass::End_Piggyback(ILocomotion ** pointer) +/// Returns with the locomotor that was riding, or nothing when none was. +std::unique_ptr WalkLocomotionClass::End_Piggyback(void) { - if (pointer == NULL) { - return(E_POINTER); - } - if (Piggybacker != NULL) { - *pointer = Piggybacker; - Piggybacker.Detach(); - return(S_OK); - } - return(S_FALSE); + return(std::move(Piggybacker)); } @@ -737,7 +697,7 @@ HRESULT STDMETHODCALLTYPE WalkLocomotionClass::End_Piggyback(ILocomotion ** poin /// not resumed part way through a step. /// /// bool; Is it safe to end the piggyback? -boolean STDMETHODCALLTYPE WalkLocomotionClass::Is_Ok_To_End(void) +bool WalkLocomotionClass::Is_Ok_To_End(void) { if (!Is_Moving() && Piggybacker != NULL && !IsProcessingMovement) { return(true); @@ -753,14 +713,14 @@ boolean STDMETHODCALLTYPE WalkLocomotionClass::Is_Ok_To_End(void) /// /// Pointer to the class ID to fill in. /// Returns with S_OK if the class ID was fetched, otherwise an error code. -HRESULT STDMETHODCALLTYPE WalkLocomotionClass::Piggyback_CLSID(GUID * classid) +HRESULT WalkLocomotionClass::Piggyback_CLSID(GUID * classid) { if (classid == NULL) { return(E_POINTER); } if (Piggybacker != NULL) { - *classid = Locomotion_Class_ID(Piggybacker); + *classid = Locomotion_Class_ID(Piggybacker.get()); return(S_OK); } return(GetClassID(classid)); diff --git a/code/walk.h b/code/walk.h index 9ea0db2b3..3ec724a5f 100644 --- a/code/walk.h +++ b/code/walk.h @@ -16,6 +16,8 @@ #include "ipiggy.h" #include "loco.h" +#include + class WalkLocomotionClass : public LocomotionClass, public IPiggyback { @@ -33,15 +35,14 @@ class WalkLocomotionClass : public LocomotionClass, public IPiggyback virtual void Serialize(SaveStreamClass & stream) override; - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID * ppvObject) override; virtual ULONG STDMETHODCALLTYPE AddRef(void) override {return(BASECLASS::AddRef());} virtual ULONG STDMETHODCALLTYPE Release(void) override {return(BASECLASS::Release());} - virtual HRESULT STDMETHODCALLTYPE Begin_Piggyback(ILocomotion * pointer) override; - virtual HRESULT STDMETHODCALLTYPE End_Piggyback(ILocomotion ** pointer) override; - virtual boolean STDMETHODCALLTYPE Is_Ok_To_End(void) override; - virtual HRESULT STDMETHODCALLTYPE Piggyback_CLSID(GUID * classid) override; - virtual boolean STDMETHODCALLTYPE Is_Piggybacking(void) override {return(Piggybacker != NULL);} + virtual bool Begin_Piggyback(std::unique_ptr carried) override; + virtual std::unique_ptr End_Piggyback(void) override; + virtual bool Is_Ok_To_End(void) override; + virtual HRESULT Piggyback_CLSID(GUID * classid) override; + virtual bool Is_Piggybacking(void) override {return(Piggybacker != NULL);} virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; virtual Coord STDMETHODCALLTYPE Destination(void) override; @@ -100,5 +101,5 @@ class WalkLocomotionClass : public LocomotionClass, public IPiggyback * temporarily -- a jump jet coming down to cover the last few cells on foot, say -- * and the suspended locomotor is handed back when the walk is finished. */ - ILocomotionPtr Piggybacker; + std::unique_ptr Piggybacker; }; From 0fc7c4774bb2d6e7b2aa907c900c91e0330647ca Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Mon, 7 Sep 2026 00:10:53 +0200 Subject: [PATCH 025/179] Drop IUnknown from the object model and locomotors --- code/abstract.cpp | 58 ------- code/abstract.h | 11 -- code/aircraft.cpp | 54 +------ code/aircraft.h | 15 +- code/airctype.cpp | 2 +- code/airctype.h | 2 +- code/aitrig.cpp | 2 +- code/aitrig.h | 2 +- code/alphashp.cpp | 2 +- code/alphashp.h | 2 +- code/anim.cpp | 4 +- code/anim.h | 2 +- code/animtype.cpp | 2 +- code/animtype.h | 2 +- code/base.h | 1 - code/blight.cpp | 2 +- code/blight.h | 2 +- code/brain.cpp | 2 +- code/brain.h | 2 +- code/building.cpp | 4 +- code/building.h | 2 +- code/builtype.cpp | 2 +- code/builtype.h | 2 +- code/bullet.cpp | 46 +----- code/bullet.h | 4 +- code/bullettype.cpp | 2 +- code/bullettype.h | 2 +- code/campaign.cpp | 2 +- code/campaign.h | 2 +- code/cell.cpp | 2 +- code/cell.h | 2 +- code/drive.cpp | 66 +++----- code/drive.h | 52 +++---- code/droppod.cpp | 16 +- code/droppod.h | 18 +-- code/empulse.cpp | 2 +- code/empulse.h | 2 +- code/enviro.h | 1 - code/factory.cpp | 2 +- code/factory.h | 2 +- code/fly.cpp | 58 +++---- code/fly.h | 38 ++--- code/fog.cpp | 2 +- code/fog.h | 2 +- code/foot.cpp | 2 +- code/globals.cpp | 4 - code/house.cpp | 26 +--- code/house.h | 4 +- code/houstype.cpp | 50 +----- code/houstype.h | 5 +- code/hover.cpp | 36 ++--- code/hover.h | 38 ++--- code/iflyctrl.h | 22 +-- code/iflyctrl_i.c | 52 ------- code/iloco.h | 107 +++++++------ code/iloco_i.c | 52 ------- code/ilocos.h | 1 - code/ilocos_i.c | 62 +------- code/infantry.cpp | 2 +- code/infantry.h | 2 +- code/infatype.cpp | 2 +- code/infatype.h | 2 +- code/ini.cpp | 83 +++++++--- code/ini.h | 2 +- code/init.cpp | 2 +- code/ion.h | 3 +- code/ipiggy_i.c | 52 ------- code/isotile.cpp | 2 +- code/isotile.h | 3 +- code/isotype.cpp | 2 +- code/isotype.h | 2 +- code/isun.h | 3 +- code/isun_i.c | 160 +------------------- code/jumpjet.cpp | 22 +-- code/jumpjet.h | 22 +-- code/levitate.cpp | 6 +- code/levitate.h | 18 +-- code/light.cpp | 2 +- code/light.h | 2 +- code/loco.cpp | 137 ++++------------- code/loco.h | 101 ++++++------ code/logic.cpp | 6 - code/mech.cpp | 24 +-- code/mech.h | 26 ++-- code/overlay.h | 2 +- code/overtype.cpp | 2 +- code/overtype.h | 2 +- code/particle.cpp | 2 +- code/particle.h | 2 +- code/partsys.cpp | 2 +- code/partsys.h | 2 +- code/persist.h | 14 +- code/psystype.cpp | 2 +- code/psystype.h | 2 +- code/ptype.cpp | 2 +- code/ptype.h | 2 +- code/saveload.cpp | 2 +- code/scenario.cpp | 6 +- code/script.cpp | 4 +- code/script.h | 4 +- code/side.cpp | 2 +- code/side.h | 2 +- code/smudge.cpp | 2 +- code/smudge.h | 2 +- code/smudtype.cpp | 2 +- code/smudtype.h | 2 +- code/super.cpp | 2 +- code/super.h | 2 +- code/suprtype.cpp | 2 +- code/suprtype.h | 2 +- code/tactical.cpp | 2 +- code/tactical.h | 2 +- code/taction.cpp | 2 +- code/taction.h | 2 +- code/tag.cpp | 2 +- code/tag.h | 2 +- code/tagtype.cpp | 2 +- code/tagtype.h | 2 +- code/taskforc.cpp | 2 +- code/taskforc.h | 2 +- code/team.cpp | 2 +- code/team.h | 2 +- code/teamtype.cpp | 2 +- code/teamtype.h | 2 +- code/techno.cpp | 4 +- code/teleport.cpp | 18 +-- code/teleport.h | 16 +- code/terrain.cpp | 2 +- code/terrain.h | 2 +- code/terrtype.cpp | 2 +- code/terrtype.h | 2 +- code/tevent.cpp | 2 +- code/tevent.h | 2 +- code/tiberium.cpp | 2 +- code/tiberium.h | 2 +- code/tracker.cpp | 14 +- code/trigger.cpp | 2 +- code/trigger.h | 2 +- code/trigtype.cpp | 2 +- code/trigtype.h | 2 +- code/tube.cpp | 2 +- code/tube.h | 2 +- code/tunnel.cpp | 38 ++--- code/tunnel.h | 34 ++--- code/typelist.h | 1 - code/unit.cpp | 2 +- code/unit.h | 2 +- code/unittype.cpp | 2 +- code/unittype.h | 2 +- code/vanim.cpp | 2 +- code/vanim.h | 2 +- code/vanimtype.cpp | 2 +- code/vanimtype.h | 2 +- code/vein.cpp | 2 +- code/vein.h | 2 +- code/walk.cpp | 28 ++-- code/walk.h | 32 ++-- code/warhead.cpp | 2 +- code/warhead.h | 2 +- code/wave.cpp | 2 +- code/wave.h | 2 +- code/waypoint.cpp | 2 +- code/waypoint.h | 2 +- code/weapon.cpp | 2 +- code/weapon.h | 2 +- manual/content/internals/class-hierarchy.md | 2 +- manual/content/internals/locomotion.md | 4 +- 167 files changed, 652 insertions(+), 1354 deletions(-) delete mode 100644 code/iflyctrl_i.c delete mode 100644 code/iloco_i.c delete mode 100644 code/ipiggy_i.c diff --git a/code/abstract.cpp b/code/abstract.cpp index c078c9f38..6e3a025f7 100644 --- a/code/abstract.cpp +++ b/code/abstract.cpp @@ -58,7 +58,6 @@ /// AbstractClass::AbstractClass(void) : ID(-1), - RefCount(0), Dirty(false) { } @@ -107,62 +106,6 @@ void AbstractClass::Create_ID(void) } -/// -/// Fetches a COM interface pointer from this object. -/// This is the IUnknown implementation shared by every game object. Abstract -/// objects expose IUnknown alone; the save game system reaches them through -/// IPersistent, which needs no identifier. -/// -/// The identifier of the interface being asked for. -/// Receives the interface pointer, or NULL when the -/// interface is not supported. -/// -/// Returns with S_OK when the interface was supplied. Otherwise E_NOINTERFACE is -/// returned for an unsupported interface, or E_POINTER when no output pointer was given. -/// -HRESULT STDMETHODCALLTYPE AbstractClass::QueryInterface(REFIID riid, LPVOID * ppvObject) -{ - if (ppvObject == NULL) { - return(E_POINTER); - } - - *ppvObject = NULL; - - if (riid == IID_IUnknown) { - *ppvObject = (IUnknown *)(IPersistent *)this; - } - if (*ppvObject == NULL) { - return(E_NOINTERFACE); - } - - AddRef(); - return(S_OK); -} - - -/// -/// Satisfies the IUnknown reference count contract. -/// The game owns its objects outright and they outlive any interface pointer -/// handed out, so nothing is actually counted. -/// -/// Returns with the reference count, which is always one. -ULONG STDMETHODCALLTYPE AbstractClass::AddRef(void) -{ - return(1); -} - - -/// -/// Satisfies the IUnknown release contract. -/// Releasing an interface never destroys a game object -- see AddRef. -/// -/// Returns with the reference count, which is always one. -ULONG STDMETHODCALLTYPE AbstractClass::Release(void) -{ - return(1); -} - - /// /// Writes this object to the save stream. /// @@ -249,7 +192,6 @@ void AbstractClass::Post_Load(void) void AbstractClass::Serialize(SaveStreamClass & stream) { stream.Serialize(ID); - // RefCount -- belongs to the running session rather than the record. stream.Serialize(Dirty); } diff --git a/code/abstract.h b/code/abstract.h index 3d8685b93..d77b11eb1 100644 --- a/code/abstract.h +++ b/code/abstract.h @@ -87,13 +87,6 @@ class AbstractClass : public IPersistent __declspec(property(get = Fetch_RTTI)) RTTIType RTTI; int ID; - /* - * This is the count of outstanding COM references to this object. Only projectiles - * are genuinely reference counted -- everything else answers 1 to AddRef and to - * Release -- so elsewhere it merely rides along, preserved by hand across a load. - */ - LONG RefCount; - /* * If this object has changed since it was last written out, then this flag will be * true. Save clears it on request. @@ -106,9 +99,6 @@ class AbstractClass : public IPersistent AbstractClass(void); virtual ~AbstractClass(void); - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID * ppvObject) override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) override; - virtual ULONG STDMETHODCALLTYPE Release(void) override; virtual HRESULT Load(SaveStreamClass & stream) override; virtual HRESULT Save(SaveStreamClass & stream, BOOL cleardirty) override; @@ -120,7 +110,6 @@ class AbstractClass : public IPersistent AbstractClass & operator = (const AbstractClass & that) { ID = that.ID; - RefCount = that.RefCount; Dirty = that.Dirty; return(*this); } diff --git a/code/aircraft.cpp b/code/aircraft.cpp index 3d4251718..881382453 100644 --- a/code/aircraft.cpp +++ b/code/aircraft.cpp @@ -266,48 +266,6 @@ void AircraftClass::Init(void) } -/// -/// Fetches the requested interface from this aircraft. -/// Aircraft add the fly control interface to the set that every game object supports, so -/// that the flying locomotor can interrogate them about how they wish to be flown. -/// -/// The identifier of the interface being asked for. -/// Pointer to the pointer to fill in with the interface. -/// Returns with S_OK if the interface was supplied. -HRESULT STDMETHODCALLTYPE AircraftClass::QueryInterface(struct _GUID const &guid, void **ppv) -{ - HRESULT res = BASECLASS::QueryInterface(guid, ppv); - if (FAILED(res)) { - if (guid == IID_IFlyControl) { - *ppv = (IFlyControl *)(this); - } - res = S_OK; - AddRef(); - } - return(res); -} - - -/// -/// Adds a reference to this aircraft. -/// -/// Returns with the new number of references outstanding. -ULONG STDMETHODCALLTYPE AircraftClass::AddRef(void) -{ - return(BASECLASS::AddRef()); -} - - -/// -/// Releases a reference to this aircraft. -/// -/// Returns with the number of references still outstanding. -ULONG STDMETHODCALLTYPE AircraftClass::Release(void) -{ - return(BASECLASS::Release()); -} - - /*********************************************************************************************** * AircraftClass::Unlimbo -- Removes an aircraft from the limbo state. * * * @@ -4027,7 +3985,7 @@ void AircraftClass::Detach(AbstractClass const * target, bool all) /// can pick up or set down its cargo. /// /// Returns with the height above ground level to settle at. -LONG STDMETHODCALLTYPE AircraftClass::Landing_Altitude(void) +LONG AircraftClass::Landing_Altitude(void) { if (Class->IsCarryall && !Cargo.Is_Something_Attached() && In_Radio_Contact()) { BuildingClass * bptr = (BuildingClass *)Contact_With_Whom(); @@ -4055,7 +4013,7 @@ LONG STDMETHODCALLTYPE AircraftClass::Landing_Altitude(void) /// while loaded, or settles into the default parked pose. /// /// Returns with the facing to land at. -LONG STDMETHODCALLTYPE AircraftClass::Landing_Direction(void) +LONG AircraftClass::Landing_Direction(void) { TechnoClass * tptr = Contact_With_Whom(); if (tptr != NULL) { @@ -4074,7 +4032,7 @@ LONG STDMETHODCALLTYPE AircraftClass::Landing_Direction(void) /// empty one. /// /// Returns with true if there is cargo aboard this aircraft. -BOOL STDMETHODCALLTYPE AircraftClass::Is_Loaded(void) +BOOL AircraftClass::Is_Loaded(void) { return(Cargo.Is_Something_Attached()); } @@ -4086,7 +4044,7 @@ BOOL STDMETHODCALLTYPE AircraftClass::Is_Loaded(void) /// from a hover. Only a visible and unguided projectile is suited to strafing. /// /// Returns with true if the aircraft should make strafing attack runs. -LONG STDMETHODCALLTYPE AircraftClass::Is_Strafe(void) +LONG AircraftClass::Is_Strafe(void) { const WeaponDataStruct * data = Get_Class_Weapon_Data(0); if (data == NULL) { @@ -4112,7 +4070,7 @@ LONG STDMETHODCALLTYPE AircraftClass::Is_Strafe(void) /// to an attack run. /// /// Returns with true if the aircraft must hold its present heading. -LONG STDMETHODCALLTYPE AircraftClass::Is_Locked(void) +LONG AircraftClass::Is_Locked(void) { return(IsLockedStraight); } @@ -4238,7 +4196,7 @@ RTTIType AircraftClass::Fetch_RTTI(void) const /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE AircraftClass::GetClassID(CLSID * retval) +HRESULT AircraftClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_AircraftClass; diff --git a/code/aircraft.h b/code/aircraft.h index 64d199680..1dd9cb66a 100644 --- a/code/aircraft.h +++ b/code/aircraft.h @@ -57,23 +57,20 @@ class AircraftClass : public FootClass, public IFlyControl AircraftClass(AircraftTypeClass const * type = NULL, HouseClass * house = NULL); virtual ~AircraftClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID * ppvObject) override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) override; - virtual ULONG STDMETHODCALLTYPE Release(void) override; /* * IFlyControl methods. */ - virtual LONG STDMETHODCALLTYPE Landing_Altitude(void) override; - virtual LONG STDMETHODCALLTYPE Landing_Direction(void) override; - virtual BOOL STDMETHODCALLTYPE Is_Loaded(void) override; - virtual LONG STDMETHODCALLTYPE Is_Strafe(void) override; - virtual LONG STDMETHODCALLTYPE Is_Locked(void) override; + virtual LONG Landing_Altitude(void) override; + virtual LONG Landing_Direction(void) override; + virtual BOOL Is_Loaded(void) override; + virtual LONG Is_Strafe(void) override; + virtual LONG Is_Locked(void) override; virtual void Init(void) override; virtual void Detach(AbstractClass const * target, bool all = true) override; diff --git a/code/airctype.cpp b/code/airctype.cpp index c86250758..487e651bd 100644 --- a/code/airctype.cpp +++ b/code/airctype.cpp @@ -334,7 +334,7 @@ void AircraftTypeClass::Serialize(SaveStreamClass & stream) /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if there was nowhere to put the answer. -HRESULT STDMETHODCALLTYPE AircraftTypeClass::GetClassID(CLSID * retval) +HRESULT AircraftTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_AircraftTypeClass; diff --git a/code/airctype.h b/code/airctype.h index 8f462fbf2..101531e19 100644 --- a/code/airctype.h +++ b/code/airctype.h @@ -57,7 +57,7 @@ class AircraftTypeClass : public TechnoTypeClass AircraftTypeClass(char const * ininame = NULL); virtual ~AircraftTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/aitrig.cpp b/code/aitrig.cpp index 8b0f19074..3c5acb351 100644 --- a/code/aitrig.cpp +++ b/code/aitrig.cpp @@ -98,7 +98,7 @@ AITriggerTypeClass::~AITriggerTypeClass(void) /// object is read back in. /// /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE AITriggerTypeClass::GetClassID(CLSID * retval) +HRESULT AITriggerTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_AITriggerTypeClass; diff --git a/code/aitrig.h b/code/aitrig.h index 6d141b801..11b6deb3f 100644 --- a/code/aitrig.h +++ b/code/aitrig.h @@ -60,7 +60,7 @@ class AITriggerTypeClass : public AbstractTypeClass AITriggerTypeClass(const char *name = NULL); ~AITriggerTypeClass(void); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; static AITriggerTypeClass * Find_Or_Make(char const * ininame); diff --git a/code/alphashp.cpp b/code/alphashp.cpp index 4b89d1fff..6ca083383 100644 --- a/code/alphashp.cpp +++ b/code/alphashp.cpp @@ -100,7 +100,7 @@ AlphaShapeClass::~AlphaShapeClass(void) /// /// Pointer to the buffer that will receive the class identifier. /// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT STDMETHODCALLTYPE AlphaShapeClass::GetClassID(CLSID * retval) +HRESULT AlphaShapeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_AlphaShapeClass; diff --git a/code/alphashp.h b/code/alphashp.h index fc891a87d..bbdbd012c 100644 --- a/code/alphashp.h +++ b/code/alphashp.h @@ -34,7 +34,7 @@ class AlphaShapeClass : public AbstractClass AlphaShapeClass(void); ~AlphaShapeClass(void); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/anim.cpp b/code/anim.cpp index 62116e0ce..880e11c74 100644 --- a/code/anim.cpp +++ b/code/anim.cpp @@ -245,7 +245,7 @@ AnimClass::AnimClass(AnimTypeClass const * type, Coord const & coord, int timede /// /// Constructs a blank animation object. /// This constructor serves the load system, which creates an empty animation through the -/// class factory and then fills it in from the save game. The animation joins the master +/// class table and then fills it in from the save game. The animation joins the master /// animation list but has no type and is nowhere on the map. /// AnimClass::AnimClass(void) : @@ -1736,7 +1736,7 @@ void AnimClass::Post_Load_Game(void) /// /// Pointer to the location to store the class identifier at. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE AnimClass::GetClassID(CLSID * retval) +HRESULT AnimClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_AnimClass; diff --git a/code/anim.h b/code/anim.h index 8be08019f..53cfb73d6 100644 --- a/code/anim.h +++ b/code/anim.h @@ -64,7 +64,7 @@ class AnimClass : public ObjectClass, public StageClass AnimClass(void); virtual ~AnimClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/animtype.cpp b/code/animtype.cpp index 95ed94f4d..90e67aaa6 100644 --- a/code/animtype.cpp +++ b/code/animtype.cpp @@ -584,7 +584,7 @@ void AnimTypeClass::Serialize(SaveStreamClass & stream) /// /// Pointer to the place to store the class identifier. /// Returns with S_OK, or E_POINTER if there was nowhere to store the answer. -HRESULT STDMETHODCALLTYPE AnimTypeClass::GetClassID(CLSID * retval) +HRESULT AnimTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_AnimTypeClass; diff --git a/code/animtype.h b/code/animtype.h index 11d63eed9..4a77ad7a8 100644 --- a/code/animtype.h +++ b/code/animtype.h @@ -430,7 +430,7 @@ class AnimTypeClass : public ObjectTypeClass AnimTypeClass(char const * ininame = NULL); virtual ~AnimTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/base.h b/code/base.h index c69d62662..7c8a0ed0b 100644 --- a/code/base.h +++ b/code/base.h @@ -37,7 +37,6 @@ #include "house.hh" #include "struct.hh" -#include class CCINIClass; diff --git a/code/blight.cpp b/code/blight.cpp index 7850a58b5..b731bba5c 100644 --- a/code/blight.cpp +++ b/code/blight.cpp @@ -286,7 +286,7 @@ void BuildingLightClass::AI(void) /// be created when the game is loaded back in. /// /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE BuildingLightClass::GetClassID(CLSID * retval) +HRESULT BuildingLightClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_BuildingLightClass; diff --git a/code/blight.h b/code/blight.h index c12e01e09..c58e3bb17 100644 --- a/code/blight.h +++ b/code/blight.h @@ -25,7 +25,7 @@ class BuildingLightClass : public ObjectClass BuildingLightClass(TechnoClass * owner = NULL); virtual ~BuildingLightClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/brain.cpp b/code/brain.cpp index ccbcfc272..0ee94e9be 100644 --- a/code/brain.cpp +++ b/code/brain.cpp @@ -55,7 +55,7 @@ NeuronClass::~NeuronClass(void) /// /// Pointer to the place to store the class identifier. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE NeuronClass::GetClassID(CLSID * retval) +HRESULT NeuronClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_NeuronClass; diff --git a/code/brain.h b/code/brain.h index 9ae55f0df..c9ae1a8e6 100644 --- a/code/brain.h +++ b/code/brain.h @@ -24,7 +24,7 @@ class NeuronClass : public AbstractClass NeuronClass(void); virtual ~NeuronClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual RTTIType Fetch_RTTI(void) const override { return(RTTI_NEURON); } diff --git a/code/building.cpp b/code/building.cpp index 0a4d296d4..accb08f10 100644 --- a/code/building.cpp +++ b/code/building.cpp @@ -5974,7 +5974,7 @@ int BuildingClass::Do_MISSION_MISSILE(void) Status = DONE; return(1); } else { - bullet->Release(); + delete bullet; Begin_Mode(BSTATE_IDLE); // keep the door closed. Assign_Mission(MISSION_GUARD); return(4 * TICKS_PER_SECOND); @@ -10245,7 +10245,7 @@ void BuildingClass::Discharge_Turret(void) /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE BuildingClass::GetClassID(CLSID * retval) +HRESULT BuildingClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_BuildingClass; diff --git a/code/building.h b/code/building.h index 31b5e40d1..60d4f1093 100644 --- a/code/building.h +++ b/code/building.h @@ -347,7 +347,7 @@ class BuildingClass : public TechnoClass BuildingClass(BuildingTypeClass const * type = NULL, HouseClass * house = NULL); virtual ~BuildingClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/builtype.cpp b/code/builtype.cpp index 4e2f35282..69ee88d5e 100644 --- a/code/builtype.cpp +++ b/code/builtype.cpp @@ -1923,7 +1923,7 @@ void BuildingTypeClass::Serialize(SaveStreamClass & stream) /// /// Pointer to the class ID to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE BuildingTypeClass::GetClassID(CLSID * retval) +HRESULT BuildingTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_BuildingTypeClass; diff --git a/code/builtype.h b/code/builtype.h index 3a9cde2eb..bfb43855b 100644 --- a/code/builtype.h +++ b/code/builtype.h @@ -834,7 +834,7 @@ class BuildingTypeClass : public TechnoTypeClass BuildingTypeClass(char const * ininame = NULL); virtual ~BuildingTypeClass() override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/bullet.cpp b/code/bullet.cpp index dfb151bb5..ee436ca2b 100644 --- a/code/bullet.cpp +++ b/code/bullet.cpp @@ -52,8 +52,6 @@ #include "bullet.h" -#include "classfactory.h" - #include "_convert.h" #include "_map.h" #include "_rules.h" @@ -97,7 +95,6 @@ #include -extern ULONG COMRefCount; /*********************************************************************************************** @@ -139,8 +136,6 @@ BulletClass::BulletClass(void) : AnimFrame(0), AnimRate(0) { - // The game holds the one reference a bullet exists under until Release deletes it. - RefCount = 1; Create_ID(); Bullets.Add(this); } @@ -1465,35 +1460,6 @@ void BulletClass::Serialize(SaveStreamClass & stream) } -/// -/// Takes out a reference on this projectile. -/// This is the IUnknown implementation used by the COM machinery that owns projectiles. -/// -/// Returns with the number of references now outstanding. -ULONG STDMETHODCALLTYPE BulletClass::AddRef(void) -{ - COMRefCount++; - return(InterlockedIncrement(&RefCount)); -} - - -/// -/// Drops a reference to this projectile. -/// This is the IUnknown implementation. The projectile deletes itself when the last -/// reference to it is released. -/// -/// Returns with the number of references still outstanding. -ULONG STDMETHODCALLTYPE BulletClass::Release(void) -{ - COMRefCount--; - ULONG count = InterlockedDecrement(&RefCount); - if (count == 0) { - delete this; - } - return(count); -} - - /// /// Can this projectile steer toward its target? /// The flight logic calls this routine to decide whether the projectile should be turned @@ -1511,9 +1477,7 @@ bool BulletClass::Is_Homing(void) const /// /// Creates a projectile and fills in the data for the shot. -/// This routine is used by the weapon firing code in place of a bare new -- projectiles are -/// COM objects, so the instance must come from the class factory. The projectile is inert -/// until it is unlimboed with a starting position and velocity. +/// The projectile is inert until it is unlimboed with a starting position and velocity. /// /// The object that fired the shot. It receives credit for any kill. /// The damage the projectile will inflict when it detonates. @@ -1522,11 +1486,7 @@ bool BulletClass::Is_Homing(void) const /// made. BulletClass * Create_Bullet(BulletTypeClass const *type, AbstractClass *target, TechnoClass *payback, int strength, WarheadTypeClass const *warhead, int max_speed, int range, bool bright) { - BulletClass * bullet = dynamic_cast(Create_Object(CLSID_BulletClass)); - if (bullet == NULL) { - return(NULL); - } - + BulletClass * bullet = new BulletClass; bullet->Set_Bullet_Data(type, target, payback, strength, warhead, max_speed, range, bright); return(bullet); } @@ -1579,7 +1539,7 @@ RTTIType BulletClass::Fetch_RTTI(void) const /// kind of object it is about to read back from the stream. /// /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE BulletClass::GetClassID(CLSID * retval) +HRESULT BulletClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_BulletClass; diff --git a/code/bullet.h b/code/bullet.h index a51519ad8..4012b7e60 100644 --- a/code/bullet.h +++ b/code/bullet.h @@ -52,8 +52,6 @@ class BulletClass : public ObjectClass typedef ObjectClass BASECLASS; public: - virtual ULONG STDMETHODCALLTYPE AddRef(void) override; - virtual ULONG STDMETHODCALLTYPE Release(void) override; public: @@ -78,7 +76,7 @@ class BulletClass : public ObjectClass BulletClass(void); virtual ~BulletClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/bullettype.cpp b/code/bullettype.cpp index 4fd298dcc..a5125f111 100644 --- a/code/bullettype.cpp +++ b/code/bullettype.cpp @@ -360,7 +360,7 @@ void BulletTypeClass::Serialize(SaveStreamClass & stream) /// Fetches the class identifier that the save game code stores for this object. /// /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE BulletTypeClass::GetClassID(CLSID * retval) +HRESULT BulletTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_BulletTypeClass; diff --git a/code/bullettype.h b/code/bullettype.h index 9a8c96831..f50b45bd6 100644 --- a/code/bullettype.h +++ b/code/bullettype.h @@ -237,7 +237,7 @@ class BulletTypeClass : public ObjectTypeClass BulletTypeClass(char const * name = NULL); virtual ~BulletTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/campaign.cpp b/code/campaign.cpp index 2731a716a..5793d434b 100644 --- a/code/campaign.cpp +++ b/code/campaign.cpp @@ -133,7 +133,7 @@ void Read_Battle_INI(CCINIClass const & ini) /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE CampaignClass::GetClassID(CLSID * retval) +HRESULT CampaignClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_CampaignClass; diff --git a/code/campaign.h b/code/campaign.h index a59c4da89..73c4d40f9 100644 --- a/code/campaign.h +++ b/code/campaign.h @@ -23,7 +23,7 @@ class CampaignClass : public AbstractTypeClass CampaignClass(char const * name = NULL); virtual ~CampaignClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/cell.cpp b/code/cell.cpp index 83dd800f6..7aa584f69 100644 --- a/code/cell.cpp +++ b/code/cell.cpp @@ -5174,7 +5174,7 @@ void CellClass::Detach(AbstractClass const * target) /// /// Pointer to the location to store the class identifier in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE CellClass::GetClassID(CLSID * retval) +HRESULT CellClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_CellClass; diff --git a/code/cell.h b/code/cell.h index 790b5b7dc..9df9a4549 100644 --- a/code/cell.h +++ b/code/cell.h @@ -517,7 +517,7 @@ class CellClass : public AbstractClass CellClass(void); virtual ~CellClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/drive.cpp b/code/drive.cpp index 23f987ecd..4ba92adef 100644 --- a/code/drive.cpp +++ b/code/drive.cpp @@ -295,7 +295,7 @@ void DriveLocomotionClass::Set_Slope(int ramp) /// when it is first placed on the map. /// /// The ramp the unit is to be sitting on. -void STDMETHODCALLTYPE DriveLocomotionClass::Force_New_Slope(int ramp) +void DriveLocomotionClass::Force_New_Slope(int ramp) { PreviousRamp = ramp; CurrentRamp = ramp; @@ -309,7 +309,7 @@ void STDMETHODCALLTYPE DriveLocomotionClass::Force_New_Slope(int ramp) /// between cells, even if it is not making any headway at this moment. /// /// bool; Is the unit under way or owing a move? -boolean STDMETHODCALLTYPE DriveLocomotionClass::Is_Moving(void) +bool DriveLocomotionClass::Is_Moving(void) { if (DestinationCoord != COORD_NONE) { return(true); @@ -327,7 +327,7 @@ boolean STDMETHODCALLTYPE DriveLocomotionClass::Is_Moving(void) /// has been given a destination but has not gotten rolling yet does not. /// /// bool; Is the unit moving right now? -boolean STDMETHODCALLTYPE DriveLocomotionClass::Is_Moving_Now(void) +bool DriveLocomotionClass::Is_Moving_Now(void) { if (LinkedTo->PrimaryFacing.Is_Rotating()) { return(true); @@ -344,7 +344,7 @@ boolean STDMETHODCALLTYPE DriveLocomotionClass::Is_Moving_Now(void) /// /// Returns with the destination coordinate, or COORD_NONE if the unit has /// nowhere it must be. -Coord STDMETHODCALLTYPE DriveLocomotionClass::Destination(void) +Coord DriveLocomotionClass::Destination(void) { return(DestinationCoord); } @@ -355,7 +355,7 @@ Coord STDMETHODCALLTYPE DriveLocomotionClass::Destination(void) /// /// Returns with the coordinate being driven toward. A unit that is not under /// way returns its current position instead. -Coord STDMETHODCALLTYPE DriveLocomotionClass::Head_To_Coord(void) +Coord DriveLocomotionClass::Head_To_Coord(void) { if (HeadToCoord != COORD_NONE) { return(HeadToCoord); @@ -370,7 +370,7 @@ Coord STDMETHODCALLTYPE DriveLocomotionClass::Head_To_Coord(void) /// raised to the deck, since that is where the vehicle will actually end up driving. /// /// The location to drive to. -void STDMETHODCALLTYPE DriveLocomotionClass::Move_To(Coord to) +void DriveLocomotionClass::Move_To(Coord to) { if (LinkedTo->StunDuration <= 0) { DestinationCoord = to; @@ -388,7 +388,7 @@ void STDMETHODCALLTYPE DriveLocomotionClass::Move_To(Coord to) /// The destination is given up and the driver begins slowing down. A train engine passes /// the order back along the line so that every car it is pulling stops with it. /// -void STDMETHODCALLTYPE DriveLocomotionClass::Stop_Moving(void) +void DriveLocomotionClass::Stop_Moving(void) { if (HeadToCoord != COORD_NONE) { if (LinkedTo->TClass->IsTrain) { @@ -439,7 +439,7 @@ BOOL DriveLocomotionClass::Is_Angled(void) const /// /// Pointer to the voxel cache key to be updated. May be NULL. /// Returns with the matrix the unit is to be rendered through. -Matrix3D STDMETHODCALLTYPE DriveLocomotionClass::Draw_Matrix(int *key) +Matrix3D DriveLocomotionClass::Draw_Matrix(int *key) { Matrix3D m; @@ -504,7 +504,7 @@ Matrix3D STDMETHODCALLTYPE DriveLocomotionClass::Draw_Matrix(int *key) /// The driver adopts the slope of the cell the vehicle appears on straight away, so that /// a unit unlimboed onto a ramp is never seen tilting itself into place. /// -void STDMETHODCALLTYPE DriveLocomotionClass::Unlimbo(void) +void DriveLocomotionClass::Unlimbo(void) { Force_New_Slope(LinkedTo->Get_Cell_Ptr()->Ramp); } @@ -534,7 +534,7 @@ void STDMETHODCALLTYPE DriveLocomotionClass::Unlimbo(void) * 09/26/1993 JLB : Created. * * 04/15/1994 JLB : Converted to member function. * *=============================================================================================*/ -boolean STDMETHODCALLTYPE DriveLocomotionClass::Process(void) +bool DriveLocomotionClass::Process(void) { Set_Slope(LinkedTo->Get_Cell_Ptr()->Ramp); @@ -721,7 +721,7 @@ void DriveLocomotionClass::Mark_Track(Coord const & headto, MarkType type) * HISTORY: * * 03/17/1995 JLB : Created. * *=============================================================================================*/ -void STDMETHODCALLTYPE DriveLocomotionClass::Force_Track(int track, Coord coord) +void DriveLocomotionClass::Force_Track(int track, Coord coord) { assert(LinkedTo->IsActive); @@ -2072,7 +2072,7 @@ bool DriveLocomotionClass::Incoming(Cell cell) /// Fetches the display layer the driving unit belongs to. /// /// Returns with LAYER_GROUND, since a driving unit travels on the ground. -LayerType STDMETHODCALLTYPE DriveLocomotionClass::In_Which_Layer(void) +LayerType DriveLocomotionClass::In_Which_Layer(void) { return(LAYER_GROUND); } @@ -2085,7 +2085,7 @@ LayerType STDMETHODCALLTYPE DriveLocomotionClass::In_Which_Layer(void) /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE DriveLocomotionClass::GetClassID(CLSID * retval) +HRESULT DriveLocomotionClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_DriveLocomotion; @@ -2098,7 +2098,7 @@ HRESULT STDMETHODCALLTYPE DriveLocomotionClass::GetClassID(CLSID * retval) /// A driving vehicle sits at the depth of the ground it is standing on. /// /// Returns with the adjustment to apply to the unit's draw depth. -int STDMETHODCALLTYPE DriveLocomotionClass::Z_Adjust(void) +int DriveLocomotionClass::Z_Adjust(void) { return(0); } @@ -2108,7 +2108,7 @@ int STDMETHODCALLTYPE DriveLocomotionClass::Z_Adjust(void) /// Fetches the depth gradient the unit is to be drawn with. /// /// Returns with the gradient the base locomotor asks for. -ZGradientType STDMETHODCALLTYPE DriveLocomotionClass::Z_Gradient(void) +ZGradientType DriveLocomotionClass::Z_Gradient(void) { return(BASECLASS::Z_Gradient()); } @@ -2138,7 +2138,7 @@ bool DriveLocomotionClass::Abandon_Navigation(void) /// be told about every cell of the track it is committed to. /// /// The MarkType to apply to the cells occupied. -void STDMETHODCALLTYPE DriveLocomotionClass::Mark_All_Occupation_Bits(int mark) +void DriveLocomotionClass::Mark_All_Occupation_Bits(int mark) { if (HeadToCoord != COORD_NONE) { Mark_Track(HeadToCoord, (MarkType)mark); @@ -2154,7 +2154,7 @@ void STDMETHODCALLTYPE DriveLocomotionClass::Mark_All_Occupation_Bits(int mark) /// /// The location to test against. /// bool; Is the unit moving there? -boolean STDMETHODCALLTYPE DriveLocomotionClass::Is_Moving_Here(Coord to) +bool DriveLocomotionClass::Is_Moving_Here(Coord to) { Coord coord = Head_To_Coord(); @@ -2200,7 +2200,7 @@ boolean STDMETHODCALLTYPE DriveLocomotionClass::Is_Moving_Here(Coord to) /// such a hop is due, but performs none of it. /// /// bool; Will the driver jump tracks? -boolean STDMETHODCALLTYPE DriveLocomotionClass::Will_Jump_Tracks(void) +bool DriveLocomotionClass::Will_Jump_Tracks(void) { /// This repeats the track jump test that While_Moving performs. assert(LinkedTo->IsActive); @@ -2254,7 +2254,7 @@ boolean STDMETHODCALLTYPE DriveLocomotionClass::Will_Jump_Tracks(void) /// While locked, this driver will not report itself ready to end a piggyback, so a /// temporary locomotor riding on top of it keeps control of the unit. /// -void STDMETHODCALLTYPE DriveLocomotionClass::Lock(void) +void DriveLocomotionClass::Lock(void) { IsLocomotorUnlocked = false; } @@ -2265,7 +2265,7 @@ void STDMETHODCALLTYPE DriveLocomotionClass::Lock(void) /// This is the counterpart to Lock. The driver may once again report itself ready to /// end a piggyback. /// -void STDMETHODCALLTYPE DriveLocomotionClass::Unlock(void) +void DriveLocomotionClass::Unlock(void) { IsLocomotorUnlocked = true; } @@ -2276,7 +2276,7 @@ void STDMETHODCALLTYPE DriveLocomotionClass::Unlock(void) /// /// Returns with the track control number, or -1 if the unit is not on a /// track. -int STDMETHODCALLTYPE DriveLocomotionClass::Get_Track_Number(void) +int DriveLocomotionClass::Get_Track_Number(void) { return(TrackNumber); } @@ -2287,7 +2287,7 @@ int STDMETHODCALLTYPE DriveLocomotionClass::Get_Track_Number(void) /// /// Returns with the index into the track the unit has reached, or -1 if the /// unit is not following one. -int STDMETHODCALLTYPE DriveLocomotionClass::Get_Track_Index(void) +int DriveLocomotionClass::Get_Track_Index(void) { return(TrackIndex); } @@ -2297,32 +2297,12 @@ int STDMETHODCALLTYPE DriveLocomotionClass::Get_Track_Index(void) /// Fetches the movement the driver has banked up along its track. /// /// Returns with the accumulated movement not yet spent advancing the unit. -int STDMETHODCALLTYPE DriveLocomotionClass::Get_Speed_Accum(void) +int DriveLocomotionClass::Get_Speed_Accum(void) { return(SpeedAccum); } -/// -/// Adds a reference to this locomotor. -/// -/// Returns with the reference count once the new reference is counted. -ULONG STDMETHODCALLTYPE DriveLocomotionClass::AddRef(void) -{ - return(BASECLASS::AddRef()); -} - - -/// -/// Releases a reference to this locomotor. -/// -/// Returns with the reference count remaining after the release. -ULONG STDMETHODCALLTYPE DriveLocomotionClass::Release(void) -{ - return(BASECLASS::Release()); -} - - /*************************************************************************** ** Smooth turn track tables. These are coordinate offsets from the center ** of the destination cell. These are the raw tracks that are modified diff --git a/code/drive.h b/code/drive.h index 5c3d2b587..6cf608614 100644 --- a/code/drive.h +++ b/code/drive.h @@ -60,36 +60,34 @@ class DriveLocomotionClass : public LocomotionClass, public IPiggyback DriveLocomotionClass(void); virtual ~DriveLocomotionClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) override; - virtual ULONG STDMETHODCALLTYPE Release(void) override; - - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual Coord STDMETHODCALLTYPE Head_To_Coord(void) override; - virtual Matrix3D STDMETHODCALLTYPE Draw_Matrix(int *key) override; - virtual int STDMETHODCALLTYPE Z_Adjust(void) override; - virtual ZGradientType STDMETHODCALLTYPE Z_Gradient(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual void STDMETHODCALLTYPE Do_Turn(DirType coord) override; - virtual void STDMETHODCALLTYPE Unlimbo(void) override; - virtual void STDMETHODCALLTYPE Force_Track(int track, Coord coord) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; - virtual void STDMETHODCALLTYPE Force_New_Slope(int ramp) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) override; - virtual void STDMETHODCALLTYPE Mark_All_Occupation_Bits(int mark) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Here(Coord to) override; - virtual boolean STDMETHODCALLTYPE Will_Jump_Tracks(void) override; - virtual void STDMETHODCALLTYPE Lock(void) override; - virtual void STDMETHODCALLTYPE Unlock(void) override; - virtual int STDMETHODCALLTYPE Get_Track_Number(void) override; - virtual int STDMETHODCALLTYPE Get_Track_Index(void) override; - virtual int STDMETHODCALLTYPE Get_Speed_Accum(void) override; + + virtual bool Is_Moving(void) override; + virtual Coord Destination(void) override; + virtual Coord Head_To_Coord(void) override; + virtual Matrix3D Draw_Matrix(int *key) override; + virtual int Z_Adjust(void) override; + virtual ZGradientType Z_Gradient(void) override; + virtual bool Process(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual void Do_Turn(DirType coord) override; + virtual void Unlimbo(void) override; + virtual void Force_Track(int track, Coord coord) override; + virtual LayerType In_Which_Layer(void) override; + virtual void Force_New_Slope(int ramp) override; + virtual bool Is_Moving_Now(void) override; + virtual void Mark_All_Occupation_Bits(int mark) override; + virtual bool Is_Moving_Here(Coord to) override; + virtual bool Will_Jump_Tracks(void) override; + virtual void Lock(void) override; + virtual void Unlock(void) override; + virtual int Get_Track_Number(void) override; + virtual int Get_Track_Index(void) override; + virtual int Get_Speed_Accum(void) override; virtual bool Begin_Piggyback(std::unique_ptr carried) override; virtual std::unique_ptr End_Piggyback(void) override; diff --git a/code/droppod.cpp b/code/droppod.cpp index 3a08f68e5..bbefe129b 100644 --- a/code/droppod.cpp +++ b/code/droppod.cpp @@ -56,7 +56,7 @@ DropPodLocomotionClass::~DropPodLocomotionClass(void) /// Is the drop pod in motion? /// A pod exists only for the duration of its fall, so it always reports movement. /// -boolean STDMETHODCALLTYPE DropPodLocomotionClass::Is_Moving(void) +bool DropPodLocomotionClass::Is_Moving(void) { return(true); } @@ -67,7 +67,7 @@ boolean STDMETHODCALLTYPE DropPodLocomotionClass::Is_Moving(void) /// /// Returns with the landing coordinate, or COORD_NONE if no destination has /// been assigned yet. -Coord STDMETHODCALLTYPE DropPodLocomotionClass::Destination(void) +Coord DropPodLocomotionClass::Destination(void) { return(DestinationCoord); } @@ -80,7 +80,7 @@ Coord STDMETHODCALLTYPE DropPodLocomotionClass::Destination(void) /// passenger is unlimboed, or destroyed along with its surroundings if there is nowhere /// for it to stand. /// -boolean STDMETHODCALLTYPE DropPodLocomotionClass::Process(void) +bool DropPodLocomotionClass::Process(void) { Coord coord = LinkedTo->PositionCoord; Coord smoke_coord = coord; @@ -165,7 +165,7 @@ boolean STDMETHODCALLTYPE DropPodLocomotionClass::Process(void) /// has a destination ignores any later request. /// /// The coordinate the pod should land on. -void STDMETHODCALLTYPE DropPodLocomotionClass::Move_To(Coord to) +void DropPodLocomotionClass::Move_To(Coord to) { if (DestinationCoord == COORD_NONE) { @@ -219,7 +219,7 @@ void STDMETHODCALLTYPE DropPodLocomotionClass::Move_To(Coord to) /// Fetches the class ID that this locomotor is persisted under. /// /// Returns with S_OK, or E_POINTER if no return pointer was supplied. -HRESULT STDMETHODCALLTYPE DropPodLocomotionClass::GetClassID(CLSID * retval) +HRESULT DropPodLocomotionClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_BallisticLocomotion; @@ -258,7 +258,7 @@ void DropPodLocomotionClass::Serialize(SaveStreamClass & stream) /// Stops the pod's descent. /// A pod cannot be halted in mid air, so this request is quietly ignored. /// -void STDMETHODCALLTYPE DropPodLocomotionClass::Stop_Moving(void) +void DropPodLocomotionClass::Stop_Moving(void) { // empty } @@ -312,7 +312,7 @@ bool DropPodLocomotionClass::Is_Ok_To_End(void) /// A pod is always falling, so it draws along with the other airborne objects right up /// until it lands and gives its object back. /// -LayerType STDMETHODCALLTYPE DropPodLocomotionClass::In_Which_Layer(void) +LayerType DropPodLocomotionClass::In_Which_Layer(void) { return(LAYER_AIR); } @@ -343,7 +343,7 @@ HRESULT DropPodLocomotionClass::Piggyback_CLSID(GUID * classid) /// Fetches the drawing code for the drop pod. /// The renderer uses this to choose the artwork that suits the pod's approach. /// -int STDMETHODCALLTYPE DropPodLocomotionClass::Drawing_Code(void) +int DropPodLocomotionClass::Drawing_Code(void) { return((unsigned)Direction % 2); } diff --git a/code/droppod.h b/code/droppod.h index cdb4c7496..84939659d 100644 --- a/code/droppod.h +++ b/code/droppod.h @@ -31,20 +31,18 @@ class DropPodLocomotionClass : public LocomotionClass, public IPiggyback DropPodLocomotionClass(void); virtual ~DropPodLocomotionClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) override {return(BASECLASS::AddRef());} - virtual ULONG STDMETHODCALLTYPE Release(void) override {return(BASECLASS::Release());} - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; - virtual int STDMETHODCALLTYPE Drawing_Code(void) override; + virtual bool Is_Moving(void) override; + virtual Coord Destination(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual bool Process(void) override; + virtual LayerType In_Which_Layer(void) override; + virtual int Drawing_Code(void) override; virtual bool Begin_Piggyback(std::unique_ptr carried) override; virtual std::unique_ptr End_Piggyback(void) override; diff --git a/code/empulse.cpp b/code/empulse.cpp index 501892e45..0b1c2b152 100644 --- a/code/empulse.cpp +++ b/code/empulse.cpp @@ -290,7 +290,7 @@ void EMPulseClass::Compute_CRC(CRCEngine &crc) const /// /// Pointer to the buffer that will receive the class identifier. /// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT STDMETHODCALLTYPE EMPulseClass::GetClassID(CLSID * retval) +HRESULT EMPulseClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_EMPulseClass; diff --git a/code/empulse.h b/code/empulse.h index c059e0d4a..2d8edc6f8 100644 --- a/code/empulse.h +++ b/code/empulse.h @@ -30,7 +30,7 @@ class EMPulseClass : public AbstractClass virtual RTTIType Fetch_RTTI(void) const override {return(RTTI_EMPULSE);} - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/enviro.h b/code/enviro.h index 8599da2f4..79b1e7187 100644 --- a/code/enviro.h +++ b/code/enviro.h @@ -13,7 +13,6 @@ #include "diff.hh" -#include class SaveStreamClass; diff --git a/code/factory.cpp b/code/factory.cpp index 2ce156040..9ba093aac 100644 --- a/code/factory.cpp +++ b/code/factory.cpp @@ -638,7 +638,7 @@ bool FactoryClass::Completed(void) /// identifier to know what kind of object to create before handing it the stream. /// /// Returns with S_OK, or E_POINTER if no return location was supplied. -HRESULT STDMETHODCALLTYPE FactoryClass::GetClassID(CLSID * retval) +HRESULT FactoryClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_FactoryClass; diff --git a/code/factory.h b/code/factory.h index 7886611ef..5b27ebad6 100644 --- a/code/factory.h +++ b/code/factory.h @@ -54,7 +54,7 @@ class FactoryClass : public AbstractClass, private StageClass FactoryClass(void); ~FactoryClass(void); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/fly.cpp b/code/fly.cpp index 0b64fe905..606d76f7a 100644 --- a/code/fly.cpp +++ b/code/fly.cpp @@ -117,7 +117,7 @@ FlyLocomotionClass::~FlyLocomotionClass(void) /// an aircraft that has been told to go somewhere but has yet to build up any speed. /// /// bool; Is the aircraft moving or trying to? -boolean STDMETHODCALLTYPE FlyLocomotionClass::Is_Moving(void) +bool FlyLocomotionClass::Is_Moving(void) { return(IsMoving || LinkedTo->PitchAngle > 0); } @@ -129,7 +129,7 @@ boolean STDMETHODCALLTYPE FlyLocomotionClass::Is_Moving(void) /// the aircraft has any speed at all. /// /// bool; Is the aircraft moving right now? -boolean STDMETHODCALLTYPE FlyLocomotionClass::Is_Moving_Now(void) +bool FlyLocomotionClass::Is_Moving_Now(void) { if (CurrentSpeed == 0) { return(false); @@ -143,7 +143,7 @@ boolean STDMETHODCALLTYPE FlyLocomotionClass::Is_Moving_Now(void) /// /// Returns with the destination coordinate. If the aircraft is not going /// anywhere, COORD_NONE is returned. -Coord STDMETHODCALLTYPE FlyLocomotionClass::Destination(void) +Coord FlyLocomotionClass::Destination(void) { if (Is_Moving()) { return(DestinationCoord); @@ -159,7 +159,7 @@ Coord STDMETHODCALLTYPE FlyLocomotionClass::Destination(void) /// disposed of if it has wandered off the edge of the world. /// /// bool; Is the aircraft still under way? -boolean STDMETHODCALLTYPE FlyLocomotionClass::Process(void) +bool FlyLocomotionClass::Process(void) { if (!IsLanding && !IsTakingOff && TargetSpeed >= 1.0 && FlightLevel == 0) { FlightLevel = LinkedTo->TClass->Flight_Level(); @@ -234,7 +234,7 @@ boolean STDMETHODCALLTYPE FlyLocomotionClass::Process(void) /// destination is a request to stop, which brings a flying aircraft down to land. /// /// The coordinate to head for, or COORD_NONE to stop and land. -void STDMETHODCALLTYPE FlyLocomotionClass::Move_To(Coord to) +void FlyLocomotionClass::Move_To(Coord to) { if (((Coord)to).As_Cell() != DestinationCoord.As_Cell() || !IsLanding) { @@ -242,7 +242,7 @@ void STDMETHODCALLTYPE FlyLocomotionClass::Move_To(Coord to) if ((Coord)to == COORD_NONE) { int landing_altitude = 0; - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); if (flyctrl != NULL) { landing_altitude = flyctrl->Landing_Altitude(); } @@ -261,7 +261,7 @@ void STDMETHODCALLTYPE FlyLocomotionClass::Move_To(Coord to) DestinationCoord.Z = LinkedTo->TClass->Flight_Level() + Map.Get_Height_GL(to); } - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); int landing_altitude = 0; if (flyctrl != NULL) { landing_altitude = flyctrl->Landing_Altitude(); @@ -286,7 +286,7 @@ void STDMETHODCALLTYPE FlyLocomotionClass::Move_To(Coord to) /// assigns that as the new destination. An aircraft with nowhere at all to go is destroyed /// rather than left loitering in an illegal spot. /// -void STDMETHODCALLTYPE FlyLocomotionClass::Stop_Moving(void) +void FlyLocomotionClass::Stop_Moving(void) { if (Is_Moving()) { @@ -608,7 +608,7 @@ void FlyLocomotionClass::Movement_AI(void) if (current_height < FlightLevel && LinkedTo->Strength > 0) { bool is_loaded = false; - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); if (flyctrl != NULL) { is_loaded = flyctrl->Is_Loaded() != 0; } @@ -698,7 +698,7 @@ void FlyLocomotionClass::Movement_AI(void) } if (LinkedTo->Strength > 0 && Is_In_Flight() && DestinationCoord != COORD_NONE) { - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); if (!Needs_To_Land()) { TargetSpeed = 1.0; @@ -881,7 +881,7 @@ bool FlyLocomotionClass::Process_Take_Off(void) height -= BRIDGE_LEPTON_HEIGHT; } - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); int landing_altitude = 0; if (flyctrl) { landing_altitude = flyctrl->Landing_Altitude(); @@ -962,7 +962,7 @@ bool FlyLocomotionClass::Process_Landing(void) TargetSpeed = 0; int landing_altitude = 0; - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); if (flyctrl) { landing_altitude = flyctrl->Landing_Altitude(); } @@ -1072,7 +1072,7 @@ bool FlyLocomotionClass::Process_Landing(void) /// Returns with the distance remaining to the destination. int FlyLocomotionClass::Nearing_Target(bool stage_approach, Coord coord) { - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); /* * A strafing aircraft that is over an ammo-bearing attack run should ignore the @@ -1303,7 +1303,7 @@ int FlyLocomotionClass::Nearing_Target(bool stage_approach, Coord coord) /// Optional cache key for the resulting orientation. It may be NULL, and /// is set to -1 for an attitude that is not worth caching. /// Returns with the matrix to draw the aircraft with. -Matrix3D STDMETHODCALLTYPE FlyLocomotionClass::Draw_Matrix(int * key) +Matrix3D FlyLocomotionClass::Draw_Matrix(int * key) { Matrix3D mtx; mtx.Make_Identity(); @@ -1398,10 +1398,10 @@ Matrix3D STDMETHODCALLTYPE FlyLocomotionClass::Draw_Matrix(int * key) /// look pinned in place while it hovers. Dropships and grounded aircraft do not bob. /// /// Returns with the pixel offset to shift the aircraft by. -Point2D STDMETHODCALLTYPE FlyLocomotionClass::Draw_Point(void) +Point2D FlyLocomotionClass::Draw_Point(void) { int y = 0; - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); int landing_altitude = 0; if (flyctrl) { @@ -1421,7 +1421,7 @@ Point2D STDMETHODCALLTYPE FlyLocomotionClass::Draw_Point(void) /// The shadow is drawn where the aircraft's position puts it, so no adjustment is needed. /// /// Returns with the pixel offset to shift the shadow by. -Point2D STDMETHODCALLTYPE FlyLocomotionClass::Shadow_Point(void) +Point2D FlyLocomotionClass::Shadow_Point(void) { return(Point2D(0, 0)); } @@ -1470,7 +1470,7 @@ void FlyLocomotionClass::Land(void) /// Optional cache key for the shadow orientation. It may be NULL, and a /// value of -1 marks the shadow as not worth caching. /// Returns with the matrix to draw the shadow with. -Matrix3D STDMETHODCALLTYPE FlyLocomotionClass::Shadow_Matrix(int * key) +Matrix3D FlyLocomotionClass::Shadow_Matrix(int * key) { int ramp = Map[(Coord const &)LinkedTo->PositionCoord].Ramp; if (LinkedTo->TClass->IsDropship) { @@ -1493,7 +1493,7 @@ Matrix3D STDMETHODCALLTYPE FlyLocomotionClass::Shadow_Matrix(int * key) /// This routine snaps the body around immediately rather than rotating it over time. /// /// The facing to set the aircraft body to. -void STDMETHODCALLTYPE FlyLocomotionClass::Do_Turn(DirType coord) +void FlyLocomotionClass::Do_Turn(DirType coord) { LinkedTo->SecondaryFacing.Set(coord); } @@ -1522,7 +1522,7 @@ bool FlyLocomotionClass::Is_In_Flight(void) /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE FlyLocomotionClass::GetClassID(CLSID * retval) +HRESULT FlyLocomotionClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_FlyerLocomotion; @@ -1559,7 +1559,7 @@ void FlyLocomotionClass::Serialize(SaveStreamClass & stream) /// /// Returns with LAYER_GROUND while the aircraft is on the deck, or LAYER_TOP once /// it is above it. -LayerType STDMETHODCALLTYPE FlyLocomotionClass::In_Which_Layer(void) +LayerType FlyLocomotionClass::In_Which_Layer(void) { return(LinkedTo->HeightAGL <= 0 ? LAYER_GROUND : LAYER_TOP); } @@ -1572,7 +1572,7 @@ LayerType STDMETHODCALLTYPE FlyLocomotionClass::In_Which_Layer(void) /// stop, so that it falls out of the sky rather than coasting on to its objective. /// /// bool; Was the power successfully cut? -boolean STDMETHODCALLTYPE FlyLocomotionClass::Power_Off(void) +bool FlyLocomotionClass::Power_Off(void) { if (Is_Moving()) { Tumble(); @@ -1587,7 +1587,7 @@ boolean STDMETHODCALLTYPE FlyLocomotionClass::Power_Off(void) /// Is the aircraft still under power? /// /// bool; Does the aircraft still have power? -boolean STDMETHODCALLTYPE FlyLocomotionClass::Is_Powered(void) +bool FlyLocomotionClass::Is_Powered(void) { return(BASECLASS::Is_Powered()); } @@ -1599,7 +1599,7 @@ boolean STDMETHODCALLTYPE FlyLocomotionClass::Is_Powered(void) /// by one. /// /// bool; Does an ion storm affect this aircraft? -boolean STDMETHODCALLTYPE FlyLocomotionClass::Is_Ion_Sensitive(void) +bool FlyLocomotionClass::Is_Ion_Sensitive(void) { return(!LinkedTo->TClass->IsHunterSeeker); } @@ -1625,7 +1625,7 @@ void FlyLocomotionClass::Tumble(void) /// Fetches the speed the aircraft is currently traveling at. /// /// Returns with the distance the aircraft will cover in one game frame. -int STDMETHODCALLTYPE FlyLocomotionClass::Apparent_Speed(void) +int FlyLocomotionClass::Apparent_Speed(void) { return(LinkedTo->TClass->MaxSpeed * CurrentSpeed); } @@ -1638,7 +1638,7 @@ int STDMETHODCALLTYPE FlyLocomotionClass::Apparent_Speed(void) /// /// Returns with the status code for taking off, landing, moving, or sitting /// idle. -int STDMETHODCALLTYPE FlyLocomotionClass::Get_Status(void) +int FlyLocomotionClass::Get_Status(void) { if (IsLanding) { return(1); @@ -1660,7 +1660,7 @@ int STDMETHODCALLTYPE FlyLocomotionClass::Get_Status(void) /// targets in a multiplay game so that the drone makes a nuisance of itself where it will /// be noticed. /// -void STDMETHODCALLTYPE FlyLocomotionClass::Acquire_Hunter_Seeker_Target(void) +void FlyLocomotionClass::Acquire_Hunter_Seeker_Target(void) { if (LinkedTo->TarCom == NULL) { @@ -1722,7 +1722,7 @@ bool FlyLocomotionClass::Needs_To_Land(void) return(true); } - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); if (flyctrl != NULL && !flyctrl->Is_Strafe()) { return(true); } @@ -1751,7 +1751,7 @@ bool FlyLocomotionClass::Is_Locked_To_Straight_Flight(void) return(true); } - IFlyControlPtr flyctrl(LinkedTo); + IFlyControl * const flyctrl = dynamic_cast(LinkedTo); if (flyctrl) { if (flyctrl->Is_Locked()) { return(true); diff --git a/code/fly.h b/code/fly.h index 96ffe8cc3..10813387f 100644 --- a/code/fly.h +++ b/code/fly.h @@ -58,28 +58,28 @@ class FlyLocomotionClass : public LocomotionClass FlyLocomotionClass(void); virtual ~FlyLocomotionClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual Matrix3D STDMETHODCALLTYPE Draw_Matrix(int *key) override; - virtual Point2D STDMETHODCALLTYPE Draw_Point(void) override; - virtual Point2D STDMETHODCALLTYPE Shadow_Point(void) override; - virtual Matrix3D STDMETHODCALLTYPE Shadow_Matrix(int *key) override; - virtual void STDMETHODCALLTYPE Do_Turn(DirType coord) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; - virtual boolean STDMETHODCALLTYPE Power_Off(void) override; - virtual boolean STDMETHODCALLTYPE Is_Powered(void) override; - virtual boolean STDMETHODCALLTYPE Is_Ion_Sensitive(void) override; - virtual int STDMETHODCALLTYPE Apparent_Speed(void) override; - virtual int STDMETHODCALLTYPE Get_Status(void) override; - virtual void STDMETHODCALLTYPE Acquire_Hunter_Seeker_Target(void) override; + virtual bool Is_Moving(void) override; + virtual bool Is_Moving_Now(void) override; + virtual Coord Destination(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual bool Process(void) override; + virtual Matrix3D Draw_Matrix(int *key) override; + virtual Point2D Draw_Point(void) override; + virtual Point2D Shadow_Point(void) override; + virtual Matrix3D Shadow_Matrix(int *key) override; + virtual void Do_Turn(DirType coord) override; + virtual LayerType In_Which_Layer(void) override; + virtual bool Power_Off(void) override; + virtual bool Is_Powered(void) override; + virtual bool Is_Ion_Sensitive(void) override; + virtual int Apparent_Speed(void) override; + virtual int Get_Status(void) override; + virtual void Acquire_Hunter_Seeker_Target(void) override; /*--------------------------------------------------------------------- ** Member function prototypes. diff --git a/code/fog.cpp b/code/fog.cpp index 76acc1e3c..2bd9e9d89 100644 --- a/code/fog.cpp +++ b/code/fog.cpp @@ -600,7 +600,7 @@ RTTIType FoggedObjectClass::Fetch_RTTI(void) const /// /// Pointer to the class ID to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE FoggedObjectClass::GetClassID(CLSID * retval) +HRESULT FoggedObjectClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_FoggedObjectClass; diff --git a/code/fog.h b/code/fog.h index 873145049..f2b183b1d 100644 --- a/code/fog.h +++ b/code/fog.h @@ -40,7 +40,7 @@ class FoggedObjectClass : public AbstractClass FoggedObjectClass(TerrainClass * object); virtual ~FoggedObjectClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/foot.cpp b/code/foot.cpp index 5fa552428..4592132b6 100644 --- a/code/foot.cpp +++ b/code/foot.cpp @@ -3408,7 +3408,7 @@ ZGradientType FootClass::Get_Z_Gradient(void) const /// void FootClass::Draw_Voxel_Shadow(VoxelDataStruct const & voxeldata, int layer_index, int key, VoxelIndexClass * cache, Rect const & cliprect, Point2D const & point, Matrix3D const & matrix, bool force_cache) const { - if (Locomotion != NULL && Locomotion->Is_To_Have_Shadow() == (boolean)true) { + if (Locomotion != NULL && Locomotion->Is_To_Have_Shadow() == (bool)true) { Point2D drawpoint = point; if (Locomotion != NULL) { drawpoint = Point2D(Locomotion->Shadow_Point()) + point; diff --git a/code/globals.cpp b/code/globals.cpp index 89eef0178..01daef65f 100644 --- a/code/globals.cpp +++ b/code/globals.cpp @@ -37,10 +37,6 @@ #include "isun_i.c" #include "ilocos.h" #include "ilocos_i.c" -#include "ipiggy.h" -#include "ipiggy_i.c" -#include "iflyctrl.h" -#include "iflyctrl_i.c" #undef INCLUDE_COM #include "_voxel.h" diff --git a/code/house.cpp b/code/house.cpp index d06493b0c..9ba85d7f6 100644 --- a/code/house.cpp +++ b/code/house.cpp @@ -6637,7 +6637,7 @@ void HouseClass::Serialize(SaveStreamClass & stream) /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE HouseClass::GetClassID(CLSID * retval) +HRESULT HouseClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_HouseClass; @@ -9213,30 +9213,6 @@ void HouseClass::AI_Drop_Pods(SuperClass * super) } -/// -/// Adds a reference to this house. -/// Houses are permanent heap objects rather than reference counted ones, so this routine -/// exists only to satisfy the IUnknown contract. -/// -/// Returns with the reference count, which is always one. -ULONG STDMETHODCALLTYPE HouseClass::AddRef(void) -{ - return(1); -} - - -/// -/// Releases a reference to this house. -/// Houses are permanent heap objects rather than reference counted ones, so this routine -/// exists only to satisfy the IUnknown contract. It never destroys the house. -/// -/// Returns with the reference count, which is always one. -ULONG STDMETHODCALLTYPE HouseClass::Release(void) -{ - return(1); -} - - /// /// Fetches the RTTI type of this object. /// diff --git a/code/house.h b/code/house.h index f2fd31f51..6671012c2 100644 --- a/code/house.h +++ b/code/house.h @@ -735,13 +735,11 @@ class HouseClass : public AbstractClass HouseClass(HouseTypeClass const * type = NULL); virtual ~HouseClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) override; - virtual ULONG STDMETHODCALLTYPE Release(void) override; int Available_Money(void); int Available_Storage(void); diff --git a/code/houstype.cpp b/code/houstype.cpp index 8a7be00cc..4298f6062 100644 --- a/code/houstype.cpp +++ b/code/houstype.cpp @@ -259,39 +259,13 @@ void HouseTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the requested interface from this house type. -/// House types serve up the persistence and RTTI interfaces that the save game system asks -/// them for. -/// -/// Returns with S_OK, or E_NOINTERFACE if the interface is not supported. -HRESULT STDMETHODCALLTYPE HouseTypeClass::QueryInterface(REFIID riid, LPVOID * ppvObject) -{ - if (ppvObject == NULL) { - return(E_POINTER); - } - - *ppvObject = NULL; - - if (riid == IID_IUnknown) { - *ppvObject = (IUnknown *)(IPersistent *)this; - } - if (*ppvObject == NULL) { - return(E_NOINTERFACE); - } - - AddRef(); - return(S_OK); -} - - /// /// Fetches the class identifier of this object. /// The save game system stores this identifier so that the object can be recreated as the /// correct class when the game is loaded. /// /// Returns with S_OK, or E_POINTER if no return pointer was supplied. -HRESULT STDMETHODCALLTYPE HouseTypeClass::GetClassID(CLSID * retval) +HRESULT HouseTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_HouseTypeClass; @@ -332,25 +306,3 @@ int HouseTypeClass::Fetch_Heap_ID(void) const } -/// -/// Adds a reference to this house type. -/// House types are not reference counted -- they live for the duration of the game, so this -/// routine exists only to satisfy the IUnknown contract. -/// -/// Returns with the reference count, which is always one. -ULONG STDMETHODCALLTYPE HouseTypeClass::AddRef(void) -{ - return(1); -} - - -/// -/// Releases a reference to this house type. -/// House types are not reference counted -- they live for the duration of the game, so this -/// routine exists only to satisfy the IUnknown contract. -/// -/// Returns with the reference count, which is always one. -ULONG STDMETHODCALLTYPE HouseTypeClass::Release(void) -{ - return(1); -} diff --git a/code/houstype.h b/code/houstype.h index e5c401a47..4d0160b10 100644 --- a/code/houstype.h +++ b/code/houstype.h @@ -102,11 +102,8 @@ class HouseTypeClass : public AbstractTypeClass HouseTypeClass(char const * ininame = NULL); virtual ~HouseTypeClass() override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID * ppvObject) override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) override; - virtual ULONG STDMETHODCALLTYPE Release(void) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/hover.cpp b/code/hover.cpp index b108e633d..85458e79c 100644 --- a/code/hover.cpp +++ b/code/hover.cpp @@ -66,7 +66,7 @@ HoverLocomotionClass::HoverLocomotionClass(void) : /// /// Pointer to the object this locomotor will drive. /// Returns with the result of the attach operation. -HRESULT STDMETHODCALLTYPE HoverLocomotionClass::Link_To_Object(void *pointer) +HRESULT HoverLocomotionClass::Link_To_Object(void *pointer) { HRESULT res = BASECLASS::Link_To_Object(pointer); FacingClass face(2 * LinkedTo->TClass->ROT); @@ -141,7 +141,7 @@ void HoverLocomotionClass::Gravity_AI(void) /// /// Pointer to the render cache key to update; may be NULL. /// Returns with the matrix to render the object with. -Matrix3D STDMETHODCALLTYPE HoverLocomotionClass::Draw_Matrix(int *key) +Matrix3D HoverLocomotionClass::Draw_Matrix(int *key) { if (!Is_Powered()) { int ramp = Map[(Coord const &)(LinkedTo->PositionCoord)].Ramp; @@ -164,7 +164,7 @@ Matrix3D STDMETHODCALLTYPE HoverLocomotionClass::Draw_Matrix(int *key) /// water, and applies the bob and sag of the hover cushion. /// /// bool; Is the object still moving? -boolean STDMETHODCALLTYPE HoverLocomotionClass::Process(void) +bool HoverLocomotionClass::Process(void) { if (Is_Moving() && Is_Moving1()) { Motion_AI(); @@ -288,7 +288,7 @@ boolean STDMETHODCALLTYPE HoverLocomotionClass::Process(void) /// Does the object have a move order outstanding? /// /// bool; Is the object either headed somewhere or bound for a destination? -boolean STDMETHODCALLTYPE HoverLocomotionClass::Is_Moving(void) +bool HoverLocomotionClass::Is_Moving(void) { return(DestinationCoord != COORD_NONE || HeadToCoord != COORD_NONE); } @@ -300,7 +300,7 @@ boolean STDMETHODCALLTYPE HoverLocomotionClass::Is_Moving(void) /// moving, but it is not moving now. /// /// bool; Is the object traveling at this moment? -boolean STDMETHODCALLTYPE HoverLocomotionClass::Is_Moving_Now(void) +bool HoverLocomotionClass::Is_Moving_Now(void) { return(Is_Moving() && Height != 0.0); } @@ -311,7 +311,7 @@ boolean STDMETHODCALLTYPE HoverLocomotionClass::Is_Moving_Now(void) /// /// Returns with the destination coordinate, or COORD_NONE if the object has no /// move order outstanding. -Coord STDMETHODCALLTYPE HoverLocomotionClass::Destination(void) +Coord HoverLocomotionClass::Destination(void) { if (DestinationCoord != COORD_NONE) { return(DestinationCoord); @@ -325,7 +325,7 @@ Coord STDMETHODCALLTYPE HoverLocomotionClass::Destination(void) /// /// Returns with the intermediate destination, or with the object's current /// position if it is not headed anywhere. -Coord STDMETHODCALLTYPE HoverLocomotionClass::Head_To_Coord(void) +Coord HoverLocomotionClass::Head_To_Coord(void) { if (HeadToCoord != COORD_NONE) { return(HeadToCoord); @@ -341,7 +341,7 @@ Coord STDMETHODCALLTYPE HoverLocomotionClass::Head_To_Coord(void) /// the drive is started if the object is not already under way. /// /// The coordinate to move to. -void STDMETHODCALLTYPE HoverLocomotionClass::Move_To(Coord to) +void HoverLocomotionClass::Move_To(Coord to) { DestinationCoord = to; if (Is_Powered() && Is_Ion_Sensitive() && IonStormClass::Is_Ion_Storm_Active()) { @@ -678,7 +678,7 @@ void HoverLocomotionClass::Motion_AI(void) /// The object will still coast into the spot it has already reserved, but it will not /// carry on toward its former destination once it arrives. /// -void STDMETHODCALLTYPE HoverLocomotionClass::Stop_Moving(void) +void HoverLocomotionClass::Stop_Moving(void) { if (DestinationCoord != HeadToCoord) { DestinationCoord = COORD_NONE; @@ -918,7 +918,7 @@ void HoverLocomotionClass::Start_Of_Move(int num) /// and slews as it sinks rather than dropping neatly in place. /// /// bool; Was the power turned off? -boolean STDMETHODCALLTYPE HoverLocomotionClass::Power_Off(void) +bool HoverLocomotionClass::Power_Off(void) { if (Is_Powered() && LinkedTo->CurrentMission != MISSION_SLEEP) { Do_Shove(); @@ -937,7 +937,7 @@ boolean STDMETHODCALLTYPE HoverLocomotionClass::Power_Off(void) /// way onto the ground, so it is treated as powered for as long as it has height to lose. /// /// bool; Is the object still under power? -boolean STDMETHODCALLTYPE HoverLocomotionClass::Is_Powered(void) +bool HoverLocomotionClass::Is_Powered(void) { if (!BASECLASS::Is_Powered() && LinkedTo->HeightAGL <= 0) { return(false); @@ -953,7 +953,7 @@ boolean STDMETHODCALLTYPE HoverLocomotionClass::Is_Powered(void) /// units across the factory doorway. /// /// bool; Should an ion storm cut this object's power? -boolean STDMETHODCALLTYPE HoverLocomotionClass::Is_Ion_Sensitive(void) +bool HoverLocomotionClass::Is_Ion_Sensitive(void) { BuildingClass *bptr; if (LinkedTo->In_Radio_Contact()) { @@ -997,7 +997,7 @@ boolean STDMETHODCALLTYPE HoverLocomotionClass::Is_Ion_Sensitive(void) /// /// The direction to push the object toward. /// bool; Was the object pushed? -boolean STDMETHODCALLTYPE HoverLocomotionClass::Push(DirType dir) +bool HoverLocomotionClass::Push(DirType dir) { if (Is_Powered() && !WasPushed) { @@ -1042,7 +1042,7 @@ boolean STDMETHODCALLTYPE HoverLocomotionClass::Push(DirType dir) /// /// The direction to shove the object toward. /// bool; Was the object shoved? -boolean STDMETHODCALLTYPE HoverLocomotionClass::Shove(DirType dir) +bool HoverLocomotionClass::Shove(DirType dir) { if (Push(dir)) { Do_Shove(); @@ -1074,7 +1074,7 @@ void HoverLocomotionClass::Do_Shove(void) /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE HoverLocomotionClass::GetClassID(CLSID * retval) +HRESULT HoverLocomotionClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_HoverLocomotion; @@ -1109,7 +1109,7 @@ void HoverLocomotionClass::Serialize(SaveStreamClass & stream) /// layer with ordinary vehicles. /// /// Returns with the layer this object belongs in. -LayerType STDMETHODCALLTYPE HoverLocomotionClass::In_Which_Layer(void) +LayerType HoverLocomotionClass::In_Which_Layer(void) { return(LAYER_GROUND); } @@ -1140,7 +1140,7 @@ void HoverLocomotionClass::Start(void) /// /// The marking operation to perform; MARK_UP releases the cell, /// anything else reserves it. -void STDMETHODCALLTYPE HoverLocomotionClass::Mark_All_Occupation_Bits(int mark) +void HoverLocomotionClass::Mark_All_Occupation_Bits(int mark) { if (mark == MARK_UP) { Coord coord = Head_To_Coord(); @@ -1159,7 +1159,7 @@ void STDMETHODCALLTYPE HoverLocomotionClass::Mark_All_Occupation_Bits(int mark) /// /// The coordinate to compare the current destination against. /// bool; Is the object moving to this location? -boolean STDMETHODCALLTYPE HoverLocomotionClass::Is_Moving_Here(Coord to) +bool HoverLocomotionClass::Is_Moving_Here(Coord to) { Coord coord = Head_To_Coord(); diff --git a/code/hover.h b/code/hover.h index ab7bce672..055d4e080 100644 --- a/code/hover.h +++ b/code/hover.h @@ -36,28 +36,28 @@ class HoverLocomotionClass : public LocomotionClass HoverLocomotionClass(void); virtual ~HoverLocomotionClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; - virtual HRESULT STDMETHODCALLTYPE Link_To_Object(void *pointer) override; - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual Coord STDMETHODCALLTYPE Head_To_Coord(void) override; - virtual Matrix3D STDMETHODCALLTYPE Draw_Matrix(int *key) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual void STDMETHODCALLTYPE Do_Turn(DirType coord) override; - virtual boolean STDMETHODCALLTYPE Power_Off(void) override; - virtual boolean STDMETHODCALLTYPE Is_Powered(void) override; - virtual boolean STDMETHODCALLTYPE Is_Ion_Sensitive(void) override; - virtual boolean STDMETHODCALLTYPE Push(DirType dir) override; - virtual boolean STDMETHODCALLTYPE Shove(DirType dir) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) override; - virtual void STDMETHODCALLTYPE Mark_All_Occupation_Bits(int mark) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Here(Coord to) override; + virtual HRESULT Link_To_Object(void *pointer) override; + virtual bool Is_Moving(void) override; + virtual Coord Destination(void) override; + virtual Coord Head_To_Coord(void) override; + virtual Matrix3D Draw_Matrix(int *key) override; + virtual bool Process(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual void Do_Turn(DirType coord) override; + virtual bool Power_Off(void) override; + virtual bool Is_Powered(void) override; + virtual bool Is_Ion_Sensitive(void) override; + virtual bool Push(DirType dir) override; + virtual bool Shove(DirType dir) override; + virtual LayerType In_Which_Layer(void) override; + virtual bool Is_Moving_Now(void) override; + virtual void Mark_All_Occupation_Bits(int mark) override; + virtual bool Is_Moving_Here(Coord to) override; private: diff --git a/code/iflyctrl.h b/code/iflyctrl.h index 958e59d47..527143e0f 100644 --- a/code/iflyctrl.h +++ b/code/iflyctrl.h @@ -9,43 +9,35 @@ #pragma once -#include +#include "win.h" -/// Names and comments from TLBs -EXTERN_C const IID IID_IFlyControl; -MIDL_INTERFACE("820F501C-4F39-11D2-9B70-00104B972FE8") -IFlyControl : public IUnknown +struct IFlyControl { -public: /* * Landing altitude */ - virtual LONG STDMETHODCALLTYPE Landing_Altitude(void) = 0; + virtual LONG Landing_Altitude(void) = 0; /* * Lading direction */ - virtual LONG STDMETHODCALLTYPE Landing_Direction(void) = 0; + virtual LONG Landing_Direction(void) = 0; /* * Loaded with cargo? */ - virtual BOOL STDMETHODCALLTYPE Is_Loaded(void) = 0; + virtual BOOL Is_Loaded(void) = 0; /* * Does it strafe over the target rather than hover? */ - virtual LONG STDMETHODCALLTYPE Is_Strafe(void) = 0; + virtual LONG Is_Strafe(void) = 0; /* * Is the aircraft locked into straight flight? */ - virtual LONG STDMETHODCALLTYPE Is_Locked(void) = 0; + virtual LONG Is_Locked(void) = 0; }; -/* - * IFlyControl com smart pointer declaration. - */ -_COM_SMARTPTR_TYPEDEF(IFlyControl, __uuidof(IFlyControl)); diff --git a/code/iflyctrl_i.c b/code/iflyctrl_i.c deleted file mode 100644 index 994c2ab42..000000000 --- a/code/iflyctrl_i.c +++ /dev/null @@ -1,52 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -/* this file contains the actual definitions of */ -/* the IIDs and CLSIDs */ - -/* link this file in with the server and any clients */ - - -/* File created by MIDL compiler version X.XX.XXXX */ -/* at XXX XXX XX XX:XX:XX XXXX - */ -/* Compiler settings for XXXX.idl: - Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext - error checks: none -*/ -//@@MIDL_FILE_HEADING( ) -#ifdef __cplusplus -extern "C"{ -#endif - - -#ifndef __IID_DEFINED__ -#define __IID_DEFINED__ - -typedef struct _IID -{ - unsigned long x; - unsigned short s1; - unsigned short s2; - unsigned char c[8]; -} IID; - -#endif // __IID_DEFINED__ - -#ifndef CLSID_DEFINED -#define CLSID_DEFINED -typedef IID CLSID; -#endif // CLSID_DEFINED - -const IID IID_IFlyControl = {0x820F501C,0x4F39,0x11D2,{0x9B,0x70,0x00,0x10,0x4B,0x97,0x2F,0xE8}}; - - -#ifdef __cplusplus -} -#endif diff --git a/code/iloco.h b/code/iloco.h index 15bb5f01e..7df217c37 100644 --- a/code/iloco.h +++ b/code/iloco.h @@ -20,252 +20,247 @@ #include "visual.hh" #include "zgrad.hh" -#include +#include +#include #include -/// Names and comments from TLBs -EXTERN_C const IID IID_ILocomotion; /* * Game object locomotion handler. */ -MIDL_INTERFACE("070F3290-9841-11D1-B709-00A024DDAFD1") -ILocomotion : public IUnknown +struct ILocomotion { -public: + virtual ~ILocomotion(void) {} + /* * Links object to locomotor. */ - virtual HRESULT STDMETHODCALLTYPE Link_To_Object(void *pointer) = 0; + virtual HRESULT Link_To_Object(void *pointer) = 0; /* * Sees if object is moving. */ - virtual boolean STDMETHODCALLTYPE Is_Moving(void) = 0; + virtual bool Is_Moving(void) = 0; /* * Fetches destination coordinate. */ - virtual Coord STDMETHODCALLTYPE Destination(void) = 0; + virtual Coord Destination(void) = 0; /* * Fetches immediate (next cell) destination coordinate. */ - virtual Coord STDMETHODCALLTYPE Head_To_Coord(void) = 0; + virtual Coord Head_To_Coord(void) = 0; /* * Determine if specific cell can be entered. */ - virtual MoveType STDMETHODCALLTYPE Can_Enter_Cell(Cell cell) = 0; + virtual MoveType Can_Enter_Cell(Cell cell) = 0; /* * Should object cast a shadow? */ - virtual boolean STDMETHODCALLTYPE Is_To_Have_Shadow(void) = 0; + virtual bool Is_To_Have_Shadow(void) = 0; /* * Fetch voxel draw matrix. */ - virtual Matrix3D STDMETHODCALLTYPE Draw_Matrix(int *key) = 0; + virtual Matrix3D Draw_Matrix(int *key) = 0; /* * Fetch shadow draw matrix. */ - virtual Matrix3D STDMETHODCALLTYPE Shadow_Matrix(int *key) = 0; + virtual Matrix3D Shadow_Matrix(int *key) = 0; /* * Draw point center location. */ - virtual Point2D STDMETHODCALLTYPE Draw_Point(void) = 0; + virtual Point2D Draw_Point(void) = 0; /* * Shadow draw point center location. */ - virtual Point2D STDMETHODCALLTYPE Shadow_Point(void) = 0; + virtual Point2D Shadow_Point(void) = 0; /* * Visual character for drawing. */ - virtual VisualType STDMETHODCALLTYPE Visual_Character(boolean flag) = 0; + virtual VisualType Visual_Character(bool flag) = 0; /* * Z adjust control value. */ - virtual int STDMETHODCALLTYPE Z_Adjust(void) = 0; + virtual int Z_Adjust(void) = 0; /* * Z gradient control value. */ - virtual ZGradientType STDMETHODCALLTYPE Z_Gradient(void) = 0; + virtual ZGradientType Z_Gradient(void) = 0; /* * Process movement of object. */ - virtual boolean STDMETHODCALLTYPE Process(void) = 0; + virtual bool Process(void) = 0; /* * Instruct to move to location specified. */ - virtual void STDMETHODCALLTYPE Move_To(Coord to) = 0; + virtual void Move_To(Coord to) = 0; /* * Stop moving at first opportunity. */ - virtual void STDMETHODCALLTYPE Stop_Moving(void) = 0; + virtual void Stop_Moving(void) = 0; /* * Try to face direction specified. */ - virtual void STDMETHODCALLTYPE Do_Turn(DirType coord) = 0; + virtual void Do_Turn(DirType coord) = 0; /* * Object is appearing in the world. */ - virtual void STDMETHODCALLTYPE Unlimbo(void) = 0; + virtual void Unlimbo(void) = 0; /* * Special tilting AI function. */ - virtual void STDMETHODCALLTYPE Tilt_Pitch_AI(void) = 0; + virtual void Tilt_Pitch_AI(void) = 0; /* * Locomotor becomes powered. */ - virtual boolean STDMETHODCALLTYPE Power_On(void) = 0; + virtual bool Power_On(void) = 0; /* * Locomotor loses power. */ - virtual boolean STDMETHODCALLTYPE Power_Off(void) = 0; + virtual bool Power_Off(void) = 0; /* * Is locomotor powered? */ - virtual boolean STDMETHODCALLTYPE Is_Powered(void) = 0; + virtual bool Is_Powered(void) = 0; /* * Is locomotor sensitive to ion storms? */ - virtual boolean STDMETHODCALLTYPE Is_Ion_Sensitive(void) = 0; + virtual bool Is_Ion_Sensitive(void) = 0; /* * Push object in direction specified. */ - virtual boolean STDMETHODCALLTYPE Push(DirType dir) = 0; + virtual bool Push(DirType dir) = 0; /* * Shove object (with spin) in direction specified. */ - virtual boolean STDMETHODCALLTYPE Shove(DirType dir) = 0; + virtual bool Shove(DirType dir) = 0; /* * Force drive track -- special case only. */ - virtual void STDMETHODCALLTYPE Force_Track(int track, Coord coord) = 0; + virtual void Force_Track(int track, Coord coord) = 0; /* * What display layer is it located in. */ - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) = 0; + virtual LayerType In_Which_Layer(void) = 0; /* * Don't use this function. */ - virtual void STDMETHODCALLTYPE Force_Immediate_Destination(Coord coord) = 0; + virtual void Force_Immediate_Destination(Coord coord) = 0; /* * Force a voxel unit to a given slope. Used in cratering. */ - virtual void STDMETHODCALLTYPE Force_New_Slope(int ramp) = 0; + virtual void Force_New_Slope(int ramp) = 0; /* * Is it actually moving across the ground this very second? */ - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) = 0; + virtual bool Is_Moving_Now(void) = 0; /* * Actual current speed of object expressed as leptons per game frame. */ - virtual int STDMETHODCALLTYPE Apparent_Speed(void) = 0; + virtual int Apparent_Speed(void) = 0; /* * Special drawing feedback code (locomotor specific meaning) */ - virtual int STDMETHODCALLTYPE Drawing_Code(void) = 0; + virtual int Drawing_Code(void) = 0; /* * Queries if any locomotor specific state prevents the object from firing. */ - virtual FireErrorType STDMETHODCALLTYPE Can_Fire(void) = 0; + virtual FireErrorType Can_Fire(void) = 0; /* * Queries the general state of the locomotor. */ - virtual int STDMETHODCALLTYPE Get_Status(void) = 0; + virtual int Get_Status(void) = 0; /* * Forces a hunter seeker droid to find a target. */ - virtual void STDMETHODCALLTYPE Acquire_Hunter_Seeker_Target(void) = 0; + virtual void Acquire_Hunter_Seeker_Target(void) = 0; /* * Is this object surfacing? */ - virtual boolean STDMETHODCALLTYPE Is_Surfacing(void) = 0; + virtual bool Is_Surfacing(void) = 0; /* * Lifts all occupation bits associated with the object off the map */ - virtual void STDMETHODCALLTYPE Mark_All_Occupation_Bits(int mark) = 0; + virtual void Mark_All_Occupation_Bits(int mark) = 0; /* * Is this object in the process of moving into this coord. */ - virtual boolean STDMETHODCALLTYPE Is_Moving_Here(Coord to) = 0; + virtual bool Is_Moving_Here(Coord to) = 0; /* * Will this object jump tracks? */ - virtual boolean STDMETHODCALLTYPE Will_Jump_Tracks(void) = 0; + virtual bool Will_Jump_Tracks(void) = 0; /* * Infantry moving query function */ - virtual boolean STDMETHODCALLTYPE Is_Really_Moving_Now(void) = 0; + virtual bool Is_Really_Moving_Now(void) = 0; /* * Falsifies the IsReallyMoving flag in WalkLocomotionClass */ - virtual void STDMETHODCALLTYPE Stop_Movement_Animation(void) = 0; + virtual void Stop_Movement_Animation(void) = 0; /* * Locks the locomotor from being deleted */ - virtual void STDMETHODCALLTYPE Lock(void) = 0; + virtual void Lock(void) = 0; /* * Unlocks the locomotor from being deleted */ - virtual void STDMETHODCALLTYPE Unlock(void) = 0; + virtual void Unlock(void) = 0; /* * Queries internal variables */ - virtual int STDMETHODCALLTYPE Get_Track_Number(void) = 0; + virtual int Get_Track_Number(void) = 0; /* * Queries internal variables */ - virtual int STDMETHODCALLTYPE Get_Track_Index(void) = 0; + virtual int Get_Track_Index(void) = 0; /* * Queries internal variables */ - virtual int STDMETHODCALLTYPE Get_Speed_Accum(void) = 0; + virtual int Get_Speed_Accum(void) = 0; }; -/* - * ILocomtion com smart pointer declaration. - */ -_COM_SMARTPTR_TYPEDEF(ILocomotion, __uuidof(ILocomotion)); diff --git a/code/iloco_i.c b/code/iloco_i.c deleted file mode 100644 index cecc3d209..000000000 --- a/code/iloco_i.c +++ /dev/null @@ -1,52 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -/* this file contains the actual definitions of */ -/* the IIDs and CLSIDs */ - -/* link this file in with the server and any clients */ - - -/* File created by MIDL compiler version X.XX.XXXX */ -/* at XXX XXX XX XX:XX:XX XXXX - */ -/* Compiler settings for XXXX.idl: - Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext - error checks: none -*/ -//@@MIDL_FILE_HEADING( ) -#ifdef __cplusplus -extern "C"{ -#endif - - -#ifndef __IID_DEFINED__ -#define __IID_DEFINED__ - -typedef struct _IID -{ - unsigned long x; - unsigned short s1; - unsigned short s2; - unsigned char c[8]; -} IID; - -#endif // __IID_DEFINED__ - -#ifndef CLSID_DEFINED -#define CLSID_DEFINED -typedef IID CLSID; -#endif // CLSID_DEFINED - -const IID IID_ILocomotion = {0x070F3290,0x9841,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -#ifdef __cplusplus -} -#endif diff --git a/code/ilocos.h b/code/ilocos.h index ee939a7e0..e78b8b45c 100644 --- a/code/ilocos.h +++ b/code/ilocos.h @@ -13,7 +13,6 @@ /// Names and comments from TLBs -EXTERN_C const IID LIBID_LocomotionLibrary; EXTERN_C const CLSID CLSID_DriveLocomotion; EXTERN_C const CLSID CLSID_HoverLocomotion; diff --git a/code/ilocos_i.c b/code/ilocos_i.c index a1fb1faf0..e5e3498f1 100644 --- a/code/ilocos_i.c +++ b/code/ilocos_i.c @@ -7,77 +7,19 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -/* this file contains the actual definitions of */ -/* the IIDs and CLSIDs */ - -/* link this file in with the server and any clients */ - - -/* File created by MIDL compiler version X.XX.XXXX */ -/* at XXX XXX XX XX:XX:XX XXXX - */ -/* Compiler settings for XXXX.idl: - Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext - error checks: none -*/ -//@@MIDL_FILE_HEADING( ) -#ifdef __cplusplus -extern "C"{ -#endif - - -#ifndef __IID_DEFINED__ -#define __IID_DEFINED__ - -typedef struct _IID -{ - unsigned long x; - unsigned short s1; - unsigned short s2; - unsigned char c[8]; -} IID; - -#endif // __IID_DEFINED__ - -#ifndef CLSID_DEFINED -#define CLSID_DEFINED -typedef IID CLSID; -#endif // CLSID_DEFINED - - -const IID LIBID_LocomotionLibrary = {0x4A582740,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +// The class identifiers ilocos.h declares; globals.cpp includes this file once. +extern "C" { const CLSID CLSID_DriveLocomotion = {0x4A582741,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_HoverLocomotion = {0x4A582742,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_TunnelLocomotion = {0x4A582743,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_WalkLocomotion = {0x4A582744,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_BallisticLocomotion = {0x4A582745,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_FlyerLocomotion = {0x4A582746,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_TeleportLocomotion = {0x4A582747,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_MechLocomotion = {0x55D141B8,0xDB94,0x11D1,{0xAC,0x98,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_JumpjetLocomotion = {0x92612C46,0xF71F,0x11D1,{0xAC,0x9F,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_LevitateLocomotion = {0x3DC0B295,0x6546,0x11D3,{0x80,0xB0,0x00,0x90,0x27,0x92,0x49,0x4C}}; - -#ifdef __cplusplus } -#endif diff --git a/code/infantry.cpp b/code/infantry.cpp index 56c02ec49..7772d98c7 100644 --- a/code/infantry.cpp +++ b/code/infantry.cpp @@ -4333,7 +4333,7 @@ int InfantryClass::Do_MISSION_GUARD(void) /// /// Pointer to the buffer to fill in with the class identifier. /// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT STDMETHODCALLTYPE InfantryClass::GetClassID(CLSID * retval) +HRESULT InfantryClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_InfantryClass; diff --git a/code/infantry.h b/code/infantry.h index 2369c581c..3368f1789 100644 --- a/code/infantry.h +++ b/code/infantry.h @@ -126,7 +126,7 @@ class InfantryClass : public FootClass InfantryClass(InfantryTypeClass const * type = NULL, HouseClass * house = NULL); virtual ~InfantryClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/infatype.cpp b/code/infatype.cpp index e4c42f96b..3f05ffffa 100644 --- a/code/infatype.cpp +++ b/code/infatype.cpp @@ -519,7 +519,7 @@ void InfantryTypeClass::Serialize(SaveStreamClass & stream) /// correct class when the game is loaded. /// /// Returns with S_OK, or E_POINTER if no return pointer was supplied. -HRESULT STDMETHODCALLTYPE InfantryTypeClass::GetClassID(CLSID * retval) +HRESULT InfantryTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_InfantryTypeClass; diff --git a/code/infatype.h b/code/infatype.h index 340f22dcc..a4783ab4f 100644 --- a/code/infatype.h +++ b/code/infatype.h @@ -172,7 +172,7 @@ class InfantryTypeClass : public TechnoTypeClass InfantryTypeClass(char const * ininame = NULL); virtual ~InfantryTypeClass() override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/ini.cpp b/code/ini.cpp index 4db0ee6f2..ae518d8ff 100644 --- a/code/ini.cpp +++ b/code/ini.cpp @@ -957,6 +957,65 @@ int INIClass::Get_Int(char const * section, char const * entry, int defvalue) co } +// A class identifier as the registry writes it, braces optional: eight, four, four, four +// and twelve hexadecimal digits separated by hyphens. +static bool Parse_CLSID(char const * text, CLSID & clsid) +{ + char digits[40]; + unsigned int length = 0; + + for (char const * ptr = text; *ptr != '\0'; ptr++) { + if (*ptr == '{' || *ptr == '}') { + continue; + } + if (length >= sizeof(digits) - 1) { + return(false); + } + digits[length++] = *ptr; + } + digits[length] = '\0'; + + unsigned int data1 = 0; + unsigned int data2 = 0; + unsigned int data3 = 0; + unsigned int data4[8] = { 0 }; + int const scanned = sscanf(digits, "%8x-%4x-%4x-%2x%2x-%2x%2x%2x%2x%2x%2x", + &data1, &data2, &data3, + &data4[0], &data4[1], &data4[2], &data4[3], + &data4[4], &data4[5], &data4[6], &data4[7]); + if (scanned != 11 || length != 36) { + return(false); + } + + clsid.Data1 = data1; + clsid.Data2 = (unsigned short)data2; + clsid.Data3 = (unsigned short)data3; + for (int index = 0; index < 8; index++) { + clsid.Data4[index] = (unsigned char)data4[index]; + } + return(true); +} + + +// The buffer holds the 38 characters of the braced form and its terminator. +static void Format_CLSID(CLSID const & clsid, char * text) +{ + sprintf(text, "{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", + (unsigned long)clsid.Data1, (unsigned int)clsid.Data2, (unsigned int)clsid.Data3, + clsid.Data4[0], clsid.Data4[1], clsid.Data4[2], clsid.Data4[3], + clsid.Data4[4], clsid.Data4[5], clsid.Data4[6], clsid.Data4[7]); +} + + +/// +/// Stores a class identifier into the INI database. +/// This routine will convert the identifier into its printable brace and hyphen form before +/// storing it, so that the resulting entry stays readable and can be edited by hand. +/// +/// The identifier for the section that the entry will be placed in. +/// The entry identifier to tag to the class identifier specified. +/// The class identifier to store. +/// bool; Was the class identifier placed into the INI database? /// /// Fetches a class identifier from the specified section. /// This routine will fetch the printable form of a class identifier from the entry and @@ -973,10 +1032,8 @@ CLSID const INIClass::Get_CLSID(char const * section, char const * entry, CLSID char buffer[128]; if (Get_String(section, entry, "", buffer, sizeof(buffer))) { - wchar_t olestr[128]; - MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, buffer, -1, olestr, ARRAY_SIZE(olestr)); CLSID clsid; - if (SUCCEEDED(CLSIDFromString(olestr, &clsid))) { + if (Parse_CLSID(buffer, clsid)) { return(clsid); } } @@ -984,26 +1041,10 @@ CLSID const INIClass::Get_CLSID(char const * section, char const * entry, CLSID } -/// -/// Stores a class identifier into the INI database. -/// This routine will convert the identifier into its printable brace and hyphen form before -/// storing it, so that the resulting entry stays readable and can be edited by hand. -/// -/// The identifier for the section that the entry will be placed in. -/// The entry identifier to tag to the class identifier specified. -/// The class identifier to store. -/// bool; Was the class identifier placed into the INI database? bool INIClass::Put_CLSID(char const * section, char const * entry, CLSID const & value) { - char buffer[128]; - LPOLESTR olestr = NULL; - - StringFromCLSID(value, &olestr); - if (WideCharToMultiByte(CP_ACP, 0, olestr, -1, buffer, sizeof(buffer), NULL, NULL) == 0) { - /// BUG, return not used - GetLastError(); - } - SysFreeString(olestr); + char buffer[40]; + Format_CLSID(value, buffer); return(Put_String(section, entry, buffer)); } diff --git a/code/ini.h b/code/ini.h index a4f33badd..68b5a9c93 100644 --- a/code/ini.h +++ b/code/ini.h @@ -34,7 +34,7 @@ #include "crc.h" #include "index.h" -#include +#include #include #include #include diff --git a/code/init.cpp b/code/init.cpp index fe9a7cb8a..2c66421da 100644 --- a/code/init.cpp +++ b/code/init.cpp @@ -6054,7 +6054,7 @@ void Delete_All_Objects(void) } Process_Deferred_Deletion(); while (Bullets.Count()) { - Bullets[0]->Release(); + delete Bullets[0]; } Process_Deferred_Deletion(); while (Objects.Count()) { diff --git a/code/ion.h b/code/ion.h index 2479b14ea..14ceff579 100644 --- a/code/ion.h +++ b/code/ion.h @@ -13,9 +13,10 @@ #include "theme.hh" -#include class SaveStreamClass; +#include "win.h" + class ShapeSet; class IonStormClass diff --git a/code/ipiggy_i.c b/code/ipiggy_i.c deleted file mode 100644 index 46006be43..000000000 --- a/code/ipiggy_i.c +++ /dev/null @@ -1,52 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -/* this file contains the actual definitions of */ -/* the IIDs and CLSIDs */ - -/* link this file in with the server and any clients */ - - -/* File created by MIDL compiler version X.XX.XXXX */ -/* at XXX XXX XX XX:XX:XX XXXX - */ -/* Compiler settings for XXXX.idl: - Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext - error checks: none -*/ -//@@MIDL_FILE_HEADING( ) -#ifdef __cplusplus -extern "C"{ -#endif - - -#ifndef __IID_DEFINED__ -#define __IID_DEFINED__ - -typedef struct _IID -{ - unsigned long x; - unsigned short s1; - unsigned short s2; - unsigned char c[8]; -} IID; - -#endif // __IID_DEFINED__ - -#ifndef CLSID_DEFINED -#define CLSID_DEFINED -typedef IID CLSID; -#endif // CLSID_DEFINED - -const IID IID_IPiggyback = {0x92FEA800,0xA184,0x11D1,{0xB7,0x0A,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - -#ifdef __cplusplus -} -#endif diff --git a/code/isotile.cpp b/code/isotile.cpp index b48f7c625..e493d4de4 100644 --- a/code/isotile.cpp +++ b/code/isotile.cpp @@ -233,7 +233,7 @@ RTTIType IsometricTileClass::Fetch_RTTI(void) const /// /// Pointer to the buffer that will receive the class identifier. /// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT STDMETHODCALLTYPE IsometricTileClass::GetClassID(CLSID * retval) +HRESULT IsometricTileClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_IsometricTileClass; diff --git a/code/isotile.h b/code/isotile.h index f99cc2683..9f3662e0d 100644 --- a/code/isotile.h +++ b/code/isotile.h @@ -15,7 +15,6 @@ #include "isotype.hh" -#include class IsometricTileTypeClass; @@ -26,7 +25,7 @@ class IsometricTileClass : public ObjectClass IsometricTileClass(IsometricTileType type, Cell const &cell); virtual ~IsometricTileClass() override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/isotype.cpp b/code/isotype.cpp index e7cf9c706..cd249c9fb 100644 --- a/code/isotype.cpp +++ b/code/isotype.cpp @@ -2823,7 +2823,7 @@ void IsometricTileTypeClass::Serialize(SaveStreamClass & stream) /// /// Receives the class identifier. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE IsometricTileTypeClass::GetClassID(CLSID * retval) +HRESULT IsometricTileTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_IsometricTileTypeClass; diff --git a/code/isotype.h b/code/isotype.h index 1e35b77f6..942741615 100644 --- a/code/isotype.h +++ b/code/isotype.h @@ -194,7 +194,7 @@ class IsometricTileTypeClass : public ObjectTypeClass IsometricTileTypeClass(IsometricTileType type = ISOTILE_CLEAR, int unknown1 = 0, unsigned char unknown2 = 0, char const *ininame = NULL, bool skip_registration = false); virtual ~IsometricTileTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/isun.h b/code/isun.h index ea17f62b3..065e9d090 100644 --- a/code/isun.h +++ b/code/isun.h @@ -9,9 +9,8 @@ #pragma once -#include +#include -/// Names and comments from TLBs #define GAME_VERNAME TEXT("Tiberian Sun") diff --git a/code/isun_i.c b/code/isun_i.c index 2f35f17e8..9b7a5c566 100644 --- a/code/isun_i.c +++ b/code/isun_i.c @@ -7,226 +7,70 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -/* this file contains the actual definitions of */ -/* the IIDs and CLSIDs */ +// The class identifiers isun.h declares; globals.cpp includes this file once. -/* link this file in with the server and any clients */ - - -/* File created by MIDL compiler version X.XX.XXXX */ -/* at XXX XXX XX XX:XX:XX XXXX - */ -/* Compiler settings for XXXX.idl: - Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext - error checks: none -*/ -//@@MIDL_FILE_HEADING( ) -#ifdef __cplusplus -extern "C"{ -#endif - - -#ifndef __IID_DEFINED__ -#define __IID_DEFINED__ - -typedef struct _IID -{ - unsigned long x; - unsigned short s1; - unsigned short s2; - unsigned char c[8]; -} IID; - -#endif // __IID_DEFINED__ - -#ifndef CLSID_DEFINED -#define CLSID_DEFINED -typedef IID CLSID; -#endif // CLSID_DEFINED +extern "C" { const CLSID CLSID_HouseClass = {0xD9D4A910,0x87C6,0x11D1,{0xB7,0x07,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_SuperWeaponTypeClass = {0x0CF2BCE7,0x36E4,0x11D2,{0xB8,0xD8,0x00,0x60,0x08,0xC8,0x09,0xED}}; - - const CLSID CLSID_SuperWeaponClass = {0xD7F754C6,0x391C,0x11D2,{0x9B,0x64,0x00,0x10,0x4B,0x97,0x2F,0xE8}}; - - const CLSID CLSID_UnitTypeClass = {0xDCBD42EA,0x0546,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_InfantryTypeClass = {0xAE8B33D8,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_AircraftTypeClass = {0xAE8B33D9,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_BuildingTypeClass = {0xAE8B33DB,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_BulletTypeClass = {0x5AF2CE77,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_TerrainTypeClass = {0x5AF2CE7B,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_IsometricTileTypeClass = {0x5AF2CE7A,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_OverlayTypeClass = {0x5AF2CE79,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_SmudgeTypeClass = {0x5AF2CE78,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_AnimTypeClass = {0xAE8B33DA,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_HouseTypeClass = {0x1DD43928,0x046B,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_IsometricTileClass = {0x0E272DC0,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_VoxelAnimClass = {0x0E272DC1,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_AircraftClass = {0x0E272DC2,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_AnimClass = {0x0E272DC3,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_InfantryClass = {0x0E272DC4,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_SmudgeClass = {0x0E272DC5,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_BuildingClass = {0x0E272DC6,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_OverlayClass = {0x0E272DC7,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_ParticleSystemClass = {0x0E272DC8,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_ParticleSystemTypeClass = {0x703E044A,0x0FB1,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_BulletClass = {0x0E272DC9,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_UnitClass = {0x0E272DCA,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_ParticleClass = {0x0E272DCC,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_ParticleTypeClass = {0x703E044B,0x0FB1,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_WaveClass = {0x0E272DCD,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_BuildingLightClass = {0x54822258,0xD8A8,0x11D1,{0xB4,0x62,0x00,0x60,0x97,0xC6,0xA9,0x79}}; - - const CLSID CLSID_TerrainClass = {0x0E272DCE,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_TubeClass = {0x0B4CA41C,0xB3A7,0x11D1,{0xB4,0x57,0x00,0x60,0x97,0xC6,0xA9,0x79}}; - - const CLSID CLSID_TeamClass = {0x0E272DCF,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; - - const CLSID CLSID_TaskForceClass = {0x61DE341E,0x0774,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_TeamTypeClass = {0xD1DBA64E,0x0778,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_VoxelAnimTypeClass = {0x2EBB6D66,0x0D4D,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_ScriptClass = {0x42F3A646,0x0789,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_ScriptTypeClass = {0x42F3A647,0x0789,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_TagClass = {0x54F6E432,0x09ED,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_TagTypeClass = {0x54F6E433,0x09ED,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_TriggerClass = {0xC02D1590,0x0A2A,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_TriggerTypeClass = {0xC02D1591,0x0A2A,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_ActionClass = {0x4F0EC392,0x0A55,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_EventClass = {0x4F0EC393,0x0A55,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_FactoryClass = {0x34ECD9A8,0x0AB0,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_WeaponTypeClass = {0x9FD219CA,0x0F7B,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_WarheadTypeClass = {0xA8C54DA4,0x0F7B,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_WaypointPath = {0xF73125BA,0x1054,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_LightSource = {0x6F9C48F0,0x1207,0x11D2,{0x81,0x74,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_CampaignClass = {0xFFDAC848,0x1517,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_SideClass = {0xC53DD372,0x151E,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_TiberiumClass = {0xC53DD373,0x151E,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_CellClass = {0xC1BF99CE,0x1A8C,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_EMPulseClass = {0xB825CB22,0x200E,0x11D2,{0x9F,0xA9,0x00,0x60,0x08,0x9A,0xD4,0x58}}; - - const CLSID CLSID_TacticalMapClass = {0xCF56B38A,0x240D,0x11D2,{0x81,0x7C,0x00,0x60,0x08,0x05,0x5B,0xB5}}; - - const CLSID CLSID_AITriggerTypeClass = {0xBA093524,0x4CF4,0x11D2,{0xBC,0x26,0x00,0x10,0x4B,0x8F,0xB0,0x4D}}; - - const CLSID CLSID_AITriggerClass = {0x03C4CE76,0x4CF5,0x11D2,{0xBC,0x26,0x00,0x10,0x4B,0x8F,0xB0,0x4D}}; - - const CLSID CLSID_NeuronClass = {0x241AB316,0x4CF5,0x11D2,{0xBC,0x26,0x00,0x10,0x4B,0x8F,0xB0,0x4D}}; - - const CLSID CLSID_FoggedObjectClass = {0x1C470B0E,0x69D7,0x11D2,{0xB8,0xF2,0x00,0x60,0x08,0xC8,0x09,0xED}}; - - const CLSID CLSID_AlphaShapeClass = {0x623C7584,0x74E7,0x11D2,{0xB8,0xF5,0x00,0x60,0x08,0xC8,0x09,0xED}}; - - const CLSID CLSID_VeinholeMonsterClass = {0x5192D06A,0xC632,0x11D2,{0xB9,0x0B,0x00,0x60,0x08,0xC8,0x09,0xED}}; - -#ifdef __cplusplus } -#endif diff --git a/code/jumpjet.cpp b/code/jumpjet.cpp index 717456b26..5e4ab7369 100644 --- a/code/jumpjet.cpp +++ b/code/jumpjet.cpp @@ -64,7 +64,7 @@ JumpjetLocomotionClass::~JumpjetLocomotionClass(void) /// This asks whether the unit has a destination, not whether it happens to be in the air. /// /// bool; Does the jumpjet have somewhere to be? -boolean STDMETHODCALLTYPE JumpjetLocomotionClass::Is_Moving(void) +bool JumpjetLocomotionClass::Is_Moving(void) { return(IsMoving); } @@ -75,7 +75,7 @@ boolean STDMETHODCALLTYPE JumpjetLocomotionClass::Is_Moving(void) /// /// Returns with the destination coordinate. Otherwise, COORD_NONE is /// returned. -Coord STDMETHODCALLTYPE JumpjetLocomotionClass::Destination(void) +Coord JumpjetLocomotionClass::Destination(void) { if (Is_Moving()) { return(HeadToCoord); @@ -92,7 +92,7 @@ Coord STDMETHODCALLTYPE JumpjetLocomotionClass::Destination(void) /// and resubmits the object to the map when its display layer changes. An ion storm will /// bring down anything caught off the ground. /// -boolean STDMETHODCALLTYPE JumpjetLocomotionClass::Process(void) +bool JumpjetLocomotionClass::Process(void) { LayerType layer = In_Which_Layer(); @@ -167,7 +167,7 @@ boolean STDMETHODCALLTYPE JumpjetLocomotionClass::Process(void) /// /// The coordinate to fly to, or COORD_NONE to give the unit no /// destination at all. -void STDMETHODCALLTYPE JumpjetLocomotionClass::Move_To(Coord to) +void JumpjetLocomotionClass::Move_To(Coord to) { if (HeadToCoord != COORD_NONE && CurrentState != GROUNDED && IsLanding) { LinkedTo->Clear_Occupy_Bit(HeadToCoord); @@ -200,7 +200,7 @@ void STDMETHODCALLTYPE JumpjetLocomotionClass::Move_To(Coord to) /// it could put down in. A unit with nowhere at all to land is destroyed rather than left /// hanging in the air. /// -void STDMETHODCALLTYPE JumpjetLocomotionClass::Stop_Moving(void) +void JumpjetLocomotionClass::Stop_Moving(void) { if (IsMoving) { if (HeadToCoord != COORD_NONE && CurrentState != GROUNDED && IsLanding) { @@ -230,7 +230,7 @@ void STDMETHODCALLTYPE JumpjetLocomotionClass::Stop_Moving(void) /// through the locomotor's own facing tracker. /// /// The direction the unit should be facing. -void STDMETHODCALLTYPE JumpjetLocomotionClass::Do_Turn(DirType coord) +void JumpjetLocomotionClass::Do_Turn(DirType coord) { LinkedTo->PrimaryFacing.Set(coord); } @@ -242,7 +242,7 @@ void STDMETHODCALLTYPE JumpjetLocomotionClass::Do_Turn(DirType coord) /// when a save game is loaded. /// /// Returns with S_OK, or E_POINTER if there is nowhere to put the answer. -HRESULT STDMETHODCALLTYPE JumpjetLocomotionClass::GetClassID(CLSID * retval) +HRESULT JumpjetLocomotionClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_JumpjetLocomotion; @@ -277,7 +277,7 @@ void JumpjetLocomotionClass::Serialize(SaveStreamClass & stream) /// measured from the bridge deck rather than from the ground. /// /// Returns with the layer this object should be drawn in. -LayerType STDMETHODCALLTYPE JumpjetLocomotionClass::In_Which_Layer(void) +LayerType JumpjetLocomotionClass::In_Which_Layer(void) { int height = LinkedTo->HeightAGL; if (!LinkedTo->IsOnBridge) { @@ -479,7 +479,7 @@ void JumpjetLocomotionClass::Process_Unknown(void) /// not it has been given a destination. /// /// bool; Is the jumpjet in flight toward somewhere? -boolean STDMETHODCALLTYPE JumpjetLocomotionClass::Is_Moving_Now(void) +bool JumpjetLocomotionClass::Is_Moving_Now(void) { if (CurrentState != GROUNDED && CurrentState != HOVERING) { return(true); @@ -684,7 +684,7 @@ int JumpjetLocomotionClass::Desired_Flight_Level(void) const /// that reservation is given up when the object is lifted off the map. /// /// The marking operation being performed, such as MARK_UP. -void STDMETHODCALLTYPE JumpjetLocomotionClass::Mark_All_Occupation_Bits(int mark) +void JumpjetLocomotionClass::Mark_All_Occupation_Bits(int mark) { if (mark == MARK_UP) { Coord headto = Head_To_Coord(); @@ -702,7 +702,7 @@ void STDMETHODCALLTYPE JumpjetLocomotionClass::Mark_All_Occupation_Bits(int mark /// destination. /// /// Returns with the coordinate being flown to. -Coord STDMETHODCALLTYPE JumpjetLocomotionClass::Head_To_Coord(void) +Coord JumpjetLocomotionClass::Head_To_Coord(void) { if (CurrentState == GROUNDED) { return(LinkedTo->PositionCoord); diff --git a/code/jumpjet.h b/code/jumpjet.h index e8bc9d07e..fffdefd2b 100644 --- a/code/jumpjet.h +++ b/code/jumpjet.h @@ -25,20 +25,20 @@ class JumpjetLocomotionClass : public LocomotionClass JumpjetLocomotionClass(void); virtual ~JumpjetLocomotionClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual Coord STDMETHODCALLTYPE Head_To_Coord(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual void STDMETHODCALLTYPE Do_Turn(DirType coord) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) override; - virtual void STDMETHODCALLTYPE Mark_All_Occupation_Bits(int mark) override; + virtual bool Is_Moving(void) override; + virtual Coord Destination(void) override; + virtual Coord Head_To_Coord(void) override; + virtual bool Process(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual void Do_Turn(DirType coord) override; + virtual LayerType In_Which_Layer(void) override; + virtual bool Is_Moving_Now(void) override; + virtual void Mark_All_Occupation_Bits(int mark) override; /*--------------------------------------------------------------------- ** Member function prototypes. diff --git a/code/levitate.cpp b/code/levitate.cpp index 28836e322..bb53a64a1 100644 --- a/code/levitate.cpp +++ b/code/levitate.cpp @@ -825,7 +825,7 @@ bool LevitateLocomotionClass::Needs_New_Target(void) /// the vertical hover (Hover_AI). /// /// True while the unit is still moving. -boolean LevitateLocomotionClass::Process(void) +bool LevitateLocomotionClass::Process(void) { State_AI(); @@ -846,7 +846,7 @@ boolean LevitateLocomotionClass::Process(void) /// Reports whether the locomotor is in any state other than STATE_IDLE. /// /// True while moving. -boolean LevitateLocomotionClass::Is_Moving(void) +bool LevitateLocomotionClass::Is_Moving(void) { return(State != STATE_IDLE); } @@ -856,7 +856,7 @@ boolean LevitateLocomotionClass::Is_Moving(void) /// Reports whether the locomotor is in any state other than STATE_IDLE (identical to Is_Moving). /// /// True while moving. -boolean LevitateLocomotionClass::Is_Moving_Now(void) +bool LevitateLocomotionClass::Is_Moving_Now(void) { return(State != STATE_IDLE); } diff --git a/code/levitate.h b/code/levitate.h index bd273ba6b..bd5bf3ce2 100644 --- a/code/levitate.h +++ b/code/levitate.h @@ -28,18 +28,18 @@ class LevitateLocomotionClass : public LocomotionClass LevitateLocomotionClass(void); virtual ~LevitateLocomotionClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; - virtual HRESULT STDMETHODCALLTYPE Link_To_Object(void *pointer) override; - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual Coord STDMETHODCALLTYPE Head_To_Coord(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) override; - virtual void STDMETHODCALLTYPE Mark_All_Occupation_Bits(int mark) override; + virtual HRESULT Link_To_Object(void *pointer) override; + virtual bool Is_Moving(void) override; + virtual Coord Destination(void) override; + virtual Coord Head_To_Coord(void) override; + virtual bool Process(void) override; + virtual LayerType In_Which_Layer(void) override; + virtual bool Is_Moving_Now(void) override; + virtual void Mark_All_Occupation_Bits(int mark) override; /*--------------------------------------------------------------------- diff --git a/code/light.cpp b/code/light.cpp index 8ff5eacec..0175aa94e 100644 --- a/code/light.cpp +++ b/code/light.cpp @@ -283,7 +283,7 @@ void LightSourceClass::Compute_CRC(CRCEngine & crc) const /// /// Destination for the class identifier. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE LightSourceClass::GetClassID(CLSID * retval) +HRESULT LightSourceClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_LightSource; diff --git a/code/light.h b/code/light.h index 0e875e507..68bb1fec5 100644 --- a/code/light.h +++ b/code/light.h @@ -25,7 +25,7 @@ class LightSourceClass : public AbstractClass LightSourceClass(void); virtual ~LightSourceClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/loco.cpp b/code/loco.cpp index 96521e16d..7f848c37e 100644 --- a/code/loco.cpp +++ b/code/loco.cpp @@ -33,7 +33,6 @@ #include #include -extern ULONG COMRefCount; /// @@ -45,8 +44,7 @@ extern ULONG COMRefCount; LocomotionClass::LocomotionClass(void) : LinkedTo(NULL), IsPowered(true), - Dirty(true), - RefCount(0) + Dirty(true) { } @@ -67,7 +65,7 @@ LocomotionClass::~LocomotionClass(void) /// /// Pointer to the foot class object this locomotor will carry about. /// Returns with S_OK, since the attachment cannot fail. -HRESULT STDMETHODCALLTYPE LocomotionClass::Link_To_Object(void *pointer) +HRESULT LocomotionClass::Link_To_Object(void *pointer) { LinkedTo = (FootClass *)pointer; return(S_OK); @@ -83,7 +81,7 @@ HRESULT STDMETHODCALLTYPE LocomotionClass::Link_To_Object(void *pointer) /// Optional cache key for the voxel renderer, which the facing is folded /// into. May be NULL, and a key of -1 means the drawing is not to be cached. /// Returns with the matrix to transform the object by. -Matrix3D STDMETHODCALLTYPE LocomotionClass::Draw_Matrix(int *key) +Matrix3D LocomotionClass::Draw_Matrix(int *key) { Matrix3D draw_matrix(true); @@ -105,7 +103,7 @@ Matrix3D STDMETHODCALLTYPE LocomotionClass::Draw_Matrix(int *key) /// Optional cache key for the voxel renderer, which the slope and facing are /// folded into. May be NULL, and a key of -1 means the shadow is not to be cached. /// Returns with the matrix to transform the shadow by. -Matrix3D STDMETHODCALLTYPE LocomotionClass::Shadow_Matrix(int *key) +Matrix3D LocomotionClass::Shadow_Matrix(int *key) { int ramp = Map[LinkedTo->Get_Coord()].Ramp; @@ -126,7 +124,7 @@ Matrix3D STDMETHODCALLTYPE LocomotionClass::Shadow_Matrix(int *key) /// down by however far the object is flying above the terrain. /// /// Returns with the pixel offset to shift the shadow by when drawing. -Point2D STDMETHODCALLTYPE LocomotionClass::Shadow_Point(void) +Point2D LocomotionClass::Shadow_Point(void) { Point2D pt; @@ -143,7 +141,7 @@ Point2D STDMETHODCALLTYPE LocomotionClass::Shadow_Point(void) /// more. /// /// bool; Is the locomotor powered after the change? -boolean STDMETHODCALLTYPE LocomotionClass::Power_On(void) +bool LocomotionClass::Power_On(void) { IsPowered = true; return(Is_Powered()); @@ -156,7 +154,7 @@ boolean STDMETHODCALLTYPE LocomotionClass::Power_On(void) /// by an EMP pulse or its owner loses base power. /// /// bool; Is the locomotor powered after the change? -boolean STDMETHODCALLTYPE LocomotionClass::Power_Off(void) +bool LocomotionClass::Power_Off(void) { IsPowered = false; return(Is_Powered()); @@ -169,7 +167,7 @@ boolean STDMETHODCALLTYPE LocomotionClass::Power_Off(void) /// loss of base power leaves a unit stranded. /// /// bool; Is the locomotor powered? -boolean STDMETHODCALLTYPE LocomotionClass::Is_Powered(void) +bool LocomotionClass::Is_Powered(void) { return(IsPowered); } @@ -181,76 +179,12 @@ boolean STDMETHODCALLTYPE LocomotionClass::Is_Powered(void) /// this routine so that a storm can bring their objects down. /// /// bool; Is the locomotor sensitive to ion storms? -boolean STDMETHODCALLTYPE LocomotionClass::Is_Ion_Sensitive(void) +bool LocomotionClass::Is_Ion_Sensitive(void) { return(false); } -/// -/// Adds a reference to this locomotor. -/// Anything that holds on to a locomotor takes a reference first, which keeps the -/// locomotor alive until that holder releases it again. -/// -/// Returns with the number of references now outstanding. -ULONG STDMETHODCALLTYPE LocomotionClass::AddRef(void) -{ - ++COMRefCount; - return(InterlockedIncrement(&RefCount)); -} - - -/// -/// Releases a reference to this locomotor. -/// When the last reference goes away the locomotor destroys itself, so the caller must -/// not touch its pointer afterward. -/// -/// Returns with the number of references still outstanding. -ULONG STDMETHODCALLTYPE LocomotionClass::Release(void) -{ - --COMRefCount; - - ULONG count = InterlockedDecrement(&RefCount); - if (count == 0) { - delete this; - } - return(count); -} - - -/// -/// Fetches one of the interfaces this locomotor implements. -/// A locomotor answers to IUnknown and ILocomotion. Any other interface asked for is -/// refused. -/// -/// The identifier of the interface being asked for. -/// Pointer to the location to store the interface pointer in. -/// Returns with S_OK, or E_NOINTERFACE if the interface is not supported. -/// An interface fetched successfully carries a reference. The caller must release -/// it when finished with it. -LONG STDMETHODCALLTYPE LocomotionClass::QueryInterface(REFIID riid, LPVOID *ppvObject) -{ - if (ppvObject == NULL) { - return(E_POINTER); - } - - *ppvObject = NULL; - - if (riid == IID_IUnknown) { - *ppvObject = (IUnknown *)(ILocomotion *)this; - } - if (riid == IID_ILocomotion) { - *ppvObject = (ILocomotion *)this; - } - if (*ppvObject == NULL) { - return(E_NOINTERFACE); - } - - AddRef(); - return(S_OK); -} - - std::unique_ptr Create_Locomotor(CLSID const & classid) { IPersistent * const object = Create_Object(classid); @@ -345,7 +279,6 @@ void LocomotionClass::Serialize(SaveStreamClass & stream) stream.Serialize(IsPowered); stream.Serialize(Dirty); - // RefCount -- belongs to the running session rather than the record. } @@ -364,7 +297,7 @@ void LocomotionClass::Post_Load(void) /// occupying. The base locomotor cannot be moved and declines. /// /// bool; Did the object step out of the way? -boolean STDMETHODCALLTYPE LocomotionClass::Push(DirType dir) +bool LocomotionClass::Push(DirType dir) { return(false); } @@ -376,7 +309,7 @@ boolean STDMETHODCALLTYPE LocomotionClass::Push(DirType dir) /// displaced to clear the way. The base locomotor will not budge. /// /// bool; Was the object shoved out of the way? -boolean STDMETHODCALLTYPE LocomotionClass::Shove(DirType dir) +bool LocomotionClass::Shove(DirType dir) { return(false); } @@ -387,7 +320,7 @@ boolean STDMETHODCALLTYPE LocomotionClass::Shove(DirType dir) /// Locomotors that rock their object about -- over bumps, on landing, or when it takes a /// hit -- use this routine to ease the body back toward level. /// -void STDMETHODCALLTYPE LocomotionClass::Tilt_Pitch_AI(void) +void LocomotionClass::Tilt_Pitch_AI(void) { } @@ -398,7 +331,7 @@ void STDMETHODCALLTYPE LocomotionClass::Tilt_Pitch_AI(void) /// against the terrain it is traveling over. The base locomotor needs no such favor. /// /// Returns with the depth adjustment to apply when drawing the object. -int STDMETHODCALLTYPE LocomotionClass::Z_Adjust(void) +int LocomotionClass::Z_Adjust(void) { return(0); } @@ -410,7 +343,7 @@ int STDMETHODCALLTYPE LocomotionClass::Z_Adjust(void) /// shape. The base locomotor reports the upright case. /// /// Returns with the Z gradient to render the object with. -ZGradientType STDMETHODCALLTYPE LocomotionClass::Z_Gradient(void) +ZGradientType LocomotionClass::Z_Gradient(void) { return(ZGRAD_90DEG); } @@ -422,7 +355,7 @@ ZGradientType STDMETHODCALLTYPE LocomotionClass::Z_Gradient(void) /// otherwise leaves plain sight. The base locomotor never alters the appearance. /// /// Returns with the visual character to render the object with. -VisualType STDMETHODCALLTYPE LocomotionClass::Visual_Character(boolean flag) +VisualType LocomotionClass::Visual_Character(bool flag) { return(VISUAL_NORMAL); } @@ -434,7 +367,7 @@ VisualType STDMETHODCALLTYPE LocomotionClass::Visual_Character(boolean flag) /// locomotor makes its object bob, hop, or sink. The base locomotor draws in place. /// /// Returns with the pixel offset to shift the object by when drawing. -Point2D STDMETHODCALLTYPE LocomotionClass::Draw_Point(void) +Point2D LocomotionClass::Draw_Point(void) { Point2D pt; pt.X = 0; @@ -449,7 +382,7 @@ Point2D STDMETHODCALLTYPE LocomotionClass::Draw_Point(void) /// otherwise hidden -- will override this routine to suppress the shadow. /// /// bool; Should a shadow be drawn for the object? -boolean STDMETHODCALLTYPE LocomotionClass::Is_To_Have_Shadow(void) +bool LocomotionClass::Is_To_Have_Shadow(void) { return(true); } @@ -462,7 +395,7 @@ boolean STDMETHODCALLTYPE LocomotionClass::Is_To_Have_Shadow(void) /// unrestricted and welcomes every cell. /// /// Returns with the move legality of the cell. -MoveType STDMETHODCALLTYPE LocomotionClass::Can_Enter_Cell(Cell cell) +MoveType LocomotionClass::Can_Enter_Cell(Cell cell) { return(MOVE_OK); } @@ -474,7 +407,7 @@ MoveType STDMETHODCALLTYPE LocomotionClass::Can_Enter_Cell(Cell cell) /// outside code must dictate exactly where the object ends up next. /// /// The coordinate the object should head to immediately. -void STDMETHODCALLTYPE LocomotionClass::Force_Immediate_Destination(Coord coord) +void LocomotionClass::Force_Immediate_Destination(Coord coord) { } @@ -486,7 +419,7 @@ void STDMETHODCALLTYPE LocomotionClass::Force_Immediate_Destination(Coord coord) /// /// The track number the object should be placed onto. /// The coordinate to treat as the start of the track. -void STDMETHODCALLTYPE LocomotionClass::Force_Track(int track, Coord coord) +void LocomotionClass::Force_Track(int track, Coord coord) { } @@ -496,7 +429,7 @@ void STDMETHODCALLTYPE LocomotionClass::Force_Track(int track, Coord coord) /// This gives derived locomotors their chance to pick up a starting facing, slope, or /// altitude from the ground the object has just arrived on. /// -void STDMETHODCALLTYPE LocomotionClass::Unlimbo(void) +void LocomotionClass::Unlimbo(void) { } @@ -506,7 +439,7 @@ void STDMETHODCALLTYPE LocomotionClass::Unlimbo(void) /// The base locomotor has no body of its own to rotate, so the request goes unheeded. /// /// The direction that the object should come to face. -void STDMETHODCALLTYPE LocomotionClass::Do_Turn(DirType coord) +void LocomotionClass::Do_Turn(DirType coord) { } @@ -516,7 +449,7 @@ void STDMETHODCALLTYPE LocomotionClass::Do_Turn(DirType coord) /// This routine is called when the object must give up on wherever it was going. Derived /// locomotors use it to abandon their journey and bring the object to a legal rest. /// -void STDMETHODCALLTYPE LocomotionClass::Stop_Moving(void) +void LocomotionClass::Stop_Moving(void) { } @@ -526,7 +459,7 @@ void STDMETHODCALLTYPE LocomotionClass::Stop_Moving(void) /// This is how the object hands its locomotor a new place to go. The base locomotor /// cannot move anything, so the request is quietly ignored. /// -void STDMETHODCALLTYPE LocomotionClass::Move_To(Coord to) +void LocomotionClass::Move_To(Coord to) { } @@ -538,7 +471,7 @@ void STDMETHODCALLTYPE LocomotionClass::Move_To(Coord to) /// is already at rest. /// /// bool; Is the locomotor at rest, with nothing further to do? -boolean STDMETHODCALLTYPE LocomotionClass::Process(void) +bool LocomotionClass::Process(void) { return(true); } @@ -550,7 +483,7 @@ boolean STDMETHODCALLTYPE LocomotionClass::Process(void) /// destination at all. /// /// Returns with the destination coordinate, or COORD_NONE if there is none. -Coord STDMETHODCALLTYPE LocomotionClass::Destination(void) +Coord LocomotionClass::Destination(void) { Coord coord; coord.X = COORD_NONE.X; @@ -566,7 +499,7 @@ Coord STDMETHODCALLTYPE LocomotionClass::Destination(void) /// has nowhere to go, it reports the object's own position. /// /// Returns with the coordinate currently being moved toward. -Coord STDMETHODCALLTYPE LocomotionClass::Head_To_Coord(void) +Coord LocomotionClass::Head_To_Coord(void) { return(LinkedTo->PositionCoord); } @@ -578,7 +511,7 @@ Coord STDMETHODCALLTYPE LocomotionClass::Head_To_Coord(void) /// The base locomotor never carries its object anywhere, so it always answers no. /// /// bool; Is the object moving? -boolean STDMETHODCALLTYPE LocomotionClass::Is_Moving(void) +bool LocomotionClass::Is_Moving(void) { return(false); } @@ -590,7 +523,7 @@ boolean STDMETHODCALLTYPE LocomotionClass::Is_Moving(void) /// Only locomotors that tilt their object with the ground need to act on it. /// /// The ramp type of the slope the object should now conform to. -void STDMETHODCALLTYPE LocomotionClass::Force_New_Slope(int ramp) +void LocomotionClass::Force_New_Slope(int ramp) { } @@ -601,7 +534,7 @@ void STDMETHODCALLTYPE LocomotionClass::Force_New_Slope(int ramp) /// currently traveling. The base locomotor has no preference. /// /// Returns with the drawing code, or zero for the ordinary presentation. -int STDMETHODCALLTYPE LocomotionClass::Drawing_Code(void) +int LocomotionClass::Drawing_Code(void) { return(0); } @@ -613,7 +546,7 @@ int STDMETHODCALLTYPE LocomotionClass::Drawing_Code(void) /// will override this routine. The base locomotor never stands in the way. /// /// Returns with the reason firing is disallowed, or FIRE_OK if it is permitted. -FireErrorType STDMETHODCALLTYPE LocomotionClass::Can_Fire(void) +FireErrorType LocomotionClass::Can_Fire(void) { return(FIRE_OK); } @@ -625,14 +558,8 @@ FireErrorType STDMETHODCALLTYPE LocomotionClass::Can_Fire(void) /// visible motion differs from the object's logical speed will override this routine. /// /// Returns with the apparent speed of the linked object. -int STDMETHODCALLTYPE LocomotionClass::Apparent_Speed(void) +int LocomotionClass::Apparent_Speed(void) { return(LinkedTo->Current_Speed()); } - -/// Unlike the other interface identifiers, this one is defined in the locomotion module. -#define INITGUID -#undef DEFINE_GUID -#include -#include "iloco_i.c" diff --git a/code/loco.h b/code/loco.h index a0eab6096..161ed4434 100644 --- a/code/loco.h +++ b/code/loco.h @@ -35,58 +35,54 @@ class LocomotionClass : public IPersistent, public ILocomotion LocomotionClass(void); virtual ~LocomotionClass(void); - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppvObj) override; - virtual ULONG STDMETHODCALLTYPE AddRef() override; - virtual ULONG STDMETHODCALLTYPE Release() override; - virtual HRESULT Load(SaveStreamClass & stream) override; virtual HRESULT Save(SaveStreamClass & stream, BOOL cleardirty) override; - virtual HRESULT STDMETHODCALLTYPE Link_To_Object(void *object) override; - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual Coord STDMETHODCALLTYPE Head_To_Coord(void) override; - virtual MoveType STDMETHODCALLTYPE Can_Enter_Cell(Cell cell) override; - virtual boolean STDMETHODCALLTYPE Is_To_Have_Shadow(void) override; - virtual Matrix3D STDMETHODCALLTYPE Draw_Matrix(int *key) override; - virtual Matrix3D STDMETHODCALLTYPE Shadow_Matrix(int *key) override; - virtual Point2D STDMETHODCALLTYPE Draw_Point(void) override; - virtual Point2D STDMETHODCALLTYPE Shadow_Point(void) override; - virtual VisualType STDMETHODCALLTYPE Visual_Character(boolean flag) override; - virtual int STDMETHODCALLTYPE Z_Adjust(void) override; - virtual ZGradientType STDMETHODCALLTYPE Z_Gradient(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual void STDMETHODCALLTYPE Do_Turn(DirType coord) override; - virtual void STDMETHODCALLTYPE Unlimbo(void) override; - virtual void STDMETHODCALLTYPE Tilt_Pitch_AI(void) override; - virtual boolean STDMETHODCALLTYPE Power_On(void) override; - virtual boolean STDMETHODCALLTYPE Power_Off(void) override; - virtual boolean STDMETHODCALLTYPE Is_Powered(void) override; - virtual boolean STDMETHODCALLTYPE Is_Ion_Sensitive(void) override; - virtual boolean STDMETHODCALLTYPE Push(DirType dir) override; - virtual boolean STDMETHODCALLTYPE Shove(DirType dir) override; - virtual void STDMETHODCALLTYPE Force_Track(int track, Coord coord) override; - virtual void STDMETHODCALLTYPE Force_Immediate_Destination(Coord coord) override; - virtual void STDMETHODCALLTYPE Force_New_Slope(int ramp) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) override {return(Is_Moving());} - virtual int STDMETHODCALLTYPE Apparent_Speed(void) override; - virtual int STDMETHODCALLTYPE Drawing_Code(void) override; - virtual FireErrorType STDMETHODCALLTYPE Can_Fire(void) override; - virtual int STDMETHODCALLTYPE Get_Status() override {return(0);} - virtual void STDMETHODCALLTYPE Acquire_Hunter_Seeker_Target(void) override {} - virtual boolean STDMETHODCALLTYPE Is_Surfacing() override {return(false);} - virtual void STDMETHODCALLTYPE Mark_All_Occupation_Bits(int mark) override {} - virtual boolean STDMETHODCALLTYPE Is_Moving_Here(Coord to) override {return(false);} - virtual boolean STDMETHODCALLTYPE Will_Jump_Tracks(void) override {return(false);} - virtual boolean STDMETHODCALLTYPE Is_Really_Moving_Now(void) override {return(Is_Moving_Now());} - virtual void STDMETHODCALLTYPE Stop_Movement_Animation(void) override {} - virtual void STDMETHODCALLTYPE Lock(void) override {} - virtual void STDMETHODCALLTYPE Unlock(void) override {} - virtual int STDMETHODCALLTYPE Get_Track_Number(void) override {return(-1);} - virtual int STDMETHODCALLTYPE Get_Track_Index(void) override {return(-1);} - virtual int STDMETHODCALLTYPE Get_Speed_Accum(void) override {return(-1);} + virtual HRESULT Link_To_Object(void *object) override; + virtual bool Is_Moving(void) override; + virtual Coord Destination(void) override; + virtual Coord Head_To_Coord(void) override; + virtual MoveType Can_Enter_Cell(Cell cell) override; + virtual bool Is_To_Have_Shadow(void) override; + virtual Matrix3D Draw_Matrix(int *key) override; + virtual Matrix3D Shadow_Matrix(int *key) override; + virtual Point2D Draw_Point(void) override; + virtual Point2D Shadow_Point(void) override; + virtual VisualType Visual_Character(bool flag) override; + virtual int Z_Adjust(void) override; + virtual ZGradientType Z_Gradient(void) override; + virtual bool Process(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual void Do_Turn(DirType coord) override; + virtual void Unlimbo(void) override; + virtual void Tilt_Pitch_AI(void) override; + virtual bool Power_On(void) override; + virtual bool Power_Off(void) override; + virtual bool Is_Powered(void) override; + virtual bool Is_Ion_Sensitive(void) override; + virtual bool Push(DirType dir) override; + virtual bool Shove(DirType dir) override; + virtual void Force_Track(int track, Coord coord) override; + virtual void Force_Immediate_Destination(Coord coord) override; + virtual void Force_New_Slope(int ramp) override; + virtual bool Is_Moving_Now(void) override {return(Is_Moving());} + virtual int Apparent_Speed(void) override; + virtual int Drawing_Code(void) override; + virtual FireErrorType Can_Fire(void) override; + virtual int Get_Status() override {return(0);} + virtual void Acquire_Hunter_Seeker_Target(void) override {} + virtual bool Is_Surfacing() override {return(false);} + virtual void Mark_All_Occupation_Bits(int mark) override {} + virtual bool Is_Moving_Here(Coord to) override {return(false);} + virtual bool Will_Jump_Tracks(void) override {return(false);} + virtual bool Is_Really_Moving_Now(void) override {return(Is_Moving_Now());} + virtual void Stop_Movement_Animation(void) override {} + virtual void Lock(void) override {} + virtual void Unlock(void) override {} + virtual int Get_Track_Number(void) override {return(-1);} + virtual int Get_Track_Index(void) override {return(-1);} + virtual int Get_Speed_Accum(void) override {return(-1);} /* @@ -133,11 +129,4 @@ class LocomotionClass : public IPersistent, public ILocomotion * persistence machinery never assumes a locomotor is already safely on disk. */ bool Dirty; - - /* - * This is the number of outstanding references to this locomotor. Releasing the - * last one destroys the locomotor, which is how its lifetime is managed through - * the COM interfaces it presents. - */ - LONG RefCount; }; diff --git a/code/logic.cpp b/code/logic.cpp index 2935b2a1e..3d1129fe3 100644 --- a/code/logic.cpp +++ b/code/logic.cpp @@ -79,12 +79,6 @@ #include -/* - * Global COM reference count. - */ -ULONG COMRefCount = 0; - - unsigned FramesThisSecond=0; unsigned LastFramesPerSecond=0; unsigned TotalFrames=0; diff --git a/code/mech.cpp b/code/mech.cpp index ee1d17cd5..07376fc4e 100644 --- a/code/mech.cpp +++ b/code/mech.cpp @@ -56,7 +56,7 @@ MechLocomotionClass::~MechLocomotionClass(void) /// Has the mech been given somewhere to walk to? /// /// bool; Is the mech under movement orders? -boolean STDMETHODCALLTYPE MechLocomotionClass::Is_Moving(void) +bool MechLocomotionClass::Is_Moving(void) { return(IsMoving); } @@ -67,7 +67,7 @@ boolean STDMETHODCALLTYPE MechLocomotionClass::Is_Moving(void) /// /// Returns with the destination assigned, or COORD_NONE if the unit has not been /// given one. -Coord STDMETHODCALLTYPE MechLocomotionClass::Destination(void) +Coord MechLocomotionClass::Destination(void) { if (Is_Moving()) { return(DestinationCoord); @@ -81,7 +81,7 @@ Coord STDMETHODCALLTYPE MechLocomotionClass::Destination(void) /// /// Returns with the location being stepped into, or the unit's own location if it /// is not part way between cells. -Coord STDMETHODCALLTYPE MechLocomotionClass::Head_To_Coord(void) +Coord MechLocomotionClass::Head_To_Coord(void) { if (HeadToCoord != COORD_NONE) { return(HeadToCoord); @@ -95,7 +95,7 @@ Coord STDMETHODCALLTYPE MechLocomotionClass::Head_To_Coord(void) /// This is the locomotor's entry point from the owning unit's AI. /// /// bool; Does the mech still have somewhere to walk to? -boolean STDMETHODCALLTYPE MechLocomotionClass::Process(void) +bool MechLocomotionClass::Process(void) { Movement_AI(true); return(Is_Moving()); @@ -108,7 +108,7 @@ boolean STDMETHODCALLTYPE MechLocomotionClass::Process(void) /// raised to the deck above it, since that is where a walking unit can actually get to. /// /// The location to walk to. -void STDMETHODCALLTYPE MechLocomotionClass::Move_To(Coord to) +void MechLocomotionClass::Move_To(Coord to) { if (LinkedTo->StunDuration <= 0) { Coord coord = to; @@ -126,7 +126,7 @@ void STDMETHODCALLTYPE MechLocomotionClass::Move_To(Coord to) /// A unit caught part way between cells is left in motion so that it finishes the step it /// is taking before coming to rest. /// -void STDMETHODCALLTYPE MechLocomotionClass::Stop_Moving(void) +void MechLocomotionClass::Stop_Moving(void) { DestinationCoord = COORD_NONE; if (HeadToCoord == COORD_NONE) { @@ -155,7 +155,7 @@ void MechLocomotionClass::Do_Turn(DirType coord) /// destination -- it will walk there and then pick its path up again. /// /// The location to step into immediately. -void STDMETHODCALLTYPE MechLocomotionClass::Force_Immediate_Destination(Coord coord) +void MechLocomotionClass::Force_Immediate_Destination(Coord coord) { HeadToCoord = coord; } @@ -658,7 +658,7 @@ bool MechLocomotionClass::Mark_Head_To(Coord const & coord) /// /// Pointer to the buffer to fill in with the class identifier. /// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT STDMETHODCALLTYPE MechLocomotionClass::GetClassID(CLSID * retval) +HRESULT MechLocomotionClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_MechLocomotion; @@ -684,7 +684,7 @@ void MechLocomotionClass::Serialize(SaveStreamClass & stream) /// Fetches the display layer that the mech is rendered in. /// /// Returns with the layer appropriate to a unit that walks on the ground. -LayerType STDMETHODCALLTYPE MechLocomotionClass::In_Which_Layer(void) +LayerType MechLocomotionClass::In_Which_Layer(void) { return(LAYER_GROUND); } @@ -696,7 +696,7 @@ LayerType STDMETHODCALLTYPE MechLocomotionClass::In_Which_Layer(void) /// standing still -- blocked, or waiting on a path -- is not moving now. /// /// bool; Is the mech turning or walking right now? -boolean STDMETHODCALLTYPE MechLocomotionClass::Is_Moving_Now(void) +bool MechLocomotionClass::Is_Moving_Now(void) { if (LinkedTo->PrimaryFacing.Is_Rotating()) { return(true); @@ -714,7 +714,7 @@ boolean STDMETHODCALLTYPE MechLocomotionClass::Is_Moving_Now(void) /// it is walking toward stays reserved for it. /// /// The marking operation to perform; MARK_UP releases the cell. -void STDMETHODCALLTYPE MechLocomotionClass::Mark_All_Occupation_Bits(int mark) +void MechLocomotionClass::Mark_All_Occupation_Bits(int mark) { if (mark == MARK_UP) { LinkedTo->Clear_Occupy_Bit((Coord)Head_To_Coord()); @@ -731,7 +731,7 @@ void STDMETHODCALLTYPE MechLocomotionClass::Mark_All_Occupation_Bits(int mark) /// /// The location to test against. /// bool; Is the mech heading into that location? -boolean STDMETHODCALLTYPE MechLocomotionClass::Is_Moving_Here(Coord to) +bool MechLocomotionClass::Is_Moving_Here(Coord to) { Coord coord = Head_To_Coord(); diff --git a/code/mech.h b/code/mech.h index 9a0a1cab6..bf7123ed3 100644 --- a/code/mech.h +++ b/code/mech.h @@ -27,22 +27,22 @@ class MechLocomotionClass : public LocomotionClass MechLocomotionClass(void); virtual ~MechLocomotionClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual Coord STDMETHODCALLTYPE Head_To_Coord(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual void STDMETHODCALLTYPE Do_Turn(DirType coord) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; - virtual void STDMETHODCALLTYPE Force_Immediate_Destination(Coord coord) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) override; - virtual void STDMETHODCALLTYPE Mark_All_Occupation_Bits(int mark) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Here(Coord to) override; + virtual bool Is_Moving(void) override; + virtual Coord Destination(void) override; + virtual Coord Head_To_Coord(void) override; + virtual bool Process(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual void Do_Turn(DirType coord) override; + virtual LayerType In_Which_Layer(void) override; + virtual void Force_Immediate_Destination(Coord coord) override; + virtual bool Is_Moving_Now(void) override; + virtual void Mark_All_Occupation_Bits(int mark) override; + virtual bool Is_Moving_Here(Coord to) override; /*--------------------------------------------------------------------- ** Member function prototypes. diff --git a/code/overlay.h b/code/overlay.h index 7ee7f1254..2cea474f0 100644 --- a/code/overlay.h +++ b/code/overlay.h @@ -63,7 +63,7 @@ class OverlayClass : public ObjectClass OverlayClass(OverlayTypeClass const * ttype, Cell const & pos = CELL_NONE, HousesType = HOUSE_NONE); virtual ~OverlayClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override {if (retval == NULL) return(E_POINTER);*retval = CLSID_OverlayClass;return(S_OK);} + virtual HRESULT GetClassID(CLSID * retval) override {if (retval == NULL) return(E_POINTER);*retval = CLSID_OverlayClass;return(S_OK);} virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/overtype.cpp b/code/overtype.cpp index c00b1614a..3c41c51ed 100644 --- a/code/overtype.cpp +++ b/code/overtype.cpp @@ -488,7 +488,7 @@ void OverlayTypeClass::Serialize(SaveStreamClass & stream) /// is read back out of a save file. /// /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE OverlayTypeClass::GetClassID(CLSID * retval) +HRESULT OverlayTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_OverlayTypeClass; diff --git a/code/overtype.h b/code/overtype.h index 1920dd7f3..ac5eb5c9b 100644 --- a/code/overtype.h +++ b/code/overtype.h @@ -159,7 +159,7 @@ class OverlayTypeClass: public ObjectTypeClass OverlayTypeClass(char const * ininame = NULL); ~OverlayTypeClass(void); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/particle.cpp b/code/particle.cpp index 5a66b8402..563274359 100644 --- a/code/particle.cpp +++ b/code/particle.cpp @@ -1000,7 +1000,7 @@ int ParticleClass::Shape_Number(void) const /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE ParticleClass::GetClassID(CLSID * retval) +HRESULT ParticleClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_ParticleClass; diff --git a/code/particle.h b/code/particle.h index bbbe5f55a..20b3809a1 100644 --- a/code/particle.h +++ b/code/particle.h @@ -31,7 +31,7 @@ class ParticleClass : public ObjectClass ParticleClass(ParticleTypeClass const * type, Coord const & origin, Coord const & target = COORD_NONE, ParticleSystemClass * partsys = NULL); virtual ~ParticleClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual HRESULT Save(SaveStreamClass & stream, BOOL cleardirty) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/partsys.cpp b/code/partsys.cpp index cf5f1d608..65d95ee02 100644 --- a/code/partsys.cpp +++ b/code/partsys.cpp @@ -879,7 +879,7 @@ void ParticleSystemClass::Compute_CRC(CRCEngine & crc) const /// /// Pointer to the class identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE ParticleSystemClass::GetClassID(CLSID * retval) +HRESULT ParticleSystemClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_ParticleSystemClass; diff --git a/code/partsys.h b/code/partsys.h index 91db45788..73739bfa5 100644 --- a/code/partsys.h +++ b/code/partsys.h @@ -31,7 +31,7 @@ class ParticleSystemClass : public ObjectClass ParticleSystemClass(void); virtual ~ParticleSystemClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/persist.h b/code/persist.h index 05fed10e6..c4b39a2c3 100644 --- a/code/persist.h +++ b/code/persist.h @@ -9,15 +9,19 @@ #pragma once -#include +#include "win.h" + +#include class SaveStreamClass; -// Not a COM interface: it has no identifier, and the loader reaches it by dynamic_cast -// from the IUnknown a class factory hands out. -struct IPersistent : public IUnknown +// What a saved game asks of an object it carries: the class identifier the record is +// tagged with, and the record itself. +struct IPersistent { - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * classid) = 0; + virtual ~IPersistent(void) {} + + virtual HRESULT GetClassID(CLSID * classid) = 0; virtual HRESULT Load(SaveStreamClass & stream) = 0; // Restores what the record could not carry, once the record has been checked; an object // takes its place in the map or a side table here, never while its record is still in doubt. diff --git a/code/psystype.cpp b/code/psystype.cpp index 4a6cae08f..967f9d5e9 100644 --- a/code/psystype.cpp +++ b/code/psystype.cpp @@ -188,7 +188,7 @@ void ParticleSystemTypeClass::Compute_CRC(CRCEngine & crc) const /// /// Pointer to the class ID to be filled in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE ParticleSystemTypeClass::GetClassID(CLSID * retval) +HRESULT ParticleSystemTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_ParticleSystemTypeClass; diff --git a/code/psystype.h b/code/psystype.h index 3e1b60e9f..8c2f4c406 100644 --- a/code/psystype.h +++ b/code/psystype.h @@ -25,7 +25,7 @@ class ParticleSystemTypeClass : public ObjectTypeClass ParticleSystemTypeClass(char const * ininame = NULL); virtual ~ParticleSystemTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/ptype.cpp b/code/ptype.cpp index 4ff73a7d5..53620fc39 100644 --- a/code/ptype.cpp +++ b/code/ptype.cpp @@ -227,7 +227,7 @@ void ParticleTypeClass::Compute_CRC(CRCEngine & crc) const /// /// Pointer to the location to store the class identifier. /// Returns with S_OK, or E_POINTER if no storage location was supplied. -HRESULT STDMETHODCALLTYPE ParticleTypeClass::GetClassID(CLSID * retval) +HRESULT ParticleTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_ParticleTypeClass; diff --git a/code/ptype.h b/code/ptype.h index fd5f29303..dbbbe7214 100644 --- a/code/ptype.h +++ b/code/ptype.h @@ -28,7 +28,7 @@ class ParticleTypeClass : public ObjectTypeClass ParticleTypeClass(char const * ininame = NULL); virtual ~ParticleTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/saveload.cpp b/code/saveload.cpp index a2ff20608..2e764baff 100644 --- a/code/saveload.cpp +++ b/code/saveload.cpp @@ -1015,7 +1015,7 @@ bool Save_Game(const char *file_name, char const * descr) info.Set_Game_Type(Session.Type); FILETIME FileTime; - CoFileTimeNow(&FileTime); + GetSystemTimeAsFileTime(&FileTime); info.Set_Last_Time(FileTime); info.Set_Start_Time(FileTime); info.Set_Play_Time(FileTime); diff --git a/code/scenario.cpp b/code/scenario.cpp index 17981714f..39fbfe195 100644 --- a/code/scenario.cpp +++ b/code/scenario.cpp @@ -1057,11 +1057,7 @@ void Clear_Scenario(void) LightSourceClass::Recalc = false; while (Objects.Count()) { - if (Objects[0]->RTTI == RTTI_BULLET) { - Objects[0]->Release(); - } else { - delete Objects[0]; - } + delete Objects[0]; } LightSourceClass::Recalc = true; diff --git a/code/script.cpp b/code/script.cpp index 0e9a4869f..8566ad2b0 100644 --- a/code/script.cpp +++ b/code/script.cpp @@ -158,7 +158,7 @@ bool ScriptClass::Has_Missions_Remaining(void) /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE ScriptClass::GetClassID(CLSID * retval) +HRESULT ScriptClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_ScriptClass; @@ -375,7 +375,7 @@ ScriptTypeClass * ScriptTypeClass::Find_Or_Make(char const * name) /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE ScriptTypeClass::GetClassID(CLSID * retval) +HRESULT ScriptTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_ScriptTypeClass; diff --git a/code/script.h b/code/script.h index f13faa0d1..643a9a532 100644 --- a/code/script.h +++ b/code/script.h @@ -29,7 +29,7 @@ class ScriptClass : public AbstractClass ScriptClass(ScriptTypeClass *type = NULL); virtual ~ScriptClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; @@ -76,7 +76,7 @@ class ScriptTypeClass : public AbstractTypeClass static ScriptTypeClass * Find_Or_Make(char const * ininame = NULL); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; static void Read_All(CCINIClass const & ini, INIScopeType scope); static void Write_All(CCINIClass & ini, INIScopeType scope); diff --git a/code/side.cpp b/code/side.cpp index 1e081a06b..160e79ca2 100644 --- a/code/side.cpp +++ b/code/side.cpp @@ -139,7 +139,7 @@ bool SideClass::Read_INI(CCINIClass const & ini) /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE SideClass::GetClassID(CLSID * retval) +HRESULT SideClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_SideClass; diff --git a/code/side.h b/code/side.h index be97d6b84..539f53d14 100644 --- a/code/side.h +++ b/code/side.h @@ -30,7 +30,7 @@ class SideClass : public AbstractTypeClass SideClass(char const * ininame = NULL); virtual ~SideClass() override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; /* ** Query functions. diff --git a/code/smudge.cpp b/code/smudge.cpp index dd77b643d..7c09d4c2b 100644 --- a/code/smudge.cpp +++ b/code/smudge.cpp @@ -308,7 +308,7 @@ RTTIType SmudgeClass::Fetch_RTTI(void) const /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE SmudgeClass::GetClassID(CLSID * retval) +HRESULT SmudgeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_SmudgeClass; diff --git a/code/smudge.h b/code/smudge.h index 3fb659f24..802bee1d4 100644 --- a/code/smudge.h +++ b/code/smudge.h @@ -59,7 +59,7 @@ class SmudgeClass : public ObjectClass SmudgeClass(SmudgeTypeClass const * type, Coord const & pos = COORD_NONE, HousesType = HOUSE_NONE); virtual ~SmudgeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/smudtype.cpp b/code/smudtype.cpp index afbdf5090..7872b53d2 100644 --- a/code/smudtype.cpp +++ b/code/smudtype.cpp @@ -343,7 +343,7 @@ void SmudgeTypeClass::Serialize(SaveStreamClass & stream) /// is read back out of a save file. /// /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE SmudgeTypeClass::GetClassID(CLSID * retval) +HRESULT SmudgeTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_SmudgeTypeClass; diff --git a/code/smudtype.h b/code/smudtype.h index 373a0bda7..0ed1afedd 100644 --- a/code/smudtype.h +++ b/code/smudtype.h @@ -59,7 +59,7 @@ class SmudgeTypeClass : public ObjectTypeClass SmudgeTypeClass(char const * ininame = NULL); virtual ~SmudgeTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/super.cpp b/code/super.cpp index c1339fa81..589a7ae6d 100644 --- a/code/super.cpp +++ b/code/super.cpp @@ -805,7 +805,7 @@ bool SuperClass::Is_Charging(void) const /// Fetches the persistent class identifier for the super weapon. /// /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE SuperClass::GetClassID(CLSID * retval) +HRESULT SuperClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_SuperWeaponClass; diff --git a/code/super.h b/code/super.h index d5dd0954e..9482e558f 100644 --- a/code/super.h +++ b/code/super.h @@ -48,7 +48,7 @@ class SuperClass : public AbstractClass SuperClass(SuperWeaponTypeClass * type, HouseClass * owner); virtual ~SuperClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/suprtype.cpp b/code/suprtype.cpp index 6c5834955..2d25a57a6 100644 --- a/code/suprtype.cpp +++ b/code/suprtype.cpp @@ -103,7 +103,7 @@ SuperWeaponTypeClass::~SuperWeaponTypeClass(void) /// read back in. /// /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE SuperWeaponTypeClass::GetClassID(CLSID * retval) +HRESULT SuperWeaponTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_SuperWeaponTypeClass; diff --git a/code/suprtype.h b/code/suprtype.h index 8542bcbc9..26e807f41 100644 --- a/code/suprtype.h +++ b/code/suprtype.h @@ -33,7 +33,7 @@ class SuperWeaponTypeClass : public AbstractTypeClass SuperWeaponTypeClass(char const * ininame = NULL); virtual ~SuperWeaponTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/tactical.cpp b/code/tactical.cpp index bca9e834e..848c1788e 100644 --- a/code/tactical.cpp +++ b/code/tactical.cpp @@ -3730,7 +3730,7 @@ bool Tactical::Draw_3D_Line(Coord const & coord1, Coord const & coord2, int colo /// back out of a save game. /// /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE Tactical::GetClassID(CLSID * retval) +HRESULT Tactical::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_TacticalMapClass; diff --git a/code/tactical.h b/code/tactical.h index 9c5a73732..3af1ccf2c 100644 --- a/code/tactical.h +++ b/code/tactical.h @@ -103,7 +103,7 @@ class Tactical : public AbstractClass Tactical(void); virtual ~Tactical(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual RTTIType Fetch_RTTI(void) const override {return(RTTI_TACTICALMAP);} diff --git a/code/taction.cpp b/code/taction.cpp index 4977fd861..639fd2335 100644 --- a/code/taction.cpp +++ b/code/taction.cpp @@ -2963,7 +2963,7 @@ NeedType Action_Needs(TActionType action) /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TActionClass::GetClassID(CLSID * retval) +HRESULT TActionClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_ActionClass; diff --git a/code/taction.h b/code/taction.h index 1dd6a0595..216968356 100644 --- a/code/taction.h +++ b/code/taction.h @@ -140,7 +140,7 @@ class TActionClass : public AbstractClass TActionClass(void); virtual ~TActionClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/tag.cpp b/code/tag.cpp index 518b7d56d..7ff4afe64 100644 --- a/code/tag.cpp +++ b/code/tag.cpp @@ -429,7 +429,7 @@ void TagClass::Detach(AbstractClass const * target, bool all) /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TagClass::GetClassID(CLSID * retval) +HRESULT TagClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_TagClass; diff --git a/code/tag.h b/code/tag.h index fe24ffe53..48dc3c128 100644 --- a/code/tag.h +++ b/code/tag.h @@ -29,7 +29,7 @@ class TagClass : public AbstractClass TagClass(TagTypeClass * type=NULL); virtual ~TagClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/tagtype.cpp b/code/tagtype.cpp index 95afd121a..4069fb24f 100644 --- a/code/tagtype.cpp +++ b/code/tagtype.cpp @@ -383,7 +383,7 @@ TagTypeClass * TagTypeClass::Find_Or_Make(char const * name) /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TagTypeClass::GetClassID(CLSID * retval) +HRESULT TagTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_TagTypeClass; diff --git a/code/tagtype.h b/code/tagtype.h index 1d99464e3..d8ed014b5 100644 --- a/code/tagtype.h +++ b/code/tagtype.h @@ -33,7 +33,7 @@ class TagTypeClass : public AbstractTypeClass TagTypeClass(char const * name = NULL); virtual ~TagTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; static TagTypeClass * From_Name(char const * name); diff --git a/code/taskforc.cpp b/code/taskforc.cpp index 5e32142cf..3b1e21090 100644 --- a/code/taskforc.cpp +++ b/code/taskforc.cpp @@ -325,7 +325,7 @@ void TaskForceClass::Serialize(SaveStreamClass & stream) /// be created when the game is loaded back in. /// /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TaskForceClass::GetClassID(CLSID * retval) +HRESULT TaskForceClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_TaskForceClass; diff --git a/code/taskforc.h b/code/taskforc.h index dc0d30b99..d867b2c9c 100644 --- a/code/taskforc.h +++ b/code/taskforc.h @@ -25,7 +25,7 @@ class TaskForceClass : public AbstractTypeClass TaskForceClass(char const *name=NULL); virtual ~TaskForceClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; static TaskForceClass * Find_Or_Make(char const * name); diff --git a/code/team.cpp b/code/team.cpp index 270cecb7a..25aa6f721 100644 --- a/code/team.cpp +++ b/code/team.cpp @@ -2303,7 +2303,7 @@ void TeamClass::Serialize(SaveStreamClass & stream) /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TeamClass::GetClassID(CLSID * retval) +HRESULT TeamClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_TeamClass; diff --git a/code/team.h b/code/team.h index bc465e36f..2dd603251 100644 --- a/code/team.h +++ b/code/team.h @@ -265,7 +265,7 @@ class TeamClass : public AbstractClass TeamClass(TeamTypeClass const * team=0, HouseClass * owner=0, void * = NULL); virtual ~TeamClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/teamtype.cpp b/code/teamtype.cpp index c406abec7..f4bade88c 100644 --- a/code/teamtype.cpp +++ b/code/teamtype.cpp @@ -897,7 +897,7 @@ void TeamTypeClass::Serialize(SaveStreamClass & stream) /// /// Returns with S_OK and the class identifier filled in, or E_POINTER if no /// destination was supplied. -HRESULT STDMETHODCALLTYPE TeamTypeClass::GetClassID(CLSID * retval) +HRESULT TeamTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_TeamTypeClass; diff --git a/code/teamtype.h b/code/teamtype.h index 850e04144..d9a67ec01 100644 --- a/code/teamtype.h +++ b/code/teamtype.h @@ -68,7 +68,7 @@ class TeamTypeClass : public AbstractTypeClass TeamTypeClass(char const * name = NULL); virtual ~TeamTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; static TeamTypeClass * Find_Or_Make(char const * ininame = NULL); diff --git a/code/techno.cpp b/code/techno.cpp index 323a0c83d..f2d7916eb 100644 --- a/code/techno.cpp +++ b/code/techno.cpp @@ -4064,7 +4064,7 @@ BulletClass * TechnoClass::Fire_At(AbstractClass * target, int which) if (valid_arc) { if (!bullet->Unlimbo(turret_coord, velocity)) { - bullet->Release(); + delete bullet; bullet = NULL; } else { @@ -4187,7 +4187,7 @@ BulletClass * TechnoClass::Fire_At(AbstractClass * target, int which) } } } else { - bullet->Release(); + delete bullet; bullet = NULL; } } diff --git a/code/teleport.cpp b/code/teleport.cpp index ab912d72e..1f96bc102 100644 --- a/code/teleport.cpp +++ b/code/teleport.cpp @@ -36,7 +36,7 @@ TeleportLocomotionClass::TeleportLocomotionClass(void) : /// The object counts as moving from the moment a destination is handed to this /// locomotor until the jump has actually been made. /// -boolean STDMETHODCALLTYPE TeleportLocomotionClass::Is_Moving(void) +bool TeleportLocomotionClass::Is_Moving(void) { if (DestinationCoord != COORD_NONE) { return(true); @@ -50,7 +50,7 @@ boolean STDMETHODCALLTYPE TeleportLocomotionClass::Is_Moving(void) /// This is the plain opposite of Is_Moving. An object with a teleport ordered counts as /// being on the move even though it has not gone anywhere yet. /// -boolean TeleportLocomotionClass::Is_Stationary(void) +bool TeleportLocomotionClass::Is_Stationary(void) { if (Is_Moving() == false) { return(true); @@ -64,7 +64,7 @@ boolean TeleportLocomotionClass::Is_Stationary(void) /// /// Returns with the pending teleport destination, or with the object's current /// position if no teleport has been ordered. -Coord STDMETHODCALLTYPE TeleportLocomotionClass::Destination(void) +Coord TeleportLocomotionClass::Destination(void) { if (Is_Moving()) { return(DestinationCoord); @@ -78,7 +78,7 @@ Coord STDMETHODCALLTYPE TeleportLocomotionClass::Destination(void) /// The jump is not made here. It happens the next time this locomotor is processed. /// /// The coordinate to teleport the object to. -void STDMETHODCALLTYPE TeleportLocomotionClass::Move_To(Coord to) +void TeleportLocomotionClass::Move_To(Coord to) { DestinationCoord = to; } @@ -89,7 +89,7 @@ void STDMETHODCALLTYPE TeleportLocomotionClass::Move_To(Coord to) /// The pending destination is forgotten, so the object stays where it is rather than /// making the jump. /// -void STDMETHODCALLTYPE TeleportLocomotionClass::Stop_Moving(void) +void TeleportLocomotionClass::Stop_Moving(void) { DestinationCoord = COORD_NONE; } @@ -102,7 +102,7 @@ void STDMETHODCALLTYPE TeleportLocomotionClass::Stop_Moving(void) /// it now stands. The whole journey is over by the time this routine returns. /// /// bool; Is there more movement still to process? A teleport never leaves any. -boolean STDMETHODCALLTYPE TeleportLocomotionClass::Process(void) +bool TeleportLocomotionClass::Process(void) { if (Is_Moving()) { LinkedTo->Mark(MARK_UP); @@ -112,7 +112,7 @@ boolean STDMETHODCALLTYPE TeleportLocomotionClass::Process(void) LinkedTo->Per_Cell_Process(PCP_END); LinkedTo->Look(); } - return(VARIANT_FALSE); + return(false); } @@ -123,7 +123,7 @@ boolean STDMETHODCALLTYPE TeleportLocomotionClass::Process(void) /// /// Pointer to the class identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TeleportLocomotionClass::GetClassID(CLSID * retval) +HRESULT TeleportLocomotionClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_TeleportLocomotion; @@ -149,7 +149,7 @@ void TeleportLocomotionClass::Serialize(SaveStreamClass & stream) /// the way to its destination, so it never rises out of the ground layer. /// /// Returns with the layer the object should be rendered in. -LayerType STDMETHODCALLTYPE TeleportLocomotionClass::In_Which_Layer(void) +LayerType TeleportLocomotionClass::In_Which_Layer(void) { return(LAYER_GROUND); } diff --git a/code/teleport.h b/code/teleport.h index 4efad8853..a7cb4f90a 100644 --- a/code/teleport.h +++ b/code/teleport.h @@ -19,18 +19,18 @@ class TeleportLocomotionClass : public LocomotionClass public: TeleportLocomotionClass(void); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; + virtual bool Is_Moving(void) override; + virtual Coord Destination(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual bool Process(void) override; + virtual LayerType In_Which_Layer(void) override; - virtual boolean Is_Stationary(void); + virtual bool Is_Stationary(void); private: /* diff --git a/code/terrain.cpp b/code/terrain.cpp index f1e6561f7..18512889d 100644 --- a/code/terrain.cpp +++ b/code/terrain.cpp @@ -1097,7 +1097,7 @@ RTTIType TerrainClass::Fetch_RTTI(void) const /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TerrainClass::GetClassID(CLSID * retval) +HRESULT TerrainClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_TerrainClass; diff --git a/code/terrain.h b/code/terrain.h index ade05210a..ef7dc10d2 100644 --- a/code/terrain.h +++ b/code/terrain.h @@ -59,7 +59,7 @@ class TerrainClass : public ObjectClass, public StageClass TerrainClass(TerrainTypeClass const * type, Cell const & cell); virtual ~TerrainClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/terrtype.cpp b/code/terrtype.cpp index 0e86c765b..4a5c015ea 100644 --- a/code/terrtype.cpp +++ b/code/terrtype.cpp @@ -437,7 +437,7 @@ void TerrainTypeClass::Serialize(SaveStreamClass & stream) /// /// Pointer to the buffer to fill in with the class identifier. /// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT STDMETHODCALLTYPE TerrainTypeClass::GetClassID(CLSID * retval) +HRESULT TerrainTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_TerrainTypeClass; diff --git a/code/terrtype.h b/code/terrtype.h index 9bf8ec3c0..001a46ade 100644 --- a/code/terrtype.h +++ b/code/terrtype.h @@ -111,7 +111,7 @@ class TerrainTypeClass : public ObjectTypeClass TerrainTypeClass(char const * ininame = NULL); virtual ~TerrainTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/tevent.cpp b/code/tevent.cpp index 167c46731..6a0ca2ce7 100644 --- a/code/tevent.cpp +++ b/code/tevent.cpp @@ -846,7 +846,7 @@ void TEventClass::Compute_CRC(CRCEngine & crc) const /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TEventClass::GetClassID(CLSID * retval) +HRESULT TEventClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_EventClass; diff --git a/code/tevent.h b/code/tevent.h index 6dab13515..fe4e0f102 100644 --- a/code/tevent.h +++ b/code/tevent.h @@ -104,7 +104,7 @@ class TEventClass : public AbstractClass TEventClass(void); virtual ~TEventClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/tiberium.cpp b/code/tiberium.cpp index edc633665..ad21b0da6 100644 --- a/code/tiberium.cpp +++ b/code/tiberium.cpp @@ -200,7 +200,7 @@ void TiberiumClass::Compute_CRC(CRCEngine & crc) const /// tiberium type is read back in. /// /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TiberiumClass::GetClassID(CLSID * retval) +HRESULT TiberiumClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_TiberiumClass; diff --git a/code/tiberium.h b/code/tiberium.h index 155f34e94..063ead26e 100644 --- a/code/tiberium.h +++ b/code/tiberium.h @@ -37,7 +37,7 @@ class TiberiumClass : public AbstractTypeClass TiberiumClass(char const * ininame = NULL); virtual ~TiberiumClass() override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/tracker.cpp b/code/tracker.cpp index 1658bb586..146974e1d 100644 --- a/code/tracker.cpp +++ b/code/tracker.cpp @@ -224,15 +224,13 @@ void Process_Deferred_Deletion(void) break; } } - if (obj->Release()) { - if (typeid(BuildingClass) == typeid(*obj) - || typeid(UnitClass) == typeid(*obj) - || typeid(InfantryClass) == typeid(*obj) - || typeid(AircraftClass) == typeid(*obj)) { - ((ObjectClass *)obj)->IsActive = true; - } - delete obj; + if (typeid(BuildingClass) == typeid(*obj) + || typeid(UnitClass) == typeid(*obj) + || typeid(InfantryClass) == typeid(*obj) + || typeid(AircraftClass) == typeid(*obj)) { + ((ObjectClass *)obj)->IsActive = true; } + delete obj; } else { ++index; } diff --git a/code/trigger.cpp b/code/trigger.cpp index bc1c9720d..30519332b 100644 --- a/code/trigger.cpp +++ b/code/trigger.cpp @@ -507,7 +507,7 @@ void TriggerClass::Compute_CRC(CRCEngine & crc) const /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TriggerClass::GetClassID(CLSID * retval) +HRESULT TriggerClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_TriggerClass; diff --git a/code/trigger.h b/code/trigger.h index 771341c7d..ebba32ee9 100644 --- a/code/trigger.h +++ b/code/trigger.h @@ -65,7 +65,7 @@ class TriggerClass : public AbstractClass TriggerClass(TriggerTypeClass * trigtype=NULL); virtual ~TriggerClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/trigtype.cpp b/code/trigtype.cpp index 9014fe2ae..c1604099c 100644 --- a/code/trigtype.cpp +++ b/code/trigtype.cpp @@ -818,7 +818,7 @@ void TriggerTypeClass::Compute_CRC(CRCEngine & crc) const /// /// Pointer to the location to store the class identifier. /// Returns with S_OK, or E_POINTER if no storage location was supplied. -HRESULT STDMETHODCALLTYPE TriggerTypeClass::GetClassID(CLSID * retval) +HRESULT TriggerTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_TriggerTypeClass; diff --git a/code/trigtype.h b/code/trigtype.h index f8ba9e5a2..ed2792f2b 100644 --- a/code/trigtype.h +++ b/code/trigtype.h @@ -55,7 +55,7 @@ class TriggerTypeClass : public AbstractTypeClass static TriggerTypeClass * Find_Or_Make(char const * ininame = NULL); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; /* ** File I/O routines diff --git a/code/tube.cpp b/code/tube.cpp index 0d67042bc..8ff1663c1 100644 --- a/code/tube.cpp +++ b/code/tube.cpp @@ -274,7 +274,7 @@ RTTIType TubeClass::Fetch_RTTI(void) const /// /// Pointer to the place to store the class identifier. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TubeClass::GetClassID(CLSID * retval) +HRESULT TubeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_TubeClass; diff --git a/code/tube.h b/code/tube.h index 71e193c44..d2247a2f6 100644 --- a/code/tube.h +++ b/code/tube.h @@ -22,7 +22,7 @@ class TubeClass : public AbstractClass { typedef AbstractClass BASECLASS; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/tunnel.cpp b/code/tunnel.cpp index aa3fa9ff1..8a49c905b 100644 --- a/code/tunnel.cpp +++ b/code/tunnel.cpp @@ -53,7 +53,7 @@ TunnelLocomotionClass::TunnelLocomotionClass(void) : /// Reports whether the unit is anywhere in the dig cycle (State != STATE_IDLE). /// /// True while dig-moving. -boolean STDMETHODCALLTYPE TunnelLocomotionClass::Is_Moving(void) +bool TunnelLocomotionClass::Is_Moving(void) { if (State != STATE_IDLE) { return(true); @@ -67,7 +67,7 @@ boolean STDMETHODCALLTYPE TunnelLocomotionClass::Is_Moving(void) /// (not STATE_IDLE and not STATE_TURNING). /// /// True while actively moving. -boolean STDMETHODCALLTYPE TunnelLocomotionClass::Is_Moving_Now(void) +bool TunnelLocomotionClass::Is_Moving_Now(void) { if (Is_Moving() && State != STATE_TURNING) { return(true); @@ -80,7 +80,7 @@ boolean STDMETHODCALLTYPE TunnelLocomotionClass::Is_Moving_Now(void) /// Returns the burrow destination while moving, or the current position when idle. /// /// The destination coordinate. -Coord STDMETHODCALLTYPE TunnelLocomotionClass::Destination(void) +Coord TunnelLocomotionClass::Destination(void) { if (Is_Moving()) { return(DestinationCoord); @@ -96,7 +96,7 @@ Coord STDMETHODCALLTYPE TunnelLocomotionClass::Destination(void) /// ignores the order altogether. /// /// The location to travel to. -void STDMETHODCALLTYPE TunnelLocomotionClass::Move_To(Coord to) +void TunnelLocomotionClass::Move_To(Coord to) { if (LinkedTo->StunDuration <= 0) { Coord coord = to; @@ -121,7 +121,7 @@ void STDMETHODCALLTYPE TunnelLocomotionClass::Move_To(Coord to) /// already traveling underground must make for the nearest ground it can surface on. If /// there is no such ground to be had, it stays buried for good. /// -void STDMETHODCALLTYPE TunnelLocomotionClass::Stop_Moving(void) +void TunnelLocomotionClass::Stop_Moving(void) { switch (State) { case STATE_ABORTING: @@ -180,7 +180,7 @@ void STDMETHODCALLTYPE TunnelLocomotionClass::Stop_Moving(void) /// the ground and underground layers. /// /// bool; Is the unit still working through its dig cycle? -boolean STDMETHODCALLTYPE TunnelLocomotionClass::Process(void) +bool TunnelLocomotionClass::Process(void) { if (Is_Moving()) { int agl = LinkedTo->HeightAGL; @@ -256,7 +256,7 @@ boolean STDMETHODCALLTYPE TunnelLocomotionClass::Process(void) /// /// Should the buried unit be hidden outright rather than rippled? /// Returns with the visual treatment to draw the unit with. -VisualType STDMETHODCALLTYPE TunnelLocomotionClass::Visual_Character(boolean flag) +VisualType TunnelLocomotionClass::Visual_Character(bool flag) { if (State == STATE_TUNNELING) { if (flag) { @@ -457,7 +457,7 @@ void TunnelLocomotionClass::Process_Emerging(void) /// /// The shape cache key to fold this pose into. May be NULL. /// Returns with the matrix to draw the unit with. -Matrix3D STDMETHODCALLTYPE TunnelLocomotionClass::Draw_Matrix(int * key) +Matrix3D TunnelLocomotionClass::Draw_Matrix(int * key) { if (State == STATE_IDLE) { int ramp = Map[(Coord const &)(LinkedTo->PositionCoord)].Ramp; @@ -528,7 +528,7 @@ Matrix3D STDMETHODCALLTYPE TunnelLocomotionClass::Draw_Matrix(int * key) /// derived from the terrain-height delta and the rotation progress. /// /// The Z pixel adjustment. -int STDMETHODCALLTYPE TunnelLocomotionClass::Z_Adjust(void) +int TunnelLocomotionClass::Z_Adjust(void) { static int tunnel_Z_Adjust[] = {45, 45}; @@ -583,7 +583,7 @@ int STDMETHODCALLTYPE TunnelLocomotionClass::Z_Adjust(void) /// be shaded down its length rather than across the flat, as the base locomotor would. /// /// Returns with the Z gradient to draw the unit with. -ZGradientType STDMETHODCALLTYPE TunnelLocomotionClass::Z_Gradient(void) +ZGradientType TunnelLocomotionClass::Z_Gradient(void) { if (State == STATE_DESCENDING || State == STATE_DIGGING_IN || State == STATE_ABORTING || State == STATE_EMERGING || State == STATE_ASCENDING) { return(ZGRAD_90DEG); @@ -597,7 +597,7 @@ ZGradientType STDMETHODCALLTYPE TunnelLocomotionClass::Z_Gradient(void) /// dig-in, emerging, aborting), false once it is pitched down or underground. /// /// True if it casts a shadow. -boolean STDMETHODCALLTYPE TunnelLocomotionClass::Is_To_Have_Shadow(void) +bool TunnelLocomotionClass::Is_To_Have_Shadow(void) { if (State == STATE_IDLE || State == STATE_TURNING || State == STATE_ABORTING || State == STATE_DIGGING_IN || State == STATE_EMERGING) { return(true); @@ -612,7 +612,7 @@ boolean STDMETHODCALLTYPE TunnelLocomotionClass::Is_To_Have_Shadow(void) /// /// Cell to test. /// MOVE_OK or MOVE_NO. -MoveType STDMETHODCALLTYPE TunnelLocomotionClass::Can_Enter_Cell(Cell cell) +MoveType TunnelLocomotionClass::Can_Enter_Cell(Cell cell) { if (!Debug_Map && !Map[cell].Can_Burrow_Here()) { return(MOVE_NO); @@ -625,7 +625,7 @@ MoveType STDMETHODCALLTYPE TunnelLocomotionClass::Can_Enter_Cell(Cell cell) /// Sets the unit's desired facing (used while turning to face the dig destination). /// /// Desired facing. -void STDMETHODCALLTYPE TunnelLocomotionClass::Do_Turn(DirType coord) +void TunnelLocomotionClass::Do_Turn(DirType coord) { DirType dir = coord; LinkedTo->PrimaryFacing.Set_Desired(dir); @@ -634,12 +634,12 @@ void STDMETHODCALLTYPE TunnelLocomotionClass::Do_Turn(DirType coord) /// /// Fetches the class identifier for this locomotor. -/// This routine is part of the COM persistence support. The save system records the -/// identifier so that the right locomotor can be created again when the game is loaded. +/// The save system records the identifier so that the right locomotor can be created +/// again when the game is loaded. /// /// The location to store the class identifier in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE TunnelLocomotionClass::GetClassID(CLSID * retval) +HRESULT TunnelLocomotionClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_TunnelLocomotion; @@ -666,7 +666,7 @@ void TunnelLocomotionClass::Serialize(SaveStreamClass & stream) /// Returns the render layer: underground while travelling (STATE_TUNNELING), ground otherwise. /// /// The render layer. -LayerType STDMETHODCALLTYPE TunnelLocomotionClass::In_Which_Layer(void) +LayerType TunnelLocomotionClass::In_Which_Layer(void) { if (State != STATE_TUNNELING) { return(LAYER_GROUND); @@ -681,7 +681,7 @@ LayerType STDMETHODCALLTYPE TunnelLocomotionClass::In_Which_Layer(void) /// subterranean unit has no shot while it is lining up, digging, or under the ground. /// /// Returns with the fire error, or FIRE_OK if the unit is free to shoot. -FireErrorType STDMETHODCALLTYPE TunnelLocomotionClass::Can_Fire(void) +FireErrorType TunnelLocomotionClass::Can_Fire(void) { FireErrorType fire = BASECLASS::Can_Fire(); @@ -697,7 +697,7 @@ FireErrorType STDMETHODCALLTYPE TunnelLocomotionClass::Can_Fire(void) /// Reports whether the unit is in the act of surfacing (ascending or emerging). /// /// True while surfacing. -boolean STDMETHODCALLTYPE TunnelLocomotionClass::Is_Surfacing(void) +bool TunnelLocomotionClass::Is_Surfacing(void) { return(State == STATE_ASCENDING || State == STATE_EMERGING); } diff --git a/code/tunnel.h b/code/tunnel.h index 2aaf0ad0a..0d24d116b 100644 --- a/code/tunnel.h +++ b/code/tunnel.h @@ -29,26 +29,26 @@ class TunnelLocomotionClass : public LocomotionClass */ TunnelLocomotionClass(void); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual VisualType STDMETHODCALLTYPE Visual_Character(boolean flag) override; - virtual Matrix3D STDMETHODCALLTYPE Draw_Matrix(int *key) override; - virtual int STDMETHODCALLTYPE Z_Adjust(void) override; - virtual ZGradientType STDMETHODCALLTYPE Z_Gradient(void) override; - virtual boolean STDMETHODCALLTYPE Is_To_Have_Shadow(void) override; - virtual MoveType STDMETHODCALLTYPE Can_Enter_Cell(Cell cell) override; - virtual void STDMETHODCALLTYPE Do_Turn(DirType coord) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; - virtual FireErrorType STDMETHODCALLTYPE Can_Fire(void) override; - virtual boolean STDMETHODCALLTYPE Is_Surfacing(void) override; + virtual bool Is_Moving(void) override; + virtual bool Is_Moving_Now(void) override; + virtual Coord Destination(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual bool Process(void) override; + virtual VisualType Visual_Character(bool flag) override; + virtual Matrix3D Draw_Matrix(int *key) override; + virtual int Z_Adjust(void) override; + virtual ZGradientType Z_Gradient(void) override; + virtual bool Is_To_Have_Shadow(void) override; + virtual MoveType Can_Enter_Cell(Cell cell) override; + virtual void Do_Turn(DirType coord) override; + virtual LayerType In_Which_Layer(void) override; + virtual FireErrorType Can_Fire(void) override; + virtual bool Is_Surfacing(void) override; void Process_Turning(void); void Process_Digging_In(void); diff --git a/code/typelist.h b/code/typelist.h index f1572a0a1..4dcafb564 100644 --- a/code/typelist.h +++ b/code/typelist.h @@ -19,7 +19,6 @@ #include "win.h" #include -#include template class TypeList : public DynamicVectorClass diff --git a/code/unit.cpp b/code/unit.cpp index a4a688a90..7961f15e2 100644 --- a/code/unit.cpp +++ b/code/unit.cpp @@ -6624,7 +6624,7 @@ bool UnitClass::Is_Immobilized(void) const /// /// Pointer to the buffer to fill in with the class identifier. /// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT STDMETHODCALLTYPE UnitClass::GetClassID(CLSID * retval) +HRESULT UnitClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_UnitClass; diff --git a/code/unit.h b/code/unit.h index 20373e578..f042bc282 100644 --- a/code/unit.h +++ b/code/unit.h @@ -134,7 +134,7 @@ class UnitClass : public FootClass UnitClass(UnitTypeClass const * type = NULL, HouseClass * house = NULL); virtual ~UnitClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/unittype.cpp b/code/unittype.cpp index 00723fb19..45cc609b9 100644 --- a/code/unittype.cpp +++ b/code/unittype.cpp @@ -499,7 +499,7 @@ int UnitTypeClass::Repair_Step(void) const /// Fetches the persistent class identifier for the unit type. /// /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE UnitTypeClass::GetClassID(CLSID * retval) +HRESULT UnitTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_UnitTypeClass; diff --git a/code/unittype.h b/code/unittype.h index b5b5f63d9..723cc197c 100644 --- a/code/unittype.h +++ b/code/unittype.h @@ -313,7 +313,7 @@ class UnitTypeClass : public TechnoTypeClass UnitTypeClass(char const * ininame = NULL); virtual ~UnitTypeClass() override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/vanim.cpp b/code/vanim.cpp index ea0b2f8d0..3af2a031c 100644 --- a/code/vanim.cpp +++ b/code/vanim.cpp @@ -553,7 +553,7 @@ void VoxelAnimClass::Compute_CRC(CRCEngine & crc) const /// /// Pointer to the buffer that will receive the class identifier. /// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT STDMETHODCALLTYPE VoxelAnimClass::GetClassID(CLSID * retval) +HRESULT VoxelAnimClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_VoxelAnimClass; diff --git a/code/vanim.h b/code/vanim.h index d7e9f9e4e..197b1ee4f 100644 --- a/code/vanim.h +++ b/code/vanim.h @@ -33,7 +33,7 @@ class VoxelAnimClass : public ObjectClass, public BounceClass VoxelAnimClass(VoxelAnimTypeClass const * type, Coord const & coord, HouseClass * house = NULL); virtual ~VoxelAnimClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/vanimtype.cpp b/code/vanimtype.cpp index a2f6646b3..566e13428 100644 --- a/code/vanimtype.cpp +++ b/code/vanimtype.cpp @@ -254,7 +254,7 @@ void VoxelAnimTypeClass::Compute_CRC(CRCEngine & crc) const /// /// Pointer to the class identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE VoxelAnimTypeClass::GetClassID(CLSID * retval) +HRESULT VoxelAnimTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_VoxelAnimTypeClass; diff --git a/code/vanimtype.h b/code/vanimtype.h index aa01e9e9a..3a1d09720 100644 --- a/code/vanimtype.h +++ b/code/vanimtype.h @@ -32,7 +32,7 @@ class VoxelAnimTypeClass : public ObjectTypeClass VoxelAnimTypeClass(char const * ininame = NULL); ~VoxelAnimTypeClass(void); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/vein.cpp b/code/vein.cpp index aaaf65728..363a2a38b 100644 --- a/code/vein.cpp +++ b/code/vein.cpp @@ -1096,7 +1096,7 @@ void VeinholeMonsterClass::Reduce_Veins_At(CellClass * cellptr) /// recreate when the saved game is read back in. /// /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE VeinholeMonsterClass::GetClassID(CLSID * retval) +HRESULT VeinholeMonsterClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_VeinholeMonsterClass; diff --git a/code/vein.h b/code/vein.h index 07eee9d52..b43df8d46 100644 --- a/code/vein.h +++ b/code/vein.h @@ -34,7 +34,7 @@ class VeinholeMonsterClass : public ObjectClass VeinholeMonsterClass(Cell const & cell); ~VeinholeMonsterClass(void); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; /*--------------------------------------------------------------------- ** Member function prototypes. diff --git a/code/walk.cpp b/code/walk.cpp index 6eb37e4e0..39cca6825 100644 --- a/code/walk.cpp +++ b/code/walk.cpp @@ -64,7 +64,7 @@ WalkLocomotionClass::~WalkLocomotionClass(void) /// Is the infantry traveling somewhere? /// /// bool; Does the infantry have somewhere it is trying to get to? -boolean STDMETHODCALLTYPE WalkLocomotionClass::Is_Moving(void) +bool WalkLocomotionClass::Is_Moving(void) { return(IsMoving); } @@ -76,7 +76,7 @@ boolean STDMETHODCALLTYPE WalkLocomotionClass::Is_Moving(void) /// merely under orders to travel but is standing still. /// /// bool; Is the infantry moving right now? -boolean STDMETHODCALLTYPE WalkLocomotionClass::Is_Moving_Now(void) +bool WalkLocomotionClass::Is_Moving_Now(void) { if (Is_Moving() && LinkedTo->Speed > 0 && HeadToCoord != COORD_NONE) { return(true); @@ -90,7 +90,7 @@ boolean STDMETHODCALLTYPE WalkLocomotionClass::Is_Moving_Now(void) /// /// Returns with the coordinate being traveled to, or COORD_NONE if the infantry has /// nowhere it needs to be. -Coord STDMETHODCALLTYPE WalkLocomotionClass::Destination(void) +Coord WalkLocomotionClass::Destination(void) { if (Is_Moving()) { return(DestinationCoord); @@ -104,7 +104,7 @@ Coord STDMETHODCALLTYPE WalkLocomotionClass::Destination(void) /// /// Returns with the immediate destination, or the current position if the infantry /// is not part way between spots. -Coord STDMETHODCALLTYPE WalkLocomotionClass::Head_To_Coord(void) +Coord WalkLocomotionClass::Head_To_Coord(void) { if (HeadToCoord != COORD_NONE) { return(HeadToCoord); @@ -119,7 +119,7 @@ Coord STDMETHODCALLTYPE WalkLocomotionClass::Head_To_Coord(void) /// infantry along its path. /// /// bool; Is the infantry still traveling somewhere? -boolean STDMETHODCALLTYPE WalkLocomotionClass::Process(void) +bool WalkLocomotionClass::Process(void) { IsProcessingMovement = true; Movement_AI(true); @@ -135,7 +135,7 @@ boolean STDMETHODCALLTYPE WalkLocomotionClass::Process(void) /// than beneath it. /// /// The coordinate to travel to, or COORD_NONE to clear the destination. -void STDMETHODCALLTYPE WalkLocomotionClass::Move_To(Coord to) +void WalkLocomotionClass::Move_To(Coord to) { if (LinkedTo->StunDuration <= 0) { DestinationCoord = to; @@ -158,7 +158,7 @@ void STDMETHODCALLTYPE WalkLocomotionClass::Move_To(Coord to) /// The step already under way is allowed to finish; it is the ultimate destination /// that is forgotten. /// -void STDMETHODCALLTYPE WalkLocomotionClass::Stop_Moving(void) +void WalkLocomotionClass::Stop_Moving(void) { DestinationCoord = COORD_NONE; if (HeadToCoord == COORD_NONE) { @@ -172,7 +172,7 @@ void STDMETHODCALLTYPE WalkLocomotionClass::Stop_Moving(void) /// Infantry snap around instantly, so there is no rotation to play out over time. /// /// The direction the infantry should face. -void STDMETHODCALLTYPE WalkLocomotionClass::Do_Turn(DirType dir) +void WalkLocomotionClass::Do_Turn(DirType dir) { LinkedTo->PrimaryFacing.Set(dir); } @@ -184,7 +184,7 @@ void STDMETHODCALLTYPE WalkLocomotionClass::Do_Turn(DirType dir) /// redirected without waiting for the current step to finish. /// /// The coordinate to step to, or COORD_NONE to abandon the step. -void STDMETHODCALLTYPE WalkLocomotionClass::Force_Immediate_Destination(Coord coord) +void WalkLocomotionClass::Force_Immediate_Destination(Coord coord) { HeadToCoord = coord; if (HeadToCoord == COORD_NONE && DestinationCoord == COORD_NONE) { @@ -615,7 +615,7 @@ bool WalkLocomotionClass::Mark_Head_To(Coord const & coord) /// /// Pointer to the class ID to fill in. /// Returns with S_OK if the class ID was fetched, otherwise E_POINTER. -HRESULT STDMETHODCALLTYPE WalkLocomotionClass::GetClassID(CLSID * retval) +HRESULT WalkLocomotionClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_WalkLocomotion; @@ -657,7 +657,7 @@ void WalkLocomotionClass::Serialize(SaveStreamClass & stream) /// Fetches the display layer that walking objects belong in. /// /// Returns with the layer that objects using this locomotor render into. -LayerType STDMETHODCALLTYPE WalkLocomotionClass::In_Which_Layer(void) +LayerType WalkLocomotionClass::In_Which_Layer(void) { return(LAYER_GROUND); } @@ -733,7 +733,7 @@ HRESULT WalkLocomotionClass::Piggyback_CLSID(GUID * classid) /// spot it had claimed becomes available to others again. /// /// The occupancy marking operation being performed. -void STDMETHODCALLTYPE WalkLocomotionClass::Mark_All_Occupation_Bits(int mark) +void WalkLocomotionClass::Mark_All_Occupation_Bits(int mark) { if (mark == MARK_UP) { LinkedTo->Clear_Occupy_Bit(Head_To_Coord()); @@ -748,7 +748,7 @@ void STDMETHODCALLTYPE WalkLocomotionClass::Mark_All_Occupation_Bits(int mark) /// /// The coordinate to test the immediate destination against. /// bool; Is the infantry walking to that spot? -boolean STDMETHODCALLTYPE WalkLocomotionClass::Is_Moving_Here(Coord to) +bool WalkLocomotionClass::Is_Moving_Here(Coord to) { Coord headto = Head_To_Coord(); if (headto.As_Cell() == Coord(to).As_Cell() && abs(headto.Z - to.Z) <= LEVEL_LEPTON_H) { @@ -764,7 +764,7 @@ boolean STDMETHODCALLTYPE WalkLocomotionClass::Is_Moving_Here(Coord to) /// merely holds orders to travel but has yet to take a step. /// /// bool; Is the infantry really moving at this moment? -boolean STDMETHODCALLTYPE WalkLocomotionClass::Is_Really_Moving_Now(void) +bool WalkLocomotionClass::Is_Really_Moving_Now(void) { return(IsReallyMoving); } diff --git a/code/walk.h b/code/walk.h index 3ec724a5f..a1c312f0b 100644 --- a/code/walk.h +++ b/code/walk.h @@ -31,12 +31,10 @@ class WalkLocomotionClass : public LocomotionClass, public IPiggyback WalkLocomotionClass(void); virtual ~WalkLocomotionClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) override {return(BASECLASS::AddRef());} - virtual ULONG STDMETHODCALLTYPE Release(void) override {return(BASECLASS::Release());} virtual bool Begin_Piggyback(std::unique_ptr carried) override; virtual std::unique_ptr End_Piggyback(void) override; @@ -44,20 +42,20 @@ class WalkLocomotionClass : public LocomotionClass, public IPiggyback virtual HRESULT Piggyback_CLSID(GUID * classid) override; virtual bool Is_Piggybacking(void) override {return(Piggybacker != NULL);} - virtual boolean STDMETHODCALLTYPE Is_Moving(void) override; - virtual Coord STDMETHODCALLTYPE Destination(void) override; - virtual Coord STDMETHODCALLTYPE Head_To_Coord(void) override; - virtual boolean STDMETHODCALLTYPE Process(void) override; - virtual void STDMETHODCALLTYPE Move_To(Coord to) override; - virtual void STDMETHODCALLTYPE Stop_Moving(void) override; - virtual void STDMETHODCALLTYPE Do_Turn(DirType dir) override; - virtual LayerType STDMETHODCALLTYPE In_Which_Layer(void) override; - virtual void STDMETHODCALLTYPE Force_Immediate_Destination(Coord coord) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Now(void) override; - virtual void STDMETHODCALLTYPE Mark_All_Occupation_Bits(int mark) override; - virtual boolean STDMETHODCALLTYPE Is_Moving_Here(Coord to) override; - virtual boolean STDMETHODCALLTYPE Is_Really_Moving_Now(void) override; - virtual void STDMETHODCALLTYPE Stop_Movement_Animation(void) override {IsReallyMoving = false;}; + virtual bool Is_Moving(void) override; + virtual Coord Destination(void) override; + virtual Coord Head_To_Coord(void) override; + virtual bool Process(void) override; + virtual void Move_To(Coord to) override; + virtual void Stop_Moving(void) override; + virtual void Do_Turn(DirType dir) override; + virtual LayerType In_Which_Layer(void) override; + virtual void Force_Immediate_Destination(Coord coord) override; + virtual bool Is_Moving_Now(void) override; + virtual void Mark_All_Occupation_Bits(int mark) override; + virtual bool Is_Moving_Here(Coord to) override; + virtual bool Is_Really_Moving_Now(void) override; + virtual void Stop_Movement_Animation(void) override {IsReallyMoving = false;}; void Movement_AI(bool first_pass); bool Mark_Head_To(Coord const & coord); diff --git a/code/warhead.cpp b/code/warhead.cpp index 11cd19645..9938e98ea 100644 --- a/code/warhead.cpp +++ b/code/warhead.cpp @@ -255,7 +255,7 @@ void WarheadTypeClass::Compute_CRC(CRCEngine &crc) const /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE WarheadTypeClass::GetClassID(CLSID * retval) +HRESULT WarheadTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_WarheadTypeClass; diff --git a/code/warhead.h b/code/warhead.h index 1449ecd30..7193c83ae 100644 --- a/code/warhead.h +++ b/code/warhead.h @@ -52,7 +52,7 @@ class WarheadTypeClass : public AbstractTypeClass WarheadTypeClass(char const * ininame = NULL); virtual ~WarheadTypeClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/wave.cpp b/code/wave.cpp index d579f6bc8..f1af6407f 100644 --- a/code/wave.cpp +++ b/code/wave.cpp @@ -473,7 +473,7 @@ void WaveClass::Post_Load(void) /// /// Pointer to the buffer to store the class identifier in. /// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT STDMETHODCALLTYPE WaveClass::GetClassID(CLSID * retval) +HRESULT WaveClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_WaveClass; diff --git a/code/wave.h b/code/wave.h index 8444e4dcc..914da925d 100644 --- a/code/wave.h +++ b/code/wave.h @@ -25,7 +25,7 @@ class WaveClass : public ObjectClass WaveClass(void); virtual ~WaveClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/waypoint.cpp b/code/waypoint.cpp index a71d3c6f5..4340f55e7 100644 --- a/code/waypoint.cpp +++ b/code/waypoint.cpp @@ -288,7 +288,7 @@ void WaypointPathClass::Compute_CRC(CRCEngine & crc) const /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE WaypointPathClass::GetClassID(CLSID * retval) +HRESULT WaypointPathClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_WaypointPath; diff --git a/code/waypoint.h b/code/waypoint.h index ff7f11d10..7c1b61e7a 100644 --- a/code/waypoint.h +++ b/code/waypoint.h @@ -49,7 +49,7 @@ class WaypointPathClass : public AbstractClass WaypointPathClass(int index); virtual ~WaypointPathClass(void) override; - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/weapon.cpp b/code/weapon.cpp index 697c9e14d..f6f458c5f 100644 --- a/code/weapon.cpp +++ b/code/weapon.cpp @@ -370,7 +370,7 @@ void WeaponTypeClass::Compute_CRC(CRCEngine &crc) const /// /// Pointer to the identifier to fill in. /// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT STDMETHODCALLTYPE WeaponTypeClass::GetClassID(CLSID * retval) +HRESULT WeaponTypeClass::GetClassID(CLSID * retval) { if (retval == NULL) return(E_POINTER); *retval = CLSID_WeaponTypeClass; diff --git a/code/weapon.h b/code/weapon.h index 22df6f3ed..aceccd172 100644 --- a/code/weapon.h +++ b/code/weapon.h @@ -60,7 +60,7 @@ class WeaponTypeClass : public AbstractTypeClass WeaponTypeClass(char const * ininame = NULL); ~WeaponTypeClass(void); - virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID * retval) override; + virtual HRESULT GetClassID(CLSID * retval) override; static WeaponType From_Name(char const * name); diff --git a/manual/content/internals/class-hierarchy.md b/manual/content/internals/class-hierarchy.md index 0e3097469..97897fc73 100644 --- a/manual/content/internals/class-hierarchy.md +++ b/manual/content/internals/class-hierarchy.md @@ -22,7 +22,7 @@ source_files: `AbstractClass` is the common base for persistent engine entities. Map objects and INI-backed type definitions are separate branches of that hierarchy. A runtime instance stores state for one object in the current match; a type definition stores data shared by every instance with the same INI identifier. -This page covers simulation objects and their definitions. UI controls, file classes, and locomotion COM objects use other hierarchies. +This page covers simulation objects and their definitions. UI controls, file classes, and locomotors use other hierarchies. ## Terms diff --git a/manual/content/internals/locomotion.md b/manual/content/internals/locomotion.md index 696b2ce34..18589ec13 100644 --- a/manual/content/internals/locomotion.md +++ b/manual/content/internals/locomotion.md @@ -14,11 +14,11 @@ source_files: - code/droppod.cpp --- -`FootClass::Locomotion` is the current `ILocomotion` COM interface for one mobile runtime instance. The locomotor is a separate object linked to the `FootClass`; it is not a behavioral base class of `FootClass`. +`FootClass::Locomotion` owns the current `ILocomotion` locomotor for one mobile runtime instance. The locomotor is a separate object linked to the `FootClass`; it is not a behavioral base class of `FootClass`. ## Object locomotion -`TechnoTypeClass::Locomotor` stores the CLSID used to create a type's ordinary locomotor. Concrete `FootClass` constructors create that COM object, call `Link_To_Object`, and assign it to `FootClass::Locomotion`. +`TechnoTypeClass::Locomotor` stores the CLSID used to create a type's ordinary locomotor. Concrete `FootClass` constructors create that locomotor, call `Link_To_Object`, and assign it to `FootClass::Locomotion`. Movement, destination, layer, occupation, and locomotor-specific drawing queries go through the current interface. Code must therefore inspect the runtime `Locomotion` pointer when temporary locomotion is possible; the type's `Locomotor` CLSID describes the ordinary implementation, not necessarily the one currently in control. From b75aa0007f5b645340a6484911d49f3be8be5c54 Mon Sep 17 00:00:00 2001 From: ZivDero Date: Mon, 7 Sep 2026 06:41:38 +0300 Subject: [PATCH 026/179] Free a save record whose class is not the tactical map --- code/saveload.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/code/saveload.cpp b/code/saveload.cpp index 2e764baff..c91b94719 100644 --- a/code/saveload.cpp +++ b/code/saveload.cpp @@ -789,7 +789,15 @@ static bool Get_All(SaveStreamClass & stream, bool save_net) delete TacticalMap; TacticalMap = NULL; } - Tactical * old_tactical = dynamic_cast(Load_Object(stream)); + SwizzleManagerClass::MarkType const mark = Swizzler.Mark(); + IPersistent * const object = Load_Object(stream); + Tactical * const old_tactical = dynamic_cast(object); + if (object != NULL && old_tactical == NULL) { + DebugString("Save record of %s at %u is not the tactical map\n", typeid(*object).name(), stream.Offset()); + Swizzler.Abandon(mark); + delete object; + stream.Fail(); + } if (old_tactical == NULL) { return(false); } From b8f025324a7c3cd8618bd1734961cc20a8ad0091 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Mon, 7 Sep 2026 03:32:32 +0200 Subject: [PATCH 027/179] Give persistent classes an identifier of the engine's own --- code/CMakeLists.txt | 34 +------ code/aircraft.cpp | 14 +-- code/aircraft.h | 2 +- code/airctype.cpp | 14 +-- code/airctype.h | 2 +- code/aitrig.cpp | 13 +-- code/aitrig.h | 2 +- code/alphashp.cpp | 14 +-- code/alphashp.h | 2 +- code/anim.cpp | 14 +-- code/anim.h | 2 +- code/animtype.cpp | 14 +-- code/animtype.h | 2 +- code/blight.cpp | 13 +-- code/blight.h | 2 +- code/brain.cpp | 14 +-- code/brain.h | 2 +- code/building.cpp | 28 ++---- code/building.h | 2 +- code/builtype.cpp | 14 +-- code/builtype.h | 2 +- code/bullet.cpp | 13 +-- code/bullet.h | 2 +- code/bullettype.cpp | 11 +- code/bullettype.h | 2 +- code/campaign.cpp | 14 +-- code/campaign.h | 2 +- code/cell.cpp | 14 +-- code/cell.h | 2 +- code/classfactory.cpp | 6 +- code/classfactory.h | 6 +- code/classid.h | 35 +++++++ code/classids.cpp | 82 +++++++++++++++ code/classids.h | 85 ++++++++++++++++ code/drive.cpp | 35 +------ code/drive.h | 3 +- code/droppod.cpp | 31 +----- code/droppod.h | 3 +- code/empulse.cpp | 14 +-- code/empulse.h | 2 +- code/factory.cpp | 13 +-- code/factory.h | 2 +- code/fly.cpp | 13 +-- code/fly.h | 2 +- code/fog.cpp | 14 +-- code/fog.h | 2 +- code/foot.cpp | 18 ++-- code/globals.cpp | 7 +- code/house.cpp | 14 +-- code/house.h | 2 +- code/houstype.cpp | 13 +-- code/houstype.h | 2 +- code/hover.cpp | 13 +-- code/hover.h | 2 +- code/iloco.h | 1 - code/ilocos.h | 26 ----- code/ilocos_i.c | 25 ----- code/infantry.cpp | 32 ++---- code/infantry.h | 2 +- code/infatype.cpp | 13 +-- code/infatype.h | 2 +- code/ini.cpp | 14 +-- code/ini.h | 6 +- code/ipiggy.h | 5 - code/isotile.cpp | 14 +-- code/isotile.h | 2 +- code/isotype.cpp | 12 +-- code/isotype.h | 2 +- code/isun.h | 77 -------------- code/isun_i.c | 76 -------------- code/jumpjet.cpp | 12 +-- code/jumpjet.h | 2 +- code/levitate.cpp | 13 +-- code/levitate.h | 2 +- code/light.cpp | 12 +-- code/light.h | 2 +- code/loco.cpp | 12 +-- code/loco.h | 9 +- code/mech.cpp | 13 +-- code/mech.h | 2 +- code/overlay.cpp | 1 - code/overlay.h | 4 +- code/overtype.cpp | 13 +-- code/overtype.h | 2 +- code/particle.cpp | 14 +-- code/particle.h | 2 +- code/partsys.cpp | 14 +-- code/partsys.h | 2 +- code/persist.h | 4 +- code/psystype.cpp | 14 +-- code/psystype.h | 2 +- code/ptype.cpp | 14 +-- code/ptype.h | 2 +- code/reinf.cpp | 4 +- code/saveload.cpp | 12 +-- code/savestream.cpp | 1 - code/script.cpp | 27 +---- code/script.h | 4 +- code/side.cpp | 14 +-- code/side.h | 2 +- code/smudge.cpp | 14 +-- code/smudge.h | 2 +- code/smudtype.cpp | 13 +-- code/smudtype.h | 2 +- code/startup.cpp | 133 ++++++++++++------------- code/sun.h | 4 +- code/super.cpp | 11 +- code/super.h | 2 +- code/suprtype.cpp | 13 +-- code/suprtype.h | 2 +- code/tactical.cpp | 13 +-- code/tactical.h | 2 +- code/taction.cpp | 14 +-- code/taction.h | 2 +- code/tag.cpp | 12 +-- code/tag.h | 2 +- code/tagtype.cpp | 14 +-- code/tagtype.h | 2 +- code/taskforc.cpp | 13 +-- code/taskforc.h | 2 +- code/team.cpp | 14 +-- code/team.h | 2 +- code/teamtype.cpp | 14 +-- code/teamtype.h | 2 +- code/techtype.cpp | 6 +- code/techtype.h | 3 +- code/teleport.cpp | 13 +-- code/teleport.h | 2 +- code/terrain.cpp | 14 +-- code/terrain.h | 2 +- code/terrtype.cpp | 14 +-- code/terrtype.h | 2 +- code/tevent.cpp | 14 +-- code/tevent.h | 2 +- code/tiberium.cpp | 13 +-- code/tiberium.h | 2 +- code/trigger.cpp | 14 +-- code/trigger.h | 2 +- code/trigtype.cpp | 14 +-- code/trigtype.h | 2 +- code/tube.cpp | 14 +-- code/tube.h | 2 +- code/tunnel.cpp | 13 +-- code/tunnel.h | 2 +- code/unit.cpp | 28 ++---- code/unit.h | 2 +- code/unittype.cpp | 11 +- code/unittype.h | 2 +- code/vanim.cpp | 14 +-- code/vanim.h | 2 +- code/vanimtype.cpp | 14 +-- code/vanimtype.h | 2 +- code/vein.cpp | 13 +-- code/vein.h | 2 +- code/walk.cpp | 34 +------ code/walk.h | 3 +- code/warhead.cpp | 14 +-- code/warhead.h | 2 +- code/wave.cpp | 14 +-- code/wave.h | 2 +- code/waypoint.cpp | 14 +-- code/waypoint.h | 2 +- code/weapon.cpp | 14 +-- code/weapon.h | 2 +- docs/SAVE-FORMAT.md | 6 +- manual/content/internals/locomotion.md | 6 +- 166 files changed, 561 insertions(+), 1321 deletions(-) create mode 100644 code/classid.h create mode 100644 code/classids.cpp create mode 100644 code/classids.h delete mode 100644 code/ilocos.h delete mode 100644 code/ilocos_i.c delete mode 100644 code/isun.h delete mode 100644 code/isun_i.c diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index c6a793542..901168911 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -262,37 +262,13 @@ foreach(f ${OPENTS_SRC}) endif() endforeach() -# List of all interface filenames (headers + C stubs) -set(INTERFACE_FILES - iblockci.h iblockci_i.c - iblowfish.h iblowfish_i.c - iflyctrl.h iflyctrl_i.c - ilinkstm.h - iloco.h iloco_i.c - ilocos.h ilocos_i.c - ipiggy.h ipiggy_i.c - isun.h isun_i.c +# The interface headers the locomotors are written against. +source_group("Interface Files" FILES + "${CMAKE_CURRENT_SOURCE_DIR}/iflyctrl.h" + "${CMAKE_CURRENT_SOURCE_DIR}/iloco.h" + "${CMAKE_CURRENT_SOURCE_DIR}/ipiggy.h" ) -# Convert to full paths relative to source dir -set(FULL_INTERFACE_FILES "") -foreach(f IN LISTS INTERFACE_FILES) - list(APPEND FULL_INTERFACE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/${f}") -endforeach() - -# Add them to the "Interface Files" group and exclude *_i.c from build -foreach(f IN LISTS FULL_INTERFACE_FILES) - - # Put into VS filter - source_group("Interface Files" FILES "${f}") - - # Exclude *_i.c from build, but DO NOT mark headers as header-only - if(f MATCHES "_i\\.c$") - set_source_files_properties("${f}" PROPERTIES HEADER_FILE_ONLY TRUE) - endif() - -endforeach() - # General source files source_group("Source Files" REGULAR_EXPRESSION ".*\\.(c|cpp)$") source_group("Header Files" REGULAR_EXPRESSION ".*\\.(h|hpp)$") diff --git a/code/aircraft.cpp b/code/aircraft.cpp index 881382453..e7cc386d0 100644 --- a/code/aircraft.cpp +++ b/code/aircraft.cpp @@ -89,7 +89,6 @@ * _Counts_As_Civ_Evac -- Is the specified object a candidate for civilian evac logic? * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "aircraft.h" @@ -4189,18 +4188,9 @@ RTTIType AircraftClass::Fetch_RTTI(void) const } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the persistence support. The save/load machinery uses the -/// class identifier to recreate an object of the correct type when a game is restored. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT AircraftClass::GetClassID(CLSID * retval) +ClassID AircraftClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_AircraftClass; - return(S_OK); + return(ClassID_AircraftClass); } diff --git a/code/aircraft.h b/code/aircraft.h index 1dd9cb66a..2dec27a3a 100644 --- a/code/aircraft.h +++ b/code/aircraft.h @@ -57,7 +57,7 @@ class AircraftClass : public FootClass, public IFlyControl AircraftClass(AircraftTypeClass const * type = NULL, HouseClass * house = NULL); virtual ~AircraftClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/airctype.cpp b/code/airctype.cpp index 487e651bd..6d3453a59 100644 --- a/code/airctype.cpp +++ b/code/airctype.cpp @@ -45,7 +45,6 @@ * AircraftTypeClass::operator new -- Allocates an aircraft type object from special pool. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "airctype.h" @@ -327,18 +326,9 @@ void AircraftTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of the aircraft type. -/// The save game machinery asks each object for this identifier so that it can create an -/// object of the right class again when the game is loaded. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if there was nowhere to put the answer. -HRESULT AircraftTypeClass::GetClassID(CLSID * retval) +ClassID AircraftTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_AircraftTypeClass; - return(S_OK); + return(ClassID_AircraftTypeClass); } diff --git a/code/airctype.h b/code/airctype.h index 101531e19..913c8e1a8 100644 --- a/code/airctype.h +++ b/code/airctype.h @@ -57,7 +57,7 @@ class AircraftTypeClass : public TechnoTypeClass AircraftTypeClass(char const * ininame = NULL); virtual ~AircraftTypeClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/aitrig.cpp b/code/aitrig.cpp index 3c5acb351..cfd2391dc 100644 --- a/code/aitrig.cpp +++ b/code/aitrig.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "aitrig.h" @@ -92,17 +91,9 @@ AITriggerTypeClass::~AITriggerTypeClass(void) } -/// -/// Fetches the class identifier of this object. -/// The save game system uses this identifier to work out which class to build when the -/// object is read back in. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT AITriggerTypeClass::GetClassID(CLSID * retval) +ClassID AITriggerTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_AITriggerTypeClass; - return(S_OK); + return(ClassID_AITriggerTypeClass); } diff --git a/code/aitrig.h b/code/aitrig.h index 11b6deb3f..62dd872b1 100644 --- a/code/aitrig.h +++ b/code/aitrig.h @@ -60,7 +60,7 @@ class AITriggerTypeClass : public AbstractTypeClass AITriggerTypeClass(const char *name = NULL); ~AITriggerTypeClass(void); - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; static AITriggerTypeClass * Find_Or_Make(char const * ininame); diff --git a/code/alphashp.cpp b/code/alphashp.cpp index 6ca083383..c31fead07 100644 --- a/code/alphashp.cpp +++ b/code/alphashp.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "alphashp.h" @@ -93,18 +92,9 @@ AlphaShapeClass::~AlphaShapeClass(void) } -/// -/// Fetches the class identifier used to persist this object. -/// The save system writes this identifier ahead of the object data so that the loader -/// knows what kind of object to reconstruct. -/// -/// Pointer to the buffer that will receive the class identifier. -/// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT AlphaShapeClass::GetClassID(CLSID * retval) +ClassID AlphaShapeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_AlphaShapeClass; - return(S_OK); + return(ClassID_AlphaShapeClass); } diff --git a/code/alphashp.h b/code/alphashp.h index bbdbd012c..04a67befa 100644 --- a/code/alphashp.h +++ b/code/alphashp.h @@ -34,7 +34,7 @@ class AlphaShapeClass : public AbstractClass AlphaShapeClass(void); ~AlphaShapeClass(void); - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/anim.cpp b/code/anim.cpp index 880e11c74..a8e78ca84 100644 --- a/code/anim.cpp +++ b/code/anim.cpp @@ -50,7 +50,6 @@ * Shorten_Attached_Anims -- Reduces attached animation durations. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "anim.h" @@ -1729,18 +1728,9 @@ void AnimClass::Post_Load_Game(void) } -/// -/// Fetches the persistent class identifier for animation objects. -/// The save and load machinery uses this identifier to recreate the right kind of object -/// when a game is restored. -/// -/// Pointer to the location to store the class identifier at. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT AnimClass::GetClassID(CLSID * retval) +ClassID AnimClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_AnimClass; - return(S_OK); + return(ClassID_AnimClass); } diff --git a/code/anim.h b/code/anim.h index 53cfb73d6..8cf2c763a 100644 --- a/code/anim.h +++ b/code/anim.h @@ -64,7 +64,7 @@ class AnimClass : public ObjectClass, public StageClass AnimClass(void); virtual ~AnimClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/animtype.cpp b/code/animtype.cpp index 90e67aaa6..8e4d29d6d 100644 --- a/code/animtype.cpp +++ b/code/animtype.cpp @@ -38,7 +38,6 @@ * AnimTypeClass::operator delete -- Returns an anim type class object back to the pool. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "animtype.h" @@ -577,18 +576,9 @@ void AnimTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the persistent class identifier of this object. -/// This routine is used by the save game machinery to recognize an animation type when it -/// comes back off the stream. -/// -/// Pointer to the place to store the class identifier. -/// Returns with S_OK, or E_POINTER if there was nowhere to store the answer. -HRESULT AnimTypeClass::GetClassID(CLSID * retval) +ClassID AnimTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_AnimTypeClass; - return(S_OK); + return(ClassID_AnimTypeClass); } diff --git a/code/animtype.h b/code/animtype.h index 4a77ad7a8..1b2432bf8 100644 --- a/code/animtype.h +++ b/code/animtype.h @@ -430,7 +430,7 @@ class AnimTypeClass : public ObjectTypeClass AnimTypeClass(char const * ininame = NULL); virtual ~AnimTypeClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/blight.cpp b/code/blight.cpp index b731bba5c..a45f459db 100644 --- a/code/blight.cpp +++ b/code/blight.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "blight.h" @@ -280,17 +279,9 @@ void BuildingLightClass::AI(void) } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the save game code so that an object of the right kind can -/// be created when the game is loaded back in. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT BuildingLightClass::GetClassID(CLSID * retval) +ClassID BuildingLightClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_BuildingLightClass; - return(S_OK); + return(ClassID_BuildingLightClass); } diff --git a/code/blight.h b/code/blight.h index c58e3bb17..5a5ba3731 100644 --- a/code/blight.h +++ b/code/blight.h @@ -25,7 +25,7 @@ class BuildingLightClass : public ObjectClass BuildingLightClass(TechnoClass * owner = NULL); virtual ~BuildingLightClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/brain.cpp b/code/brain.cpp index 0ee94e9be..96dd0af18 100644 --- a/code/brain.cpp +++ b/code/brain.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "brain.h" @@ -48,18 +47,9 @@ NeuronClass::~NeuronClass(void) } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the save game system so that it knows what kind of object to -/// construct when the stream is read back in. -/// -/// Pointer to the place to store the class identifier. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT NeuronClass::GetClassID(CLSID * retval) +ClassID NeuronClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_NeuronClass; - return(S_OK); + return(ClassID_NeuronClass); } diff --git a/code/brain.h b/code/brain.h index c9ae1a8e6..42693364e 100644 --- a/code/brain.h +++ b/code/brain.h @@ -24,7 +24,7 @@ class NeuronClass : public AbstractClass NeuronClass(void); virtual ~NeuronClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual RTTIType Fetch_RTTI(void) const override { return(RTTI_NEURON); } diff --git a/code/building.cpp b/code/building.cpp index accb08f10..0f7bbdaa4 100644 --- a/code/building.cpp +++ b/code/building.cpp @@ -102,7 +102,6 @@ * BuildingClass::~BuildingClass -- Destructor for building type objects. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "building.h" @@ -138,7 +137,7 @@ #include "house.h" #include "houstype.h" #include "iloco.h" -#include "ilocos.h" +#include "classids.h" #include "incdec.h" #include "infantry.h" #include "infatype.h" @@ -5516,8 +5515,8 @@ int BuildingClass::Do_MISSION_REPAIR(void) ** distance check. Fixed-wing aircraft are very inaccurate with ** their landings. */ - CLSID const clsid = Locomotion_Class_ID(tech->Locomotion.get()); - bool hover = (clsid == CLSID_HoverLocomotion) != 0; + ClassID const clsid = Locomotion_Class_ID(tech->Locomotion.get()); + bool hover = (clsid == ClassID_HoverLocomotion) != 0; if (hover) { distance = 0x96; } @@ -6234,14 +6233,14 @@ int BuildingClass::Do_MISSION_UNLOAD(void) if (unit) { unit->Assign_Mission(MISSION_MOVE); - CLSID const clsid = Locomotion_Class_ID(unit->Locomotion.get()); + ClassID const clsid = Locomotion_Class_ID(unit->Locomotion.get()); - if (clsid == CLSID_TunnelLocomotion) { + if (clsid == ClassID_TunnelLocomotion) { IPiggyback * piggy = Piggyback_Of(unit->Locomotion.get()); if (piggy != NULL && piggy->Is_Piggybacking()) { unit->Locomotion = piggy->End_Piggyback(); } - std::unique_ptr walk = Create_Locomotor(CLSID_DriveLocomotion); + std::unique_ptr walk = Create_Locomotor(ClassID_DriveLocomotion); walk->Link_To_Object(unit); piggy = Piggyback_Of(walk.get()); if (piggy != NULL) { @@ -6252,7 +6251,7 @@ int BuildingClass::Do_MISSION_UNLOAD(void) int damage = unit->Strength; unit->Take_Damage(damage, 0, Rule->C4Warhead, NULL, true); } - } else if (clsid != CLSID_DriveLocomotion) { + } else if (clsid != ClassID_DriveLocomotion) { unit->Assign_Destination(&Map[Get_Cell() + Cell(3, 1)]); } else { Coord cs; @@ -10238,18 +10237,9 @@ void BuildingClass::Discharge_Turret(void) } -/// -/// Fetches the persistent class identifier for this building. -/// This routine is part of the persistence support. The save code writes this identifier -/// ahead of the object so that the loader knows what kind of object to create. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT BuildingClass::GetClassID(CLSID * retval) +ClassID BuildingClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_BuildingClass; - return(S_OK); + return(ClassID_BuildingClass); } diff --git a/code/building.h b/code/building.h index 60d4f1093..68aa6558d 100644 --- a/code/building.h +++ b/code/building.h @@ -347,7 +347,7 @@ class BuildingClass : public TechnoClass BuildingClass(BuildingTypeClass const * type = NULL, HouseClass * house = NULL); virtual ~BuildingClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/builtype.cpp b/code/builtype.cpp index 69ee88d5e..0b8957a47 100644 --- a/code/builtype.cpp +++ b/code/builtype.cpp @@ -55,7 +55,6 @@ * BuildingTypeClass::operator new -- Allocates a building type object from the special heap.* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "builtype.h" @@ -1916,18 +1915,9 @@ void BuildingTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the save game system so that it knows what kind of object to -/// construct when the stream is read back in. -/// -/// Pointer to the class ID to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT BuildingTypeClass::GetClassID(CLSID * retval) +ClassID BuildingTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_BuildingTypeClass; - return(S_OK); + return(ClassID_BuildingTypeClass); } diff --git a/code/builtype.h b/code/builtype.h index bfb43855b..382cd6e32 100644 --- a/code/builtype.h +++ b/code/builtype.h @@ -834,7 +834,7 @@ class BuildingTypeClass : public TechnoTypeClass BuildingTypeClass(char const * ininame = NULL); virtual ~BuildingTypeClass() override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/bullet.cpp b/code/bullet.cpp index ee436ca2b..40c62322f 100644 --- a/code/bullet.cpp +++ b/code/bullet.cpp @@ -47,7 +47,6 @@ * BulletClass::~BulletClass -- Destructor for bullet objects. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "bullet.h" @@ -1533,17 +1532,9 @@ RTTIType BulletClass::Fetch_RTTI(void) const } -/// -/// Fetches the class identifier for this projectile. -/// This is the IPersist implementation the save and load machinery uses to recognize which -/// kind of object it is about to read back from the stream. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT BulletClass::GetClassID(CLSID * retval) +ClassID BulletClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_BulletClass; - return(S_OK); + return(ClassID_BulletClass); } diff --git a/code/bullet.h b/code/bullet.h index 4012b7e60..4f2f2aa2a 100644 --- a/code/bullet.h +++ b/code/bullet.h @@ -76,7 +76,7 @@ class BulletClass : public ObjectClass BulletClass(void); virtual ~BulletClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/bullettype.cpp b/code/bullettype.cpp index a5125f111..0c19cd2db 100644 --- a/code/bullettype.cpp +++ b/code/bullettype.cpp @@ -38,7 +38,6 @@ * BulletTypeClass::operator new -- Allocates a bullet type object from the special heap. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "bullettype.h" @@ -356,15 +355,9 @@ void BulletTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier that the save game code stores for this object. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT BulletTypeClass::GetClassID(CLSID * retval) +ClassID BulletTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_BulletTypeClass; - return(S_OK); + return(ClassID_BulletTypeClass); } diff --git a/code/bullettype.h b/code/bullettype.h index f50b45bd6..ecd3ceaec 100644 --- a/code/bullettype.h +++ b/code/bullettype.h @@ -237,7 +237,7 @@ class BulletTypeClass : public ObjectTypeClass BulletTypeClass(char const * name = NULL); virtual ~BulletTypeClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/campaign.cpp b/code/campaign.cpp index 5793d434b..8ac83dc87 100644 --- a/code/campaign.cpp +++ b/code/campaign.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "campaign.h" @@ -126,18 +125,9 @@ void Read_Battle_INI(CCINIClass const & ini) } -/// -/// Fetches the class identifier of this object. -/// This routine is required of every persistent object so that the save game loader -/// can recognize what to construct when the object is read back. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT CampaignClass::GetClassID(CLSID * retval) +ClassID CampaignClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_CampaignClass; - return(S_OK); + return(ClassID_CampaignClass); } diff --git a/code/campaign.h b/code/campaign.h index 73c4d40f9..585345e8d 100644 --- a/code/campaign.h +++ b/code/campaign.h @@ -23,7 +23,7 @@ class CampaignClass : public AbstractTypeClass CampaignClass(char const * name = NULL); virtual ~CampaignClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/cell.cpp b/code/cell.cpp index 7aa584f69..e5f6ae8f1 100644 --- a/code/cell.cpp +++ b/code/cell.cpp @@ -74,7 +74,6 @@ * CellClass::Wall_Update -- Updates the imagery for wall objects in cell. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "cell.h" @@ -5167,18 +5166,9 @@ void CellClass::Detach(AbstractClass const * target) } -/// -/// Fetches the class identifier of this object. -/// This is the persistence requirement that lets the save system recognize a cell when a -/// saved game is read back in. -/// -/// Pointer to the location to store the class identifier in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT CellClass::GetClassID(CLSID * retval) +ClassID CellClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_CellClass; - return(S_OK); + return(ClassID_CellClass); } diff --git a/code/cell.h b/code/cell.h index 9df9a4549..c5bda32e7 100644 --- a/code/cell.h +++ b/code/cell.h @@ -517,7 +517,7 @@ class CellClass : public AbstractClass CellClass(void); virtual ~CellClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/classfactory.cpp b/code/classfactory.cpp index 610107d15..63d70e527 100644 --- a/code/classfactory.cpp +++ b/code/classfactory.cpp @@ -17,7 +17,7 @@ namespace { struct ClassEntryType { - CLSID Class; + ClassID Class; ClassCreatorType Creator; }; @@ -28,7 +28,7 @@ std::vector Classes; // A later registration of the same identifier wins, as the last class object // published did before. -void Register_Class(CLSID const & classid, ClassCreatorType creator) +void Register_Class(ClassID const & classid, ClassCreatorType creator) { for (ClassEntryType & entry : Classes) { if (entry.Class == classid) { @@ -51,7 +51,7 @@ void Unregister_Classes(void) /// /// The object, owned by the caller, or NULL with a debug line naming the /// identifier when no class was registered for it. -IPersistent * Create_Object(CLSID const & classid) +IPersistent * Create_Object(ClassID const & classid) { for (ClassEntryType const & entry : Classes) { if (entry.Class == classid) { diff --git a/code/classfactory.h b/code/classfactory.h index 58702df74..23830735c 100644 --- a/code/classfactory.h +++ b/code/classfactory.h @@ -15,12 +15,12 @@ // registers each one; nothing is created for an identifier nobody registered. typedef IPersistent * (* ClassCreatorType)(void); -void Register_Class(CLSID const & classid, ClassCreatorType creator); +void Register_Class(ClassID const & classid, ClassCreatorType creator); void Unregister_Classes(void); -IPersistent * Create_Object(CLSID const & classid); +IPersistent * Create_Object(ClassID const & classid); template -void Register_Class(CLSID const & classid) +void Register_Class(ClassID const & classid) { Register_Class(classid, []() -> IPersistent * { return(new T); }); } diff --git a/code/classid.h b/code/classid.h new file mode 100644 index 000000000..42474d286 --- /dev/null +++ b/code/classid.h @@ -0,0 +1,35 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#pragma once + +#include + +// The identity a persistent class is saved and named by. The sixteen bytes are those of +// the COM class identifier the class once registered, kept as they are because saved +// games and the Locomotor= key carry them. +struct ClassID +{ + unsigned int Data1; + unsigned short Data2; + unsigned short Data3; + unsigned char Data4[8]; +}; + +static_assert(sizeof(ClassID) == 16, "a class identifier is sixteen bytes on disk"); + +inline bool operator==(ClassID const & left, ClassID const & right) +{ + return(std::memcmp(&left, &right, sizeof(ClassID)) == 0); +} + +inline bool operator!=(ClassID const & left, ClassID const & right) +{ + return(!(left == right)); +} diff --git a/code/classids.cpp b/code/classids.cpp new file mode 100644 index 000000000..0b88b27d7 --- /dev/null +++ b/code/classids.cpp @@ -0,0 +1,82 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#include "classids.h" + +ClassID const ClassID_HouseClass = {0xD9D4A910,0x87C6,0x11D1,{0xB7,0x07,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_SuperWeaponTypeClass = {0x0CF2BCE7,0x36E4,0x11D2,{0xB8,0xD8,0x00,0x60,0x08,0xC8,0x09,0xED}}; +ClassID const ClassID_SuperWeaponClass = {0xD7F754C6,0x391C,0x11D2,{0x9B,0x64,0x00,0x10,0x4B,0x97,0x2F,0xE8}}; +ClassID const ClassID_UnitTypeClass = {0xDCBD42EA,0x0546,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_InfantryTypeClass = {0xAE8B33D8,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_AircraftTypeClass = {0xAE8B33D9,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_BuildingTypeClass = {0xAE8B33DB,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_BulletTypeClass = {0x5AF2CE77,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_TerrainTypeClass = {0x5AF2CE7B,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_IsometricTileTypeClass = {0x5AF2CE7A,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_OverlayTypeClass = {0x5AF2CE79,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_SmudgeTypeClass = {0x5AF2CE78,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_AnimTypeClass = {0xAE8B33DA,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_HouseTypeClass = {0x1DD43928,0x046B,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_IsometricTileClass = {0x0E272DC0,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_VoxelAnimClass = {0x0E272DC1,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_AircraftClass = {0x0E272DC2,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_AnimClass = {0x0E272DC3,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_InfantryClass = {0x0E272DC4,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_SmudgeClass = {0x0E272DC5,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_BuildingClass = {0x0E272DC6,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_OverlayClass = {0x0E272DC7,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_ParticleSystemClass = {0x0E272DC8,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_ParticleSystemTypeClass = {0x703E044A,0x0FB1,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_BulletClass = {0x0E272DC9,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_UnitClass = {0x0E272DCA,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_ParticleClass = {0x0E272DCC,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_ParticleTypeClass = {0x703E044B,0x0FB1,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_WaveClass = {0x0E272DCD,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_BuildingLightClass = {0x54822258,0xD8A8,0x11D1,{0xB4,0x62,0x00,0x60,0x97,0xC6,0xA9,0x79}}; +ClassID const ClassID_TerrainClass = {0x0E272DCE,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_TubeClass = {0x0B4CA41C,0xB3A7,0x11D1,{0xB4,0x57,0x00,0x60,0x97,0xC6,0xA9,0x79}}; +ClassID const ClassID_TeamClass = {0x0E272DCF,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_TaskForceClass = {0x61DE341E,0x0774,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_TeamTypeClass = {0xD1DBA64E,0x0778,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_VoxelAnimTypeClass = {0x2EBB6D66,0x0D4D,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_ScriptClass = {0x42F3A646,0x0789,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_ScriptTypeClass = {0x42F3A647,0x0789,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_TagClass = {0x54F6E432,0x09ED,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_TagTypeClass = {0x54F6E433,0x09ED,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_TriggerClass = {0xC02D1590,0x0A2A,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_TriggerTypeClass = {0xC02D1591,0x0A2A,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_ActionClass = {0x4F0EC392,0x0A55,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_EventClass = {0x4F0EC393,0x0A55,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_FactoryClass = {0x34ECD9A8,0x0AB0,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_WeaponTypeClass = {0x9FD219CA,0x0F7B,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_WarheadTypeClass = {0xA8C54DA4,0x0F7B,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_WaypointPath = {0xF73125BA,0x1054,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_LightSource = {0x6F9C48F0,0x1207,0x11D2,{0x81,0x74,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_CampaignClass = {0xFFDAC848,0x1517,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_SideClass = {0xC53DD372,0x151E,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_TiberiumClass = {0xC53DD373,0x151E,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_CellClass = {0xC1BF99CE,0x1A8C,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_EMPulseClass = {0xB825CB22,0x200E,0x11D2,{0x9F,0xA9,0x00,0x60,0x08,0x9A,0xD4,0x58}}; +ClassID const ClassID_TacticalMapClass = {0xCF56B38A,0x240D,0x11D2,{0x81,0x7C,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_AITriggerTypeClass = {0xBA093524,0x4CF4,0x11D2,{0xBC,0x26,0x00,0x10,0x4B,0x8F,0xB0,0x4D}}; +ClassID const ClassID_AITriggerClass = {0x03C4CE76,0x4CF5,0x11D2,{0xBC,0x26,0x00,0x10,0x4B,0x8F,0xB0,0x4D}}; +ClassID const ClassID_NeuronClass = {0x241AB316,0x4CF5,0x11D2,{0xBC,0x26,0x00,0x10,0x4B,0x8F,0xB0,0x4D}}; +ClassID const ClassID_FoggedObjectClass = {0x1C470B0E,0x69D7,0x11D2,{0xB8,0xF2,0x00,0x60,0x08,0xC8,0x09,0xED}}; +ClassID const ClassID_AlphaShapeClass = {0x623C7584,0x74E7,0x11D2,{0xB8,0xF5,0x00,0x60,0x08,0xC8,0x09,0xED}}; +ClassID const ClassID_VeinholeMonsterClass = {0x5192D06A,0xC632,0x11D2,{0xB9,0x0B,0x00,0x60,0x08,0xC8,0x09,0xED}}; +ClassID const ClassID_DriveLocomotion = {0x4A582741,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_HoverLocomotion = {0x4A582742,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_TunnelLocomotion = {0x4A582743,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_WalkLocomotion = {0x4A582744,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_BallisticLocomotion = {0x4A582745,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_FlyerLocomotion = {0x4A582746,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_TeleportLocomotion = {0x4A582747,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; +ClassID const ClassID_MechLocomotion = {0x55D141B8,0xDB94,0x11D1,{0xAC,0x98,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_JumpjetLocomotion = {0x92612C46,0xF71F,0x11D1,{0xAC,0x9F,0x00,0x60,0x08,0x05,0x5B,0xB5}}; +ClassID const ClassID_LevitateLocomotion = {0x3DC0B295,0x6546,0x11D3,{0x80,0xB0,0x00,0x90,0x27,0x92,0x49,0x4C}}; diff --git a/code/classids.h b/code/classids.h new file mode 100644 index 000000000..e86803bed --- /dev/null +++ b/code/classids.h @@ -0,0 +1,85 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#pragma once + +#include "classid.h" + +// The identifiers of every class a saved game or a Locomotor= key can name. +extern ClassID const ClassID_HouseClass; +extern ClassID const ClassID_SuperWeaponTypeClass; +extern ClassID const ClassID_SuperWeaponClass; +extern ClassID const ClassID_UnitTypeClass; +extern ClassID const ClassID_InfantryTypeClass; +extern ClassID const ClassID_AircraftTypeClass; +extern ClassID const ClassID_BuildingTypeClass; +extern ClassID const ClassID_BulletTypeClass; +extern ClassID const ClassID_TerrainTypeClass; +extern ClassID const ClassID_IsometricTileTypeClass; +extern ClassID const ClassID_OverlayTypeClass; +extern ClassID const ClassID_SmudgeTypeClass; +extern ClassID const ClassID_AnimTypeClass; +extern ClassID const ClassID_HouseTypeClass; +extern ClassID const ClassID_IsometricTileClass; +extern ClassID const ClassID_VoxelAnimClass; +extern ClassID const ClassID_AircraftClass; +extern ClassID const ClassID_AnimClass; +extern ClassID const ClassID_InfantryClass; +extern ClassID const ClassID_SmudgeClass; +extern ClassID const ClassID_BuildingClass; +extern ClassID const ClassID_OverlayClass; +extern ClassID const ClassID_ParticleSystemClass; +extern ClassID const ClassID_ParticleSystemTypeClass; +extern ClassID const ClassID_BulletClass; +extern ClassID const ClassID_UnitClass; +extern ClassID const ClassID_ParticleClass; +extern ClassID const ClassID_ParticleTypeClass; +extern ClassID const ClassID_WaveClass; +extern ClassID const ClassID_BuildingLightClass; +extern ClassID const ClassID_TerrainClass; +extern ClassID const ClassID_TubeClass; +extern ClassID const ClassID_TeamClass; +extern ClassID const ClassID_TaskForceClass; +extern ClassID const ClassID_TeamTypeClass; +extern ClassID const ClassID_VoxelAnimTypeClass; +extern ClassID const ClassID_ScriptClass; +extern ClassID const ClassID_ScriptTypeClass; +extern ClassID const ClassID_TagClass; +extern ClassID const ClassID_TagTypeClass; +extern ClassID const ClassID_TriggerClass; +extern ClassID const ClassID_TriggerTypeClass; +extern ClassID const ClassID_ActionClass; +extern ClassID const ClassID_EventClass; +extern ClassID const ClassID_FactoryClass; +extern ClassID const ClassID_WeaponTypeClass; +extern ClassID const ClassID_WarheadTypeClass; +extern ClassID const ClassID_WaypointPath; +extern ClassID const ClassID_LightSource; +extern ClassID const ClassID_CampaignClass; +extern ClassID const ClassID_SideClass; +extern ClassID const ClassID_TiberiumClass; +extern ClassID const ClassID_CellClass; +extern ClassID const ClassID_EMPulseClass; +extern ClassID const ClassID_TacticalMapClass; +extern ClassID const ClassID_AITriggerTypeClass; +extern ClassID const ClassID_AITriggerClass; +extern ClassID const ClassID_NeuronClass; +extern ClassID const ClassID_FoggedObjectClass; +extern ClassID const ClassID_AlphaShapeClass; +extern ClassID const ClassID_VeinholeMonsterClass; +extern ClassID const ClassID_DriveLocomotion; +extern ClassID const ClassID_HoverLocomotion; +extern ClassID const ClassID_TunnelLocomotion; +extern ClassID const ClassID_WalkLocomotion; +extern ClassID const ClassID_BallisticLocomotion; +extern ClassID const ClassID_FlyerLocomotion; +extern ClassID const ClassID_TeleportLocomotion; +extern ClassID const ClassID_MechLocomotion; +extern ClassID const ClassID_JumpjetLocomotion; +extern ClassID const ClassID_LevitateLocomotion; diff --git a/code/drive.cpp b/code/drive.cpp index 4ba92adef..53acce07a 100644 --- a/code/drive.cpp +++ b/code/drive.cpp @@ -132,28 +132,6 @@ DriveLocomotionClass::~DriveLocomotionClass(void) } -/// -/// Fetches the class identifier of whichever locomotor is driving the unit. -/// That is the identifier of the locomotor riding along on this driver when there is one, -/// and the driver's own otherwise. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK if the identifier was supplied, E_FAIL if the locomotor -/// could not be asked, or E_POINTER if no destination was supplied. -HRESULT DriveLocomotionClass::Piggyback_CLSID(CLSID * classid) -{ - if (classid == NULL) { - return(E_POINTER); - } - - if (Piggybacker != NULL) { - *classid = Locomotion_Class_ID(Piggybacker.get()); - return(S_OK); - } - return(GetClassID(classid)); -} - - /// /// Lists the members this driver carries. /// A locomotor riding along on this one is a separate persistent object rather than a @@ -2078,18 +2056,9 @@ LayerType DriveLocomotionClass::In_Which_Layer(void) } -/// -/// Fetches the class identifier of this locomotor. -/// The persistence system uses this to know which locomotor to create when the unit is -/// loaded back in. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT DriveLocomotionClass::GetClassID(CLSID * retval) +ClassID DriveLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_DriveLocomotion; - return(S_OK); + return(ClassID_DriveLocomotion); } diff --git a/code/drive.h b/code/drive.h index 6cf608614..f830beda6 100644 --- a/code/drive.h +++ b/code/drive.h @@ -60,7 +60,7 @@ class DriveLocomotionClass : public LocomotionClass, public IPiggyback DriveLocomotionClass(void); virtual ~DriveLocomotionClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; @@ -92,7 +92,6 @@ class DriveLocomotionClass : public LocomotionClass, public IPiggyback virtual bool Begin_Piggyback(std::unique_ptr carried) override; virtual std::unique_ptr End_Piggyback(void) override; virtual bool Is_Ok_To_End(void) override; - virtual HRESULT Piggyback_CLSID(GUID * classid) override; virtual bool Is_Piggybacking(void) override {return(Piggybacker != NULL);} /*--------------------------------------------------------------------- diff --git a/code/droppod.cpp b/code/droppod.cpp index bbefe129b..dbb375f2b 100644 --- a/code/droppod.cpp +++ b/code/droppod.cpp @@ -215,15 +215,9 @@ void DropPodLocomotionClass::Move_To(Coord to) } -/// -/// Fetches the class ID that this locomotor is persisted under. -/// -/// Returns with S_OK, or E_POINTER if no return pointer was supplied. -HRESULT DropPodLocomotionClass::GetClassID(CLSID * retval) +ClassID DropPodLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_BallisticLocomotion; - return(S_OK); + return(ClassID_BallisticLocomotion); } @@ -318,27 +312,6 @@ LayerType DropPodLocomotionClass::In_Which_Layer(void) } -/// -/// Fetches the class ID of the locomotor being carried. -/// The save system uses this to record which locomotor is to be restored underneath the -/// drop pod. When nothing is being carried, the pod supplies its own class ID instead. -/// -/// Returns with S_OK, or an error code if the class ID could not be -/// determined. -HRESULT DropPodLocomotionClass::Piggyback_CLSID(GUID * classid) -{ - if (classid == NULL) { - return(E_POINTER); - } - - if (Piggybacker != NULL) { - *classid = Locomotion_Class_ID(Piggybacker.get()); - return(S_OK); - } - return(GetClassID(classid)); -} - - /// /// Fetches the drawing code for the drop pod. /// The renderer uses this to choose the artwork that suits the pod's approach. diff --git a/code/droppod.h b/code/droppod.h index 84939659d..1727e5354 100644 --- a/code/droppod.h +++ b/code/droppod.h @@ -31,7 +31,7 @@ class DropPodLocomotionClass : public LocomotionClass, public IPiggyback DropPodLocomotionClass(void); virtual ~DropPodLocomotionClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; @@ -47,7 +47,6 @@ class DropPodLocomotionClass : public LocomotionClass, public IPiggyback virtual bool Begin_Piggyback(std::unique_ptr carried) override; virtual std::unique_ptr End_Piggyback(void) override; virtual bool Is_Ok_To_End(void) override; - virtual HRESULT Piggyback_CLSID(GUID * classid) override; virtual bool Is_Piggybacking(void) override {return(Piggybacker != NULL);} private: diff --git a/code/empulse.cpp b/code/empulse.cpp index 0b1c2b152..964bd2293 100644 --- a/code/empulse.cpp +++ b/code/empulse.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "empulse.h" @@ -283,18 +282,9 @@ void EMPulseClass::Compute_CRC(CRCEngine &crc) const } -/// -/// Fetches the class identifier used to persist this object. -/// The save system writes this identifier ahead of the object data so that the loader -/// knows what kind of object to reconstruct. -/// -/// Pointer to the buffer that will receive the class identifier. -/// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT EMPulseClass::GetClassID(CLSID * retval) +ClassID EMPulseClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_EMPulseClass; - return(S_OK); + return(ClassID_EMPulseClass); } diff --git a/code/empulse.h b/code/empulse.h index 2d8edc6f8..d04dc713f 100644 --- a/code/empulse.h +++ b/code/empulse.h @@ -30,7 +30,7 @@ class EMPulseClass : public AbstractClass virtual RTTIType Fetch_RTTI(void) const override {return(RTTI_EMPULSE);} - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/factory.cpp b/code/factory.cpp index 9ba093aac..efe17eec9 100644 --- a/code/factory.cpp +++ b/code/factory.cpp @@ -47,7 +47,6 @@ * FactoryClass::~FactoryClass -- Default destructor for factory objects. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "factory.h" @@ -632,17 +631,9 @@ bool FactoryClass::Completed(void) } -/// -/// Fetches the class identifier for a factory. -/// This routine is part of the persistence interface. The save game loader uses the -/// identifier to know what kind of object to create before handing it the stream. -/// -/// Returns with S_OK, or E_POINTER if no return location was supplied. -HRESULT FactoryClass::GetClassID(CLSID * retval) +ClassID FactoryClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_FactoryClass; - return(S_OK); + return(ClassID_FactoryClass); } diff --git a/code/factory.h b/code/factory.h index 5b27ebad6..473c80021 100644 --- a/code/factory.h +++ b/code/factory.h @@ -54,7 +54,7 @@ class FactoryClass : public AbstractClass, private StageClass FactoryClass(void); ~FactoryClass(void); - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/fly.cpp b/code/fly.cpp index 606d76f7a..2cf7d741f 100644 --- a/code/fly.cpp +++ b/code/fly.cpp @@ -1515,18 +1515,9 @@ bool FlyLocomotionClass::Is_In_Flight(void) } -/// -/// Fetches the class identifier of this locomotor. -/// This routine is used by the save and load machinery so that it knows which locomotor to -/// create when the owning object is restored. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT FlyLocomotionClass::GetClassID(CLSID * retval) +ClassID FlyLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_FlyerLocomotion; - return(S_OK); + return(ClassID_FlyerLocomotion); } diff --git a/code/fly.h b/code/fly.h index 10813387f..3981beb9d 100644 --- a/code/fly.h +++ b/code/fly.h @@ -58,7 +58,7 @@ class FlyLocomotionClass : public LocomotionClass FlyLocomotionClass(void); virtual ~FlyLocomotionClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/fog.cpp b/code/fog.cpp index 2bd9e9d89..b1dbdb91e 100644 --- a/code/fog.cpp +++ b/code/fog.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "fog.h" @@ -593,18 +592,9 @@ RTTIType FoggedObjectClass::Fetch_RTTI(void) const } -/// -/// Fetches the class ID of this object. -/// This routine is part of the persistence interface the save game system uses to -/// recreate objects of the right kind when a game is loaded. -/// -/// Pointer to the class ID to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT FoggedObjectClass::GetClassID(CLSID * retval) +ClassID FoggedObjectClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_FoggedObjectClass; - return(S_OK); + return(ClassID_FoggedObjectClass); } diff --git a/code/fog.h b/code/fog.h index f2b183b1d..938207fd1 100644 --- a/code/fog.h +++ b/code/fog.h @@ -40,7 +40,7 @@ class FoggedObjectClass : public AbstractClass FoggedObjectClass(TerrainClass * object); virtual ~FoggedObjectClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/foot.cpp b/code/foot.cpp index 4592132b6..3adf347ea 100644 --- a/code/foot.cpp +++ b/code/foot.cpp @@ -1132,8 +1132,8 @@ void FootClass::Approach_Target(void) */ bool flyer = (RTTI == RTTI_AIRCRAFT); - CLSID const clsid = Locomotion_Class_ID(Locomotion.get()); - if (clsid == CLSID_JumpjetLocomotion) { + ClassID const clsid = Locomotion_Class_ID(Locomotion.get()); + if (clsid == ClassID_JumpjetLocomotion) { flyer = true; } @@ -2385,9 +2385,9 @@ void FootClass::Assign_Destination(AbstractClass * target, bool) ParticleSystems[ATTACHED_PARTICLE_FIRE] = NULL; } - CLSID const locoid = Locomotion_Class_ID(Locomotion.get()); + ClassID const locoid = Locomotion_Class_ID(Locomotion.get()); - if (locoid == CLSID_HoverLocomotion && PathDelay == 0) { + if (locoid == ClassID_HoverLocomotion && PathDelay == 0) { PathDelay = 1; } @@ -3586,7 +3586,7 @@ void FootClass::Set_Coord(Coord const & coord) void FootClass::Link_DropPod(void) { std::unique_ptr locomotion = std::move(Locomotion); - std::unique_ptr ballistic = Create_Locomotor(CLSID_BallisticLocomotion); + std::unique_ptr ballistic = Create_Locomotor(ClassID_BallisticLocomotion); ballistic->Link_To_Object(this); IPiggyback * piggy = Piggyback_Of(ballistic.get()); piggy->Begin_Piggyback(std::move(locomotion)); @@ -4719,9 +4719,9 @@ void FootClass::Delete_Me(void) /// bool; Is the object in the air? bool FootClass::In_Air(void) const { - CLSID const clsid = Locomotion_Class_ID(Locomotion.get()); + ClassID const clsid = Locomotion_Class_ID(Locomotion.get()); - if (clsid == CLSID_HoverLocomotion) { + if (clsid == ClassID_HoverLocomotion) { return(false); } @@ -4740,7 +4740,7 @@ bool FootClass::On_Ground(void) const if (BASECLASS::On_Ground()) { return(true); } - CLSID const clsid = Locomotion_Class_ID(Locomotion.get()); + ClassID const clsid = Locomotion_Class_ID(Locomotion.get()); - return(IsDown && clsid == CLSID_HoverLocomotion); + return(IsDown && clsid == ClassID_HoverLocomotion); } diff --git a/code/globals.cpp b/code/globals.cpp index 01daef65f..ba4008b00 100644 --- a/code/globals.cpp +++ b/code/globals.cpp @@ -29,15 +29,10 @@ *---------------------------------------------------------------------------------------------* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" -/// create all com interfaces here #include "sun.h" -#include "isun_i.c" -#include "ilocos.h" -#include "ilocos_i.c" -#undef INCLUDE_COM +#include "classids.h" #include "_voxel.h" #include "globals.h" diff --git a/code/house.cpp b/code/house.cpp index 9ba85d7f6..0753346db 100644 --- a/code/house.cpp +++ b/code/house.cpp @@ -128,7 +128,6 @@ * HouseClass::Random_Cell_In_Zone -- Find a (technically) legal cell in the zone specified. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "house.h" @@ -6630,18 +6629,9 @@ void HouseClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the persistence contract. The load system uses the identifier to -/// discover which class to build when the object is read back in. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT HouseClass::GetClassID(CLSID * retval) +ClassID HouseClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_HouseClass; - return(S_OK); + return(ClassID_HouseClass); } diff --git a/code/house.h b/code/house.h index 6671012c2..b41bf31f5 100644 --- a/code/house.h +++ b/code/house.h @@ -735,7 +735,7 @@ class HouseClass : public AbstractClass HouseClass(HouseTypeClass const * type = NULL); virtual ~HouseClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/houstype.cpp b/code/houstype.cpp index 4298f6062..9083169e3 100644 --- a/code/houstype.cpp +++ b/code/houstype.cpp @@ -39,7 +39,6 @@ * HouseTypeClass::operator new -- Allocates a house type class object from special heap. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "houstype.h" @@ -259,17 +258,9 @@ void HouseTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// The save game system stores this identifier so that the object can be recreated as the -/// correct class when the game is loaded. -/// -/// Returns with S_OK, or E_POINTER if no return pointer was supplied. -HRESULT HouseTypeClass::GetClassID(CLSID * retval) +ClassID HouseTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_HouseTypeClass; - return(S_OK); + return(ClassID_HouseTypeClass); } diff --git a/code/houstype.h b/code/houstype.h index 4d0160b10..0020ea187 100644 --- a/code/houstype.h +++ b/code/houstype.h @@ -102,7 +102,7 @@ class HouseTypeClass : public AbstractTypeClass HouseTypeClass(char const * ininame = NULL); virtual ~HouseTypeClass() override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/hover.cpp b/code/hover.cpp index 85458e79c..1c3657bd0 100644 --- a/code/hover.cpp +++ b/code/hover.cpp @@ -1067,18 +1067,9 @@ void HoverLocomotionClass::Do_Shove(void) } -/// -/// Fetches the class identifier of this locomotor. -/// The persistence system uses this identifier to create a locomotor of the right kind -/// when the object it drives is loaded back in. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT HoverLocomotionClass::GetClassID(CLSID * retval) +ClassID HoverLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_HoverLocomotion; - return(S_OK); + return(ClassID_HoverLocomotion); } diff --git a/code/hover.h b/code/hover.h index 055d4e080..ae23b2726 100644 --- a/code/hover.h +++ b/code/hover.h @@ -36,7 +36,7 @@ class HoverLocomotionClass : public LocomotionClass HoverLocomotionClass(void); virtual ~HoverLocomotionClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/iloco.h b/code/iloco.h index 7df217c37..4a08e9d77 100644 --- a/code/iloco.h +++ b/code/iloco.h @@ -20,7 +20,6 @@ #include "visual.hh" #include "zgrad.hh" -#include #include #include diff --git a/code/ilocos.h b/code/ilocos.h deleted file mode 100644 index e78b8b45c..000000000 --- a/code/ilocos.h +++ /dev/null @@ -1,26 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#pragma once - -#include "iloco.h" - -/// Names and comments from TLBs - - -EXTERN_C const CLSID CLSID_DriveLocomotion; -EXTERN_C const CLSID CLSID_HoverLocomotion; -EXTERN_C const CLSID CLSID_TunnelLocomotion; -EXTERN_C const CLSID CLSID_WalkLocomotion; -EXTERN_C const CLSID CLSID_BallisticLocomotion; -EXTERN_C const CLSID CLSID_FlyerLocomotion; -EXTERN_C const CLSID CLSID_TeleportLocomotion; -EXTERN_C const CLSID CLSID_MechLocomotion; -EXTERN_C const CLSID CLSID_JumpjetLocomotion; -EXTERN_C const CLSID CLSID_LevitateLocomotion; diff --git a/code/ilocos_i.c b/code/ilocos_i.c deleted file mode 100644 index e5e3498f1..000000000 --- a/code/ilocos_i.c +++ /dev/null @@ -1,25 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -// The class identifiers ilocos.h declares; globals.cpp includes this file once. - -extern "C" { - -const CLSID CLSID_DriveLocomotion = {0x4A582741,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_HoverLocomotion = {0x4A582742,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_TunnelLocomotion = {0x4A582743,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_WalkLocomotion = {0x4A582744,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_BallisticLocomotion = {0x4A582745,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_FlyerLocomotion = {0x4A582746,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_TeleportLocomotion = {0x4A582747,0x9839,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_MechLocomotion = {0x55D141B8,0xDB94,0x11D1,{0xAC,0x98,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_JumpjetLocomotion = {0x92612C46,0xF71F,0x11D1,{0xAC,0x9F,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_LevitateLocomotion = {0x3DC0B295,0x6546,0x11D3,{0x80,0xB0,0x00,0x90,0x27,0x92,0x49,0x4C}}; - -} diff --git a/code/infantry.cpp b/code/infantry.cpp index 7772d98c7..9cc65af9b 100644 --- a/code/infantry.cpp +++ b/code/infantry.cpp @@ -79,7 +79,6 @@ * InfantryClass::~InfantryClass -- Default destructor for infantry units. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "infantry.h" @@ -108,7 +107,7 @@ #include "goptions.h" #include "house.h" #include "houstype.h" -#include "ilocos.h" +#include "classids.h" #include "incdec.h" #include "infatype.h" #include "inline.h" @@ -632,9 +631,9 @@ void InfantryClass::Draw_It(Point2D const & xpoint, Rect const & cliprect) const Cell cell = Get_Target_Cell(); if (CurrentTube == -1) { - CLSID const clsid = Locomotion_Class_ID(Locomotion.get()); + ClassID const clsid = Locomotion_Class_ID(Locomotion.get()); - if (HeightAGL > 0 && clsid == CLSID_BallisticLocomotion) { + if (HeightAGL > 0 && clsid == ClassID_BallisticLocomotion) { ShapeSet const * shapefile = (ShapeSet const *)MFCD::Retrieve("POD.SHP"); Point2D spoint = xpoint + Point2D(Locomotion->Shadow_Point()); Draw_Shape( @@ -1172,8 +1171,8 @@ void InfantryClass::Assign_Destination(AbstractClass * target, bool immediate) } if (target != NULL && Class->IsJumpJet && Locomotion->Is_Moving()) { - CLSID const clsid = Locomotion_Class_ID(Locomotion.get()); - if (clsid == CLSID_WalkLocomotion) { + ClassID const clsid = Locomotion_Class_ID(Locomotion.get()); + if (clsid == ClassID_WalkLocomotion) { NavQueue.Add_Head(target); target = Get_Target_Cell_Ptr(); if (target != NULL && ((CellClass *)target)->IsUnderBridge) { @@ -1192,7 +1191,7 @@ void InfantryClass::Assign_Destination(AbstractClass * target, bool immediate) Locomotion = piggy->End_Piggyback(); } } - std::unique_ptr walk = Create_Locomotor(CLSID_WalkLocomotion); + std::unique_ptr walk = Create_Locomotor(ClassID_WalkLocomotion); walk->Link_To_Object(this); piggy = Piggyback_Of(walk.get()); if (piggy != NULL) { @@ -4181,7 +4180,7 @@ bool InfantryClass::JumpJet_To_Walk(void) if (Is_JumpJet()) { IPiggyback * piggy = Piggyback_Of(Locomotion.get()); if (piggy != NULL && !piggy->Is_Piggybacking()) { - std::unique_ptr walk = Create_Locomotor(CLSID_WalkLocomotion); + std::unique_ptr walk = Create_Locomotor(ClassID_WalkLocomotion); walk->Link_To_Object(this); piggy = Piggyback_Of(walk.get()); if (piggy != NULL) { @@ -4222,8 +4221,8 @@ bool InfantryClass::Is_JumpJet(void) const return(false); } - CLSID const clsid = Locomotion_Class_ID(Locomotion.get()); - return((clsid == CLSID_JumpjetLocomotion) ? true : false); + ClassID const clsid = Locomotion_Class_ID(Locomotion.get()); + return((clsid == ClassID_JumpjetLocomotion) ? true : false); } @@ -4326,18 +4325,9 @@ int InfantryClass::Do_MISSION_GUARD(void) } -/// -/// Fetches the class identifier used to persist this object. -/// The save system records this identifier alongside the object data so that the -/// correct kind of object can be created again when the stream is read back. -/// -/// Pointer to the buffer to fill in with the class identifier. -/// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT InfantryClass::GetClassID(CLSID * retval) +ClassID InfantryClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_InfantryClass; - return(S_OK); + return(ClassID_InfantryClass); } diff --git a/code/infantry.h b/code/infantry.h index 3368f1789..bda23244b 100644 --- a/code/infantry.h +++ b/code/infantry.h @@ -126,7 +126,7 @@ class InfantryClass : public FootClass InfantryClass(InfantryTypeClass const * type = NULL, HouseClass * house = NULL); virtual ~InfantryClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/infatype.cpp b/code/infatype.cpp index 3f05ffffa..9d707ff34 100644 --- a/code/infatype.cpp +++ b/code/infatype.cpp @@ -45,7 +45,6 @@ * InfantryTypeClass::operator new -- Allocate an infanty type class object. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "infatype.h" @@ -513,17 +512,9 @@ void InfantryTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// The save game system stores this identifier so that the object can be recreated as the -/// correct class when the game is loaded. -/// -/// Returns with S_OK, or E_POINTER if no return pointer was supplied. -HRESULT InfantryTypeClass::GetClassID(CLSID * retval) +ClassID InfantryTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_InfantryTypeClass; - return(S_OK); + return(ClassID_InfantryTypeClass); } diff --git a/code/infatype.h b/code/infatype.h index a4783ab4f..e1b717d6c 100644 --- a/code/infatype.h +++ b/code/infatype.h @@ -172,7 +172,7 @@ class InfantryTypeClass : public TechnoTypeClass InfantryTypeClass(char const * ininame = NULL); virtual ~InfantryTypeClass() override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/ini.cpp b/code/ini.cpp index ae518d8ff..7b2874133 100644 --- a/code/ini.cpp +++ b/code/ini.cpp @@ -959,7 +959,7 @@ int INIClass::Get_Int(char const * section, char const * entry, int defvalue) co // A class identifier as the registry writes it, braces optional: eight, four, four, four // and twelve hexadecimal digits separated by hyphens. -static bool Parse_CLSID(char const * text, CLSID & clsid) +static bool Parse_ClassID(char const * text, ClassID & clsid) { char digits[40]; unsigned int length = 0; @@ -998,7 +998,7 @@ static bool Parse_CLSID(char const * text, CLSID & clsid) // The buffer holds the 38 characters of the braced form and its terminator. -static void Format_CLSID(CLSID const & clsid, char * text) +static void Format_ClassID(ClassID const & clsid, char * text) { sprintf(text, "{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", (unsigned long)clsid.Data1, (unsigned int)clsid.Data2, (unsigned int)clsid.Data3, @@ -1027,13 +1027,13 @@ static void Format_CLSID(CLSID const & clsid, char * text) /// The default identifier to use if the entry could not be found. /// Returns with the class identifier specified in the INI database or else returns /// the default value. -CLSID const INIClass::Get_CLSID(char const * section, char const * entry, CLSID defvalue) const +ClassID const INIClass::Get_ClassID(char const * section, char const * entry, ClassID defvalue) const { char buffer[128]; if (Get_String(section, entry, "", buffer, sizeof(buffer))) { - CLSID clsid; - if (Parse_CLSID(buffer, clsid)) { + ClassID clsid; + if (Parse_ClassID(buffer, clsid)) { return(clsid); } } @@ -1041,10 +1041,10 @@ CLSID const INIClass::Get_CLSID(char const * section, char const * entry, CLSID } -bool INIClass::Put_CLSID(char const * section, char const * entry, CLSID const & value) +bool INIClass::Put_ClassID(char const * section, char const * entry, ClassID const & value) { char buffer[40]; - Format_CLSID(value, buffer); + Format_ClassID(value, buffer); return(Put_String(section, entry, buffer)); } diff --git a/code/ini.h b/code/ini.h index 68b5a9c93..ac28a63f9 100644 --- a/code/ini.h +++ b/code/ini.h @@ -34,7 +34,7 @@ #include "crc.h" #include "index.h" -#include +#include "classid.h" #include #include #include @@ -113,7 +113,7 @@ class INIClass { TPoint3D const Get_Point(char const * section, char const * entry, TPoint3D const & defvalue) const; TPoint2D const Get_Point(char const * section, char const * entry, TPoint2D const & defvalue) const; TPoint3D const Get_Point(char const * section, char const * entry, TPoint3D const & defvalue) const; - CLSID const Get_CLSID(char const * section, char const * entry, CLSID defvalue) const; + ClassID const Get_ClassID(char const * section, char const * entry, ClassID defvalue) const; /* ** Put a data type to the section and entry specified. @@ -130,7 +130,7 @@ class INIClass { bool Put_Point(char const * section, char const * entry, TPoint3D const & value); bool Put_Point(char const * section, char const * entry, TPoint3D const & value); bool Put_Point(char const * section, char const * entry, TPoint2D const & value); - bool Put_CLSID(char const * section, char const * entry, CLSID const & value); + bool Put_ClassID(char const * section, char const * entry, ClassID const & value); // Callers size the buffers they hand to Get_String from this. It does not bound a line // of the file; the reader keeps a line of any length. diff --git a/code/ipiggy.h b/code/ipiggy.h index 2f319cb91..3afbd6f18 100644 --- a/code/ipiggy.h +++ b/code/ipiggy.h @@ -29,11 +29,6 @@ struct IPiggyback */ virtual bool Is_Ok_To_End(void) = 0; - /* - * Fetches piggybacked locomotor class ID. - */ - virtual HRESULT Piggyback_CLSID(GUID * classid) = 0; - /* * Is it currently piggy backing another locomotor? */ diff --git a/code/isotile.cpp b/code/isotile.cpp index e493d4de4..92ad2b072 100644 --- a/code/isotile.cpp +++ b/code/isotile.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "isotile.h" @@ -226,18 +225,9 @@ RTTIType IsometricTileClass::Fetch_RTTI(void) const } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the persistence contract and is called by the save system -/// when it must record what kind of object it is about to write out. -/// -/// Pointer to the buffer that will receive the class identifier. -/// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT IsometricTileClass::GetClassID(CLSID * retval) +ClassID IsometricTileClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_IsometricTileClass; - return(S_OK); + return(ClassID_IsometricTileClass); } diff --git a/code/isotile.h b/code/isotile.h index 9f3662e0d..6833b02d1 100644 --- a/code/isotile.h +++ b/code/isotile.h @@ -25,7 +25,7 @@ class IsometricTileClass : public ObjectClass IsometricTileClass(IsometricTileType type, Cell const &cell); virtual ~IsometricTileClass() override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/isotype.cpp b/code/isotype.cpp index cd249c9fb..9c1fffcce 100644 --- a/code/isotype.cpp +++ b/code/isotype.cpp @@ -11,7 +11,6 @@ * disclaimers apply; see LICENSE.md. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "isotype.h" @@ -2818,16 +2817,9 @@ void IsometricTileTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier that this tile type persists under. -/// -/// Receives the class identifier. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT IsometricTileTypeClass::GetClassID(CLSID * retval) +ClassID IsometricTileTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_IsometricTileTypeClass; - return(S_OK); + return(ClassID_IsometricTileTypeClass); } diff --git a/code/isotype.h b/code/isotype.h index 942741615..3e631371c 100644 --- a/code/isotype.h +++ b/code/isotype.h @@ -194,7 +194,7 @@ class IsometricTileTypeClass : public ObjectTypeClass IsometricTileTypeClass(IsometricTileType type = ISOTILE_CLEAR, int unknown1 = 0, unsigned char unknown2 = 0, char const *ininame = NULL, bool skip_registration = false); virtual ~IsometricTileTypeClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/isun.h b/code/isun.h deleted file mode 100644 index 065e9d090..000000000 --- a/code/isun.h +++ /dev/null @@ -1,77 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#pragma once - -#include - - -#define GAME_VERNAME TEXT("Tiberian Sun") - -EXTERN_C const CLSID CLSID_HouseClass; -EXTERN_C const CLSID CLSID_SuperWeaponTypeClass; -EXTERN_C const CLSID CLSID_SuperWeaponClass; -EXTERN_C const CLSID CLSID_UnitTypeClass; -EXTERN_C const CLSID CLSID_InfantryTypeClass; -EXTERN_C const CLSID CLSID_AircraftTypeClass; -EXTERN_C const CLSID CLSID_BuildingTypeClass; -EXTERN_C const CLSID CLSID_BulletTypeClass; -EXTERN_C const CLSID CLSID_TerrainTypeClass; -EXTERN_C const CLSID CLSID_IsometricTileTypeClass; -EXTERN_C const CLSID CLSID_OverlayTypeClass; -EXTERN_C const CLSID CLSID_SmudgeTypeClass; -EXTERN_C const CLSID CLSID_AnimTypeClass; -EXTERN_C const CLSID CLSID_HouseTypeClass; -EXTERN_C const CLSID CLSID_IsometricTileClass; -EXTERN_C const CLSID CLSID_VoxelAnimClass; -EXTERN_C const CLSID CLSID_AircraftClass; -EXTERN_C const CLSID CLSID_AnimClass; -EXTERN_C const CLSID CLSID_InfantryClass; -EXTERN_C const CLSID CLSID_SmudgeClass; -EXTERN_C const CLSID CLSID_BuildingClass; -EXTERN_C const CLSID CLSID_OverlayClass; -EXTERN_C const CLSID CLSID_ParticleSystemClass; -EXTERN_C const CLSID CLSID_ParticleSystemTypeClass; -EXTERN_C const CLSID CLSID_BulletClass; -EXTERN_C const CLSID CLSID_UnitClass; -EXTERN_C const CLSID CLSID_ParticleClass; -EXTERN_C const CLSID CLSID_ParticleTypeClass; -EXTERN_C const CLSID CLSID_WaveClass; -EXTERN_C const CLSID CLSID_BuildingLightClass; -EXTERN_C const CLSID CLSID_TerrainClass; -EXTERN_C const CLSID CLSID_TubeClass; -EXTERN_C const CLSID CLSID_TeamClass; -EXTERN_C const CLSID CLSID_TaskForceClass; -EXTERN_C const CLSID CLSID_TeamTypeClass; -EXTERN_C const CLSID CLSID_VoxelAnimTypeClass; -EXTERN_C const CLSID CLSID_ScriptClass; -EXTERN_C const CLSID CLSID_ScriptTypeClass; -EXTERN_C const CLSID CLSID_TagClass; -EXTERN_C const CLSID CLSID_TagTypeClass; -EXTERN_C const CLSID CLSID_TriggerClass; -EXTERN_C const CLSID CLSID_TriggerTypeClass; -EXTERN_C const CLSID CLSID_ActionClass; -EXTERN_C const CLSID CLSID_EventClass; -EXTERN_C const CLSID CLSID_FactoryClass; -EXTERN_C const CLSID CLSID_WeaponTypeClass; -EXTERN_C const CLSID CLSID_WarheadTypeClass; -EXTERN_C const CLSID CLSID_WaypointPath; -EXTERN_C const CLSID CLSID_LightSource; -EXTERN_C const CLSID CLSID_CampaignClass; -EXTERN_C const CLSID CLSID_SideClass; -EXTERN_C const CLSID CLSID_TiberiumClass; -EXTERN_C const CLSID CLSID_CellClass; -EXTERN_C const CLSID CLSID_EMPulseClass; -EXTERN_C const CLSID CLSID_TacticalMapClass; -EXTERN_C const CLSID CLSID_AITriggerTypeClass; -EXTERN_C const CLSID CLSID_AITriggerClass; -EXTERN_C const CLSID CLSID_NeuronClass; -EXTERN_C const CLSID CLSID_FoggedObjectClass; -EXTERN_C const CLSID CLSID_AlphaShapeClass; -EXTERN_C const CLSID CLSID_VeinholeMonsterClass; diff --git a/code/isun_i.c b/code/isun_i.c deleted file mode 100644 index 9b7a5c566..000000000 --- a/code/isun_i.c +++ /dev/null @@ -1,76 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -// The class identifiers isun.h declares; globals.cpp includes this file once. - -extern "C" { - -const CLSID CLSID_HouseClass = {0xD9D4A910,0x87C6,0x11D1,{0xB7,0x07,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_SuperWeaponTypeClass = {0x0CF2BCE7,0x36E4,0x11D2,{0xB8,0xD8,0x00,0x60,0x08,0xC8,0x09,0xED}}; -const CLSID CLSID_SuperWeaponClass = {0xD7F754C6,0x391C,0x11D2,{0x9B,0x64,0x00,0x10,0x4B,0x97,0x2F,0xE8}}; -const CLSID CLSID_UnitTypeClass = {0xDCBD42EA,0x0546,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_InfantryTypeClass = {0xAE8B33D8,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_AircraftTypeClass = {0xAE8B33D9,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_BuildingTypeClass = {0xAE8B33DB,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_BulletTypeClass = {0x5AF2CE77,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_TerrainTypeClass = {0x5AF2CE7B,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_IsometricTileTypeClass = {0x5AF2CE7A,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_OverlayTypeClass = {0x5AF2CE79,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_SmudgeTypeClass = {0x5AF2CE78,0x0634,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_AnimTypeClass = {0xAE8B33DA,0x061C,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_HouseTypeClass = {0x1DD43928,0x046B,0x11D2,{0xAC,0xA4,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_IsometricTileClass = {0x0E272DC0,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_VoxelAnimClass = {0x0E272DC1,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_AircraftClass = {0x0E272DC2,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_AnimClass = {0x0E272DC3,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_InfantryClass = {0x0E272DC4,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_SmudgeClass = {0x0E272DC5,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_BuildingClass = {0x0E272DC6,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_OverlayClass = {0x0E272DC7,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_ParticleSystemClass = {0x0E272DC8,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_ParticleSystemTypeClass = {0x703E044A,0x0FB1,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_BulletClass = {0x0E272DC9,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_UnitClass = {0x0E272DCA,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_ParticleClass = {0x0E272DCC,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_ParticleTypeClass = {0x703E044B,0x0FB1,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_WaveClass = {0x0E272DCD,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_BuildingLightClass = {0x54822258,0xD8A8,0x11D1,{0xB4,0x62,0x00,0x60,0x97,0xC6,0xA9,0x79}}; -const CLSID CLSID_TerrainClass = {0x0E272DCE,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_TubeClass = {0x0B4CA41C,0xB3A7,0x11D1,{0xB4,0x57,0x00,0x60,0x97,0xC6,0xA9,0x79}}; -const CLSID CLSID_TeamClass = {0x0E272DCF,0x9C0F,0x11D1,{0xB7,0x09,0x00,0xA0,0x24,0xDD,0xAF,0xD1}}; -const CLSID CLSID_TaskForceClass = {0x61DE341E,0x0774,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_TeamTypeClass = {0xD1DBA64E,0x0778,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_VoxelAnimTypeClass = {0x2EBB6D66,0x0D4D,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_ScriptClass = {0x42F3A646,0x0789,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_ScriptTypeClass = {0x42F3A647,0x0789,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_TagClass = {0x54F6E432,0x09ED,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_TagTypeClass = {0x54F6E433,0x09ED,0x11D2,{0xAC,0xA5,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_TriggerClass = {0xC02D1590,0x0A2A,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_TriggerTypeClass = {0xC02D1591,0x0A2A,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_ActionClass = {0x4F0EC392,0x0A55,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_EventClass = {0x4F0EC393,0x0A55,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_FactoryClass = {0x34ECD9A8,0x0AB0,0x11D2,{0xAC,0xA7,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_WeaponTypeClass = {0x9FD219CA,0x0F7B,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_WarheadTypeClass = {0xA8C54DA4,0x0F7B,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_WaypointPath = {0xF73125BA,0x1054,0x11D2,{0x81,0x72,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_LightSource = {0x6F9C48F0,0x1207,0x11D2,{0x81,0x74,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_CampaignClass = {0xFFDAC848,0x1517,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_SideClass = {0xC53DD372,0x151E,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_TiberiumClass = {0xC53DD373,0x151E,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_CellClass = {0xC1BF99CE,0x1A8C,0x11D2,{0x81,0x75,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_EMPulseClass = {0xB825CB22,0x200E,0x11D2,{0x9F,0xA9,0x00,0x60,0x08,0x9A,0xD4,0x58}}; -const CLSID CLSID_TacticalMapClass = {0xCF56B38A,0x240D,0x11D2,{0x81,0x7C,0x00,0x60,0x08,0x05,0x5B,0xB5}}; -const CLSID CLSID_AITriggerTypeClass = {0xBA093524,0x4CF4,0x11D2,{0xBC,0x26,0x00,0x10,0x4B,0x8F,0xB0,0x4D}}; -const CLSID CLSID_AITriggerClass = {0x03C4CE76,0x4CF5,0x11D2,{0xBC,0x26,0x00,0x10,0x4B,0x8F,0xB0,0x4D}}; -const CLSID CLSID_NeuronClass = {0x241AB316,0x4CF5,0x11D2,{0xBC,0x26,0x00,0x10,0x4B,0x8F,0xB0,0x4D}}; -const CLSID CLSID_FoggedObjectClass = {0x1C470B0E,0x69D7,0x11D2,{0xB8,0xF2,0x00,0x60,0x08,0xC8,0x09,0xED}}; -const CLSID CLSID_AlphaShapeClass = {0x623C7584,0x74E7,0x11D2,{0xB8,0xF5,0x00,0x60,0x08,0xC8,0x09,0xED}}; -const CLSID CLSID_VeinholeMonsterClass = {0x5192D06A,0xC632,0x11D2,{0xB9,0x0B,0x00,0x60,0x08,0xC8,0x09,0xED}}; - -} diff --git a/code/jumpjet.cpp b/code/jumpjet.cpp index 5e4ab7369..61fd2c653 100644 --- a/code/jumpjet.cpp +++ b/code/jumpjet.cpp @@ -236,17 +236,9 @@ void JumpjetLocomotionClass::Do_Turn(DirType coord) } -/// -/// Fetches the class identifier of this locomotor. -/// The persistence machinery uses the identifier to build the right kind of locomotor back -/// when a save game is loaded. -/// -/// Returns with S_OK, or E_POINTER if there is nowhere to put the answer. -HRESULT JumpjetLocomotionClass::GetClassID(CLSID * retval) +ClassID JumpjetLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_JumpjetLocomotion; - return(S_OK); + return(ClassID_JumpjetLocomotion); } diff --git a/code/jumpjet.h b/code/jumpjet.h index fffdefd2b..5a3872031 100644 --- a/code/jumpjet.h +++ b/code/jumpjet.h @@ -25,7 +25,7 @@ class JumpjetLocomotionClass : public LocomotionClass JumpjetLocomotionClass(void); virtual ~JumpjetLocomotionClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/levitate.cpp b/code/levitate.cpp index bb53a64a1..ca88f79b0 100644 --- a/code/levitate.cpp +++ b/code/levitate.cpp @@ -892,18 +892,9 @@ void LevitateLocomotionClass::Stop(void) } -/// -/// Fetches the class identifier of this locomotor. -/// The persistence system uses this identifier to create a locomotor of the right kind -/// when the object it drives is loaded back in. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT LevitateLocomotionClass::GetClassID(CLSID * retval) +ClassID LevitateLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_LevitateLocomotion; - return(S_OK); + return(ClassID_LevitateLocomotion); } diff --git a/code/levitate.h b/code/levitate.h index bd5bf3ce2..afc24654b 100644 --- a/code/levitate.h +++ b/code/levitate.h @@ -28,7 +28,7 @@ class LevitateLocomotionClass : public LocomotionClass LevitateLocomotionClass(void); virtual ~LevitateLocomotionClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/light.cpp b/code/light.cpp index 0175aa94e..40c6bb860 100644 --- a/code/light.cpp +++ b/code/light.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "light.h" @@ -278,16 +277,9 @@ void LightSourceClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier this object is persisted under. -/// -/// Destination for the class identifier. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT LightSourceClass::GetClassID(CLSID * retval) +ClassID LightSourceClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_LightSource; - return(S_OK); + return(ClassID_LightSource); } diff --git a/code/light.h b/code/light.h index 68bb1fec5..9585de25c 100644 --- a/code/light.h +++ b/code/light.h @@ -25,7 +25,7 @@ class LightSourceClass : public AbstractClass LightSourceClass(void); virtual ~LightSourceClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/loco.cpp b/code/loco.cpp index 7f848c37e..35d78bc0b 100644 --- a/code/loco.cpp +++ b/code/loco.cpp @@ -185,7 +185,7 @@ bool LocomotionClass::Is_Ion_Sensitive(void) } -std::unique_ptr Create_Locomotor(CLSID const & classid) +std::unique_ptr Create_Locomotor(ClassID const & classid) { IPersistent * const object = Create_Object(classid); ILocomotion * const locomotion = dynamic_cast(object); @@ -211,14 +211,10 @@ std::unique_ptr Load_Locomotor(SaveStreamClass & stream) } -CLSID Locomotion_Class_ID(ILocomotion * locomotion) +ClassID Locomotion_Class_ID(ILocomotion * locomotion) { - CLSID classid = CLSID_NULL; - IPersistent * const persist = dynamic_cast(locomotion); - if (persist != NULL) { - persist->GetClassID(&classid); - } - return(classid); + IPersistent const * const persist = dynamic_cast(locomotion); + return(persist != NULL ? persist->Class_ID() : ClassID()); } diff --git a/code/loco.h b/code/loco.h index 161ed4434..976dcd9ec 100644 --- a/code/loco.h +++ b/code/loco.h @@ -10,19 +10,20 @@ #pragma once #include "coord.h" -#include "ilocos.h" +#include "classids.h" +#include "iloco.h" #include "persist.h" class FootClass; class SaveStreamClass; // The class identifier of a locomotor reached through its locomotion interface, or -// CLSID_NULL when it is not one of ours. -CLSID Locomotion_Class_ID(ILocomotion * locomotion); +// all zero when it is not one of ours. +ClassID Locomotion_Class_ID(ILocomotion * locomotion); // A new, unlinked locomotor of the registered class, or nothing when the identifier // names no locomotor. -std::unique_ptr Create_Locomotor(CLSID const & classid); +std::unique_ptr Create_Locomotor(ClassID const & classid); // The locomotor whose record is next in the stream, or nothing when the record names // something that is not one, which fails the stream. diff --git a/code/mech.cpp b/code/mech.cpp index 07376fc4e..0ddf517a6 100644 --- a/code/mech.cpp +++ b/code/mech.cpp @@ -651,18 +651,9 @@ bool MechLocomotionClass::Mark_Head_To(Coord const & coord) } -/// -/// Fetches the class identifier of this locomotor. -/// The persistence layer uses this identifier to create a locomotor of the right kind -/// when a saved game is loaded. -/// -/// Pointer to the buffer to fill in with the class identifier. -/// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT MechLocomotionClass::GetClassID(CLSID * retval) +ClassID MechLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_MechLocomotion; - return(S_OK); + return(ClassID_MechLocomotion); } diff --git a/code/mech.h b/code/mech.h index bf7123ed3..cb7a6b6ec 100644 --- a/code/mech.h +++ b/code/mech.h @@ -27,7 +27,7 @@ class MechLocomotionClass : public LocomotionClass MechLocomotionClass(void); virtual ~MechLocomotionClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/overlay.cpp b/code/overlay.cpp index 2000810a1..2de57e657 100644 --- a/code/overlay.cpp +++ b/code/overlay.cpp @@ -36,7 +36,6 @@ * OverlayClass::new -- Allocates a overlay object from pool * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "overlay.h" diff --git a/code/overlay.h b/code/overlay.h index 2cea474f0..bf5a122a3 100644 --- a/code/overlay.h +++ b/code/overlay.h @@ -32,7 +32,7 @@ #pragma once -#include "isun.h" +#include "classids.h" #include "object.h" #include "overlay.hh" @@ -63,7 +63,7 @@ class OverlayClass : public ObjectClass OverlayClass(OverlayTypeClass const * ttype, Cell const & pos = CELL_NONE, HousesType = HOUSE_NONE); virtual ~OverlayClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override {if (retval == NULL) return(E_POINTER);*retval = CLSID_OverlayClass;return(S_OK);} + virtual ClassID Class_ID(void) const override {return(ClassID_OverlayClass);} virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/overtype.cpp b/code/overtype.cpp index 3c41c51ed..8ce1f2cdd 100644 --- a/code/overtype.cpp +++ b/code/overtype.cpp @@ -46,7 +46,6 @@ * OverlayTypeClass::operator new -- Allocate an overlay type class object from pool. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "overtype.h" @@ -482,17 +481,9 @@ void OverlayTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// The save system asks for this so that it knows which class to construct when the object -/// is read back out of a save file. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT OverlayTypeClass::GetClassID(CLSID * retval) +ClassID OverlayTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_OverlayTypeClass; - return(S_OK); + return(ClassID_OverlayTypeClass); } diff --git a/code/overtype.h b/code/overtype.h index ac5eb5c9b..b80e1f56b 100644 --- a/code/overtype.h +++ b/code/overtype.h @@ -159,7 +159,7 @@ class OverlayTypeClass: public ObjectTypeClass OverlayTypeClass(char const * ininame = NULL); ~OverlayTypeClass(void); - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/particle.cpp b/code/particle.cpp index 563274359..dbdf2dcbd 100644 --- a/code/particle.cpp +++ b/code/particle.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "particle.h" @@ -993,18 +992,9 @@ int ParticleClass::Shape_Number(void) const } -/// -/// Fetches the class identifier of this object. -/// The persistence code uses this identifier to recreate the correct object when the -/// save file is loaded back in. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT ParticleClass::GetClassID(CLSID * retval) +ClassID ParticleClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_ParticleClass; - return(S_OK); + return(ClassID_ParticleClass); } diff --git a/code/particle.h b/code/particle.h index 20b3809a1..c17a87efe 100644 --- a/code/particle.h +++ b/code/particle.h @@ -31,7 +31,7 @@ class ParticleClass : public ObjectClass ParticleClass(ParticleTypeClass const * type, Coord const & origin, Coord const & target = COORD_NONE, ParticleSystemClass * partsys = NULL); virtual ~ParticleClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual HRESULT Save(SaveStreamClass & stream, BOOL cleardirty) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/partsys.cpp b/code/partsys.cpp index 65d95ee02..9629812fe 100644 --- a/code/partsys.cpp +++ b/code/partsys.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "partsys.h" @@ -872,18 +871,9 @@ void ParticleSystemClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier for this object. -/// This routine is part of the persistence support. The save process records the -/// identifier so that the load process knows what kind of object to build. -/// -/// Pointer to the class identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT ParticleSystemClass::GetClassID(CLSID * retval) +ClassID ParticleSystemClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_ParticleSystemClass; - return(S_OK); + return(ClassID_ParticleSystemClass); } diff --git a/code/partsys.h b/code/partsys.h index 73739bfa5..41bc50380 100644 --- a/code/partsys.h +++ b/code/partsys.h @@ -31,7 +31,7 @@ class ParticleSystemClass : public ObjectClass ParticleSystemClass(void); virtual ~ParticleSystemClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/persist.h b/code/persist.h index c4b39a2c3..d87f60c24 100644 --- a/code/persist.h +++ b/code/persist.h @@ -11,7 +11,7 @@ #include "win.h" -#include +#include "classid.h" class SaveStreamClass; @@ -21,7 +21,7 @@ struct IPersistent { virtual ~IPersistent(void) {} - virtual HRESULT GetClassID(CLSID * classid) = 0; + virtual ClassID Class_ID(void) const = 0; virtual HRESULT Load(SaveStreamClass & stream) = 0; // Restores what the record could not carry, once the record has been checked; an object // takes its place in the map or a side table here, never while its record is still in doubt. diff --git a/code/psystype.cpp b/code/psystype.cpp index 967f9d5e9..439a4fdd9 100644 --- a/code/psystype.cpp +++ b/code/psystype.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "psystype.h" @@ -181,18 +180,9 @@ void ParticleSystemTypeClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the persistence interface. The save system records the class -/// ID so that the right kind of object can be manufactured when the game is reloaded. -/// -/// Pointer to the class ID to be filled in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT ParticleSystemTypeClass::GetClassID(CLSID * retval) +ClassID ParticleSystemTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_ParticleSystemTypeClass; - return(S_OK); + return(ClassID_ParticleSystemTypeClass); } diff --git a/code/psystype.h b/code/psystype.h index 8c2f4c406..c9d1fb379 100644 --- a/code/psystype.h +++ b/code/psystype.h @@ -25,7 +25,7 @@ class ParticleSystemTypeClass : public ObjectTypeClass ParticleSystemTypeClass(char const * ininame = NULL); virtual ~ParticleSystemTypeClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/ptype.cpp b/code/ptype.cpp index 53620fc39..5644fea15 100644 --- a/code/ptype.cpp +++ b/code/ptype.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "ptype.h" @@ -220,18 +219,9 @@ void ParticleTypeClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier of this object. -/// The save game system uses this identifier to know which kind of object to build -/// when the stream is read back in. -/// -/// Pointer to the location to store the class identifier. -/// Returns with S_OK, or E_POINTER if no storage location was supplied. -HRESULT ParticleTypeClass::GetClassID(CLSID * retval) +ClassID ParticleTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_ParticleTypeClass; - return(S_OK); + return(ClassID_ParticleTypeClass); } diff --git a/code/ptype.h b/code/ptype.h index dbbbe7214..23599468b 100644 --- a/code/ptype.h +++ b/code/ptype.h @@ -28,7 +28,7 @@ class ParticleTypeClass : public ObjectTypeClass ParticleTypeClass(char const * ininame = NULL); virtual ~ParticleTypeClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/reinf.cpp b/code/reinf.cpp index d2bafbfb6..c3b072a86 100644 --- a/code/reinf.cpp +++ b/code/reinf.cpp @@ -50,7 +50,7 @@ #include "foot.h" #include "globals.h" #include "house.h" -#include "ilocos.h" +#include "classids.h" #include "incdec.h" #include "inline.h" #include "mouse.h" @@ -533,7 +533,7 @@ inline bool _Can_Burrow(FootClass * object) { while (object != NULL) { TechnoTypeClass const * tclass = object->TClass; - if (tclass->Locomotor != CLSID_TunnelLocomotion) { + if (tclass->Locomotor != ClassID_TunnelLocomotion) { return(false); } object = (FootClass *)object->Next; diff --git a/code/saveload.cpp b/code/saveload.cpp index c91b94719..3fc6c9821 100644 --- a/code/saveload.cpp +++ b/code/saveload.cpp @@ -42,7 +42,6 @@ * Save_Misc_Values -- saves miscellaneous variables * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "saveload.h" @@ -169,19 +168,14 @@ HRESULT Save_Object(SaveStreamClass & stream, IPersistent * persist) return(E_POINTER); } - CLSID classid; - HRESULT result = persist->GetClassID(&classid); - if (FAILED(result)) { - return(result); - } - + ClassID classid = persist->Class_ID(); stream.Serialize_Bytes(&classid, sizeof(classid)); unsigned int const lengthat = stream.Offset(); unsigned int length = 0; stream.Serialize(length); unsigned int const start = stream.Offset(); - result = persist->Save(stream, TRUE); + HRESULT result = persist->Save(stream, TRUE); if (FAILED(result)) { return(result); } @@ -212,7 +206,7 @@ HRESULT Save_Object(SaveStreamClass & stream, ILocomotion * locomotion) /// not match what the object consumed. IPersistent * Load_Object(SaveStreamClass & stream) { - CLSID classid; + ClassID classid; unsigned int length = 0; stream.Serialize_Bytes(&classid, sizeof(classid)); stream.Serialize(length); diff --git a/code/savestream.cpp b/code/savestream.cpp index c6f9e5915..9c7ce0d36 100644 --- a/code/savestream.cpp +++ b/code/savestream.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "savestream.h" diff --git a/code/script.cpp b/code/script.cpp index 8566ad2b0..c1be22e65 100644 --- a/code/script.cpp +++ b/code/script.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "script.h" @@ -151,18 +150,9 @@ bool ScriptClass::Has_Missions_Remaining(void) } -/// -/// Fetches the class identifier used to persist this script. -/// This routine is part of the IPersistent contract that the save game system relies -/// on to recreate objects when a game is loaded. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT ScriptClass::GetClassID(CLSID * retval) +ClassID ScriptClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_ScriptClass; - return(S_OK); + return(ClassID_ScriptClass); } @@ -368,18 +358,9 @@ ScriptTypeClass * ScriptTypeClass::Find_Or_Make(char const * name) } -/// -/// Fetches the class identifier used to persist this script type. -/// This routine is part of the IPersistent contract that the save game system relies -/// on to recreate objects when a game is loaded. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT ScriptTypeClass::GetClassID(CLSID * retval) +ClassID ScriptTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_ScriptTypeClass; - return(S_OK); + return(ClassID_ScriptTypeClass); } diff --git a/code/script.h b/code/script.h index 643a9a532..cdabf2457 100644 --- a/code/script.h +++ b/code/script.h @@ -29,7 +29,7 @@ class ScriptClass : public AbstractClass ScriptClass(ScriptTypeClass *type = NULL); virtual ~ScriptClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; @@ -76,7 +76,7 @@ class ScriptTypeClass : public AbstractTypeClass static ScriptTypeClass * Find_Or_Make(char const * ininame = NULL); - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; static void Read_All(CCINIClass const & ini, INIScopeType scope); static void Write_All(CCINIClass & ini, INIScopeType scope); diff --git a/code/side.cpp b/code/side.cpp index 160e79ca2..679ba158a 100644 --- a/code/side.cpp +++ b/code/side.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "side.h" @@ -132,18 +131,9 @@ bool SideClass::Read_INI(CCINIClass const & ini) } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the IPersist interface. It is used by the save and load -/// system to recognize what kind of object it is about to create. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT SideClass::GetClassID(CLSID * retval) +ClassID SideClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_SideClass; - return(S_OK); + return(ClassID_SideClass); } diff --git a/code/side.h b/code/side.h index 539f53d14..f93d10c45 100644 --- a/code/side.h +++ b/code/side.h @@ -30,7 +30,7 @@ class SideClass : public AbstractTypeClass SideClass(char const * ininame = NULL); virtual ~SideClass() override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; /* ** Query functions. diff --git a/code/smudge.cpp b/code/smudge.cpp index 7c09d4c2b..ef0a2c89d 100644 --- a/code/smudge.cpp +++ b/code/smudge.cpp @@ -38,7 +38,6 @@ * SmudgeClass::operator new -- Creator of smudge objects. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "smudge.h" @@ -301,16 +300,7 @@ RTTIType SmudgeClass::Fetch_RTTI(void) const } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the IPersist interface. It is used by the save and load -/// system to recognize what kind of object it is about to create. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT SmudgeClass::GetClassID(CLSID * retval) +ClassID SmudgeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_SmudgeClass; - return(S_OK); + return(ClassID_SmudgeClass); } diff --git a/code/smudge.h b/code/smudge.h index 802bee1d4..f37359599 100644 --- a/code/smudge.h +++ b/code/smudge.h @@ -59,7 +59,7 @@ class SmudgeClass : public ObjectClass SmudgeClass(SmudgeTypeClass const * type, Coord const & pos = COORD_NONE, HousesType = HOUSE_NONE); virtual ~SmudgeClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/smudtype.cpp b/code/smudtype.cpp index 7872b53d2..8f3e12cb2 100644 --- a/code/smudtype.cpp +++ b/code/smudtype.cpp @@ -44,7 +44,6 @@ * SmudgetypeClass::Occupy_List -- Determines occupation list for smudge object. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "smudtype.h" @@ -337,17 +336,9 @@ void SmudgeTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// The save system asks for this so that it knows which class to construct when the object -/// is read back out of a save file. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT SmudgeTypeClass::GetClassID(CLSID * retval) +ClassID SmudgeTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_SmudgeTypeClass; - return(S_OK); + return(ClassID_SmudgeTypeClass); } diff --git a/code/smudtype.h b/code/smudtype.h index 0ed1afedd..ad920b460 100644 --- a/code/smudtype.h +++ b/code/smudtype.h @@ -59,7 +59,7 @@ class SmudgeTypeClass : public ObjectTypeClass SmudgeTypeClass(char const * ininame = NULL); virtual ~SmudgeTypeClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/startup.cpp b/code/startup.cpp index 5ca22da0a..8b7b51646 100644 --- a/code/startup.cpp +++ b/code/startup.cpp @@ -32,7 +32,6 @@ * main -- Initial startup routine (preps library systems). * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "_alpha.h" @@ -229,72 +228,72 @@ static void RegisterClasses(void) { #define REGISTER_CLASS(_class, _clsid) Register_Class<_class>(_clsid); - REGISTER_CLASS(WaveClass, CLSID_WaveClass); - REGISTER_CLASS(TerrainTypeClass, CLSID_TerrainTypeClass); - REGISTER_CLASS(TerrainClass, CLSID_TerrainClass); - REGISTER_CLASS(SuperWeaponTypeClass, CLSID_SuperWeaponTypeClass); - REGISTER_CLASS(SuperClass, CLSID_SuperWeaponClass); - REGISTER_CLASS(Tactical, CLSID_TacticalMapClass); - REGISTER_CLASS(CellClass, CLSID_CellClass); - REGISTER_CLASS(EMPulseClass, CLSID_EMPulseClass); - REGISTER_CLASS(LightSourceClass, CLSID_LightSource); - REGISTER_CLASS(SideClass, CLSID_SideClass); - REGISTER_CLASS(TiberiumClass, CLSID_TiberiumClass); - REGISTER_CLASS(TubeClass, CLSID_TubeClass); - REGISTER_CLASS(CampaignClass, CLSID_CampaignClass); - REGISTER_CLASS(BuildingLightClass, CLSID_BuildingLightClass); - REGISTER_CLASS(WaypointPathClass, CLSID_WaypointPath); - REGISTER_CLASS(TEventClass, CLSID_EventClass); - REGISTER_CLASS(VoxelAnimTypeClass, CLSID_VoxelAnimTypeClass); - REGISTER_CLASS(VoxelAnimClass, CLSID_VoxelAnimClass); - REGISTER_CLASS(TActionClass, CLSID_ActionClass); - REGISTER_CLASS(TriggerClass, CLSID_TriggerClass); - REGISTER_CLASS(TriggerTypeClass, CLSID_TriggerTypeClass); - REGISTER_CLASS(ScriptClass, CLSID_ScriptClass); - REGISTER_CLASS(ScriptTypeClass, CLSID_ScriptTypeClass); - REGISTER_CLASS(TagClass, CLSID_TagClass); - REGISTER_CLASS(TagTypeClass, CLSID_TagTypeClass); - REGISTER_CLASS(TeamClass, CLSID_TeamClass); - REGISTER_CLASS(TeamTypeClass, CLSID_TeamTypeClass); - REGISTER_CLASS(TaskForceClass, CLSID_TaskForceClass); - REGISTER_CLASS(UnitTypeClass, CLSID_UnitTypeClass); - REGISTER_CLASS(BuildingTypeClass, CLSID_BuildingTypeClass); - REGISTER_CLASS(AircraftTypeClass, CLSID_AircraftTypeClass); - REGISTER_CLASS(InfantryTypeClass, CLSID_InfantryTypeClass); - REGISTER_CLASS(BulletTypeClass, CLSID_BulletTypeClass); - REGISTER_CLASS(IsometricTileTypeClass, CLSID_IsometricTileTypeClass); - REGISTER_CLASS(OverlayTypeClass, CLSID_OverlayTypeClass); - REGISTER_CLASS(SmudgeTypeClass, CLSID_SmudgeTypeClass); - REGISTER_CLASS(UnitClass, CLSID_UnitClass); - REGISTER_CLASS(BuildingClass, CLSID_BuildingClass); - REGISTER_CLASS(AircraftClass, CLSID_AircraftClass); - REGISTER_CLASS(InfantryClass, CLSID_InfantryClass); - REGISTER_CLASS(AnimClass, CLSID_AnimClass); - REGISTER_CLASS(AnimTypeClass, CLSID_AnimTypeClass); - REGISTER_CLASS(HouseTypeClass, CLSID_HouseTypeClass); - REGISTER_CLASS(HouseClass, CLSID_HouseClass); - REGISTER_CLASS(DriveLocomotionClass, CLSID_DriveLocomotion); - REGISTER_CLASS(JumpjetLocomotionClass, CLSID_JumpjetLocomotion); - REGISTER_CLASS(HoverLocomotionClass, CLSID_HoverLocomotion); - REGISTER_CLASS(TunnelLocomotionClass, CLSID_TunnelLocomotion); - REGISTER_CLASS(WalkLocomotionClass, CLSID_WalkLocomotion); - REGISTER_CLASS(DropPodLocomotionClass, CLSID_BallisticLocomotion); - REGISTER_CLASS(FlyLocomotionClass, CLSID_FlyerLocomotion); - REGISTER_CLASS(TeleportLocomotionClass, CLSID_TeleportLocomotion); - REGISTER_CLASS(MechLocomotionClass, CLSID_MechLocomotion); - REGISTER_CLASS(LevitateLocomotionClass, CLSID_LevitateLocomotion); - REGISTER_CLASS(BulletClass, CLSID_BulletClass); - REGISTER_CLASS(FactoryClass, CLSID_FactoryClass); - REGISTER_CLASS(WarheadTypeClass, CLSID_WarheadTypeClass); - REGISTER_CLASS(WeaponTypeClass, CLSID_WeaponTypeClass); - REGISTER_CLASS(ParticleClass, CLSID_ParticleClass); - REGISTER_CLASS(ParticleTypeClass, CLSID_ParticleTypeClass); - REGISTER_CLASS(ParticleSystemClass, CLSID_ParticleSystemClass); - REGISTER_CLASS(ParticleSystemTypeClass, CLSID_ParticleSystemTypeClass); - REGISTER_CLASS(AITriggerTypeClass, CLSID_AITriggerTypeClass); - REGISTER_CLASS(NeuronClass, CLSID_NeuronClass); - REGISTER_CLASS(FoggedObjectClass, CLSID_FoggedObjectClass); - REGISTER_CLASS(AlphaShapeClass, CLSID_AlphaShapeClass); + REGISTER_CLASS(WaveClass, ClassID_WaveClass); + REGISTER_CLASS(TerrainTypeClass, ClassID_TerrainTypeClass); + REGISTER_CLASS(TerrainClass, ClassID_TerrainClass); + REGISTER_CLASS(SuperWeaponTypeClass, ClassID_SuperWeaponTypeClass); + REGISTER_CLASS(SuperClass, ClassID_SuperWeaponClass); + REGISTER_CLASS(Tactical, ClassID_TacticalMapClass); + REGISTER_CLASS(CellClass, ClassID_CellClass); + REGISTER_CLASS(EMPulseClass, ClassID_EMPulseClass); + REGISTER_CLASS(LightSourceClass, ClassID_LightSource); + REGISTER_CLASS(SideClass, ClassID_SideClass); + REGISTER_CLASS(TiberiumClass, ClassID_TiberiumClass); + REGISTER_CLASS(TubeClass, ClassID_TubeClass); + REGISTER_CLASS(CampaignClass, ClassID_CampaignClass); + REGISTER_CLASS(BuildingLightClass, ClassID_BuildingLightClass); + REGISTER_CLASS(WaypointPathClass, ClassID_WaypointPath); + REGISTER_CLASS(TEventClass, ClassID_EventClass); + REGISTER_CLASS(VoxelAnimTypeClass, ClassID_VoxelAnimTypeClass); + REGISTER_CLASS(VoxelAnimClass, ClassID_VoxelAnimClass); + REGISTER_CLASS(TActionClass, ClassID_ActionClass); + REGISTER_CLASS(TriggerClass, ClassID_TriggerClass); + REGISTER_CLASS(TriggerTypeClass, ClassID_TriggerTypeClass); + REGISTER_CLASS(ScriptClass, ClassID_ScriptClass); + REGISTER_CLASS(ScriptTypeClass, ClassID_ScriptTypeClass); + REGISTER_CLASS(TagClass, ClassID_TagClass); + REGISTER_CLASS(TagTypeClass, ClassID_TagTypeClass); + REGISTER_CLASS(TeamClass, ClassID_TeamClass); + REGISTER_CLASS(TeamTypeClass, ClassID_TeamTypeClass); + REGISTER_CLASS(TaskForceClass, ClassID_TaskForceClass); + REGISTER_CLASS(UnitTypeClass, ClassID_UnitTypeClass); + REGISTER_CLASS(BuildingTypeClass, ClassID_BuildingTypeClass); + REGISTER_CLASS(AircraftTypeClass, ClassID_AircraftTypeClass); + REGISTER_CLASS(InfantryTypeClass, ClassID_InfantryTypeClass); + REGISTER_CLASS(BulletTypeClass, ClassID_BulletTypeClass); + REGISTER_CLASS(IsometricTileTypeClass, ClassID_IsometricTileTypeClass); + REGISTER_CLASS(OverlayTypeClass, ClassID_OverlayTypeClass); + REGISTER_CLASS(SmudgeTypeClass, ClassID_SmudgeTypeClass); + REGISTER_CLASS(UnitClass, ClassID_UnitClass); + REGISTER_CLASS(BuildingClass, ClassID_BuildingClass); + REGISTER_CLASS(AircraftClass, ClassID_AircraftClass); + REGISTER_CLASS(InfantryClass, ClassID_InfantryClass); + REGISTER_CLASS(AnimClass, ClassID_AnimClass); + REGISTER_CLASS(AnimTypeClass, ClassID_AnimTypeClass); + REGISTER_CLASS(HouseTypeClass, ClassID_HouseTypeClass); + REGISTER_CLASS(HouseClass, ClassID_HouseClass); + REGISTER_CLASS(DriveLocomotionClass, ClassID_DriveLocomotion); + REGISTER_CLASS(JumpjetLocomotionClass, ClassID_JumpjetLocomotion); + REGISTER_CLASS(HoverLocomotionClass, ClassID_HoverLocomotion); + REGISTER_CLASS(TunnelLocomotionClass, ClassID_TunnelLocomotion); + REGISTER_CLASS(WalkLocomotionClass, ClassID_WalkLocomotion); + REGISTER_CLASS(DropPodLocomotionClass, ClassID_BallisticLocomotion); + REGISTER_CLASS(FlyLocomotionClass, ClassID_FlyerLocomotion); + REGISTER_CLASS(TeleportLocomotionClass, ClassID_TeleportLocomotion); + REGISTER_CLASS(MechLocomotionClass, ClassID_MechLocomotion); + REGISTER_CLASS(LevitateLocomotionClass, ClassID_LevitateLocomotion); + REGISTER_CLASS(BulletClass, ClassID_BulletClass); + REGISTER_CLASS(FactoryClass, ClassID_FactoryClass); + REGISTER_CLASS(WarheadTypeClass, ClassID_WarheadTypeClass); + REGISTER_CLASS(WeaponTypeClass, ClassID_WeaponTypeClass); + REGISTER_CLASS(ParticleClass, ClassID_ParticleClass); + REGISTER_CLASS(ParticleTypeClass, ClassID_ParticleTypeClass); + REGISTER_CLASS(ParticleSystemClass, ClassID_ParticleSystemClass); + REGISTER_CLASS(ParticleSystemTypeClass, ClassID_ParticleSystemTypeClass); + REGISTER_CLASS(AITriggerTypeClass, ClassID_AITriggerTypeClass); + REGISTER_CLASS(NeuronClass, ClassID_NeuronClass); + REGISTER_CLASS(FoggedObjectClass, ClassID_FoggedObjectClass); + REGISTER_CLASS(AlphaShapeClass, ClassID_AlphaShapeClass); } /// diff --git a/code/sun.h b/code/sun.h index 1364b5222..d5b7885df 100644 --- a/code/sun.h +++ b/code/sun.h @@ -13,9 +13,7 @@ #pragma once -#ifdef INCLUDE_COM -#include "isun.h" -#endif +#include "classids.h" #include /// Everything from here on is the content of defines.h. diff --git a/code/super.cpp b/code/super.cpp index 589a7ae6d..e9b9117ed 100644 --- a/code/super.cpp +++ b/code/super.cpp @@ -40,7 +40,6 @@ * SuperClass::Suspend -- Suspend the charging of the super weapon. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "super.h" @@ -801,15 +800,9 @@ bool SuperClass::Is_Charging(void) const } -/// -/// Fetches the persistent class identifier for the super weapon. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT SuperClass::GetClassID(CLSID * retval) +ClassID SuperClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_SuperWeaponClass; - return(S_OK); + return(ClassID_SuperWeaponClass); } diff --git a/code/super.h b/code/super.h index 9482e558f..2de481e3c 100644 --- a/code/super.h +++ b/code/super.h @@ -48,7 +48,7 @@ class SuperClass : public AbstractClass SuperClass(SuperWeaponTypeClass * type, HouseClass * owner); virtual ~SuperClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/suprtype.cpp b/code/suprtype.cpp index 2d25a57a6..67a37738d 100644 --- a/code/suprtype.cpp +++ b/code/suprtype.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "suprtype.h" @@ -97,17 +96,9 @@ SuperWeaponTypeClass::~SuperWeaponTypeClass(void) } -/// -/// Fetches the class identifier of this object. -/// The save game system uses this to know which class to construct when the object is -/// read back in. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT SuperWeaponTypeClass::GetClassID(CLSID * retval) +ClassID SuperWeaponTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_SuperWeaponTypeClass; - return(S_OK); + return(ClassID_SuperWeaponTypeClass); } diff --git a/code/suprtype.h b/code/suprtype.h index 26e807f41..66ff6b4e0 100644 --- a/code/suprtype.h +++ b/code/suprtype.h @@ -33,7 +33,7 @@ class SuperWeaponTypeClass : public AbstractTypeClass SuperWeaponTypeClass(char const * ininame = NULL); virtual ~SuperWeaponTypeClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/tactical.cpp b/code/tactical.cpp index 848c1788e..1935c14eb 100644 --- a/code/tactical.cpp +++ b/code/tactical.cpp @@ -11,7 +11,6 @@ * disclaimers apply; see LICENSE.md. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "tactical.h" @@ -3724,17 +3723,9 @@ bool Tactical::Draw_3D_Line(Coord const & coord1, Coord const & coord2, int colo } -/// -/// Fetches the class identifier of the tactical map. -/// This routine is used by the persistence system to recognize the object when it is read -/// back out of a save game. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT Tactical::GetClassID(CLSID * retval) +ClassID Tactical::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TacticalMapClass; - return(S_OK); + return(ClassID_TacticalMapClass); } diff --git a/code/tactical.h b/code/tactical.h index 3af1ccf2c..91ba6de7d 100644 --- a/code/tactical.h +++ b/code/tactical.h @@ -103,7 +103,7 @@ class Tactical : public AbstractClass Tactical(void); virtual ~Tactical(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual RTTIType Fetch_RTTI(void) const override {return(RTTI_TACTICALMAP);} diff --git a/code/taction.cpp b/code/taction.cpp index 639fd2335..ee9549525 100644 --- a/code/taction.cpp +++ b/code/taction.cpp @@ -40,7 +40,6 @@ * ActionChoiceClass::Draw_It -- Display the action choice as part of a list box. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "taction.h" @@ -2956,18 +2955,9 @@ NeedType Action_Needs(TActionType action) } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the persistence machinery to recognize what kind of object it -/// is about to load back. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT TActionClass::GetClassID(CLSID * retval) +ClassID TActionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_ActionClass; - return(S_OK); + return(ClassID_ActionClass); } diff --git a/code/taction.h b/code/taction.h index 216968356..c87a3f454 100644 --- a/code/taction.h +++ b/code/taction.h @@ -140,7 +140,7 @@ class TActionClass : public AbstractClass TActionClass(void); virtual ~TActionClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/tag.cpp b/code/tag.cpp index 7ff4afe64..83dd8384a 100644 --- a/code/tag.cpp +++ b/code/tag.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "tag.h" @@ -424,16 +423,9 @@ void TagClass::Detach(AbstractClass const * target, bool all) } -/// -/// Fetches the class identifier that this tag persists under. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT TagClass::GetClassID(CLSID * retval) +ClassID TagClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TagClass; - return(S_OK); + return(ClassID_TagClass); } diff --git a/code/tag.h b/code/tag.h index 48dc3c128..8e04d6ab9 100644 --- a/code/tag.h +++ b/code/tag.h @@ -29,7 +29,7 @@ class TagClass : public AbstractClass TagClass(TagTypeClass * type=NULL); virtual ~TagClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/tagtype.cpp b/code/tagtype.cpp index 4069fb24f..26bef7fbf 100644 --- a/code/tagtype.cpp +++ b/code/tagtype.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "tagtype.h" @@ -376,18 +375,9 @@ TagTypeClass * TagTypeClass::Find_Or_Make(char const * name) } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the save and load system so that a tag type can be -/// recognized when it is read back out of a stream. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT TagTypeClass::GetClassID(CLSID * retval) +ClassID TagTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TagTypeClass; - return(S_OK); + return(ClassID_TagTypeClass); } diff --git a/code/tagtype.h b/code/tagtype.h index d8ed014b5..59c60e29a 100644 --- a/code/tagtype.h +++ b/code/tagtype.h @@ -33,7 +33,7 @@ class TagTypeClass : public AbstractTypeClass TagTypeClass(char const * name = NULL); virtual ~TagTypeClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; static TagTypeClass * From_Name(char const * name); diff --git a/code/taskforc.cpp b/code/taskforc.cpp index 3b1e21090..6f8a85060 100644 --- a/code/taskforc.cpp +++ b/code/taskforc.cpp @@ -11,7 +11,6 @@ * disclaimers apply; see LICENSE.md. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "taskforc.h" @@ -319,17 +318,9 @@ void TaskForceClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the save game code so that an object of the right kind can -/// be created when the game is loaded back in. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT TaskForceClass::GetClassID(CLSID * retval) +ClassID TaskForceClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TaskForceClass; - return(S_OK); + return(ClassID_TaskForceClass); } diff --git a/code/taskforc.h b/code/taskforc.h index d867b2c9c..d9ab3d0cd 100644 --- a/code/taskforc.h +++ b/code/taskforc.h @@ -25,7 +25,7 @@ class TaskForceClass : public AbstractTypeClass TaskForceClass(char const *name=NULL); virtual ~TaskForceClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; static TaskForceClass * Find_Or_Make(char const * name); diff --git a/code/team.cpp b/code/team.cpp index 25aa6f721..4c360d150 100644 --- a/code/team.cpp +++ b/code/team.cpp @@ -70,7 +70,6 @@ * _Is_It_Playing -- Determines if unit is active and an initiated team member. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "team.h" @@ -2296,18 +2295,9 @@ void TeamClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the persistence contract and is what allows the save game loader -/// to recognize a team when it reads one back in. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT TeamClass::GetClassID(CLSID * retval) +ClassID TeamClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TeamClass; - return(S_OK); + return(ClassID_TeamClass); } diff --git a/code/team.h b/code/team.h index 2dd603251..c110225ea 100644 --- a/code/team.h +++ b/code/team.h @@ -265,7 +265,7 @@ class TeamClass : public AbstractClass TeamClass(TeamTypeClass const * team=0, HouseClass * owner=0, void * = NULL); virtual ~TeamClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/teamtype.cpp b/code/teamtype.cpp index f4bade88c..a4809b702 100644 --- a/code/teamtype.cpp +++ b/code/teamtype.cpp @@ -54,7 +54,6 @@ * TeamTypeClass::~TeamTypeClass -- class destructor * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "teamtype.h" @@ -890,18 +889,9 @@ void TeamTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// This is part of the persistence contract that the save and load code leans on to -/// recognize what it is reading back. -/// -/// Returns with S_OK and the class identifier filled in, or E_POINTER if no -/// destination was supplied. -HRESULT TeamTypeClass::GetClassID(CLSID * retval) +ClassID TeamTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TeamTypeClass; - return(S_OK); + return(ClassID_TeamTypeClass); } diff --git a/code/teamtype.h b/code/teamtype.h index d9a67ec01..d7c369fd1 100644 --- a/code/teamtype.h +++ b/code/teamtype.h @@ -68,7 +68,7 @@ class TeamTypeClass : public AbstractTypeClass TeamTypeClass(char const * name = NULL); virtual ~TeamTypeClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; static TeamTypeClass * Find_Or_Make(char const * ininame = NULL); diff --git a/code/techtype.cpp b/code/techtype.cpp index 17841f17c..bfac628bc 100644 --- a/code/techtype.cpp +++ b/code/techtype.cpp @@ -27,7 +27,7 @@ #include "combat.h" #include "findmake.h" #include "globals.h" -#include "ilocos.h" +#include "classids.h" #include "infatype.h" #include "mixfile.h" #include "psystype.h" @@ -122,7 +122,7 @@ TechnoTypeClass::TechnoTypeClass(char const * ininame, SpeedType speed) : CloakingSpeed(7), DebrisTypes(), DebrisMaximums(), - Locomotor(CLSID_TeleportLocomotion), + Locomotor(ClassID_TeleportLocomotion), VoxelCenterY(0), VoxelCenterX(0), Weight(1), @@ -505,7 +505,7 @@ bool TechnoTypeClass::Read_INI(CCINIClass const & ini) } PitchSpeed = ini.Get_Float(Name(), "PitchSpeed", PitchSpeed); - Locomotor = ini.Get_CLSID(IniName, "Locomotor", Locomotor); + Locomotor = ini.Get_ClassID(IniName, "Locomotor", Locomotor); CloakingSpeed = ini.Get_Int(Name(), "CloakingSpeed", CloakingSpeed); ThreatAvoidanceCoefficient = ini.Get_Float(Name(), "ThreatAvoidanceCoefficient", ThreatAvoidanceCoefficient); SlowdownDistance = ini.Get_Int(Name(), "SlowdownDistance", SlowdownDistance); diff --git a/code/techtype.h b/code/techtype.h index f50e7bb4f..36e759513 100644 --- a/code/techtype.h +++ b/code/techtype.h @@ -14,6 +14,7 @@ #pragma once #include "_weapon.h" +#include "classids.h" #include "objtype.h" #include "typelist.h" @@ -122,7 +123,7 @@ class TechnoTypeClass : public ObjectTypeClass * about. It is what decides whether the object drives, walks, hovers, flies or * tunnels, and an instance of it is created for every object as it is unlimboed. */ - CLSID Locomotor; + ClassID Locomotor; /* * These are the half extents of this object's voxel model, measured off the artwork diff --git a/code/teleport.cpp b/code/teleport.cpp index 1f96bc102..5442f1cc6 100644 --- a/code/teleport.cpp +++ b/code/teleport.cpp @@ -116,18 +116,9 @@ bool TeleportLocomotionClass::Process(void) } -/// -/// Fetches the class identifier of this locomotor. -/// This routine is used by the persistence system to record which locomotor was -/// written, so that the right one can be created when the save game is loaded. -/// -/// Pointer to the class identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT TeleportLocomotionClass::GetClassID(CLSID * retval) +ClassID TeleportLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TeleportLocomotion; - return(S_OK); + return(ClassID_TeleportLocomotion); } diff --git a/code/teleport.h b/code/teleport.h index a7cb4f90a..3e41d3396 100644 --- a/code/teleport.h +++ b/code/teleport.h @@ -19,7 +19,7 @@ class TeleportLocomotionClass : public LocomotionClass public: TeleportLocomotionClass(void); - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/terrain.cpp b/code/terrain.cpp index 18512889d..2b71d3f13 100644 --- a/code/terrain.cpp +++ b/code/terrain.cpp @@ -52,7 +52,6 @@ * TerrainClass::~TerrainClass -- Default destructor for terrain class objects. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "terrain.h" @@ -1090,16 +1089,7 @@ RTTIType TerrainClass::Fetch_RTTI(void) const } -/// -/// Fetches the class identifier for this object. -/// This routine is part of the IPersistent implementation. The save system records -/// the identifier so that it knows what to recreate when the game is loaded back in. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT TerrainClass::GetClassID(CLSID * retval) +ClassID TerrainClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TerrainClass; - return(S_OK); + return(ClassID_TerrainClass); } diff --git a/code/terrain.h b/code/terrain.h index ef7dc10d2..649b99d28 100644 --- a/code/terrain.h +++ b/code/terrain.h @@ -59,7 +59,7 @@ class TerrainClass : public ObjectClass, public StageClass TerrainClass(TerrainTypeClass const * type, Cell const & cell); virtual ~TerrainClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/terrtype.cpp b/code/terrtype.cpp index 4a5c015ea..82932b0ff 100644 --- a/code/terrtype.cpp +++ b/code/terrtype.cpp @@ -44,7 +44,6 @@ * TerrainTypeClass::operator new -- Allocates a terrain type object from special pool. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "terrtype.h" @@ -430,18 +429,9 @@ void TerrainTypeClass::Serialize(SaveStreamClass & stream) } -/// -/// Fetches the class identifier of this object. -/// The save system uses this identifier to know what kind of object to create when the -/// save file is loaded back in. -/// -/// Pointer to the buffer to fill in with the class identifier. -/// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT TerrainTypeClass::GetClassID(CLSID * retval) +ClassID TerrainTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TerrainTypeClass; - return(S_OK); + return(ClassID_TerrainTypeClass); } diff --git a/code/terrtype.h b/code/terrtype.h index 001a46ade..37b1fdee9 100644 --- a/code/terrtype.h +++ b/code/terrtype.h @@ -111,7 +111,7 @@ class TerrainTypeClass : public ObjectTypeClass TerrainTypeClass(char const * ininame = NULL); virtual ~TerrainTypeClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/tevent.cpp b/code/tevent.cpp index 6a0ca2ce7..2b724d56a 100644 --- a/code/tevent.cpp +++ b/code/tevent.cpp @@ -39,7 +39,6 @@ * TEventClass::operator () -- Action operator to see if event is satisfied. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "tevent.h" @@ -839,18 +838,9 @@ void TEventClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the persistence machinery to recognize what kind of object it -/// is about to load back. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT TEventClass::GetClassID(CLSID * retval) +ClassID TEventClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_EventClass; - return(S_OK); + return(ClassID_EventClass); } diff --git a/code/tevent.h b/code/tevent.h index fe4e0f102..c7dc9e1bf 100644 --- a/code/tevent.h +++ b/code/tevent.h @@ -104,7 +104,7 @@ class TEventClass : public AbstractClass TEventClass(void); virtual ~TEventClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/tiberium.cpp b/code/tiberium.cpp index ad21b0da6..7a72ab37c 100644 --- a/code/tiberium.cpp +++ b/code/tiberium.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "tiberium.h" @@ -194,17 +193,9 @@ void TiberiumClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier of the tiberium class. -/// This routine tells the save game loader which kind of object to create when this -/// tiberium type is read back in. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT TiberiumClass::GetClassID(CLSID * retval) +ClassID TiberiumClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TiberiumClass; - return(S_OK); + return(ClassID_TiberiumClass); } diff --git a/code/tiberium.h b/code/tiberium.h index 063ead26e..ed9615c08 100644 --- a/code/tiberium.h +++ b/code/tiberium.h @@ -37,7 +37,7 @@ class TiberiumClass : public AbstractTypeClass TiberiumClass(char const * ininame = NULL); virtual ~TiberiumClass() override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/trigger.cpp b/code/trigger.cpp index 30519332b..2bf8a078a 100644 --- a/code/trigger.cpp +++ b/code/trigger.cpp @@ -42,7 +42,6 @@ * TriggerClass::~TriggerClass -- Destructor for trigger objects. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "trigger.h" @@ -500,18 +499,9 @@ void TriggerClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the persistence support. The save game system uses the -/// identifier to work out what kind of object to build when the stream is read back in. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT TriggerClass::GetClassID(CLSID * retval) +ClassID TriggerClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TriggerClass; - return(S_OK); + return(ClassID_TriggerClass); } diff --git a/code/trigger.h b/code/trigger.h index ebba32ee9..35bf3233d 100644 --- a/code/trigger.h +++ b/code/trigger.h @@ -65,7 +65,7 @@ class TriggerClass : public AbstractClass TriggerClass(TriggerTypeClass * trigtype=NULL); virtual ~TriggerClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/trigtype.cpp b/code/trigtype.cpp index c1604099c..849f89755 100644 --- a/code/trigtype.cpp +++ b/code/trigtype.cpp @@ -46,7 +46,6 @@ * TriggerTypeClass::~TriggerTypeClass -- Deleting a trigger type deletes associated triggers* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "trigtype.h" @@ -811,18 +810,9 @@ void TriggerTypeClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier of this object. -/// The save game system uses this identifier to know which kind of object to build -/// when the stream is read back in. -/// -/// Pointer to the location to store the class identifier. -/// Returns with S_OK, or E_POINTER if no storage location was supplied. -HRESULT TriggerTypeClass::GetClassID(CLSID * retval) +ClassID TriggerTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TriggerTypeClass; - return(S_OK); + return(ClassID_TriggerTypeClass); } diff --git a/code/trigtype.h b/code/trigtype.h index ed2792f2b..77c95d070 100644 --- a/code/trigtype.h +++ b/code/trigtype.h @@ -55,7 +55,7 @@ class TriggerTypeClass : public AbstractTypeClass static TriggerTypeClass * Find_Or_Make(char const * ininame = NULL); - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; /* ** File I/O routines diff --git a/code/tube.cpp b/code/tube.cpp index 8ff1663c1..e0f17408a 100644 --- a/code/tube.cpp +++ b/code/tube.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "tube.h" @@ -267,16 +266,7 @@ RTTIType TubeClass::Fetch_RTTI(void) const } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the save game system so that it knows what kind of object to -/// construct when the stream is read back in. -/// -/// Pointer to the place to store the class identifier. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT TubeClass::GetClassID(CLSID * retval) +ClassID TubeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TubeClass; - return(S_OK); + return(ClassID_TubeClass); } diff --git a/code/tube.h b/code/tube.h index d2247a2f6..f511cd589 100644 --- a/code/tube.h +++ b/code/tube.h @@ -22,7 +22,7 @@ class TubeClass : public AbstractClass { typedef AbstractClass BASECLASS; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/tunnel.cpp b/code/tunnel.cpp index 8a49c905b..e4727006e 100644 --- a/code/tunnel.cpp +++ b/code/tunnel.cpp @@ -632,18 +632,9 @@ void TunnelLocomotionClass::Do_Turn(DirType coord) } -/// -/// Fetches the class identifier for this locomotor. -/// The save system records the identifier so that the right locomotor can be created -/// again when the game is loaded. -/// -/// The location to store the class identifier in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT TunnelLocomotionClass::GetClassID(CLSID * retval) +ClassID TunnelLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_TunnelLocomotion; - return(S_OK); + return(ClassID_TunnelLocomotion); } diff --git a/code/tunnel.h b/code/tunnel.h index 0d24d116b..03ae0794f 100644 --- a/code/tunnel.h +++ b/code/tunnel.h @@ -29,7 +29,7 @@ class TunnelLocomotionClass : public LocomotionClass */ TunnelLocomotionClass(void); - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/unit.cpp b/code/unit.cpp index 7961f15e2..0be0b8565 100644 --- a/code/unit.cpp +++ b/code/unit.cpp @@ -95,7 +95,6 @@ * UnitClass::~UnitClass -- Destructor for unit objects. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "unit.h" @@ -127,7 +126,7 @@ #include "fog.h" #include "house.h" #include "houstype.h" -#include "ilocos.h" +#include "classids.h" #include "incdec.h" #include "infantry.h" #include "infatype.h" @@ -2106,8 +2105,8 @@ void UnitClass::Per_Cell_Process(PCPType why) Cell center = Center_Coord(); Cell whom_center = whom->Center_Coord(); if (Center_Coord().As_Cell() == whom->Center_Coord().As_Cell() && whom->RTTI == RTTI_BUILDING) { - CLSID const clsid = Locomotion_Class_ID(Locomotion.get()); - if (clsid == CLSID_HoverLocomotion && static_cast(whom)->Class->IsCanUnitRepair && NavCom == NULL) { + ClassID const clsid = Locomotion_Class_ID(Locomotion.get()); + if (clsid == ClassID_HoverLocomotion && static_cast(whom)->Class->IsCanUnitRepair && NavCom == NULL) { NavCom = whom; } if (whom == NavCom) { @@ -5220,8 +5219,8 @@ void UnitClass::Assign_Destination(AbstractClass * target, bool immediate) * re-target the nearest reachable cell when driving rather than burrowing. */ if (target != NULL && Class->IsSubterranean && Locomotion->Is_Moving()) { - CLSID const clsid = Locomotion_Class_ID(Locomotion.get()); - if (clsid == CLSID_DriveLocomotion) { + ClassID const clsid = Locomotion_Class_ID(Locomotion.get()); + if (clsid == ClassID_DriveLocomotion) { NavQueue.Add_Head(target); RouteQueue.Clear(); CellClass * tcell = Get_Target_Cell_Ptr(); @@ -5312,8 +5311,8 @@ void UnitClass::Assign_Destination(AbstractClass * target, bool immediate) * (Mirrors BuildingClass weapons-factory exit, building.cpp:6236-6251.) */ if (target != NULL && !Locomotion->Is_Moving()) { - CLSID const clsid = Locomotion_Class_ID(Locomotion.get()); - if (clsid == CLSID_TunnelLocomotion && Get_Height_AGL() == 0) { + ClassID const clsid = Locomotion_Class_ID(Locomotion.get()); + if (clsid == ClassID_TunnelLocomotion && Get_Height_AGL() == 0) { Coord tc = target->Center_Coord(); int gl = Map.Get_Height_GL(tc); if (tc.Z < gl) tc.Z = gl; @@ -5338,7 +5337,7 @@ void UnitClass::Assign_Destination(AbstractClass * target, bool immediate) if (piggy != NULL && piggy->Is_Piggybacking()) { Locomotion = piggy->End_Piggyback(); } - std::unique_ptr walk = Create_Locomotor(CLSID_DriveLocomotion); + std::unique_ptr walk = Create_Locomotor(ClassID_DriveLocomotion); walk->Link_To_Object(this); piggy = Piggyback_Of(walk.get()); if (piggy != NULL) { @@ -6619,16 +6618,9 @@ bool UnitClass::Is_Immobilized(void) const } -/// -/// Fetches the class identifier used by the save game persistence system. -/// -/// Pointer to the buffer to fill in with the class identifier. -/// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT UnitClass::GetClassID(CLSID * retval) +ClassID UnitClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_UnitClass; - return(S_OK); + return(ClassID_UnitClass); } diff --git a/code/unit.h b/code/unit.h index f042bc282..95f683c7c 100644 --- a/code/unit.h +++ b/code/unit.h @@ -134,7 +134,7 @@ class UnitClass : public FootClass UnitClass(UnitTypeClass const * type = NULL, HouseClass * house = NULL); virtual ~UnitClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual HRESULT Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/unittype.cpp b/code/unittype.cpp index 45cc609b9..ecbe34b95 100644 --- a/code/unittype.cpp +++ b/code/unittype.cpp @@ -45,7 +45,6 @@ * UnitTypeClass::operator new -- Allocates an object from the unit type class heap. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "unittype.h" @@ -495,15 +494,9 @@ int UnitTypeClass::Repair_Step(void) const } -/// -/// Fetches the persistent class identifier for the unit type. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT UnitTypeClass::GetClassID(CLSID * retval) +ClassID UnitTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_UnitTypeClass; - return(S_OK); + return(ClassID_UnitTypeClass); } diff --git a/code/unittype.h b/code/unittype.h index 723cc197c..acfea5043 100644 --- a/code/unittype.h +++ b/code/unittype.h @@ -313,7 +313,7 @@ class UnitTypeClass : public TechnoTypeClass UnitTypeClass(char const * ininame = NULL); virtual ~UnitTypeClass() override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/vanim.cpp b/code/vanim.cpp index 3af2a031c..afa32b025 100644 --- a/code/vanim.cpp +++ b/code/vanim.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "vanim.h" @@ -546,18 +545,9 @@ void VoxelAnimClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier used to persist this object. -/// The save system writes this identifier ahead of the object data so that the loader -/// knows what kind of object to reconstruct. -/// -/// Pointer to the buffer that will receive the class identifier. -/// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT VoxelAnimClass::GetClassID(CLSID * retval) +ClassID VoxelAnimClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_VoxelAnimClass; - return(S_OK); + return(ClassID_VoxelAnimClass); } diff --git a/code/vanim.h b/code/vanim.h index 197b1ee4f..8d8af415e 100644 --- a/code/vanim.h +++ b/code/vanim.h @@ -33,7 +33,7 @@ class VoxelAnimClass : public ObjectClass, public BounceClass VoxelAnimClass(VoxelAnimTypeClass const * type, Coord const & coord, HouseClass * house = NULL); virtual ~VoxelAnimClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/vanimtype.cpp b/code/vanimtype.cpp index 566e13428..0af776362 100644 --- a/code/vanimtype.cpp +++ b/code/vanimtype.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "vanimtype.h" @@ -247,18 +246,9 @@ void VoxelAnimTypeClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the persistence system to record what kind of object was -/// written, so that the right class can be created when the save game is loaded. -/// -/// Pointer to the class identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT VoxelAnimTypeClass::GetClassID(CLSID * retval) +ClassID VoxelAnimTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_VoxelAnimTypeClass; - return(S_OK); + return(ClassID_VoxelAnimTypeClass); } diff --git a/code/vanimtype.h b/code/vanimtype.h index 3a1d09720..9abc1adfe 100644 --- a/code/vanimtype.h +++ b/code/vanimtype.h @@ -32,7 +32,7 @@ class VoxelAnimTypeClass : public ObjectTypeClass VoxelAnimTypeClass(char const * ininame = NULL); ~VoxelAnimTypeClass(void); - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/vein.cpp b/code/vein.cpp index 363a2a38b..5a8887c92 100644 --- a/code/vein.cpp +++ b/code/vein.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "vein.h" @@ -1090,15 +1089,7 @@ void VeinholeMonsterClass::Reduce_Veins_At(CellClass * cellptr) } -/// -/// Fetches the class identifier used by the save game system. -/// This routine is called by the persistence layer so that it knows which class to -/// recreate when the saved game is read back in. -/// -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT VeinholeMonsterClass::GetClassID(CLSID * retval) +ClassID VeinholeMonsterClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_VeinholeMonsterClass; - return(S_OK); + return(ClassID_VeinholeMonsterClass); } diff --git a/code/vein.h b/code/vein.h index b43df8d46..85c456eed 100644 --- a/code/vein.h +++ b/code/vein.h @@ -34,7 +34,7 @@ class VeinholeMonsterClass : public ObjectClass VeinholeMonsterClass(Cell const & cell); ~VeinholeMonsterClass(void); - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; /*--------------------------------------------------------------------- ** Member function prototypes. diff --git a/code/walk.cpp b/code/walk.cpp index 39cca6825..3413e895d 100644 --- a/code/walk.cpp +++ b/code/walk.cpp @@ -608,18 +608,9 @@ bool WalkLocomotionClass::Mark_Head_To(Coord const & coord) } -/// -/// Fetches the class ID of this locomotor. -/// The persistence system uses this to recreate the correct locomotor when a saved -/// game is loaded. -/// -/// Pointer to the class ID to fill in. -/// Returns with S_OK if the class ID was fetched, otherwise E_POINTER. -HRESULT WalkLocomotionClass::GetClassID(CLSID * retval) +ClassID WalkLocomotionClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_WalkLocomotion; - return(S_OK); + return(ClassID_WalkLocomotion); } @@ -706,27 +697,6 @@ bool WalkLocomotionClass::Is_Ok_To_End(void) } -/// -/// Fetches the class ID of whichever locomotor is in charge. -/// This routine reports the piggybacking locomotor's identity when one has taken -/// over, otherwise it identifies this walking locomotor. -/// -/// Pointer to the class ID to fill in. -/// Returns with S_OK if the class ID was fetched, otherwise an error code. -HRESULT WalkLocomotionClass::Piggyback_CLSID(GUID * classid) -{ - if (classid == NULL) { - return(E_POINTER); - } - - if (Piggybacker != NULL) { - *classid = Locomotion_Class_ID(Piggybacker.get()); - return(S_OK); - } - return(GetClassID(classid)); -} - - /// /// Releases the sub-cell spot that this infantry has reserved. /// This routine is called when the infantry is being lifted off the map so that the diff --git a/code/walk.h b/code/walk.h index a1c312f0b..f490df50f 100644 --- a/code/walk.h +++ b/code/walk.h @@ -31,7 +31,7 @@ class WalkLocomotionClass : public LocomotionClass, public IPiggyback WalkLocomotionClass(void); virtual ~WalkLocomotionClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; @@ -39,7 +39,6 @@ class WalkLocomotionClass : public LocomotionClass, public IPiggyback virtual bool Begin_Piggyback(std::unique_ptr carried) override; virtual std::unique_ptr End_Piggyback(void) override; virtual bool Is_Ok_To_End(void) override; - virtual HRESULT Piggyback_CLSID(GUID * classid) override; virtual bool Is_Piggybacking(void) override {return(Piggybacker != NULL);} virtual bool Is_Moving(void) override; diff --git a/code/warhead.cpp b/code/warhead.cpp index 9938e98ea..39f8e8e02 100644 --- a/code/warhead.cpp +++ b/code/warhead.cpp @@ -35,7 +35,6 @@ * WarheadTypeClass::operator new -- Allocate a warhead object from the special heap. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "warhead.h" @@ -248,18 +247,9 @@ void WarheadTypeClass::Compute_CRC(CRCEngine &crc) const } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the persistence interface. The save code stores the identifier -/// so that the object can be recognized when the game is loaded back in. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT WarheadTypeClass::GetClassID(CLSID * retval) +ClassID WarheadTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_WarheadTypeClass; - return(S_OK); + return(ClassID_WarheadTypeClass); } diff --git a/code/warhead.h b/code/warhead.h index 7193c83ae..02e1a2a64 100644 --- a/code/warhead.h +++ b/code/warhead.h @@ -52,7 +52,7 @@ class WarheadTypeClass : public AbstractTypeClass WarheadTypeClass(char const * ininame = NULL); virtual ~WarheadTypeClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/wave.cpp b/code/wave.cpp index f1af6407f..50720c5e8 100644 --- a/code/wave.cpp +++ b/code/wave.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "wave.h" @@ -466,18 +465,9 @@ void WaveClass::Post_Load(void) } -/// -/// Fetches the class identifier of this object. -/// This routine is part of the persistence interface. The save game loader uses the -/// identifier to recreate the object as the right kind of class. -/// -/// Pointer to the buffer to store the class identifier in. -/// Returns with S_OK, or E_POINTER if no buffer was supplied. -HRESULT WaveClass::GetClassID(CLSID * retval) +ClassID WaveClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_WaveClass; - return(S_OK); + return(ClassID_WaveClass); } diff --git a/code/wave.h b/code/wave.h index 914da925d..d1d291d5c 100644 --- a/code/wave.h +++ b/code/wave.h @@ -25,7 +25,7 @@ class WaveClass : public ObjectClass WaveClass(void); virtual ~WaveClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/waypoint.cpp b/code/waypoint.cpp index 4340f55e7..59d2f4fb3 100644 --- a/code/waypoint.cpp +++ b/code/waypoint.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#define INCLUDE_COM #include "always.h" #include "waypoint.h" @@ -281,18 +280,9 @@ void WaypointPathClass::Compute_CRC(CRCEngine & crc) const } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the persistence machinery, which records the identifier so that -/// it knows what kind of object to create when the game is loaded back. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT WaypointPathClass::GetClassID(CLSID * retval) +ClassID WaypointPathClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_WaypointPath; - return(S_OK); + return(ClassID_WaypointPath); } diff --git a/code/waypoint.h b/code/waypoint.h index 7c1b61e7a..5fdac3fb5 100644 --- a/code/waypoint.h +++ b/code/waypoint.h @@ -49,7 +49,7 @@ class WaypointPathClass : public AbstractClass WaypointPathClass(int index); virtual ~WaypointPathClass(void) override; - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/weapon.cpp b/code/weapon.cpp index f6f458c5f..736582178 100644 --- a/code/weapon.cpp +++ b/code/weapon.cpp @@ -39,7 +39,6 @@ * WeaponTypeClass::Allowed_Threats -- Determine what threats this weapon can address. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -#define INCLUDE_COM #include "always.h" #include "weapon.h" @@ -363,18 +362,9 @@ void WeaponTypeClass::Compute_CRC(CRCEngine &crc) const } -/// -/// Fetches the class identifier of this object. -/// This routine is used by the persistence machinery to recognize what kind of object it -/// is about to load back. -/// -/// Pointer to the identifier to fill in. -/// Returns with S_OK, or E_POINTER if no destination was supplied. -HRESULT WeaponTypeClass::GetClassID(CLSID * retval) +ClassID WeaponTypeClass::Class_ID(void) const { - if (retval == NULL) return(E_POINTER); - *retval = CLSID_WeaponTypeClass; - return(S_OK); + return(ClassID_WeaponTypeClass); } diff --git a/code/weapon.h b/code/weapon.h index aceccd172..3e1e01f76 100644 --- a/code/weapon.h +++ b/code/weapon.h @@ -60,7 +60,7 @@ class WeaponTypeClass : public AbstractTypeClass WeaponTypeClass(char const * ininame = NULL); ~WeaponTypeClass(void); - virtual HRESULT GetClassID(CLSID * retval) override; + virtual ClassID Class_ID(void) const override; static WeaponType From_Name(char const * name); diff --git a/docs/SAVE-FORMAT.md b/docs/SAVE-FORMAT.md index 7fe320c53..b7326224a 100644 --- a/docs/SAVE-FORMAT.md +++ b/docs/SAVE-FORMAT.md @@ -73,9 +73,11 @@ names them. An object record is: | 4 | Length of the record body | | | The body: the swizzle identity, then the members the class's `Serialize` names | -The class identifier is the `CLSID` the object's `GetClassID` reports, the +The class identifier is the `ClassID` the object's `Class_ID` reports, the same one registered in `code/startup.cpp` and, for a locomotor, named by the -`Locomotor=` key. The reader creates the object through that registration, +`Locomotor=` key. Its sixteen bytes are those of the COM class identifier the +class once registered, so a save written before COM left the engine still +names the same classes. The reader creates the object through that registration, hands it the stream, checks that it consumed exactly the recorded length, and only then lets it finish restoring itself, so a refused record never reaches the map or a side table. A record that comes up short or long fails the load with the object's diff --git a/manual/content/internals/locomotion.md b/manual/content/internals/locomotion.md index 18589ec13..02cd3c0a0 100644 --- a/manual/content/internals/locomotion.md +++ b/manual/content/internals/locomotion.md @@ -18,9 +18,9 @@ source_files: ## Object locomotion -`TechnoTypeClass::Locomotor` stores the CLSID used to create a type's ordinary locomotor. Concrete `FootClass` constructors create that locomotor, call `Link_To_Object`, and assign it to `FootClass::Locomotion`. +`TechnoTypeClass::Locomotor` stores the class identifier used to create a type's ordinary locomotor. Concrete `FootClass` constructors create that locomotor, call `Link_To_Object`, and assign it to `FootClass::Locomotion`. -Movement, destination, layer, occupation, and locomotor-specific drawing queries go through the current interface. Code must therefore inspect the runtime `Locomotion` pointer when temporary locomotion is possible; the type's `Locomotor` CLSID describes the ordinary implementation, not necessarily the one currently in control. +Movement, destination, layer, occupation, and locomotor-specific drawing queries go through the current interface. Code must therefore inspect the runtime `Locomotion` pointer when temporary locomotion is possible; the type's `Locomotor` identifier describes the ordinary implementation, not necessarily the one currently in control. ## Piggybacking @@ -40,4 +40,4 @@ Callers that perform opportunistic restoration first consult `Is_Ok_To_End`. The `FootClass::Serialize` writes the active locomotor as a record of its own, headed by its class identifier, and recreates it from that identifier when loading. A piggyback-capable locomotor writes whether it carries another locomotor and serializes that nested locomotor when present. A save made during a temporary movement state therefore retains both the active locomotor and the one to restore. -`GetClassID` identifies the active locomotor implementation. `Piggyback_CLSID` returns the carried locomotor's `GetClassID` while piggybacking and the active locomotor's ID otherwise. These identities are distinct while a temporary locomotor is in control. +`Class_ID` identifies the active locomotor implementation, and the carried locomotor keeps its own. These identities are distinct while a temporary locomotor is in control. From 6a11983e576ed1fef61555537ab1f1695549a501 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Mon, 7 Sep 2026 03:35:06 +0200 Subject: [PATCH 028/179] Return plain results from the persistence interfaces --- code/abstract.cpp | 24 ++-- code/abstract.h | 8 +- code/aircraft.cpp | 4 +- code/aircraft.h | 2 +- code/brain.cpp | 20 ++-- code/brain.h | 6 +- code/building.cpp | 5 +- code/building.h | 2 +- code/display.cpp | 18 ++- code/display.h | 4 +- code/enviro.cpp | 8 +- code/enviro.h | 4 +- code/house.cpp | 4 +- code/house.h | 2 +- code/hover.cpp | 5 +- code/hover.h | 2 +- code/iloco.h | 3 +- code/infantry.cpp | 4 +- code/infantry.h | 2 +- code/ion.cpp | 8 +- code/ion.h | 4 +- code/layer.cpp | 14 +-- code/layer.h | 4 +- code/levitate.cpp | 4 +- code/levitate.h | 2 +- code/loco.cpp | 20 ++-- code/loco.h | 10 +- code/mouse.cpp | 82 ++++++-------- code/mouse.h | 4 +- code/particle.cpp | 6 +- code/particle.h | 2 +- code/persist.h | 4 +- code/revent.cpp | 4 +- code/saveload.cpp | 267 ++++++++++++++++++++++---------------------- code/saveload.h | 4 +- code/savestream.cpp | 16 +-- code/savestream.h | 5 +- code/session.cpp | 4 +- code/terrain.cpp | 4 +- code/terrain.h | 2 +- code/tiberium.cpp | 4 +- code/tiberium.h | 2 +- code/unit.cpp | 4 +- code/unit.h | 2 +- code/vein.cpp | 8 +- 45 files changed, 294 insertions(+), 323 deletions(-) diff --git a/code/abstract.cpp b/code/abstract.cpp index 6e3a025f7..6f0254629 100644 --- a/code/abstract.cpp +++ b/code/abstract.cpp @@ -111,8 +111,8 @@ void AbstractClass::Create_ID(void) /// /// The stream to write to. /// Should the object be marked clean once it has been written? -/// Returns with S_OK when the object was written, otherwise a failure code. -HRESULT AbstractClass::Save(SaveStreamClass & stream, BOOL cleardirty) +/// bool; Was the record written whole? +bool AbstractClass::Save(SaveStreamClass & stream, bool cleardirty) { return(Save_Members(stream, cleardirty)); } @@ -122,8 +122,8 @@ HRESULT AbstractClass::Save(SaveStreamClass & stream, BOOL cleardirty) /// Reads this object back from the save stream. /// /// The stream to read from. -/// Returns with S_OK when the object was read, otherwise a failure code. -HRESULT AbstractClass::Load(SaveStreamClass & stream) +/// bool; Was the record read whole? +bool AbstractClass::Load(SaveStreamClass & stream) { return(Load_Members(stream)); } @@ -136,16 +136,16 @@ HRESULT AbstractClass::Load(SaveStreamClass & stream) /// /// The stream to write to. /// Should the object be marked clean once it has been written? -/// Returns with S_OK when the record was written, otherwise a failure code. -HRESULT AbstractClass::Save_Members(SaveStreamClass & stream, BOOL cleardirty) +/// bool; Was the record written whole? +bool AbstractClass::Save_Members(SaveStreamClass & stream, bool cleardirty) { uintptr_t id = (uintptr_t)this; stream.Serialize(id); Serialize(stream); - if (SUCCEEDED(stream.Result()) && cleardirty) { + if (!stream.Was_Error() && cleardirty) { Dirty = false; } - return(stream.Result()); + return(!stream.Was_Error()); } @@ -155,13 +155,13 @@ HRESULT AbstractClass::Save_Members(SaveStreamClass & stream, BOOL cleardirty) /// save game can be remapped onto this object, and the members follow. /// /// The stream to read from. -/// Returns with S_OK when the record was read, otherwise a failure code. -HRESULT AbstractClass::Load_Members(SaveStreamClass & stream) +/// bool; Was the record read whole? +bool AbstractClass::Load_Members(SaveStreamClass & stream) { uintptr_t id = 0; stream.Serialize(id); if (stream.Was_Error()) { - return(stream.Result()); + return(false); } Swizzle_Here_I_Am(id, this); @@ -172,7 +172,7 @@ HRESULT AbstractClass::Load_Members(SaveStreamClass & stream) Serialize(stream); stream.Set_Context(outertype, outerid); - return(stream.Result()); + return(!stream.Was_Error()); } diff --git a/code/abstract.h b/code/abstract.h index d77b11eb1..46d3ebf59 100644 --- a/code/abstract.h +++ b/code/abstract.h @@ -74,8 +74,8 @@ class AbstractClass : public IPersistent * the members are read -- dropping a registration keyed by the identity the read * is about to replace, say. */ - HRESULT Save_Members(SaveStreamClass & stream, BOOL cleardirty); - HRESULT Load_Members(SaveStreamClass & stream); + bool Save_Members(SaveStreamClass & stream, bool cleardirty); + bool Load_Members(SaveStreamClass & stream); public: @@ -100,8 +100,8 @@ class AbstractClass : public IPersistent virtual ~AbstractClass(void); - virtual HRESULT Load(SaveStreamClass & stream) override; - virtual HRESULT Save(SaveStreamClass & stream, BOOL cleardirty) override; + virtual bool Load(SaveStreamClass & stream) override; + virtual bool Save(SaveStreamClass & stream, bool cleardirty) override; virtual int What_Am_I(void) const; virtual int Fetch_ID(void) const; diff --git a/code/aircraft.cpp b/code/aircraft.cpp index e7cc386d0..731492bdf 100644 --- a/code/aircraft.cpp +++ b/code/aircraft.cpp @@ -3887,8 +3887,8 @@ void AircraftClass::Read_INI(CCINIClass const & ini) /// again once that identity has arrived. /// /// The stream to read this object from. -/// Returns with S_OK if the aircraft was loaded successfully. -HRESULT AircraftClass::Load(SaveStreamClass & stream) +/// bool; Was the record read whole? +bool AircraftClass::Load(SaveStreamClass & stream) { TargetTracker.Remove_Index(Fetch_ID()); return(BASECLASS::Load(stream)); diff --git a/code/aircraft.h b/code/aircraft.h index 2dec27a3a..0f9d06b95 100644 --- a/code/aircraft.h +++ b/code/aircraft.h @@ -58,7 +58,7 @@ class AircraftClass : public FootClass, public IFlyControl virtual ~AircraftClass(void) override; virtual ClassID Class_ID(void) const override; - virtual HRESULT Load(SaveStreamClass & stream) override; + virtual bool Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/brain.cpp b/code/brain.cpp index 96dd0af18..ef7aae44c 100644 --- a/code/brain.cpp +++ b/code/brain.cpp @@ -145,15 +145,12 @@ bool BrainClass::Add_Neuron(NeuronClass *neuron) /// Saves this brain to the save game stream. /// /// Should the neurons be marked clean once they are written? -/// -/// Returns with S_OK when the brain was written, E_POINTER when no stream was supplied, -/// or the stream's own failure code. -/// -HRESULT BrainClass::Save(SaveStreamClass & stream, BOOL cleardirty) +/// bool; Was the record written whole? +bool BrainClass::Save(SaveStreamClass & stream, bool cleardirty) { Serialize(stream, cleardirty); - return(stream.Result()); + return(!stream.Was_Error()); } @@ -162,16 +159,13 @@ HRESULT BrainClass::Save(SaveStreamClass & stream, BOOL cleardirty) /// Whatever neurons the brain was holding are destroyed first, so the stream's neurons /// entirely replace them. /// -/// -/// Returns with S_OK when the brain was read, E_POINTER when no stream was supplied, or -/// the stream's own failure code. -/// -HRESULT BrainClass::Load(SaveStreamClass & stream) +/// bool; Was the record read whole? +bool BrainClass::Load(SaveStreamClass & stream) { stream.Set_Context("BrainClass"); Serialize(stream); - return(stream.Result()); + return(!stream.Was_Error()); } @@ -182,7 +176,7 @@ HRESULT BrainClass::Load(SaveStreamClass & stream) /// /// The stream carrying the members. /// Should the neurons be marked clean once they are written? -void BrainClass::Serialize(SaveStreamClass & stream, BOOL cleardirty) +void BrainClass::Serialize(SaveStreamClass & stream, bool cleardirty) { int count = Neurons.Count(); stream.Serialize(count); diff --git a/code/brain.h b/code/brain.h index 42693364e..198a18a5f 100644 --- a/code/brain.h +++ b/code/brain.h @@ -62,10 +62,10 @@ class BrainClass void Init(int min, int max); bool Add_Neuron(NeuronClass *neuron); - HRESULT Load(SaveStreamClass & stream); - HRESULT Save(SaveStreamClass & stream, BOOL cleardirty); + bool Load(SaveStreamClass & stream); + bool Save(SaveStreamClass & stream, bool cleardirty); - void Serialize(SaveStreamClass & stream, BOOL cleardirty = FALSE); + void Serialize(SaveStreamClass & stream, bool cleardirty = false); private: /* diff --git a/code/building.cpp b/code/building.cpp index 0f7bbdaa4..8464296c2 100644 --- a/code/building.cpp +++ b/code/building.cpp @@ -8740,9 +8740,8 @@ void BuildingClass::Clear_Occupy_Bit(Coord const & coord) /// since the one it is about to be given is the one it was saved with. Post_Load enters it /// again once that identity has arrived. /// -/// Returns with S_OK if the building was read, or the failure code from the -/// underlying stream. -HRESULT BuildingClass::Load(SaveStreamClass & stream) +/// bool; Was the record read whole? +bool BuildingClass::Load(SaveStreamClass & stream) { TargetTracker.Remove_Index(Fetch_ID()); return(BASECLASS::Load(stream)); diff --git a/code/building.h b/code/building.h index 68aa6558d..c9614f015 100644 --- a/code/building.h +++ b/code/building.h @@ -348,7 +348,7 @@ class BuildingClass : public TechnoClass virtual ~BuildingClass(void) override; virtual ClassID Class_ID(void) const override; - virtual HRESULT Load(SaveStreamClass & stream) override; + virtual bool Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/display.cpp b/code/display.cpp index baba6de9c..4c81bd3e7 100644 --- a/code/display.cpp +++ b/code/display.cpp @@ -3846,14 +3846,13 @@ LRESULT DisplayClass::Windows_Message_Proc(HWND hWnd, UINT Msg, WPARAM wParam, L /// Loads the display layers from the save game stream. /// /// The stream to read the layers from. -/// Returns with S_OK if every layer was read, otherwise the failure code of the -/// layer that could not be read. -HRESULT DisplayClass::Load(SaveStreamClass & stream) +/// bool; Was the record read whole? +bool DisplayClass::Load(SaveStreamClass & stream) { - HRESULT result = S_OK; + bool result = true; for (LayerType layer = LAYER_FIRST; layer < LAYER_COUNT; layer++) { result = Layer[layer].Load(stream); - if (FAILED(result)) break; + if (!result) break; } return(result); } @@ -3863,14 +3862,13 @@ HRESULT DisplayClass::Load(SaveStreamClass & stream) /// Saves the display layers to the save game stream. /// /// The stream to write the layers to. -/// Returns with S_OK if every layer was written, otherwise the failure code of the -/// layer that could not be written. -HRESULT DisplayClass::Save(SaveStreamClass & stream) +/// bool; Was the record written whole? +bool DisplayClass::Save(SaveStreamClass & stream) { - HRESULT result = S_OK; + bool result = true; for (LayerType layer = LAYER_FIRST; layer < LAYER_COUNT; layer++) { result = Layer[layer].Save(stream); - if (FAILED(result)) break; + if (!result) break; } return(result); } diff --git a/code/display.h b/code/display.h index d9eaf694b..2bfa6fed4 100644 --- a/code/display.h +++ b/code/display.h @@ -65,8 +65,8 @@ class DisplayClass: public MapClass friend class Tactical; public: - virtual HRESULT Load(SaveStreamClass & stream); - virtual HRESULT Save(SaveStreamClass & stream); + virtual bool Load(SaveStreamClass & stream); + virtual bool Save(SaveStreamClass & stream); virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/enviro.cpp b/code/enviro.cpp index 492f51679..ab82c1097 100644 --- a/code/enviro.cpp +++ b/code/enviro.cpp @@ -110,11 +110,11 @@ void EnvironmentClass::Restore(void) /// restored, before the scenario itself is brought back. /// /// Returns with the result reported by the stream read. -HRESULT EnvironmentClass::Load(SaveStreamClass & stream) +bool EnvironmentClass::Load(SaveStreamClass & stream) { stream.Set_Context("EnvironmentClass"); Serialize(stream); - return(stream.Result()); + return(!stream.Was_Error()); } @@ -122,10 +122,10 @@ HRESULT EnvironmentClass::Load(SaveStreamClass & stream) /// Writes the carry over environment out to a save game. /// /// Returns with the result reported by the stream write. -HRESULT EnvironmentClass::Save(SaveStreamClass & stream) +bool EnvironmentClass::Save(SaveStreamClass & stream) { Serialize(stream); - return(stream.Result()); + return(!stream.Was_Error()); } diff --git a/code/enviro.h b/code/enviro.h index 79b1e7187..89b46fa4c 100644 --- a/code/enviro.h +++ b/code/enviro.h @@ -25,8 +25,8 @@ class EnvironmentClass void Store(void); void Restore(void); - HRESULT Load(SaveStreamClass & stream); - HRESULT Save(SaveStreamClass & stream); + bool Load(SaveStreamClass & stream); + bool Save(SaveStreamClass & stream); void Serialize(SaveStreamClass & stream); diff --git a/code/house.cpp b/code/house.cpp index 0753346db..2540513cf 100644 --- a/code/house.cpp +++ b/code/house.cpp @@ -6438,8 +6438,8 @@ void HouseClass::Compute_CRC(CRCEngine & crc) const /// record, so they are disposed of before the saved members are read over the top of them. /// /// The stream to read the house from. -/// Returns with S_OK, or the failure code reported by the stream. -HRESULT HouseClass::Load(SaveStreamClass & stream) +/// bool; Was the record read whole? +bool HouseClass::Load(SaveStreamClass & stream) { while (SuperWeapon.Count()) { delete SuperWeapon[0]; diff --git a/code/house.h b/code/house.h index b41bf31f5..fa42696b2 100644 --- a/code/house.h +++ b/code/house.h @@ -736,7 +736,7 @@ class HouseClass : public AbstractClass virtual ~HouseClass(void) override; virtual ClassID Class_ID(void) const override; - virtual HRESULT Load(SaveStreamClass & stream) override; + virtual bool Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/hover.cpp b/code/hover.cpp index 1c3657bd0..369b36fc2 100644 --- a/code/hover.cpp +++ b/code/hover.cpp @@ -66,12 +66,11 @@ HoverLocomotionClass::HoverLocomotionClass(void) : /// /// Pointer to the object this locomotor will drive. /// Returns with the result of the attach operation. -HRESULT HoverLocomotionClass::Link_To_Object(void *pointer) +void HoverLocomotionClass::Link_To_Object(void *pointer) { - HRESULT res = BASECLASS::Link_To_Object(pointer); + BASECLASS::Link_To_Object(pointer); FacingClass face(2 * LinkedTo->TClass->ROT); Facing = face; - return(res); } diff --git a/code/hover.h b/code/hover.h index ae23b2726..4b22c0869 100644 --- a/code/hover.h +++ b/code/hover.h @@ -40,7 +40,7 @@ class HoverLocomotionClass : public LocomotionClass virtual void Serialize(SaveStreamClass & stream) override; - virtual HRESULT Link_To_Object(void *pointer) override; + virtual void Link_To_Object(void *pointer) override; virtual bool Is_Moving(void) override; virtual Coord Destination(void) override; virtual Coord Head_To_Coord(void) override; diff --git a/code/iloco.h b/code/iloco.h index 4a08e9d77..19f01d2bf 100644 --- a/code/iloco.h +++ b/code/iloco.h @@ -20,7 +20,6 @@ #include "visual.hh" #include "zgrad.hh" -#include #include @@ -35,7 +34,7 @@ struct ILocomotion /* * Links object to locomotor. */ - virtual HRESULT Link_To_Object(void *pointer) = 0; + virtual void Link_To_Object(void *pointer) = 0; /* * Sees if object is moving. diff --git a/code/infantry.cpp b/code/infantry.cpp index 9cc65af9b..6f0976af5 100644 --- a/code/infantry.cpp +++ b/code/infantry.cpp @@ -3967,8 +3967,8 @@ void InfantryClass::Clear_Occupy_Bit(Coord const & coord) /// since the one it is about to be given is the one it was saved with. Post_Load enters it /// again once that identity has arrived. /// -/// Returns with S_OK if the object was read successfully. -HRESULT InfantryClass::Load(SaveStreamClass & stream) +/// bool; Was the record read whole? +bool InfantryClass::Load(SaveStreamClass & stream) { TargetTracker.Remove_Index(Fetch_ID()); return(BASECLASS::Load(stream)); diff --git a/code/infantry.h b/code/infantry.h index bda23244b..1540aa8e8 100644 --- a/code/infantry.h +++ b/code/infantry.h @@ -127,7 +127,7 @@ class InfantryClass : public FootClass virtual ~InfantryClass(void) override; virtual ClassID Class_ID(void) const override; - virtual HRESULT Load(SaveStreamClass & stream) override; + virtual bool Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/ion.cpp b/code/ion.cpp index d769131b0..4348c797e 100644 --- a/code/ion.cpp +++ b/code/ion.cpp @@ -78,10 +78,10 @@ void IonStormClass::Init(void) /// Saves the ion storm state to the save game stream. /// /// Returns with the result reported by the stream write. -HRESULT IonStormClass::Save(SaveStreamClass & stream) +bool IonStormClass::Save(SaveStreamClass & stream) { Serialize(stream); - return(stream.Result()); + return(!stream.Was_Error()); } @@ -91,11 +91,11 @@ HRESULT IonStormClass::Save(SaveStreamClass & stream) /// Returns with the result reported by the stream read. /// Only the bookkeeping is restored here. Post_Load_Game must still call /// Apply_Secondary_Effect to put the world back into its storm bound state. -HRESULT IonStormClass::Load(SaveStreamClass & stream) +bool IonStormClass::Load(SaveStreamClass & stream) { stream.Set_Context("IonStormClass"); Serialize(stream); - return(stream.Result()); + return(!stream.Was_Error()); } diff --git a/code/ion.h b/code/ion.h index 14ceff579..149f25c5e 100644 --- a/code/ion.h +++ b/code/ion.h @@ -23,8 +23,8 @@ class IonStormClass { public: static void Init(void); - static HRESULT Save(SaveStreamClass & stream); - static HRESULT Load(SaveStreamClass & stream); + static bool Save(SaveStreamClass & stream); + static bool Load(SaveStreamClass & stream); static void Serialize(SaveStreamClass & stream); diff --git a/code/layer.cpp b/code/layer.cpp index 0053d4be5..4c91cf6d9 100644 --- a/code/layer.cpp +++ b/code/layer.cpp @@ -151,13 +151,12 @@ int LayerClass::Sorted_Add(ObjectClass const * const object) /// are written by their own owners -- only the layer's object pointers are recorded here, /// to be swizzled back into real addresses when the game is loaded. /// -/// Returns with S_OK if the layer was written. Otherwise, the failure code from -/// the stream is returned. -HRESULT LayerClass::Save(SaveStreamClass & stream) +/// bool; Was the record written whole? +bool LayerClass::Save(SaveStreamClass & stream) { DynamicVectorClass::Serialize(stream); - return(stream.Result()); + return(!stream.Was_Error()); } @@ -167,12 +166,11 @@ HRESULT LayerClass::Save(SaveStreamClass & stream) /// reconstructed. Whatever the layer was holding is discarded and the object pointers are /// read back, so they do not become usable until the swizzle pass has run. /// -/// Returns with S_OK if the layer was read. Otherwise, the failure code from the -/// stream is returned. -HRESULT LayerClass::Load(SaveStreamClass & stream) +/// bool; Was the record read whole? +bool LayerClass::Load(SaveStreamClass & stream) { stream.Set_Context("LayerClass"); DynamicVectorClass::Serialize(stream); - return(stream.Result()); + return(!stream.Was_Error()); } diff --git a/code/layer.h b/code/layer.h index 7d317e368..25dc07af0 100644 --- a/code/layer.h +++ b/code/layer.h @@ -40,8 +40,8 @@ class ObjectClass; class LayerClass : public DynamicVectorClass { public: - HRESULT Load(SaveStreamClass & stream); - HRESULT Save(SaveStreamClass & stream); + bool Load(SaveStreamClass & stream); + bool Save(SaveStreamClass & stream); public: diff --git a/code/levitate.cpp b/code/levitate.cpp index ca88f79b0..616718351 100644 --- a/code/levitate.cpp +++ b/code/levitate.cpp @@ -77,9 +77,9 @@ LevitateLocomotionClass::LevitateLocomotionClass(void) : /// /// Pointer to the object this locomotor will drive. /// Returns with the result of the attach operation. -HRESULT LevitateLocomotionClass::Link_To_Object(void *pointer) +void LevitateLocomotionClass::Link_To_Object(void *pointer) { - return(BASECLASS::Link_To_Object(pointer)); + BASECLASS::Link_To_Object(pointer); } diff --git a/code/levitate.h b/code/levitate.h index afc24654b..e5bd84473 100644 --- a/code/levitate.h +++ b/code/levitate.h @@ -32,7 +32,7 @@ class LevitateLocomotionClass : public LocomotionClass virtual void Serialize(SaveStreamClass & stream) override; - virtual HRESULT Link_To_Object(void *pointer) override; + virtual void Link_To_Object(void *pointer) override; virtual bool Is_Moving(void) override; virtual Coord Destination(void) override; virtual Coord Head_To_Coord(void) override; diff --git a/code/loco.cpp b/code/loco.cpp index 35d78bc0b..532b34bec 100644 --- a/code/loco.cpp +++ b/code/loco.cpp @@ -64,11 +64,9 @@ LocomotionClass::~LocomotionClass(void) /// offers depends on it having been called first. /// /// Pointer to the foot class object this locomotor will carry about. -/// Returns with S_OK, since the attachment cannot fail. -HRESULT LocomotionClass::Link_To_Object(void *pointer) +void LocomotionClass::Link_To_Object(void *pointer) { LinkedTo = (FootClass *)pointer; - return(S_OK); } @@ -225,36 +223,36 @@ ClassID Locomotion_Class_ID(ILocomotion * locomotion) /// /// Should the locomotor be marked as no longer needing a save? /// Returns with the result of the write. -HRESULT LocomotionClass::Save(SaveStreamClass & stream, BOOL cleardirty) +bool LocomotionClass::Save(SaveStreamClass & stream, bool cleardirty) { return(Save_Members(stream, cleardirty)); } -HRESULT LocomotionClass::Load(SaveStreamClass & stream) +bool LocomotionClass::Load(SaveStreamClass & stream) { return(Load_Members(stream)); } -HRESULT LocomotionClass::Save_Members(SaveStreamClass & stream, BOOL cleardirty) +bool LocomotionClass::Save_Members(SaveStreamClass & stream, bool cleardirty) { uintptr_t id = (uintptr_t)this; stream.Serialize(id); Serialize(stream); - if (SUCCEEDED(stream.Result()) && cleardirty) { + if (!stream.Was_Error() && cleardirty) { Dirty = false; } - return(stream.Result()); + return(!stream.Was_Error()); } -HRESULT LocomotionClass::Load_Members(SaveStreamClass & stream) +bool LocomotionClass::Load_Members(SaveStreamClass & stream) { uintptr_t id = 0; stream.Serialize(id); if (stream.Was_Error()) { - return(stream.Result()); + return(false); } assert(id != 0); Swizzle_Here_I_Am(id, this); @@ -265,7 +263,7 @@ HRESULT LocomotionClass::Load_Members(SaveStreamClass & stream) Serialize(stream); stream.Set_Context(outertype, outerid); - return(stream.Result()); + return(!stream.Was_Error()); } diff --git a/code/loco.h b/code/loco.h index 976dcd9ec..24ae81d76 100644 --- a/code/loco.h +++ b/code/loco.h @@ -36,10 +36,10 @@ class LocomotionClass : public IPersistent, public ILocomotion LocomotionClass(void); virtual ~LocomotionClass(void); - virtual HRESULT Load(SaveStreamClass & stream) override; - virtual HRESULT Save(SaveStreamClass & stream, BOOL cleardirty) override; + virtual bool Load(SaveStreamClass & stream) override; + virtual bool Save(SaveStreamClass & stream, bool cleardirty) override; - virtual HRESULT Link_To_Object(void *object) override; + virtual void Link_To_Object(void *object) override; virtual bool Is_Moving(void) override; virtual Coord Destination(void) override; virtual Coord Head_To_Coord(void) override; @@ -107,8 +107,8 @@ class LocomotionClass : public IPersistent, public ILocomotion * from its Load and Save; the record is the swizzle identity followed by whatever * members the class names. */ - HRESULT Save_Members(SaveStreamClass & stream, BOOL cleardirty); - HRESULT Load_Members(SaveStreamClass & stream); + bool Save_Members(SaveStreamClass & stream, bool cleardirty); + bool Load_Members(SaveStreamClass & stream); protected: /* diff --git a/code/mouse.cpp b/code/mouse.cpp index 45b1c729f..8d07ec161 100644 --- a/code/mouse.cpp +++ b/code/mouse.cpp @@ -393,18 +393,17 @@ void MouseClass::Init_Clear(void) /// back into it. Object pointers within the restored state are remapped by the swizzle /// manager, and the theater specific type data is reinitialized to match the scenario. /// -/// Returns with S_OK if the map was loaded, otherwise the stream error. -HRESULT MouseClass::Load(SaveStreamClass & stream) +/// bool; Was the record read whole? +bool MouseClass::Load(SaveStreamClass & stream) { int i; - HRESULT result = BASECLASS::Load(stream); - if (SUCCEEDED(result)) { + bool result = BASECLASS::Load(stream); + if (result) { int theater; stream.Serialize(theater); - result = stream.Result(); - if (FAILED(result)) { - return(result); + if (stream.Was_Error()) { + return(false); } LastTheater = THEATER_NONE; @@ -439,9 +438,8 @@ HRESULT MouseClass::Load(SaveStreamClass & stream) stream.Set_Context("MouseClass"); Serialize(stream); - result = stream.Result(); - if (FAILED(result)) { - return(result); + if (stream.Was_Error()) { + return(false); } /* @@ -480,24 +478,21 @@ HRESULT MouseClass::Load(SaveStreamClass & stream) } stream.Serialize_Bytes(CellZones, (int)(sizeof(*CellZones) * CellZoneCount)); - result = stream.Result(); - if (FAILED(result)) { - return(result); + if (stream.Was_Error()) { + return(false); } for (i = 0; i < MZONE_COUNT; i++) { Zones[i] = new unsigned short[ZoneCount]; stream.Serialize_Bytes(Zones[i], (int)(sizeof(unsigned short) * ZoneCount)); - result = stream.Result(); - if (FAILED(result)) { - return(result); + if (stream.Was_Error()) { + return(false); } } stream.Serialize(ZoneConnections); - result = stream.Result(); - if (FAILED(result)) { - return(result); + if (stream.Was_Error()) { + return(false); } for (i = 0; i < Array.Length(); i++) { @@ -506,13 +501,12 @@ HRESULT MouseClass::Load(SaveStreamClass & stream) } int count; stream.Serialize(count); - result = stream.Result(); - if (FAILED(result)) { - return(result); + if (stream.Was_Error()) { + return(false); } for (i = 0; i < count; i++) { if (Load_Object(stream) == NULL) { - return(stream.Result()); + return(false); } } @@ -530,7 +524,7 @@ HRESULT MouseClass::Load(SaveStreamClass & stream) DraggedWaypoint = NULL; LastTheater = Scen->Theater; - result = S_OK; + result = true; } return(result); } @@ -542,45 +536,40 @@ HRESULT MouseClass::Load(SaveStreamClass & stream) /// and zone connections, and then every valid cell, in the order that Load expects to find /// them. Each cell writes its own contents as a record of its own. /// -/// Returns with S_OK if the map was written, otherwise the stream error. -HRESULT MouseClass::Save(SaveStreamClass & stream) +/// bool; Was the record written whole? +bool MouseClass::Save(SaveStreamClass & stream) { int i; int count; - HRESULT result = BASECLASS::Save(stream); - if (SUCCEEDED(result)) { + bool result = BASECLASS::Save(stream); + if (result) { int theater = Scen->Theater; stream.Serialize(theater); - result = stream.Result(); - if (FAILED(result)) { - return(result); + if (stream.Was_Error()) { + return(false); } Serialize(stream); - result = stream.Result(); - if (FAILED(result)) { - return(result); + if (stream.Was_Error()) { + return(false); } stream.Serialize_Bytes(CellZones, (int)(sizeof(*CellZones) * CellZoneCount)); - result = stream.Result(); - if (FAILED(result)) { - return(result); + if (stream.Was_Error()) { + return(false); } for (i = 0; i < MZONE_COUNT; i++) { stream.Serialize_Bytes(Zones[i], (int)(sizeof(unsigned short) * ZoneCount)); - result = stream.Result(); - if (FAILED(result)) { - return(result); + if (stream.Was_Error()) { + return(false); } } stream.Serialize(ZoneConnections); - result = stream.Result(); - if (FAILED(result)) { - return(result); + if (stream.Was_Error()) { + return(false); } count = 0; @@ -594,9 +583,8 @@ HRESULT MouseClass::Save(SaveStreamClass & stream) cptr = Iterate(); } stream.Serialize(count); - result = stream.Result(); - if (FAILED(result)) { - return(result); + if (stream.Was_Error()) { + return(false); } Reset_Iterator(); cptr = Iterate(); @@ -612,7 +600,7 @@ HRESULT MouseClass::Save(SaveStreamClass & stream) return(result); } - result = S_OK; + result = true; } return(result); } diff --git a/code/mouse.h b/code/mouse.h index a3888c1ea..09d260c65 100644 --- a/code/mouse.h +++ b/code/mouse.h @@ -42,8 +42,8 @@ class MouseClass: public ScrollClass typedef ScrollClass BASECLASS; public: - virtual HRESULT Load(SaveStreamClass & stream) override; - virtual HRESULT Save(SaveStreamClass & stream) override; + virtual bool Load(SaveStreamClass & stream) override; + virtual bool Save(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/particle.cpp b/code/particle.cpp index dbdf2dcbd..35e3e38b4 100644 --- a/code/particle.cpp +++ b/code/particle.cpp @@ -928,10 +928,10 @@ void ParticleClass::Serialize(SaveStreamClass & stream) /// /// The stream to write this particle to. /// Should the modified flag be cleared once written? -/// Returns with S_OK if the particle was written successfully. -HRESULT ParticleClass::Save(SaveStreamClass & stream, BOOL cleardirty) +/// bool; Was the record written whole? +bool ParticleClass::Save(SaveStreamClass & stream, bool cleardirty) { - HRESULT result = BASECLASS::Save(stream, cleardirty); + bool result = BASECLASS::Save(stream, cleardirty); WasSaved = true; return(result); } diff --git a/code/particle.h b/code/particle.h index c17a87efe..a8c16b9e0 100644 --- a/code/particle.h +++ b/code/particle.h @@ -32,7 +32,7 @@ class ParticleClass : public ObjectClass virtual ~ParticleClass(void) override; virtual ClassID Class_ID(void) const override; - virtual HRESULT Save(SaveStreamClass & stream, BOOL cleardirty) override; + virtual bool Save(SaveStreamClass & stream, bool cleardirty) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/persist.h b/code/persist.h index d87f60c24..1b9524367 100644 --- a/code/persist.h +++ b/code/persist.h @@ -22,9 +22,9 @@ struct IPersistent virtual ~IPersistent(void) {} virtual ClassID Class_ID(void) const = 0; - virtual HRESULT Load(SaveStreamClass & stream) = 0; + virtual bool Load(SaveStreamClass & stream) = 0; // Restores what the record could not carry, once the record has been checked; an object // takes its place in the map or a side table here, never while its record is still in doubt. virtual void Post_Load(void) {} - virtual HRESULT Save(SaveStreamClass & stream, BOOL cleardirty) = 0; + virtual bool Save(SaveStreamClass & stream, bool cleardirty) = 0; }; diff --git a/code/revent.cpp b/code/revent.cpp index e2406955c..dedf27157 100644 --- a/code/revent.cpp +++ b/code/revent.cpp @@ -378,7 +378,7 @@ bool RadarEventClass::Save(SaveStreamClass & stream) stream.Serialize(LastRadarEventCell); - return(SUCCEEDED(stream.Result())); + return(!stream.Was_Error()); } @@ -408,7 +408,7 @@ bool RadarEventClass::Load(SaveStreamClass & stream) stream.Serialize(LastRadarEventCell); - return(SUCCEEDED(stream.Result())); + return(!stream.Was_Error()); } diff --git a/code/saveload.cpp b/code/saveload.cpp index 3fc6c9821..1a8901a0c 100644 --- a/code/saveload.cpp +++ b/code/saveload.cpp @@ -161,11 +161,11 @@ unsigned int ExpectedGameVersion = LoadOptionsClass::GAMEVER_OPENTS; /// object's Save writes; a reader that does not consume exactly that length has read a /// record of a different shape than was written. /// -/// Returns with S_OK, or the failure code of the write that went wrong. -HRESULT Save_Object(SaveStreamClass & stream, IPersistent * persist) +/// bool; Was the record written whole? +bool Save_Object(SaveStreamClass & stream, IPersistent * persist) { if (persist == NULL) { - return(E_POINTER); + return(false); } ClassID classid = persist->Class_ID(); @@ -175,22 +175,22 @@ HRESULT Save_Object(SaveStreamClass & stream, IPersistent * persist) stream.Serialize(length); unsigned int const start = stream.Offset(); - HRESULT result = persist->Save(stream, TRUE); - if (FAILED(result)) { - return(result); + bool result = persist->Save(stream, true); + if (!result) { + return(false); } length = stream.Offset() - start; stream.Overwrite_Bytes(lengthat, &length, sizeof(length)); - return(stream.Result()); + return(!stream.Was_Error()); } -HRESULT Save_Object(SaveStreamClass & stream, ILocomotion * locomotion) +bool Save_Object(SaveStreamClass & stream, ILocomotion * locomotion) { IPersistent * const persist = dynamic_cast(locomotion); if (persist == NULL) { - return(E_NOINTERFACE); + return(false); } return(Save_Object(stream, persist)); } @@ -229,7 +229,7 @@ IPersistent * Load_Object(SaveStreamClass & stream) return(NULL); } - bool ok = SUCCEEDED(persist->Load(stream)); + bool ok = persist->Load(stream); if (ok && stream.Offset() != start + length) { DebugString("Save record of %s at %u is %u bytes but %u were read\n", typeid(*persist).name(), start, length, stream.Offset() - start); @@ -251,45 +251,44 @@ IPersistent * Load_Object(SaveStreamClass & stream) /// The objects are not handed back -- each one reattaches itself to its own heap as it is /// constructed, which is what refills the game's vectors. /// -/// Returns with S_OK, or the failure code of the read that went wrong. -static HRESULT Load_Vector(SaveStreamClass & stream) +/// bool; Was the record read whole? +static bool Load_Vector(SaveStreamClass & stream) { int count = 0; stream.Serialize(count); if (stream.Was_Error()) { - return(stream.Result()); + return(false); } if (count < 0) { - return(E_FAIL); + return(false); } for (int index = 0; index < count; index++) { if (Load_Object(stream) == NULL) { - return(stream.Result()); + return(false); } } - return(S_OK); + return(true); } /// /// Saves a vector of persistent objects to the save game stream. /// -/// Returns with S_OK, or the failure code of the first object that refused to -/// save. +/// bool; Was the record read whole? template -static HRESULT Save_Vector(SaveStreamClass & stream, const DynamicVectorClass &list) +static bool Save_Vector(SaveStreamClass & stream, const DynamicVectorClass &list) { int count = list.Count(); stream.Serialize(count); for (int index = 0; index < count; index++) { - HRESULT const result = Save_Object(stream, list[index]); - if (FAILED(result)) { - return(result); + bool const result = Save_Object(stream, list[index]); + if (!result) { + return(false); } } - return(stream.Result()); + return(!stream.Was_Error()); } @@ -388,7 +387,7 @@ static bool Put_All(SaveStreamClass & stream, int save_net) Rule->Save(stream); DebugString("Saving AnimTypes\n"); - if (FAILED(Save_Vector(stream, AnimTypes))) { + if (!Save_Vector(stream, AnimTypes)) { DebugString("\t***** FAILED!\n"); return(false); } @@ -397,13 +396,13 @@ static bool Put_All(SaveStreamClass & stream, int save_net) ** Save the map. The map must be saved first, since it saves the Theater. */ DebugString("Saving Map\n"); - if (FAILED(Map.Save(stream))) { + if (!Map.Save(stream)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Tunnels\n"); - if (FAILED(Save_Vector(stream, Tubes))) { + if (!Save_Vector(stream, Tubes)) { DebugString("\t***** FAILED!\n"); return(false); } @@ -412,7 +411,7 @@ static bool Put_All(SaveStreamClass & stream, int save_net) ** Save miscellaneous variables. */ DebugString("Saving Misc. Values\n"); - if (FAILED(Save_Misc_Values(stream))) { + if (!Save_Misc_Values(stream)) { DebugString("\t***** FAILED!\n"); return(false); } @@ -421,13 +420,13 @@ static bool Put_All(SaveStreamClass & stream, int save_net) ** Save the Logic & Map layers */ DebugString("Saving Logic\n"); - if (FAILED(Logic.Save(stream))) { + if (!Logic.Save(stream)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving TacticalMap\n"); - if (FAILED(Save_Object(stream, TacticalMap))) { + if (!Save_Object(stream, TacticalMap)) { DebugString("\t***** FAILED!\n"); return(false); } @@ -437,248 +436,248 @@ static bool Put_All(SaveStreamClass & stream, int save_net) ** TFixedIHeap class. */ DebugString("Saving HouseTypes\n"); - if (FAILED(Save_Vector(stream, HouseTypes))) { + if (!Save_Vector(stream, HouseTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Houses\n"); - if (FAILED(Save_Vector(stream, Houses))) { + if (!Save_Vector(stream, Houses)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Units\n"); - if (FAILED(Save_Vector(stream, Units))) { + if (!Save_Vector(stream, Units)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving UnitTypes\n"); - if (FAILED(Save_Vector(stream, UnitTypes))) { + if (!Save_Vector(stream, UnitTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving InfantryTypes\n"); - if (FAILED(Save_Vector(stream, InfantryTypes))) { + if (!Save_Vector(stream, InfantryTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Infantry\n"); - if (FAILED(Save_Vector(stream, Infantry))) { + if (!Save_Vector(stream, Infantry)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving BuildingTypes\n"); - if (FAILED(Save_Vector(stream, BuildingTypes))) { + if (!Save_Vector(stream, BuildingTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Buildings\n"); - if (FAILED(Save_Vector(stream, Buildings))) { + if (!Save_Vector(stream, Buildings)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving AircraftTypes\n"); - if (FAILED(Save_Vector(stream, AircraftTypes))) { + if (!Save_Vector(stream, AircraftTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Aircraft\n"); - if (FAILED(Save_Vector(stream, Aircraft))) { + if (!Save_Vector(stream, Aircraft)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Anims\n"); - if (FAILED(Save_Vector(stream, Anims))) { + if (!Save_Vector(stream, Anims)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving TaskForces\n"); - if (FAILED(Save_Vector(stream, TaskForces))) { + if (!Save_Vector(stream, TaskForces)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving TeamTypes\n"); - if (FAILED(Save_Vector(stream, TeamTypes))) { + if (!Save_Vector(stream, TeamTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Teams\n"); - if (FAILED(Save_Vector(stream, Teams))) { + if (!Save_Vector(stream, Teams)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving ScriptTypes\n"); - if (FAILED(Save_Vector(stream, ScriptTypes))) { + if (!Save_Vector(stream, ScriptTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Scripts\n"); - if (FAILED(Save_Vector(stream, Scripts))) { + if (!Save_Vector(stream, Scripts)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving TagTypes\n"); - if (FAILED(Save_Vector(stream, TagTypes))) { + if (!Save_Vector(stream, TagTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Tags\n"); - if (FAILED(Save_Vector(stream, Tags))) { + if (!Save_Vector(stream, Tags)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving TriggerTypes\n"); - if (FAILED(Save_Vector(stream, TriggerTypes))) { + if (!Save_Vector(stream, TriggerTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Triggers\n"); - if (FAILED(Save_Vector(stream, Triggers))) { + if (!Save_Vector(stream, Triggers)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving AITriggerTypes\n"); - if (FAILED(Save_Vector(stream, AITriggerTypes))) { + if (!Save_Vector(stream, AITriggerTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Actions\n"); - if (FAILED(Save_Vector(stream, Actions))) { + if (!Save_Vector(stream, Actions)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Events\n"); - if (FAILED(Save_Vector(stream, Events))) { + if (!Save_Vector(stream, Events)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Factories\n"); - if (FAILED(Save_Vector(stream, Factories))) { + if (!Save_Vector(stream, Factories)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving VoxelAnimTypes\n"); - if (FAILED(Save_Vector(stream, VoxelAnimTypes))) { + if (!Save_Vector(stream, VoxelAnimTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving VoxelAnims\n"); - if (FAILED(Save_Vector(stream, VoxelAnims))) { + if (!Save_Vector(stream, VoxelAnims)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Warheads\n"); - if (FAILED(Save_Vector(stream, Warheads))) { + if (!Save_Vector(stream, Warheads)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Weapons\n"); - if (FAILED(Save_Vector(stream, Weapons))) { + if (!Save_Vector(stream, Weapons)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving ParticleTypes\n"); - if (FAILED(Save_Vector(stream, ParticleTypes))) { + if (!Save_Vector(stream, ParticleTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Particles\n"); - if (FAILED(Save_Vector(stream, Particles))) { + if (!Save_Vector(stream, Particles)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving ParticleSystemTypes\n"); - if (FAILED(Save_Vector(stream, ParticleSystemTypes))) { + if (!Save_Vector(stream, ParticleSystemTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving ParticleSystems\n"); - if (FAILED(Save_Vector(stream, ParticleSystems))) { + if (!Save_Vector(stream, ParticleSystems)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving BulletTypes\n"); - if (FAILED(Save_Vector(stream, BulletTypes))) { + if (!Save_Vector(stream, BulletTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Bullets\n"); - if (FAILED(Save_Vector(stream, Bullets))) { + if (!Save_Vector(stream, Bullets)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving WaypointPaths\n"); - if (FAILED(Save_Vector(stream, WaypointPaths))) { + if (!Save_Vector(stream, WaypointPaths)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving SmudgeTypes\n"); - if (FAILED(Save_Vector(stream, SmudgeTypes))) { + if (!Save_Vector(stream, SmudgeTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving OverlayTypes\n"); - if (FAILED(Save_Vector(stream, OverlayTypes))) { + if (!Save_Vector(stream, OverlayTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving LightSources\n"); - if (FAILED(Save_Vector(stream, LightSources))) { + if (!Save_Vector(stream, LightSources)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving BuildingLights\n"); - if (FAILED(Save_Vector(stream, BuildingLights))) { + if (!Save_Vector(stream, BuildingLights)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Sides\n"); - if (FAILED(Save_Vector(stream, Sides))) { + if (!Save_Vector(stream, Sides)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Tiberiums\n"); - if (FAILED(Save_Vector(stream, Tiberiums))) { + if (!Save_Vector(stream, Tiberiums)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Empulses\n"); - if (FAILED(Save_Vector(stream, EMPulseClass::EMPulses))) { + if (!Save_Vector(stream, EMPulseClass::EMPulses)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving SuperWeaponTypes\n"); - if (FAILED(Save_Vector(stream, SuperWeaponTypes))) { + if (!Save_Vector(stream, SuperWeaponTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving SuperWeapons\n"); - if (FAILED(Save_Vector(stream, SuperWeapons))) { + if (!Save_Vector(stream, SuperWeapons)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving TerrianTypes\n"); - if (FAILED(Save_Vector(stream, TerrainTypes))) { + if (!Save_Vector(stream, TerrainTypes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Terrains\n"); - if (FAILED(Save_Vector(stream, Terrains))) { + if (!Save_Vector(stream, Terrains)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving FoggedObjects\n"); - if (FAILED(Save_Vector(stream, FoggedObjectClass::FoggyObjects))) { + if (!Save_Vector(stream, FoggedObjectClass::FoggyObjects)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving AlphaShapes\n"); - if (FAILED(Save_Vector(stream, AlphaShapes))) { + if (!Save_Vector(stream, AlphaShapes)) { DebugString("\t***** FAILED!\n"); return(false); } DebugString("Saving Waves\n"); - if (FAILED(Save_Vector(stream, Waves))) { + if (!Save_Vector(stream, Waves)) { DebugString("\t***** FAILED!\n"); return(false); } @@ -762,17 +761,17 @@ static bool Get_All(SaveStreamClass & stream, bool save_net) return(false); } - if (FAILED(Load_Vector(stream))) { /// AnimTypes + if (!Load_Vector(stream)) { /// AnimTypes return(false); } Map.Load(stream); - if (FAILED(Load_Vector(stream))) { /// Tubes + if (!Load_Vector(stream)) { /// Tubes return(false); } - if (FAILED(Load_Misc_Values(stream))) { + if (!Load_Misc_Values(stream)) { return(false); } @@ -796,151 +795,151 @@ static bool Get_All(SaveStreamClass & stream, bool save_net) return(false); } - if (FAILED(Load_Vector(stream))) { /// HouseTypes + if (!Load_Vector(stream)) { /// HouseTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Houses + if (!Load_Vector(stream)) { /// Houses return(false); } - if (FAILED(Load_Vector(stream))) { /// Units + if (!Load_Vector(stream)) { /// Units return(false); } - if (FAILED(Load_Vector(stream))) { /// UnitTypes + if (!Load_Vector(stream)) { /// UnitTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// InfantryTypes + if (!Load_Vector(stream)) { /// InfantryTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Infantry + if (!Load_Vector(stream)) { /// Infantry return(false); } - if (FAILED(Load_Vector(stream))) { /// BuildingTypes + if (!Load_Vector(stream)) { /// BuildingTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Buildings + if (!Load_Vector(stream)) { /// Buildings return(false); } - if (FAILED(Load_Vector(stream))) { /// AircraftTypes + if (!Load_Vector(stream)) { /// AircraftTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Aircraft + if (!Load_Vector(stream)) { /// Aircraft return(false); } - if (FAILED(Load_Vector(stream))) { /// Anims + if (!Load_Vector(stream)) { /// Anims return(false); } - if (FAILED(Load_Vector(stream))) { /// TaskForces + if (!Load_Vector(stream)) { /// TaskForces return(false); } - if (FAILED(Load_Vector(stream))) { /// TeamTypes + if (!Load_Vector(stream)) { /// TeamTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Teams + if (!Load_Vector(stream)) { /// Teams return(false); } - if (FAILED(Load_Vector(stream))) { /// ScriptTypes + if (!Load_Vector(stream)) { /// ScriptTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Scripts + if (!Load_Vector(stream)) { /// Scripts return(false); } - if (FAILED(Load_Vector(stream))) { /// TagTypes + if (!Load_Vector(stream)) { /// TagTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Tags + if (!Load_Vector(stream)) { /// Tags return(false); } - if (FAILED(Load_Vector(stream))) { /// TriggerTypes + if (!Load_Vector(stream)) { /// TriggerTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Triggers + if (!Load_Vector(stream)) { /// Triggers return(false); } - if (FAILED(Load_Vector(stream))) { /// AITriggerTypes + if (!Load_Vector(stream)) { /// AITriggerTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Actions + if (!Load_Vector(stream)) { /// Actions return(false); } - if (FAILED(Load_Vector(stream))) { /// Events + if (!Load_Vector(stream)) { /// Events return(false); } - if (FAILED(Load_Vector(stream))) { /// Factories + if (!Load_Vector(stream)) { /// Factories return(false); } - if (FAILED(Load_Vector(stream))) { /// VoxelAnimTypes + if (!Load_Vector(stream)) { /// VoxelAnimTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// VoxelAnims + if (!Load_Vector(stream)) { /// VoxelAnims return(false); } - if (FAILED(Load_Vector(stream))) { /// Warheads + if (!Load_Vector(stream)) { /// Warheads return(false); } - if (FAILED(Load_Vector(stream))) { /// Weapons + if (!Load_Vector(stream)) { /// Weapons return(false); } - if (FAILED(Load_Vector(stream))) { /// ParticleTypes + if (!Load_Vector(stream)) { /// ParticleTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Particles + if (!Load_Vector(stream)) { /// Particles return(false); } - if (FAILED(Load_Vector(stream))) { /// ParticleSystemTypes + if (!Load_Vector(stream)) { /// ParticleSystemTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// ParticleSystems + if (!Load_Vector(stream)) { /// ParticleSystems return(false); } - if (FAILED(Load_Vector(stream))) { /// BulletTypes + if (!Load_Vector(stream)) { /// BulletTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Bullets + if (!Load_Vector(stream)) { /// Bullets return(false); } - if (FAILED(Load_Vector(stream))) { /// WaypointPaths + if (!Load_Vector(stream)) { /// WaypointPaths return(false); } - if (FAILED(Load_Vector(stream))) { /// SmudgeTypes + if (!Load_Vector(stream)) { /// SmudgeTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// OverlayTypes + if (!Load_Vector(stream)) { /// OverlayTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// LightSources + if (!Load_Vector(stream)) { /// LightSources return(false); } - if (FAILED(Load_Vector(stream))) { /// BuildingLights + if (!Load_Vector(stream)) { /// BuildingLights return(false); } - if (FAILED(Load_Vector(stream))) { /// Sides + if (!Load_Vector(stream)) { /// Sides return(false); } - if (FAILED(Load_Vector(stream))) { /// Tiberiums + if (!Load_Vector(stream)) { /// Tiberiums return(false); } - if (FAILED(Load_Vector(stream))) { /// EMPulseClass::EMPulses + if (!Load_Vector(stream)) { /// EMPulseClass::EMPulses return(false); } - if (FAILED(Load_Vector(stream))) { /// SuperWeaponTypes + if (!Load_Vector(stream)) { /// SuperWeaponTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// SuperWeapons + if (!Load_Vector(stream)) { /// SuperWeapons return(false); } - if (FAILED(Load_Vector(stream))) { /// TerrainTypes + if (!Load_Vector(stream)) { /// TerrainTypes return(false); } - if (FAILED(Load_Vector(stream))) { /// Terrains + if (!Load_Vector(stream)) { /// Terrains return(false); } - if (FAILED(Load_Vector(stream))) { /// FoggedObjectClass::FoggyObjects + if (!Load_Vector(stream)) { /// FoggedObjectClass::FoggyObjects return(false); } - if (FAILED(Load_Vector(stream))) { /// AlphaShapes + if (!Load_Vector(stream)) { /// AlphaShapes return(false); } - if (FAILED(Load_Vector(stream))) { /// Waves + if (!Load_Vector(stream)) { /// Waves return(false); } if (!VeinholeMonsterClass::Load_All(stream)) { @@ -1029,7 +1028,7 @@ bool Save_Game(const char *file_name, char const * descr) SaveStreamClass stream(file.Content, SaveStreamClass::MODE_SAVE); bool res = Put_All(stream, 0); if (!res) { - DebugString("\t***** FAILED! (0x%08lx)\n", (unsigned long)stream.Result()); + DebugString("\t***** FAILED!\n"); } if (res) { @@ -1119,7 +1118,7 @@ bool Load_Game(const char *file_name) SaveStreamClass stream(file.Content, SaveStreamClass::MODE_LOAD); bool res = Get_All(stream, false); if (!res) { - DebugString("\t***** FAILED! (0x%08lx at %u of %u bytes)\n", (unsigned long)stream.Result(), stream.Offset(), stream.Size()); + DebugString("\t***** FAILED! (at %u of %u bytes)\n", stream.Offset(), stream.Size()); // What was loaded stays in the heaps until the next teardown, which must not // follow the identities still sitting in its pointer slots. Swizzler.Abandon(); @@ -1224,7 +1223,7 @@ static void Serialize_Misc_Values(SaveStreamClass & stream) int Save_Misc_Values(SaveStreamClass & stream) { Serialize_Misc_Values(stream); - return(stream.Result()); + return(!stream.Was_Error()); } @@ -1245,7 +1244,7 @@ int Load_Misc_Values(SaveStreamClass & stream) { stream.Set_Context("Load_Misc_Values"); Serialize_Misc_Values(stream); - return(stream.Result()); + return(!stream.Was_Error()); } diff --git a/code/saveload.h b/code/saveload.h index c4db4bb6d..bab276bf6 100644 --- a/code/saveload.h +++ b/code/saveload.h @@ -29,8 +29,8 @@ int Save_Misc_Values(SaveStreamClass & stream); // An object travels as its class identifier, the length of its record, and the record. // A locomotor loaded this way is handed back unowned; the caller takes it. -HRESULT Save_Object(SaveStreamClass & stream, IPersistent * object); -HRESULT Save_Object(SaveStreamClass & stream, ILocomotion * locomotion); +bool Save_Object(SaveStreamClass & stream, IPersistent * object); +bool Save_Object(SaveStreamClass & stream, ILocomotion * locomotion); IPersistent * Load_Object(SaveStreamClass & stream); bool Get_Savefile_Info(char const * name, SaveVersionInfo * info); bool Save_Game(const char *file_name, char const * descr); diff --git a/code/savestream.cpp b/code/savestream.cpp index 9c7ce0d36..a37a2e55b 100644 --- a/code/savestream.cpp +++ b/code/savestream.cpp @@ -29,7 +29,7 @@ SaveStreamClass::SaveStreamClass(std::vector & buffer, ModeType m Buffer(&buffer), Cursor(mode == MODE_SAVE ? (unsigned int)buffer.size() : 0), Mode(mode), - ErrorCode(S_OK), + Failed(false), FormatVersion(mode == MODE_LOAD ? LoadedSaveVersion : ExpectedGameVersion), OwnerType(NULL), OwnerID(0) @@ -39,8 +39,8 @@ SaveStreamClass::SaveStreamClass(std::vector & buffer, ModeType m void SaveStreamClass::Fail(void) { - if (SUCCEEDED(ErrorCode)) { - ErrorCode = E_FAIL; + if (!Failed) { + Failed = true; } } @@ -53,11 +53,11 @@ void SaveStreamClass::Fail(void) /// void SaveStreamClass::Serialize_Bytes(void * data, int length) { - if (FAILED(ErrorCode)) { + if (Failed) { return; } if (length < 0) { - ErrorCode = E_FAIL; + Failed = true; return; } if (length == 0) { @@ -71,7 +71,7 @@ void SaveStreamClass::Serialize_Bytes(void * data, int length) Cursor = (unsigned int)Buffer->size(); } else { if ((unsigned int)length > Buffer->size() - Cursor) { - ErrorCode = E_FAIL; + Failed = true; return; } memcpy(bytes, Buffer->data() + Cursor, (std::size_t)length); @@ -83,11 +83,11 @@ void SaveStreamClass::Serialize_Bytes(void * data, int length) // A saver patches a length it could not know until the record was written. void SaveStreamClass::Overwrite_Bytes(unsigned int offset, void const * data, int length) { - if (FAILED(ErrorCode) || Mode != MODE_SAVE || length <= 0) { + if (Failed || Mode != MODE_SAVE || length <= 0) { return; } if (offset > Buffer->size() || (unsigned int)length > Buffer->size() - offset) { - ErrorCode = E_FAIL; + Failed = true; return; } memcpy(Buffer->data() + offset, data, (std::size_t)length); diff --git a/code/savestream.h b/code/savestream.h index 0ffba91aa..a8e1509a2 100644 --- a/code/savestream.h +++ b/code/savestream.h @@ -83,8 +83,7 @@ class SaveStreamClass * does nothing, so a class lists its members without checking each one and the * caller asks once whether the whole pass worked. */ - HRESULT Result(void) const {return(ErrorCode);} - bool Was_Error(void) const {return(FAILED(ErrorCode));} + bool Was_Error(void) const {return(Failed);} /* * Stops the pass here. A container that reads back a length no honest save could @@ -357,7 +356,7 @@ class SaveStreamClass std::vector * Buffer; unsigned int Cursor; ModeType Mode; - HRESULT ErrorCode; + bool Failed; unsigned int FormatVersion; /* diff --git a/code/session.cpp b/code/session.cpp index 14f619969..cf8a45992 100644 --- a/code/session.cpp +++ b/code/session.cpp @@ -1333,7 +1333,7 @@ bool GameOptionsType::Save(SaveStreamClass & stream) { Serialize(stream); - return(SUCCEEDED(stream.Result())); + return(!stream.Was_Error()); } @@ -1349,7 +1349,7 @@ bool GameOptionsType::Load(SaveStreamClass & stream) stream.Set_Context("GameOptionsType"); Serialize(stream); ScenarioIndex = -1; - return(SUCCEEDED(stream.Result())); + return(!stream.Was_Error()); } diff --git a/code/terrain.cpp b/code/terrain.cpp index 2b71d3f13..ee4cb3fff 100644 --- a/code/terrain.cpp +++ b/code/terrain.cpp @@ -913,8 +913,8 @@ bool TerrainClass::Render(Rect & cliprect, bool forced, bool extras_only) const /// under the identity it was constructed with is dropped before the members arrive. /// /// The stream to read the object from. -/// Returns with S_OK if the object was read successfully. -HRESULT TerrainClass::Load(SaveStreamClass & stream) +/// bool; Was the record read whole? +bool TerrainClass::Load(SaveStreamClass & stream) { TargetTracker.Remove_Index(Fetch_ID()); diff --git a/code/terrain.h b/code/terrain.h index 649b99d28..83fefb448 100644 --- a/code/terrain.h +++ b/code/terrain.h @@ -60,7 +60,7 @@ class TerrainClass : public ObjectClass, public StageClass virtual ~TerrainClass(void) override; virtual ClassID Class_ID(void) const override; - virtual HRESULT Load(SaveStreamClass & stream) override; + virtual bool Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/tiberium.cpp b/code/tiberium.cpp index 7a72ab37c..3b01431c8 100644 --- a/code/tiberium.cpp +++ b/code/tiberium.cpp @@ -204,10 +204,10 @@ ClassID TiberiumClass::Class_ID(void) const /// The spread and growth pools are dropped before the members arrive, since the counts /// they track are about to be replaced with the saved ones. /// -/// Returns with S_OK if the tiberium type was loaded. +/// bool; Was the record read whole? /// The spread and growth systems are not saved, so they come back empty. They /// must be rebuilt once the game has finished loading. -HRESULT TiberiumClass::Load(SaveStreamClass & stream) +bool TiberiumClass::Load(SaveStreamClass & stream) { Clear_Spread(); Clear_Growth(); diff --git a/code/tiberium.h b/code/tiberium.h index ed9615c08..7e02a6f8d 100644 --- a/code/tiberium.h +++ b/code/tiberium.h @@ -38,7 +38,7 @@ class TiberiumClass : public AbstractTypeClass virtual ~TiberiumClass() override; virtual ClassID Class_ID(void) const override; - virtual HRESULT Load(SaveStreamClass & stream) override; + virtual bool Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; diff --git a/code/unit.cpp b/code/unit.cpp index 0be0b8565..33eb5cdb7 100644 --- a/code/unit.cpp +++ b/code/unit.cpp @@ -6008,8 +6008,8 @@ bool UnitClass::Ready_To_Commence(void) /// again once that identity has arrived. /// /// The stream to read this unit from. -/// Returns with S_OK if the unit was read successfully. -HRESULT UnitClass::Load(SaveStreamClass & stream) +/// bool; Was the record read whole? +bool UnitClass::Load(SaveStreamClass & stream) { TargetTracker.Remove_Index(Fetch_ID()); return(BASECLASS::Load(stream)); diff --git a/code/unit.h b/code/unit.h index 95f683c7c..f61d7fb2c 100644 --- a/code/unit.h +++ b/code/unit.h @@ -135,7 +135,7 @@ class UnitClass : public FootClass virtual ~UnitClass(void) override; virtual ClassID Class_ID(void) const override; - virtual HRESULT Load(SaveStreamClass & stream) override; + virtual bool Load(SaveStreamClass & stream) override; virtual void Serialize(SaveStreamClass & stream) override; virtual void Post_Load(void) override; diff --git a/code/vein.cpp b/code/vein.cpp index 5a8887c92..c14d022c1 100644 --- a/code/vein.cpp +++ b/code/vein.cpp @@ -916,7 +916,7 @@ bool VeinholeMonsterClass::Load_All(SaveStreamClass & stream) stream.Set_Context(typeid(*monster).name(), id); monster->Serialize(stream); - if (FAILED(stream.Result())) { + if (stream.Was_Error()) { return(false); } @@ -931,7 +931,7 @@ bool VeinholeMonsterClass::Load_All(SaveStreamClass & stream) } monster->GrowthQueue->Serialize(stream, monster->GrowthNodes); - if (FAILED(stream.Result())) { + if (stream.Was_Error()) { return(false); } @@ -997,7 +997,7 @@ bool VeinholeMonsterClass::Save_All(SaveStreamClass & stream) } VeinholeMonsters[i]->Serialize(stream); - if (FAILED(stream.Result())) { + if (stream.Was_Error()) { return(false); } @@ -1012,7 +1012,7 @@ bool VeinholeMonsterClass::Save_All(SaveStreamClass & stream) } VeinholeMonsters[i]->GrowthQueue->Serialize(stream, VeinholeMonsters[i]->GrowthNodes); - if (FAILED(stream.Result())) { + if (stream.Was_Error()) { return(false); } } From 24c2e8509099bec3469e4e4509f1ceb761296ba3 Mon Sep 17 00:00:00 2001 From: Jakub Vesely <1251980+tinix0@users.noreply.github.com> Date: Fri, 28 Aug 2026 17:33:16 +0200 Subject: [PATCH 029/179] Stop narrowing pointers through 32-bit integers --- code/abstype.cpp | 4 +- code/abuffer.cpp | 20 ++--- code/abuffer.h | 22 ++--- code/aircraft.cpp | 4 +- code/aircraft.h | 2 +- code/alphashp.cpp | 8 +- code/audio/audiomovie.cpp | 2 +- code/audio/audiomovie.h | 2 +- code/blit.cpp | 8 +- code/building.cpp | 10 +-- code/building.h | 2 +- code/cell.cpp | 8 +- code/display.cpp | 6 +- code/dsurface.cpp | 182 +++++++++++++++++++------------------- code/foot.cpp | 4 +- code/foot.h | 2 +- code/globals.cpp | 2 +- code/globals.h | 2 +- code/house.cpp | 4 +- code/msanim.cpp | 2 +- code/object.cpp | 2 +- code/object.h | 2 +- code/priority.h | 23 ++--- code/radar.cpp | 2 +- code/radio.cpp | 4 +- code/radio.h | 4 +- code/srfcache.cpp | 2 +- code/techno.cpp | 2 +- code/techno.h | 2 +- code/unit.cpp | 4 +- code/unit.h | 2 +- code/vein.cpp | 2 +- code/vqa.cpp | 40 ++++----- code/vqalib/audio.cpp | 4 +- code/vqalib/buffer_.cpp | 10 +-- code/vqalib/drawer.cpp | 4 +- code/vqalib/dstream.cpp | 14 +-- code/vqalib/loader.cpp | 14 +-- code/vqalib/task.cpp | 8 +- code/vqalib/vqaplay.h | 2 +- code/vqalib/vqaplayp.h | 4 +- code/wave.cpp | 6 +- code/wstring.cpp | 2 +- code/xsurface.cpp | 2 +- code/zbuffer.cpp | 20 ++--- code/zbuffer.h | 22 ++--- 46 files changed, 252 insertions(+), 247 deletions(-) diff --git a/code/abstype.cpp b/code/abstype.cpp index f02c81965..4a8beb9e7 100644 --- a/code/abstype.cpp +++ b/code/abstype.cpp @@ -47,8 +47,8 @@ AbstractTypeClass::AbstractTypeClass(char const * ininame) : GivenName() { if (ininame == NULL) { - char pstr[16]; - sprintf(pstr, "%08X", (unsigned int)this); + char pstr[24]; + sprintf(pstr, "%p", (void *)this); IniName = TStringID<24>(pstr); } else { IniName = TStringID<24>(ininame); diff --git a/code/abuffer.cpp b/code/abuffer.cpp index 971c8300e..7701a6fc8 100644 --- a/code/abuffer.cpp +++ b/code/abuffer.cpp @@ -38,7 +38,7 @@ ABuffer::ABuffer(Rect rect) : Fill(ABUFFER_COLOR); - BufferStart = (unsigned int)(SurfacePtr->Lock()); + BufferStart = (uintptr_t)(SurfacePtr->Lock()); SurfaceOffset = 0; BufferEnd = BufferStart + BufferWidth * BufferHeight * ABUFFER_BPP; ScrollOffset = ABUFFER_MAX; @@ -70,7 +70,7 @@ void ABuffer::Copy_To(Surface *surface, Rect rect) *surfbuffptr = *pixptr; ++surfbuffptr; pixptr = (unsigned short *)((unsigned char *)pixptr + ABUFFER_BPP); - pixptr = (unsigned short *)Wrap_Overflow((unsigned int)pixptr); + pixptr = (unsigned short *)Wrap_Overflow((uintptr_t)pixptr); } surfbuffptr += steps; } @@ -100,13 +100,13 @@ void ABuffer::Release_Surface(void) /// The alpha value to fill with. /// The run is not wrapped. The caller must split any fill that would otherwise /// run off the end of the buffer. -void ABuffer::Set(unsigned int dst, int size, unsigned short value) +void ABuffer::Set(uintptr_t dst, int size, unsigned short value) { /// Write a single pixel to bring the address up to an int boundary. - if ((unsigned int)dst & 2) { + if (dst & 2) { if (size != 0) { *(unsigned short *)dst = value; - dst = (unsigned int)((unsigned short *)dst + 1); + dst = (uintptr_t)((unsigned short *)dst + 1); size--; } } @@ -168,7 +168,7 @@ void ABuffer::Pan(int x, int y, unsigned short value) /// Slide the origin along the row and fold it back into the buffer. SurfaceOffset += x_delta * ABUFFER_BPP; - unsigned int new_offset = Wrap_Underflow(SurfaceOffset + BufferStart); + uintptr_t new_offset = Wrap_Underflow(SurfaceOffset + BufferStart); new_offset = Wrap_Overflow(new_offset); SurfaceOffset = new_offset - BufferStart; @@ -209,7 +209,7 @@ void ABuffer::Pan(int x, int y, unsigned short value) /// Slide the origin by whole rows and fold it back into the buffer. SurfaceOffset += y_delta * BufferWidth * ABUFFER_BPP; - unsigned int new_offset = Wrap_Underflow(SurfaceOffset + BufferStart); + uintptr_t new_offset = Wrap_Underflow(SurfaceOffset + BufferStart); new_offset = Wrap_Overflow(new_offset); SurfaceOffset = new_offset - BufferStart; @@ -268,7 +268,7 @@ bool ABuffer::Fill(unsigned short value, Rect rect) /// The region of the buffer to reset. void ABuffer::Update(Rect rect) { - unsigned int buffptr = Get_Buffer_Offset(Point2D(rect.X, rect.Y)); + uintptr_t buffptr = Get_Buffer_Offset(Point2D(rect.X, rect.Y)); for (int i = 0; i < rect.Height; ++i) { @@ -294,9 +294,9 @@ void ABuffer::Update(Rect rect) /// /// The point within the buffer to locate. /// Returns with the address of the pixel within the alpha buffer. -unsigned int ABuffer::Get_Buffer_Offset(Point2D pos) +uintptr_t ABuffer::Get_Buffer_Offset(Point2D pos) { - unsigned int buffptr = (unsigned int)SurfacePtr->Lock(pos); + uintptr_t buffptr = (uintptr_t)SurfacePtr->Lock(pos); SurfacePtr->Unlock(); diff --git a/code/abuffer.h b/code/abuffer.h index b30d6f839..175f5688f 100644 --- a/code/abuffer.h +++ b/code/abuffer.h @@ -11,6 +11,8 @@ #include "rect.h" +#include + class Surface; class ABuffer @@ -27,7 +29,7 @@ class ABuffer void Copy_To(Surface * surface, Rect rect); - void Set(unsigned int dst, int size, unsigned short value); + void Set(uintptr_t dst, int size, unsigned short value); void Pan(int x_delta, int y_delta, unsigned short value); @@ -36,16 +38,16 @@ class ABuffer void Update(Rect rect); - unsigned int Get_Buffer_Offset(Point2D position); + uintptr_t Get_Buffer_Offset(Point2D position); - unsigned int Wrap_Overflow(unsigned int position) const; - unsigned int Wrap_Underflow(unsigned int position) const; + uintptr_t Wrap_Overflow(uintptr_t position) const; + uintptr_t Wrap_Underflow(uintptr_t position) const; Surface * Get_Surface(void) const { return(SurfacePtr); } Rect const & Get_Bounds(void) const { return(Bounds); } unsigned int Get_Buffer_Width(void) const { return(BufferWidth); } - unsigned int Get_Buffer_End(void) const { return(BufferEnd); } + uintptr_t Get_Buffer_End(void) const { return(BufferEnd); } private: void Release_Surface(void); @@ -77,8 +79,8 @@ class ABuffer * end, and the number of bytes between the two. The buffer is treated as a ring, so * an address that walks off either end is folded back around by that size. */ - unsigned int BufferStart; - unsigned int BufferEnd; + uintptr_t BufferStart; + uintptr_t BufferEnd; unsigned int BufferSize; /* @@ -97,7 +99,7 @@ class ABuffer int BufferHeight; }; -inline unsigned int ABuffer::Wrap_Overflow(unsigned int position) const +inline uintptr_t ABuffer::Wrap_Overflow(uintptr_t position) const { if (position >= BufferEnd) { position -= BufferSize; @@ -106,7 +108,7 @@ inline unsigned int ABuffer::Wrap_Overflow(unsigned int position) const } -inline unsigned int ABuffer::Wrap_Underflow(unsigned int position) const +inline uintptr_t ABuffer::Wrap_Underflow(uintptr_t position) const { if (position < BufferStart) { position += BufferSize; @@ -120,5 +122,5 @@ extern ABuffer * AlphaBuffer; inline unsigned short *Blit_Wrap_A_Buffer(unsigned short *buf) { - return((unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)buf)); + return((unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)buf)); } diff --git a/code/aircraft.cpp b/code/aircraft.cpp index 875a5e5d3..aaed4c395 100644 --- a/code/aircraft.cpp +++ b/code/aircraft.cpp @@ -2642,7 +2642,7 @@ AbstractClass * AircraftClass::New_LZ(AbstractClass * oldlz) const * HISTORY: * * 06/19/1995 JLB : Created. * *=============================================================================================*/ -RadioMessageType AircraftClass::Receive_Message(RadioClass * from, RadioMessageType message, int & param) +RadioMessageType AircraftClass::Receive_Message(RadioClass * from, RadioMessageType message, intptr_t & param) { AbstractClass * target; @@ -2743,7 +2743,7 @@ RadioMessageType AircraftClass::Receive_Message(RadioClass * from, RadioMessageT ** already at the staging location, then tell it to move onto the transport ** directly. */ - param = (int)this; + param = (intptr_t)this; if (Transmit_Message(RADIO_MOVE_HERE, param, from) != RADIO_ROGER) { Transmit_Message(RADIO_OVER_OUT, from); } else { diff --git a/code/aircraft.h b/code/aircraft.h index 8543f29b7..022f5cfe9 100644 --- a/code/aircraft.h +++ b/code/aircraft.h @@ -157,7 +157,7 @@ class AircraftClass : public FootClass, public IFlyControl void Drop_Off_Cargo(void); virtual void AI(void) override; virtual bool Enter_Idle_Mode(bool initial = false, bool = true) override; - virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, int & param) override; + virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, intptr_t & param) override; virtual void Scatter(Coord const & threat, bool forced=false, bool nokidding=false) override; /* diff --git a/code/alphashp.cpp b/code/alphashp.cpp index 4b89d1fff..d17c3c261 100644 --- a/code/alphashp.cpp +++ b/code/alphashp.cpp @@ -269,12 +269,12 @@ void AlphaShapeClass::Draw_In_Area(Point2D const & point, Rect const & cliprect) *alphaptr = BrightnessTable[pixel][*alphaptr]; } alphaptr++; - alphaptr = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned)alphaptr); + alphaptr = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)alphaptr); } shapeptr += shape_skip; maskptr += mask_skip; alphaptr += alpha_skip; - alphaptr = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned)alphaptr); + alphaptr = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)alphaptr); } } else { for (int i = top; i < bottom; i++) { @@ -346,11 +346,11 @@ void AlphaShapeClass::Draw_All(Rect const & cliprect) unsigned char pixel = *shapeptr++; *alphaptr = BrightnessTable[pixel][*alphaptr]; alphaptr++; - alphaptr = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned)alphaptr); + alphaptr = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)alphaptr); } shapeptr += shape_skip; alphaptr += alpha_skip; - alphaptr = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned)alphaptr); + alphaptr = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)alphaptr); } } else { for (int i = top; i < bottom; i++) { diff --git a/code/audio/audiomovie.cpp b/code/audio/audiomovie.cpp index 32fd4069c..d6692d0e8 100644 --- a/code/audio/audiomovie.cpp +++ b/code/audio/audiomovie.cpp @@ -308,7 +308,7 @@ long __cdecl Unlock_Audio_Handler(void) } -long __cdecl Stream_Audio_Handler(VQAHandle * vqa, long action, void * buffer, long nbytes) +intptr_t __cdecl Stream_Audio_Handler(VQAHandle * vqa, long action, void * buffer, long nbytes) { VQAHandleP * vqap = (VQAHandleP *)vqa; diff --git a/code/audio/audiomovie.h b/code/audio/audiomovie.h index 459e3691d..b0c8a9f7a 100644 --- a/code/audio/audiomovie.h +++ b/code/audio/audiomovie.h @@ -34,4 +34,4 @@ unsigned long __cdecl Timer_Callback_Audio_Handler(VQAHandle * vqa); long __cdecl Lock_Audio_Handler(void); long __cdecl Unlock_Audio_Handler(void); -long __cdecl Stream_Audio_Handler(VQAHandle * vqa, long action, void * buffer, long nbytes); +intptr_t __cdecl Stream_Audio_Handler(VQAHandle * vqa, long action, void * buffer, long nbytes); diff --git a/code/blit.cpp b/code/blit.cpp index ca2d617dd..c65ddfe9e 100644 --- a/code/blit.cpp +++ b/code/blit.cpp @@ -205,8 +205,8 @@ bool Bit_Blit(Surface & dest, Rect const & dcliprect, Rect const & ddrect, Surfa bool overlapped = false; void * dbuffer = NULL; void * sbuffer = NULL; - int zbuffer_offset = 0; - int abuffer_offset = 0; + uintptr_t zbuffer_offset = 0; + uintptr_t abuffer_offset = 0; int current_z = 0; int zbuffer_pitch = 0; int abuffer_pitch = 0; @@ -463,8 +463,8 @@ bool RLE_Blit(Surface & dest, Rect const & dcliprect, Rect const & ddrect, Surfa { static char _temp_buf[256]; - int zbuffer_offset = 0; - int abuffer_offset = 0; + uintptr_t zbuffer_offset = 0; + uintptr_t abuffer_offset = 0; char * zshapelock = NULL; int zbuffer_pitch = 0; int abuffer_pitch = 0; diff --git a/code/building.cpp b/code/building.cpp index 43f3b94c1..ada19ccf7 100644 --- a/code/building.cpp +++ b/code/building.cpp @@ -384,7 +384,7 @@ BuildingClass::~BuildingClass(void) * 06/26/1995 JLB : Forces refinery load anim to start immediately. * * 08/13/1995 JLB : Uses ScenarioInit for special loose "CAN_LOAD" check. * *=============================================================================================*/ -RadioMessageType BuildingClass::Receive_Message(RadioClass * from, RadioMessageType message, int & param) +RadioMessageType BuildingClass::Receive_Message(RadioClass * from, RadioMessageType message, intptr_t & param) { switch (message) { @@ -493,7 +493,7 @@ RadioMessageType BuildingClass::Receive_Message(RadioClass * from, RadioMessageT } Transmit_Message(RADIO_RUN_AWAY); } else { - param = (int)&Map[Get_Coord()]; + param = (intptr_t)&Map[Get_Coord()]; Transmit_Message(RADIO_MOVE_HERE, param); } return(RADIO_ROGER); @@ -528,9 +528,9 @@ RadioMessageType BuildingClass::Receive_Message(RadioClass * from, RadioMessageT } if (Transmit_Message(RADIO_NEED_TO_MOVE) == RADIO_ROGER || needs_to_move) { - param = (int)this; + param = (intptr_t)this; if (Class->IsDockUnload || Class->IsWeeder) { - param = (int)&Map[Get_Cell() + Cell(2, 1)]; + param = (intptr_t)&Map[Get_Cell() + Cell(2, 1)]; /* ** Tell the harvester to move to the docking pad of the building. @@ -547,7 +547,7 @@ RadioMessageType BuildingClass::Receive_Message(RadioClass * from, RadioMessageT } } } else if (Class->IsHelipad) { - param = (int)this; + param = (intptr_t)this; if (Transmit_Message(RADIO_MOVE_HERE, param) == RADIO_YEA_NOW_WHAT) { Transmit_Message(RADIO_TETHER); } diff --git a/code/building.h b/code/building.h index 55f1f1bca..752103902 100644 --- a/code/building.h +++ b/code/building.h @@ -492,7 +492,7 @@ class BuildingClass : public TechnoClass virtual bool Revealed(HouseClass * house) override; virtual void Repair(int control) override; virtual void Sell_Back(int control) override; - virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, int & param) override; + virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, intptr_t & param) override; virtual void AI(void) override; virtual void Cloaking_AI(bool fast) override; virtual void Assign_Target(AbstractClass * target) override; diff --git a/code/cell.cpp b/code/cell.cpp index 83dd800f6..0782ba6d4 100644 --- a/code/cell.cpp +++ b/code/cell.cpp @@ -2057,11 +2057,11 @@ void CellClass::Draw_Shroud_Or_Fog_Shape(Point2D const & drawpoint, Rect const & *alphaptr = pixel; } alphaptr++; - alphaptr = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned)alphaptr); + alphaptr = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)alphaptr); } shapeptr += shape_skip; alphaptr += alpha_skip; - alphaptr = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned)alphaptr); + alphaptr = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)alphaptr); } } else { for (int i = inter_top; i < inter_bottom; i++) { @@ -2133,11 +2133,11 @@ void CellClass::Draw_Fog_Shape(Point2D const & drawpoint, Rect const & cliprect, } } alphaptr++; - alphaptr = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned)alphaptr); + alphaptr = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)alphaptr); } shapeptr += shape_skip; alphaptr += alpha_skip; - alphaptr = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned)alphaptr); + alphaptr = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)alphaptr); } } else { for (int i = inter_top; i < inter_bottom; i++) { diff --git a/code/display.cpp b/code/display.cpp index 29798a650..5d4fc28a5 100644 --- a/code/display.cpp +++ b/code/display.cpp @@ -3479,14 +3479,14 @@ int DisplayClass::Stash_Map_State(void * stash, int) (*(unsigned char *)data) = cptr->IsIceGrowthAllowed; data += sizeof(cptr->IsIceGrowthAllowed); - unsigned int tag = 0; + uintptr_t tag = 0; if (cptr->Tag != NULL) { if (cptr->Tag->Class != NULL) { - tag = (unsigned int)cptr->Tag->Class; + tag = (uintptr_t)cptr->Tag->Class; } } - (*(unsigned int *)data) = tag; + (*(uintptr_t *)data) = tag; data += sizeof(tag); cnum++; diff --git a/code/dsurface.cpp b/code/dsurface.cpp index 4475e9986..350895110 100644 --- a/code/dsurface.cpp +++ b/code/dsurface.cpp @@ -972,9 +972,9 @@ bool DSurface::Draw_Depth_Glow_Line(Rect const & cliprect, Point2D const & start xdelta -= dz2; zbuffer += zwidth; if (zwidth > 0) { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); } else { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((uintptr_t)zbuffer); } z += zwrap; } @@ -982,7 +982,7 @@ bool DSurface::Draw_Depth_Glow_Line(Rect const & cliprect, Point2D const & start if (ydelta > 0) { zbuffer++; offset += 2; - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); ydelta -= dz2; } @@ -1028,9 +1028,9 @@ bool DSurface::Draw_Depth_Glow_Line(Rect const & cliprect, Point2D const & start ydelta -= dx2; zbuffer += zwidth; if (zwidth > 0) { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); } else { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((uintptr_t)zbuffer); } z += zwrap; } @@ -1043,7 +1043,7 @@ bool DSurface::Draw_Depth_Glow_Line(Rect const & cliprect, Point2D const & start zbuffer++; ydelta += dy2; zdelta += dz2; - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); } while (++offset < xcount); } @@ -1081,7 +1081,7 @@ bool DSurface::Draw_Depth_Glow_Line(Rect const & cliprect, Point2D const & start if (xdelta > 0) { zbuffer++; offset += 2; - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); xdelta -= dy2; } @@ -1095,9 +1095,9 @@ bool DSurface::Draw_Depth_Glow_Line(Rect const & cliprect, Point2D const & start buffer = (unsigned char *)buffer + pitch; zbuffer += zwidth; if (zwidth > 0) { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); } else { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((uintptr_t)zbuffer); } z += zwrap; } @@ -1355,15 +1355,15 @@ bool DSurface::Draw_Depth_Antialiased_Line(Rect const & cliprect, Point2D const buffer = (unsigned char *)buffer + pitch; zbuffer += zwidth; if (zwidth > 0) { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); } else { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((uintptr_t)zbuffer); } abuffer += zwidth; if (zwidth > 0) { - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } z += zwrap; } @@ -1375,9 +1375,9 @@ bool DSurface::Draw_Depth_Antialiased_Line(Rect const & cliprect, Point2D const adda += adelta2; zbuffer++; - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); xoff++; neighoff += 2; } while (xoff < xcount); @@ -1458,9 +1458,9 @@ bool DSurface::Draw_Depth_Antialiased_Line(Rect const & cliprect, Point2D const weight += 256; byteoff += 2; zbuffer++; - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } if (adda > 0) { @@ -1473,15 +1473,15 @@ bool DSurface::Draw_Depth_Antialiased_Line(Rect const & cliprect, Point2D const z += zwrap; zbuffer += zwidth; if (zwidth > 0) { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); } else { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((uintptr_t)zbuffer); } abuffer += zwidth; if (zwidth > 0) { - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } i--; } while (i != 0); @@ -1571,24 +1571,24 @@ bool DSurface::Draw_Depth_Antialiased_Line(Rect const & cliprect, Point2D const weight += 256; zbuffer += zwidth; if (zwidth > 0) { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); } else { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((uintptr_t)zbuffer); } abuffer += zwidth; if (zwidth > 0) { - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } z += zwrap; } z += astep; zbuffer++; - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); xoff += 2; neighoff += 4; } while (xoff < xcount); @@ -1672,23 +1672,23 @@ bool DSurface::Draw_Depth_Antialiased_Line(Rect const & cliprect, Point2D const weight += 256; byteoff += 2; zbuffer++; - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } buffer = (unsigned char *)buffer + pitch; zbuffer += zwidth; if (zwidth > 0) { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); } else { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((uintptr_t)zbuffer); } abuffer += zwidth; if (zwidth > 0) { - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } z += yadjust; i--; @@ -2112,15 +2112,15 @@ bool DSurface::Draw_Depth_Shaded_Line(Rect const & cliprect, Point2D const & sta xdelta -= dz2; zbuffer += zwidth; if (zwidth > 0) { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); } else { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((uintptr_t)zbuffer); } abuffer += zwidth; if (zwidth > 0) { - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } z += zwrap; } @@ -2128,9 +2128,9 @@ bool DSurface::Draw_Depth_Shaded_Line(Rect const & cliprect, Point2D const & sta if (ydelta > 0) { zbuffer++; offset += 2; - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); ydelta -= dz2; } @@ -2167,16 +2167,16 @@ bool DSurface::Draw_Depth_Shaded_Line(Rect const & cliprect, Point2D const & sta ydelta -= 2 * dx; zbuffer += zwidth; if (zwidth > 0) { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); } else { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((uintptr_t)zbuffer); } z += zwrap; abuffer += zwidth; if (zwidth > 0) { - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } } @@ -2188,9 +2188,9 @@ bool DSurface::Draw_Depth_Shaded_Line(Rect const & cliprect, Point2D const & sta zbuffer++; ydelta += dy2; zdelta += dz2; - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } } } else { @@ -2220,9 +2220,9 @@ bool DSurface::Draw_Depth_Shaded_Line(Rect const & cliprect, Point2D const & sta if (xdelta > 0) { zbuffer++; offset += 2; - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); xdelta -= dy2; } @@ -2237,16 +2237,16 @@ bool DSurface::Draw_Depth_Shaded_Line(Rect const & cliprect, Point2D const & sta zbuffer += zwidth; if (zwidth > 0) { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)zbuffer); } else { - zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((unsigned int)zbuffer); + zbuffer = (unsigned short *)DepthBuffer->Wrap_Underflow((uintptr_t)zbuffer); } z += zwrap; abuffer += zwidth; if (zwidth > 0) { - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } i--; @@ -2323,10 +2323,10 @@ int DSurface::Draw_Masked_Dashed_Line(Point2D const & startpoint, Point2D const if (astride > 0) { abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } } } else if (start.X == end.X) { @@ -2352,10 +2352,10 @@ int DSurface::Draw_Masked_Dashed_Line(Point2D const & startpoint, Point2D const if (astride > 0) { abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } } } else { @@ -2401,7 +2401,7 @@ int DSurface::Draw_Masked_Dashed_Line(Point2D const & startpoint, Point2D const if (delta > 0) { buffer = (unsigned char *)buffer + pitch; abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); delta -= dx2; } @@ -2409,10 +2409,10 @@ int DSurface::Draw_Masked_Dashed_Line(Point2D const & startpoint, Point2D const if (astride > 0) { abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } } } else { @@ -2440,7 +2440,7 @@ int DSurface::Draw_Masked_Dashed_Line(Point2D const & startpoint, Point2D const if (delta > 0) { k++; abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); delta -= dy2; } @@ -2449,10 +2449,10 @@ int DSurface::Draw_Masked_Dashed_Line(Point2D const & startpoint, Point2D const if (astride > 0) { abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } } } @@ -2478,10 +2478,10 @@ int DSurface::Draw_Masked_Dashed_Line(Point2D const & startpoint, Point2D const if (astride > 0) { abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } } } else if (start.X == end.X) { @@ -2507,10 +2507,10 @@ int DSurface::Draw_Masked_Dashed_Line(Point2D const & startpoint, Point2D const if (astride > 0) { abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } } } else { @@ -2556,7 +2556,7 @@ int DSurface::Draw_Masked_Dashed_Line(Point2D const & startpoint, Point2D const if (delta > 0) { buffer = (unsigned char *)buffer + pitch; abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); delta -= dx2; } @@ -2564,10 +2564,10 @@ int DSurface::Draw_Masked_Dashed_Line(Point2D const & startpoint, Point2D const if (astride > 0) { abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } } } else { @@ -2595,7 +2595,7 @@ int DSurface::Draw_Masked_Dashed_Line(Point2D const & startpoint, Point2D const if (delta > 0) { k++; abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); delta -= dy2; } @@ -2604,10 +2604,10 @@ int DSurface::Draw_Masked_Dashed_Line(Point2D const & startpoint, Point2D const if (astride > 0) { abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } } } @@ -2673,10 +2673,10 @@ bool DSurface::Draw_Masked_Line(Point2D const & startpoint, Point2D const & endp if (astride > 0) { abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } } } else if (start.X == end.X) { @@ -2693,10 +2693,10 @@ bool DSurface::Draw_Masked_Line(Point2D const & startpoint, Point2D const & endp if (astride > 0) { abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } buffer = (unsigned char *)buffer + pitch; @@ -2737,16 +2737,16 @@ bool DSurface::Draw_Masked_Line(Point2D const & startpoint, Point2D const & endp if (delta > 0) { buffer = (unsigned char *)buffer + pitch; abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); delta -= dx2; } if (astride > 0) { abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } delta += dy2; @@ -2770,7 +2770,7 @@ bool DSurface::Draw_Masked_Line(Point2D const & startpoint, Point2D const & endp k++; delta -= dy2; abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } delta += dx2; @@ -2778,10 +2778,10 @@ bool DSurface::Draw_Masked_Line(Point2D const & startpoint, Point2D const & endp if (astride > 0) { abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } } } @@ -2800,10 +2800,10 @@ bool DSurface::Draw_Masked_Line(Point2D const & startpoint, Point2D const & endp if (astride > 0) { abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } } } else if (start.X == end.X) { @@ -2820,10 +2820,10 @@ bool DSurface::Draw_Masked_Line(Point2D const & startpoint, Point2D const & endp if (astride > 0) { abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } buffer = (unsigned char *)buffer + pitch; @@ -2864,16 +2864,16 @@ bool DSurface::Draw_Masked_Line(Point2D const & startpoint, Point2D const & endp if (delta > 0) { buffer = (unsigned char *)buffer + pitch; abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); delta -= dx2; } if (astride > 0) { abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } delta += dy2; @@ -2897,7 +2897,7 @@ bool DSurface::Draw_Masked_Line(Point2D const & startpoint, Point2D const & endp k++; delta -= dy2; abuffer++; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } delta += dx2; @@ -2905,10 +2905,10 @@ bool DSurface::Draw_Masked_Line(Point2D const & startpoint, Point2D const & endp if (astride > 0) { abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)abuffer); } else { abuffer += astride; - abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((unsigned int)abuffer); + abuffer = (unsigned short *)AlphaBuffer->Wrap_Underflow((uintptr_t)abuffer); } } } diff --git a/code/foot.cpp b/code/foot.cpp index 05382daf0..99eb0e410 100644 --- a/code/foot.cpp +++ b/code/foot.cpp @@ -2193,7 +2193,7 @@ bool FootClass::Restore_Mission(void) * HISTORY: * * 05/14/1995 JLB : Created. * *=============================================================================================*/ -RadioMessageType FootClass::Receive_Message(RadioClass * from, RadioMessageType message, int & param) +RadioMessageType FootClass::Receive_Message(RadioClass * from, RadioMessageType message, intptr_t & param) { BuildingClass const * building = NULL; ObjectClass *object = NULL; @@ -2247,7 +2247,7 @@ RadioMessageType FootClass::Receive_Message(RadioClass * from, RadioMessageType ** then it doesn't need further movement instructions. */ case RADIO_NEED_TO_MOVE: - param = (int)NavCom; + param = (intptr_t)NavCom; if (NavCom == NULL || !Locomotion->Is_Moving()) { return(RADIO_ROGER); } diff --git a/code/foot.h b/code/foot.h index 7ec7d23c1..3a34f60b8 100644 --- a/code/foot.h +++ b/code/foot.h @@ -389,7 +389,7 @@ class FootClass : public TechnoClass virtual void Compute_CRC(CRCEngine &) const override; virtual Coord Destination_Coord(void) const override; - virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, int & param) override; + virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, intptr_t & param) override; virtual bool Can_Demolish(void) const override; bool Is_Recruitable(HouseClass const * house=NULL) const; bool Is_On_Priority_Mission(void) const; diff --git a/code/globals.cpp b/code/globals.cpp index c214917b1..5b6a9a89a 100644 --- a/code/globals.cpp +++ b/code/globals.cpp @@ -351,7 +351,7 @@ bool GameActive; ** a long, but the value wasn't supplied to a function. This is used ** specifically for the default reference value. As such, it is not stable. */ -int LParam; +intptr_t LParam; #ifdef _DEBUG diff --git a/code/globals.h b/code/globals.h index 0d7b59e48..13660c365 100644 --- a/code/globals.h +++ b/code/globals.h @@ -250,7 +250,7 @@ extern bool drag_select_aborted; extern GroundType Ground[LAND_COUNT]; -extern int LParam; +extern intptr_t LParam; /* ** Constant externs (data is not modified during game play). diff --git a/code/house.cpp b/code/house.cpp index 89914c629..a288589d9 100644 --- a/code/house.cpp +++ b/code/house.cpp @@ -7187,8 +7187,8 @@ void HouseClass::Make_Base_Nodes(void) for (index = 0; index < finalqueue.Count(); index++) { BuildingTypeClass const * b = finalqueue[index]; - if ((int)b < 0 && (int)b >= -3) { - Base.Nodes.Add(BaseNodeClass((StructType)(int)b, Cell(0, 0))); + if ((intptr_t)b < 0 && (intptr_t)b >= -3) { + Base.Nodes.Add(BaseNodeClass((StructType)(intptr_t)b, Cell(0, 0))); } else { Base.Nodes.Add(BaseNodeClass(b->HeapID, Cell(0, 0))); } diff --git a/code/msanim.cpp b/code/msanim.cpp index 64ed1ef04..535a9e6a1 100644 --- a/code/msanim.cpp +++ b/code/msanim.cpp @@ -1132,7 +1132,7 @@ void MSPrintAnim::Redraw(Surface * surface, Rect const * rect) int x = XPos; int y = YPos; - unsigned printed = Get_Printed_Char_Count(); + size_t printed = Get_Printed_Char_Count(); unsigned end = std::min(printed, strlen(String)); for (unsigned char_index = LineStart; char_index < end; ) { diff --git a/code/object.cpp b/code/object.cpp index 86d388fbb..972e42266 100644 --- a/code/object.cpp +++ b/code/object.cpp @@ -1561,7 +1561,7 @@ void ObjectClass::Detach_All(bool all) * HISTORY: * * 09/24/1994 JLB : Created. * *=============================================================================================*/ -RadioMessageType ObjectClass::Receive_Message(RadioClass *, RadioMessageType message, int & ) +RadioMessageType ObjectClass::Receive_Message(RadioClass *, RadioMessageType message, intptr_t & ) { assert(this != NULL); diff --git a/code/object.h b/code/object.h index 1b166253d..fb9b74874 100644 --- a/code/object.h +++ b/code/object.h @@ -335,7 +335,7 @@ class ObjectClass : public AbstractClass */ virtual void Per_Cell_Process(PCPType) {} virtual BuildingClass * Who_Can_Build_Me(bool intheory, bool legal) const; - virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, int & param); + virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, intptr_t & param); virtual bool Revealed(HouseClass * house); virtual void Repair(int); virtual void Sell_Back(int); diff --git a/code/priority.h b/code/priority.h index 8e7d79c76..0873ee6fc 100644 --- a/code/priority.h +++ b/code/priority.h @@ -13,6 +13,7 @@ #include #include +#include #define PARENT(index) (index >> 1) @@ -73,8 +74,8 @@ class PriorityQueueClass T ** Heap; /// Unused - unsigned MaxNodePointer; - unsigned MinNodePointer; + uintptr_t MaxNodePointer; + uintptr_t MinNodePointer; }; @@ -82,7 +83,7 @@ template PriorityQueueClass::PriorityQueueClass(int size) { MaxNodePointer = 0; - MinNodePointer = UINT_MAX; + MinNodePointer = UINTPTR_MAX; ActiveCount = 0; Size = size; Heap = new T * [size + 1](); @@ -132,12 +133,12 @@ inline bool PriorityQueueClass::Insert(T & node) Heap[index] = &node; ActiveCount++; - if ((unsigned)&node > MaxNodePointer) { - MaxNodePointer = (unsigned)&node; + if ((uintptr_t)&node > MaxNodePointer) { + MaxNodePointer = (uintptr_t)&node; } - if ((unsigned)&node < MinNodePointer) { - MinNodePointer = (unsigned)&node; + if ((uintptr_t)&node < MinNodePointer) { + MinNodePointer = (uintptr_t)&node; } return(true); @@ -274,11 +275,11 @@ void PriorityQueueClass::Serialize(S & stream, T * nodes) if (stream.Is_Loading()) { Heap[slot] = &nodes[index]; - if ((unsigned)Heap[slot] > MaxNodePointer) { - MaxNodePointer = (unsigned)Heap[slot]; + if ((uintptr_t)Heap[slot] > MaxNodePointer) { + MaxNodePointer = (uintptr_t)Heap[slot]; } - if ((unsigned)Heap[slot] < MinNodePointer) { - MinNodePointer = (unsigned)Heap[slot]; + if ((uintptr_t)Heap[slot] < MinNodePointer) { + MinNodePointer = (uintptr_t)Heap[slot]; } } } diff --git a/code/radar.cpp b/code/radar.cpp index 958706328..0077423de 100644 --- a/code/radar.cpp +++ b/code/radar.cpp @@ -1430,7 +1430,7 @@ Point2D RadarClass::Coord_To_Radar_Pixel(Coord const & coord, bool clip) int RadarTrackingStruct::Hash_Old(RadarTrackingStruct const & s) { - return(((unsigned int)s.Object) + 251 * s.Position.X); + return((int)((uintptr_t)s.Object + 251 * s.Position.X)); } int RadarTrackingStruct::Hash2(RadarTrackingStruct const & s) diff --git a/code/radio.cpp b/code/radio.cpp index 04ef8b280..95de96773 100644 --- a/code/radio.cpp +++ b/code/radio.cpp @@ -157,7 +157,7 @@ void RadioClass::Debug_Dump(MonoClass * mono) const * 05/22/1995 JLB : Recognized who is sending the message * * 06/05/1996 JLB : Radio message history tracking. * *=============================================================================================*/ -RadioMessageType RadioClass::Receive_Message(RadioClass * from, RadioMessageType message, int & param) +RadioMessageType RadioClass::Receive_Message(RadioClass * from, RadioMessageType message, intptr_t & param) { /* ** Keep a record of the last message received by this radio. @@ -219,7 +219,7 @@ RadioMessageType RadioClass::Receive_Message(RadioClass * from, RadioMessageType * HISTORY: * * 05/22/1995 JLB : Created. * *=============================================================================================*/ -RadioMessageType RadioClass::Transmit_Message(RadioMessageType message, int & param, RadioClass * to) +RadioMessageType RadioClass::Transmit_Message(RadioMessageType message, intptr_t & param, RadioClass * to) { if (to == NULL) { to = (RadioClass *)Contact_With_Whom(); diff --git a/code/radio.h b/code/radio.h index 63947a2ce..621ff2589 100644 --- a/code/radio.h +++ b/code/radio.h @@ -93,8 +93,8 @@ class RadioClass : public MissionClass // Inherited from base class(es). virtual void Detach(AbstractClass const * target, bool all = true) override; virtual void Compute_CRC(CRCEngine &) const override; - virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, int & param) override; - virtual RadioMessageType Transmit_Message(RadioMessageType message, int & param=LParam, RadioClass * to=NULL); + virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, intptr_t & param) override; + virtual RadioMessageType Transmit_Message(RadioMessageType message, intptr_t & param=LParam, RadioClass * to=NULL); virtual RadioMessageType Transmit_Message(RadioMessageType message, RadioClass * to); #ifdef _DEBUG virtual void Debug_Dump(MonoClass *mono) const override; diff --git a/code/srfcache.cpp b/code/srfcache.cpp index 692e86957..b62d1e4fc 100644 --- a/code/srfcache.cpp +++ b/code/srfcache.cpp @@ -687,7 +687,7 @@ bool SurfaceCacheClass::DrawMasked(Rect const & rect, Surface & tosurface, Surfa } } else if (src_x < image_width - right_clip) { unsigned char * mptr = mask_row; - int source_delta = (int)palsource - (int)mask; + int source_delta = (int)(palsource - mask); unsigned short * dptr = dest + dst_index; int count = image_width - right_clip - src_x; do { diff --git a/code/techno.cpp b/code/techno.cpp index 323a0c83d..b8073e886 100644 --- a/code/techno.cpp +++ b/code/techno.cpp @@ -1000,7 +1000,7 @@ bool TechnoClass::Mark(MarkType mark) * 10/17/1994 JLB : Created. * * 06/17/1995 JLB : Handles tether contact messages. * *=============================================================================================*/ -RadioMessageType TechnoClass::Receive_Message(RadioClass * from, RadioMessageType message, int & param) +RadioMessageType TechnoClass::Receive_Message(RadioClass * from, RadioMessageType message, intptr_t & param) { switch (message) { diff --git a/code/techno.h b/code/techno.h index 7e784fece..8403d4bdb 100644 --- a/code/techno.h +++ b/code/techno.h @@ -637,7 +637,7 @@ class TechnoClass : public RadioClass, virtual void Renovate(void); virtual void AI(void) override; virtual bool Revealed(HouseClass * house) override; - virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, int & param) override; + virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, intptr_t & param) override; virtual void Cloaking_AI(bool=false); virtual void Rocking_AI(void); virtual void Try_To_Cloak(void); diff --git a/code/unit.cpp b/code/unit.cpp index 3980f005d..f5bd2a508 100644 --- a/code/unit.cpp +++ b/code/unit.cpp @@ -1111,7 +1111,7 @@ void UnitClass::Jellyfish_AI(void) * HISTORY: * * 05/22/1994 JLB : Created. * *=============================================================================================*/ -RadioMessageType UnitClass::Receive_Message(RadioClass * from, RadioMessageType message, int & param) +RadioMessageType UnitClass::Receive_Message(RadioClass * from, RadioMessageType message, intptr_t & param) { switch (message) { @@ -1248,7 +1248,7 @@ RadioMessageType UnitClass::Receive_Message(RadioClass * from, RadioMessageType ** already at the staging location, then tell it to move onto the transport ** directly. */ - param = (int)this; + param = (intptr_t)this; if (Transmit_Message(RADIO_MOVE_HERE, param, from) != RADIO_ROGER) { Transmit_Message(RADIO_OVER_OUT, from); } diff --git a/code/unit.h b/code/unit.h index b1721e3e0..6f366752f 100644 --- a/code/unit.h +++ b/code/unit.h @@ -219,7 +219,7 @@ class UnitClass : public FootClass */ virtual AbstractClass * Greatest_Threat(ThreatType threat, Coord const & coord, bool) const override; virtual FacingType Desired_Load_Dir(ObjectClass * passenger, Cell & moveto) const override; - virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, int & param) override; + virtual RadioMessageType Receive_Message(RadioClass * from, RadioMessageType message, intptr_t & param) override; virtual void AI(void) override; virtual bool Ready_To_Commence(void) override; virtual int Do_MISSION_ATTACK(void) override; diff --git a/code/vein.cpp b/code/vein.cpp index 7f3130628..59299a053 100644 --- a/code/vein.cpp +++ b/code/vein.cpp @@ -985,7 +985,7 @@ bool VeinholeMonsterClass::Save_All(IStream * stream) } for (int i = 0; i < monster_count; i++) { - LONG id = (LONG)VeinholeMonsters[i]; + uintptr_t id = (uintptr_t)VeinholeMonsters[i]; if (FAILED(stream->Write(&id, sizeof(id), NULL))) { return(false); } diff --git a/code/vqa.cpp b/code/vqa.cpp index 7cbc7ad1a..5687e4635 100644 --- a/code/vqa.cpp +++ b/code/vqa.cpp @@ -38,10 +38,10 @@ DynamicVectorClass IngameVQ; -long __cdecl VQAMixFileHandler(VQAHandle * vqa, long action, void * buffer, long nbytes); -long __cdecl VQACCFileHandler(VQAHandle * vqa, long action, void * buffer, long nbytes); -long __cdecl VQAEventHandler(VQAHandle * vqa, long action, void * buffer, long nbytes); -long __cdecl VQAMemoryHandler(VQAHandle * vqa, long action, void * buffer, long nbytes); +intptr_t __cdecl VQAMixFileHandler(VQAHandle * vqa, long action, void * buffer, long nbytes); +intptr_t __cdecl VQACCFileHandler(VQAHandle * vqa, long action, void * buffer, long nbytes); +intptr_t __cdecl VQAEventHandler(VQAHandle * vqa, long action, void * buffer, long nbytes); +intptr_t __cdecl VQAMemoryHandler(VQAHandle * vqa, long action, void * buffer, long nbytes); bool VQA_Message_Handler(void) { @@ -299,7 +299,7 @@ long VQAClass::CacheHandler(long action, void * buffer, long nbytes) break; case VQACMD_SEEK: - switch ((int)buffer) { + switch ((int)(intptr_t)buffer) { case 1: Cache.file_buffer_pos += nbytes; rc = 0; @@ -855,17 +855,17 @@ long VQAClass::CCFileHandler(long action, void * buffer, long nbytes) ** VQAERR_SEEK. */ case VQACMD_SEEK: - error = (FileHandle.Seek(nbytes, (int)buffer) == 0); + error = (FileHandle.Seek(nbytes, (int)(intptr_t)buffer) == 0); break; case VQACMD_SEEKPEEK: if (nbytes > 0) { - error = FileHandle.Seek(nbytes - sizeof(tmp), (int)buffer) == 0; + error = FileHandle.Seek(nbytes - sizeof(tmp), (int)(intptr_t)buffer) == 0; if (error == 0) { error = FileHandle.Read(&tmp, sizeof(tmp)) != sizeof(tmp); } } else { - error = FileHandle.Seek(nbytes, (int)buffer) == 0; + error = FileHandle.Seek(nbytes, (int)(intptr_t)buffer) == 0; if (error == 0) { error = FileHandle.Read(&tmp, sizeof(tmp)) != sizeof(tmp); if (error == 0) { @@ -912,7 +912,7 @@ long VQAClass::CCFileHandler(long action, void * buffer, long nbytes) } -long __cdecl VQACCFileHandler(VQAHandle * vqa, long action, void * buffer, long nbytes) +intptr_t __cdecl VQACCFileHandler(VQAHandle * vqa, long action, void * buffer, long nbytes) { VQAHandleP *vqap = (VQAHandleP *)vqa; VQAConfig *config = &vqap->Config; @@ -982,17 +982,17 @@ long VQAClass::MixFileHandler(long action, void * buffer, long nbytes) ** VQAERR_SEEK. */ case VQACMD_SEEK: - error = (FileHandle.Seek(nbytes, (int)buffer) == 0); + error = (FileHandle.Seek(nbytes, (int)(intptr_t)buffer) == 0); break; case VQACMD_SEEKPEEK: if (nbytes > 0) { - error = FileHandle.Seek(nbytes - sizeof(tmp), (int)buffer) == 0; + error = FileHandle.Seek(nbytes - sizeof(tmp), (int)(intptr_t)buffer) == 0; if (error == 0) { error = FileHandle.Read(&tmp, sizeof(tmp)) != sizeof(tmp); } } else { - error = FileHandle.Seek(nbytes, (int)buffer) == 0; + error = FileHandle.Seek(nbytes, (int)(intptr_t)buffer) == 0; if (error == 0) { error = FileHandle.Read(&tmp, sizeof(tmp)) != sizeof(tmp); if (error == 0) { @@ -1039,7 +1039,7 @@ long VQAClass::MixFileHandler(long action, void * buffer, long nbytes) } -long __cdecl VQAMixFileHandler(VQAHandle * vqa, long action, void * buffer, long nbytes) +intptr_t __cdecl VQAMixFileHandler(VQAHandle * vqa, long action, void * buffer, long nbytes) { VQAHandleP *vqap = (VQAHandleP *)vqa; VQAConfig *config = &vqap->Config; @@ -1059,14 +1059,14 @@ long /*__cdecl*/ VQACacheHandler(VQAHandle * vqa, long action, void * buffer, lo } -long __cdecl VQAMemoryHandler(VQAHandle * vqa, long action, void * buffer, long nbytes) +intptr_t __cdecl VQAMemoryHandler(VQAHandle * vqa, long action, void * buffer, long nbytes) { - long error = 0; + intptr_t error = 0; switch (action) { case VQAMEM_ALLOC: - error = (int)malloc(nbytes); + error = (intptr_t)malloc(nbytes); break; case VQAMEM_FREE: @@ -1076,7 +1076,7 @@ long __cdecl VQAMemoryHandler(VQAHandle * vqa, long action, void * buffer, long case VQAMEM_LOCK: case VQAMEM_UNLOCK: - error = (int)buffer; + error = (intptr_t)buffer; break; default: @@ -1106,13 +1106,13 @@ static void VQAScalePalette(unsigned char *palette) } -long __cdecl VQAEventHandler(VQAHandle * vqa, long action, void * buffer, long nbytes) +intptr_t __cdecl VQAEventHandler(VQAHandle * vqa, long action, void * buffer, long nbytes) { VQAHandleP *vqap = (VQAHandleP *)vqa; VQAConfig *config = &vqap->Config; VQAClass *_this = config->Owner; - long error = 0; + intptr_t error = 0; switch (action) { case VQAEVENT_PALETTE: @@ -1126,7 +1126,7 @@ long __cdecl VQAEventHandler(VQAHandle * vqa, long action, void * buffer, long n break; case VQAEVENT_LOCK: - error = (int)_this->Handle_Lock_Event(); + error = (intptr_t)_this->Handle_Lock_Event(); break; case VQAEVENT_UNLOCK: diff --git a/code/vqalib/audio.cpp b/code/vqalib/audio.cpp index 8611b4d6c..bef6f5520 100644 --- a/code/vqalib/audio.cpp +++ b/code/vqalib/audio.cpp @@ -484,7 +484,7 @@ long __cdecl VQA_AudioFillCallback(VQAHandleP *vqap) } -long __cdecl VQA_AudioDoneCallback(VQAHandleP *vqap, unsigned long buffer) +long __cdecl VQA_AudioDoneCallback(VQAHandleP *vqap, void *buffer) { VQAConfig *config; VQAAudio *audio; @@ -493,7 +493,7 @@ long __cdecl VQA_AudioDoneCallback(VQAHandleP *vqap, unsigned long buffer) audio = &vqap->Audio; config = &vqap->Config; - if ((void *)buffer == audio->Buffer + audio->PlayPosition || (void *)buffer == audio->HMIBuffer) { + if (buffer == audio->Buffer + audio->PlayPosition || buffer == audio->HMIBuffer) { block = audio->Block2; diff --git a/code/vqalib/buffer_.cpp b/code/vqalib/buffer_.cpp index d46aae9cf..545a943f3 100644 --- a/code/vqalib/buffer_.cpp +++ b/code/vqalib/buffer_.cpp @@ -775,9 +775,9 @@ long VQA_Configure_Buffer(VQAHandleP *vqap) } -long __cdecl VQA_Memory_Handler(VQAHandle *vqa, long action, void *buffer, long nbytes) +intptr_t __cdecl VQA_Memory_Handler(VQAHandle *vqa, long action, void *buffer, long nbytes) { - long error = 0; + intptr_t error = 0; switch (action) { default: @@ -787,7 +787,7 @@ long __cdecl VQA_Memory_Handler(VQAHandle *vqa, long action, void *buffer, long break; case VQAMEM_ALLOC: - error = (long)malloc(nbytes); + error = (intptr_t)malloc(nbytes); break; case VQAMEM_FREE: @@ -795,11 +795,11 @@ long __cdecl VQA_Memory_Handler(VQAHandle *vqa, long action, void *buffer, long break; case VQAMEM_LOCK: - error = (long)buffer; + error = (intptr_t)buffer; break; case VQAMEM_UNLOCK: - error = (long)buffer; + error = (intptr_t)buffer; break; case VQAMEM_QUERYSIZE: diff --git a/code/vqalib/drawer.cpp b/code/vqalib/drawer.cpp index 6264750f7..72660ab0e 100644 --- a/code/vqalib/drawer.cpp +++ b/code/vqalib/drawer.cpp @@ -421,12 +421,12 @@ STATIC long Select_Frame(VQAHandleP *vqap) /* Dispatch any pending frame events. */ if (config->EventHandler != NULL) { if (curframe->Flags & VQAFRMF_LOOPED) { - config->EventHandler((VQAHandle *)vqap, VQAEVENT_LOOPED, (void *)curframe->FrameNum, vqap->LoopID); + config->EventHandler((VQAHandle *)vqap, VQAEVENT_LOOPED, (void *)(intptr_t)curframe->FrameNum, vqap->LoopID); curframe->Flags &= ~VQAFRMF_LOOPED; } if (curframe->Flags & VQAFRMF_LOOPJMP) { - config->EventHandler((VQAHandle *)vqap, VQAEVENT_LOOPJUMP, (void *)curframe->FrameNum, vqap->LoopID); + config->EventHandler((VQAHandle *)vqap, VQAEVENT_LOOPJUMP, (void *)(intptr_t)curframe->FrameNum, vqap->LoopID); curframe->Flags &= ~VQAFRMF_LOOPJMP; } diff --git a/code/vqalib/dstream.cpp b/code/vqalib/dstream.cpp index ec4c3c034..456462e0f 100644 --- a/code/vqalib/dstream.cpp +++ b/code/vqalib/dstream.cpp @@ -49,7 +49,7 @@ #include -long __cdecl Disk_VQA_Stream_Handler(VQAHandle *vqa, long action, void *buffer, long nbytes) +intptr_t __cdecl Disk_VQA_Stream_Handler(VQAHandle *vqa, long action, void *buffer, long nbytes) { long fh; long error = 0; @@ -98,17 +98,17 @@ long __cdecl Disk_VQA_Stream_Handler(VQAHandle *vqa, long action, void *buffer, * VQAERR_SEEK. */ case VQACMD_SEEK: - error = (lseek(fh, nbytes, (long)buffer) == -1); + error = (lseek(fh, nbytes, (int)(intptr_t)buffer) == -1); break; case VQACMD_SEEKPEEK: if (nbytes > 0) { - error = lseek(fh, nbytes - 1, (int)buffer) == -1; + error = lseek(fh, nbytes - 1, (int)(intptr_t)buffer) == -1; if (error == 0) { error = read(fh, &temp, 1) != 1; } } else { - error = lseek(fh, nbytes, (int)buffer) == -1; + error = lseek(fh, nbytes, (int)(intptr_t)buffer) == -1; if (error == 0) { error = read(fh, &temp, 1) != 1; } @@ -152,7 +152,7 @@ long __cdecl Disk_VQA_Stream_Handler(VQAHandle *vqa, long action, void *buffer, -long __cdecl Memory_VQA_Stream_Handler(VQAHandle *vqa, long action, void *buffer, long nbytes) +intptr_t __cdecl Memory_VQA_Stream_Handler(VQAHandle *vqa, long action, void *buffer, long nbytes) { long error = 0; int p; @@ -196,7 +196,7 @@ long __cdecl Memory_VQA_Stream_Handler(VQAHandle *vqa, long action, void *buffer */ case VQACMD_SEEK: case VQACMD_SEEKPEEK: - switch ((long)buffer) { + switch ((intptr_t)buffer) { case 1: cache->Offset += nbytes; @@ -204,7 +204,7 @@ long __cdecl Memory_VQA_Stream_Handler(VQAHandle *vqa, long action, void *buffer break; case 0: - p = (int)cache->Buffer; + p = cache->FileOffset; if (nbytes >= p) { cache->Offset = nbytes - p; break; diff --git a/code/vqalib/loader.cpp b/code/vqalib/loader.cpp index cb08b52a6..63200ec69 100644 --- a/code/vqalib/loader.cpp +++ b/code/vqalib/loader.cpp @@ -132,7 +132,7 @@ VQABool VQA_IsFrameStartOfLoop(VQAHandleP *vqap, long framenum); long VQA_ReloadPalette(VQAHandleP *vqap, long framenum, int force); _STATIC long VQA_LoadLoop(VQAHandleP *vqap, long framenum); -long __cdecl Memory_VQA_Stream_Handler(VQAHandle *vqa, long action, void *buffer, long nbytes); +intptr_t __cdecl Memory_VQA_Stream_Handler(VQAHandle *vqa, long action, void *buffer, long nbytes); _STATIC long VQA_LoadFrame_Internal(VQAHandleP *vqap, long flags); long VQA_SeekGroup(VQAHandleP *vqap, long framenum, long groupsize, VQABool preloadaudio, VQABool reset_state, VQABool &skipcodebook); @@ -212,7 +212,7 @@ long VQA_LoadFrame(VQAHandleP *vqap, long flags) cache = &vqap->LoopCache; if (frame == vqap->LoopStartFrame0 && vqap->LoopID != cache->ID) { if (frame != cache->Min) { - cache->Buffer = (char *)foffset; + cache->FileOffset = foffset; cache->Bytes = 0; cache->Offset = 0; } @@ -247,7 +247,7 @@ long VQA_LoadFrame(VQAHandleP *vqap, long flags) } if ( frame >= cache->Min && frame <= cache->Max ) { - if (foffset == (long)cache->Buffer + cache->Bytes) { + if (foffset == cache->FileOffset + cache->Bytes) { if (frame < vqap->NumFrames - 1) { tocache = VQAFRAME_OFFSET(foff[frame + 1]) - foffset; } else { @@ -269,10 +269,10 @@ long VQA_LoadFrame(VQAHandleP *vqap, long flags) } } - if ( foffset < (long)cache->Buffer + cache->Bytes ) + if ( foffset < cache->FileOffset + cache->Bytes ) { restore_handler = true; - cache->Offset = foffset - (long)cache->Buffer; + cache->Offset = foffset - cache->FileOffset; oldhandler = config->StreamHandler; config->StreamHandler = Memory_VQA_Stream_Handler; } @@ -1070,7 +1070,7 @@ long VQA_SeekLoop(VQAHandleP *vqap, long framenum, long flags) foff = vqap->Foff; if ((vqap->AltBufferFlags & VQAABUFF_ALTLOOP) && framenum == cache->Min && cache->Bytes != 0) { - if ((long)(unsigned char *)cache->Buffer + cache->Bytes <= (long)(unsigned char *)VQAFRAME_OFFSET(foff[vqap->LoopEndFrameMode2])) { + if (cache->FileOffset + cache->Bytes <= (long)VQAFRAME_OFFSET(foff[vqap->LoopEndFrameMode2])) { needs_seek = true; } cache->Offset = 0; @@ -1083,7 +1083,7 @@ long VQA_SeekLoop(VQAHandleP *vqap, long framenum, long flags) } if (rc == VQAERR_NONE) { - if (needs_seek && vqap->Config.StreamHandler((VQAHandle *)vqap, VQACMD_SEEKPEEK, 0, long((int)cache->Buffer + cache->Bytes)) != 0) { + if (needs_seek && vqap->Config.StreamHandler((VQAHandle *)vqap, VQACMD_SEEKPEEK, 0, cache->FileOffset + cache->Bytes) != 0) { return(VQAERR_SEEK); } } diff --git a/code/vqalib/task.cpp b/code/vqalib/task.cpp index e5bad55e5..e617216cd 100644 --- a/code/vqalib/task.cpp +++ b/code/vqalib/task.cpp @@ -99,8 +99,8 @@ long Load_CLIP(VQAHandleP *vqap, unsigned long iffsize); long Load_MFCI(VQAHandleP *vqap); long Load_MSCI(VQAHandleP *vqap); -long __cdecl VQA_Memory_Handler(VQAHandle *vqa, long action, void *buffer, long nbytes); -long __cdecl Disk_VQA_Stream_Handler(VQAHandle *vqa, long action, void *buffer, long nbytes); +intptr_t __cdecl VQA_Memory_Handler(VQAHandle *vqa, long action, void *buffer, long nbytes); +intptr_t __cdecl Disk_VQA_Stream_Handler(VQAHandle *vqa, long action, void *buffer, long nbytes); long VQA_LargestLoop(VQAHandleP *vqap, long); @@ -1680,11 +1680,11 @@ long User_Update(VQAHandle *vqa) if (config->EventHandler != NULL) { if (curframe->Flags & VQAFRMF_LOOPED) { - config->EventHandler((VQAHandle *)vqap, VQAEVENT_LOOPED, (void *)curframe->FrameNum, vqap->LoopID); + config->EventHandler((VQAHandle *)vqap, VQAEVENT_LOOPED, (void *)(intptr_t)curframe->FrameNum, vqap->LoopID); curframe->Flags &= ~VQAFRMF_LOOPED; } if (curframe->Flags & VQAFRMF_LOOPJMP) { - config->EventHandler((VQAHandle *)vqap, VQAEVENT_LOOPJUMP, (void *)curframe->FrameNum, vqap->LoopID); + config->EventHandler((VQAHandle *)vqap, VQAEVENT_LOOPJUMP, (void *)(intptr_t)curframe->FrameNum, vqap->LoopID); curframe->Flags &= ~VQAFRMF_LOOPJMP; } if (curframe->Flags & VQAFRMF_CHUNKS) { diff --git a/code/vqalib/vqaplay.h b/code/vqalib/vqaplay.h index 2204872cf..268e7fb24 100644 --- a/code/vqalib/vqaplay.h +++ b/code/vqalib/vqaplay.h @@ -164,7 +164,7 @@ class VQAClass; typedef void (__cdecl *UNVQ_FUNC)(uint8_t *codebook, uint8_t *pointers, uint8_t *buffer, size_t blocksperrow, size_t numrows, size_t bufwidth); // Handlers must be this type -typedef long (__cdecl *VQA_H_FUNC)(VQAHandle *vqa, long action, void *buffer, long nbytes); +typedef intptr_t (__cdecl *VQA_H_FUNC)(VQAHandle *vqa, long action, void *buffer, long nbytes); // draw callback must be this type typedef long (__cdecl *VQA_DC_FUNC)(VQAHandle *vqa, long framenum); diff --git a/code/vqalib/vqaplayp.h b/code/vqalib/vqaplayp.h index cc66e5a9f..655ae9b98 100644 --- a/code/vqalib/vqaplayp.h +++ b/code/vqalib/vqaplayp.h @@ -613,7 +613,7 @@ struct VQAMSCInfo { struct VQALoopCache { char *Ptr; int Size; - char *Buffer; + int32_t FileOffset; int Bytes; int Offset; int Min; @@ -778,7 +778,7 @@ void VQA_PauseAudio(VQAHandleP *vqap); void VQA_StopAudio(VQAHandleP *vqap); long CopyAudio(VQAHandleP *vqap); long __cdecl VQA_AudioFillCallback(VQAHandleP *vqap); -long __cdecl VQA_AudioDoneCallback(VQAHandleP *vqap, unsigned long); +long __cdecl VQA_AudioDoneCallback(VQAHandleP *vqap, void *); #endif /* Debugging system. */ diff --git a/code/wave.cpp b/code/wave.cpp index d579f6bc8..533f76587 100644 --- a/code/wave.cpp +++ b/code/wave.cpp @@ -620,7 +620,7 @@ void WaveClass::Draw_Sonic(Point2D const & point, Rect const & cliprect) if (Direction > FACING_NE && Direction < FACING_W) { unsigned short base_z = DepthBuffer->Get_Scroll_Delta(zpix); unsigned short zval = base_z - starty - 2; - unsigned int zoffset = DepthBuffer->Get_Buffer_Offset(Point2D(0, starty - TacticalRect.Y)); + uintptr_t zoffset = DepthBuffer->Get_Buffer_Offset(Point2D(0, starty - TacticalRect.Y)); int width = LogicalSurface->Get_Width(); int zwidth = DepthBuffer->Get_Buffer_Width(); @@ -696,7 +696,7 @@ void WaveClass::Draw_Sonic(Point2D const & point, Rect const & cliprect) } else { - unsigned int zoffset = DepthBuffer->Get_Buffer_Offset(Point2D(0, starty - TacticalRect.Y)); + uintptr_t zoffset = DepthBuffer->Get_Buffer_Offset(Point2D(0, starty - TacticalRect.Y)); int width = LogicalSurface->Get_Width(); int rows = endy - starty; int zwidth = DepthBuffer->Get_Buffer_Width(); @@ -822,7 +822,7 @@ void WaveClass::Draw_Laser(Point2D const & point, Rect const & cliprect) unsigned short base_z = DepthBuffer->Get_Scroll_Delta(zpix); unsigned short depth = base_z - ystart - 2; - unsigned int zoff = DepthBuffer->Get_Buffer_Offset(Point2D(0, ystart - TacticalRect.Y)); + uintptr_t zoff = DepthBuffer->Get_Buffer_Offset(Point2D(0, ystart - TacticalRect.Y)); int surfwidth = LogicalSurface->Get_Width(); int zwidth = DepthBuffer->Get_Buffer_Width(); diff --git a/code/wstring.cpp b/code/wstring.cpp index 2b5418037..282734ed1 100644 --- a/code/wstring.cpp +++ b/code/wstring.cpp @@ -558,7 +558,7 @@ char Wstring::replace(char const* replaceThis, char const* withThis) foundStr = strstr(src, replaceThis); if (foundStr) { - len = (unsigned int)foundStr - (unsigned int)src; + len = (unsigned int)(foundStr - src); if (len) { if (!dest.cat(len, src)) diff --git a/code/xsurface.cpp b/code/xsurface.cpp index b5d28b7f4..5cb77cb36 100644 --- a/code/xsurface.cpp +++ b/code/xsurface.cpp @@ -859,7 +859,7 @@ bool XSurface::Fill_Rect(Rect const & cliprect, Rect const & fillrect, int color buffer = ((unsigned char *)buffer) + pitch; } } else { - switch ((unsigned int)buffer & 3) { + switch ((uintptr_t)buffer & 3) { case 0: { int odd_pixel = width & 1; width >>= 1; diff --git a/code/zbuffer.cpp b/code/zbuffer.cpp index c6240566e..a2178aae2 100644 --- a/code/zbuffer.cpp +++ b/code/zbuffer.cpp @@ -37,7 +37,7 @@ ZBuffer::ZBuffer(Rect rect) : Fill(ZBUFFER_COLOR); - BufferStart = (unsigned int)(SurfacePtr->Lock()); + BufferStart = (uintptr_t)(SurfacePtr->Lock()); SurfaceOffset = 0; BufferEnd = BufferStart + BufferWidth * BufferHeight * ZBUFFER_BPP; ScrollOffset = ZBUFFER_MAX; @@ -69,7 +69,7 @@ void ZBuffer::Copy_To(Surface *surface, Rect rect) *surfbuffptr = *pixptr; ++surfbuffptr; pixptr = (unsigned short *)((unsigned char *)pixptr + ZBUFFER_BPP); - pixptr = (unsigned short *)Wrap_Overflow((unsigned int)pixptr); + pixptr = (unsigned short *)Wrap_Overflow((uintptr_t)pixptr); } surfbuffptr += steps; } @@ -99,13 +99,13 @@ void ZBuffer::Release_Surface(void) /// The depth value to fill with. /// The run is not wrapped. The caller must split any fill that would otherwise /// run off the end of the buffer. -void ZBuffer::Set(unsigned int dst, int size, unsigned short value) +void ZBuffer::Set(uintptr_t dst, int size, unsigned short value) { /// Write a single entry to bring the address up to an int boundary. - if ((unsigned int)dst & 2) { + if (dst & 2) { if (size != 0) { *(unsigned short *)dst = value; - dst = (unsigned int)((unsigned short *)dst + 1); + dst = (uintptr_t)((unsigned short *)dst + 1); size--; } } @@ -167,7 +167,7 @@ void ZBuffer::Pan(int x, int y, unsigned short value) /// Slide the origin along the row and fold it back into the buffer. SurfaceOffset += x_delta * ZBUFFER_BPP; - unsigned int new_offset = Wrap_Underflow(SurfaceOffset + BufferStart); + uintptr_t new_offset = Wrap_Underflow(SurfaceOffset + BufferStart); new_offset = Wrap_Overflow(new_offset); SurfaceOffset = new_offset - BufferStart; @@ -208,7 +208,7 @@ void ZBuffer::Pan(int x, int y, unsigned short value) /// Slide the origin by whole rows and fold it back into the buffer. SurfaceOffset += y_delta * BufferWidth * ZBUFFER_BPP; - unsigned int new_offset = Wrap_Underflow(SurfaceOffset + BufferStart); + uintptr_t new_offset = Wrap_Underflow(SurfaceOffset + BufferStart); new_offset = Wrap_Overflow(new_offset); SurfaceOffset = new_offset - BufferStart; @@ -267,7 +267,7 @@ bool ZBuffer::Fill(unsigned short value, Rect rect) /// The region of the buffer to reset. void ZBuffer::Update(Rect rect) { - unsigned int buffptr = Get_Buffer_Offset(Point2D(rect.X, rect.Y)); + uintptr_t buffptr = Get_Buffer_Offset(Point2D(rect.X, rect.Y)); for (int i = 0; i < rect.Height; ++i) { @@ -293,9 +293,9 @@ void ZBuffer::Update(Rect rect) /// /// The point within the buffer to locate. /// Returns with the address of the entry within the depth buffer. -unsigned int ZBuffer::Get_Buffer_Offset(Point2D pos) +uintptr_t ZBuffer::Get_Buffer_Offset(Point2D pos) { - unsigned int buffptr = (unsigned int)SurfacePtr->Lock(pos); + uintptr_t buffptr = (uintptr_t)SurfacePtr->Lock(pos); SurfacePtr->Unlock(); diff --git a/code/zbuffer.h b/code/zbuffer.h index 223fda079..41da9a329 100644 --- a/code/zbuffer.h +++ b/code/zbuffer.h @@ -11,6 +11,8 @@ #include "rect.h" +#include + class Surface; #define ZBUFFER_MAX 0x8000 @@ -29,7 +31,7 @@ class ZBuffer void Copy_To(Surface * surface, Rect rect); - void Set(unsigned int dst, int size, unsigned short value); + void Set(uintptr_t dst, int size, unsigned short value); void Pan(int x_delta, int y_delta, unsigned short value); @@ -38,16 +40,16 @@ class ZBuffer void Update(Rect rect); - unsigned int Get_Buffer_Offset(Point2D position); + uintptr_t Get_Buffer_Offset(Point2D position); - unsigned int Wrap_Overflow(unsigned int position) const; - unsigned int Wrap_Underflow(unsigned int position) const; + uintptr_t Wrap_Overflow(uintptr_t position) const; + uintptr_t Wrap_Underflow(uintptr_t position) const; Surface * Get_Surface(void) const { return(SurfacePtr); } Rect const & Get_Bounds(void) const { return(Bounds); } unsigned int Get_Buffer_Width(void) const { return(BufferWidth); } - unsigned int Get_Buffer_End(void) const { return(BufferEnd); } + uintptr_t Get_Buffer_End(void) const { return(BufferEnd); } private: void Release_Surface(void); @@ -78,8 +80,8 @@ class ZBuffer * and the number of bytes between the two. The buffer is treated as a ring, so an * address that walks off either end is folded back around by that size. */ - unsigned int BufferStart; - unsigned int BufferEnd; + uintptr_t BufferStart; + uintptr_t BufferEnd; unsigned int BufferSize; /* @@ -99,7 +101,7 @@ class ZBuffer int BufferHeight; }; -inline unsigned int ZBuffer::Wrap_Overflow(unsigned int position) const +inline uintptr_t ZBuffer::Wrap_Overflow(uintptr_t position) const { if (position >= BufferEnd) { position -= BufferSize; @@ -108,7 +110,7 @@ inline unsigned int ZBuffer::Wrap_Overflow(unsigned int position) const } -inline unsigned int ZBuffer::Wrap_Underflow(unsigned int position) const +inline uintptr_t ZBuffer::Wrap_Underflow(uintptr_t position) const { if (position < BufferStart) { position += BufferSize; @@ -122,6 +124,6 @@ extern ZBuffer *DepthBuffer; inline unsigned short *Blit_Wrap_Z_Buffer(unsigned short *buf) { - return((unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)buf)); + return((unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)buf)); } From 3b01d749c395595b7cacc024366df08a2a1c9f5a Mon Sep 17 00:00:00 2001 From: Marek Benc Date: Mon, 7 Sep 2026 23:04:21 +0200 Subject: [PATCH 030/179] Update FileClass code to build and work on Unix. --- code/always.h | 79 ++++++++ code/blowfish.h | 2 + code/cdfile.cpp | 105 +---------- code/cdfile.h | 4 - code/file.cpp | 72 +++++++ code/file.h | 188 +++++++++++++++++++ code/file_posix.cpp | 133 +++++++++++++ code/file_win.cpp | 76 ++++++++ code/gamedirs.cpp | 6 +- code/ini.cpp | 3 + code/ini.h | 7 + code/mixfile.cpp | 13 +- code/mixfile.h | 2 +- code/rawfile.cpp | 258 ++++++++++++++++++-------- code/rawfile.h | 37 ++-- code/session.cpp | 1 + code/sha.cpp | 2 +- code/sha.h | 2 +- code/vqa.h | 1 + code/wwfile.h | 7 - tests/deploymentconfig/CMakeLists.txt | 3 + tests/gamedirs/CMakeLists.txt | 3 + 22 files changed, 781 insertions(+), 223 deletions(-) create mode 100644 code/file.cpp create mode 100644 code/file.h create mode 100644 code/file_posix.cpp create mode 100644 code/file_win.cpp diff --git a/code/always.h b/code/always.h index 042ded5c2..ae7cc1631 100644 --- a/code/always.h +++ b/code/always.h @@ -91,3 +91,82 @@ #ifndef _stricmp #define _stricmp stricmp #endif + + +/* +** Define some Windows specific values that are used throghout the games +*/ +#ifndef _WIN32 + +#define _MAX_FNAME 255 +#define _MAX_EXT 8 +#define _MAX_PATH 512 +#define MAX_PATH _MAX_PATH +#define _CONTROL 0x20 // space, first non-control character in ASCII + +#undef _stricmp +#define stricmp strcasecmp +#define _stricmp strcasecmp +#define strnicmp strncasecmp +#define memicmp strncasecmp +#define __cdecl + +#include +#include +#include + +inline static void _makepath(char* path, const char* drive, const char* dir, const char* fname, const char* ext) +{ + if (!path || !fname || !ext) { + return; + } + + sprintf(path, "%s%s%s", fname, (ext[0] == '.' ? "" : "."), ext); +} + +inline static void _splitpath(const char* path, char* drive, char* dir, char* fname, char* ext) +{ + if (!path || !ext) { + return; + } + + while (*path != '\0') { + if (*path == '.') { + strcpy(ext, path + 1); + break; + } + + ++path; + } +} + +inline static char* strupr(char* str) +{ + char* ret = str; + while (*str != '\0') { + *str = toupper(*str); + ++str; + } + return(ret); +} + +inline static void strrev(char* str) +{ + int len = strlen(str); + + for (int i = 0; i < len / 2; i++) { + char c = str[i]; + str[i] = str[len - i - 1]; + str[len - i - 1] = c; + } +} + +inline static void _strlwr(char* str) +{ + while (*str != '\0') { + *str = tolower(*str); + ++str; + } +} + +#endif // not _WIN32 diff --git a/code/blowfish.h b/code/blowfish.h index 3cbc2d7c6..191781549 100644 --- a/code/blowfish.h +++ b/code/blowfish.h @@ -31,7 +31,9 @@ #pragma once +#ifdef _WIN32 #include "win.h" +#endif /// Names and comments from TLBs diff --git a/code/cdfile.cpp b/code/cdfile.cpp index 11ef777ac..0c3194616 100644 --- a/code/cdfile.cpp +++ b/code/cdfile.cpp @@ -42,6 +42,7 @@ #include "cdfile.h" #include +#include /* ** Pointer to the first search path record. @@ -180,7 +181,7 @@ void CDFileClass::Set_User_Path(char const * path) break; default: - UserPath += '\\'; + UserPath += std::filesystem::path::preferred_separator; break; } } @@ -459,105 +460,3 @@ int CDFileClass::Delete(void) return(BASECLASS::Delete()); } - - -HANDLE FindFileHandle = INVALID_HANDLE_VALUE; - -/// -/// Begins a search for the files matching the wildcard specified. -/// This routine will look in the current directory first and then work along the search -/// drive list, settling on the first drive that has a match. Only ordinary files qualify; -/// directories and system, hidden, or temporary files are passed over. Any search still -/// in progress is closed off first. -/// -/// The wildcard to search for; filled in with the file found. -/// bool; Was a matching file found? -/// Be sure that the buffer is big enough to hold the filename returned. -bool CDFileClass::Find_First_File(char *fname) -{ - WIN32_FIND_DATAA fb; - char scan_path[MAX_PATH]; - SearchDriveType *entry; - - if (fname) { - - Find_Close(); - - strcpy(scan_path, fname); - - HANDLE file_handle = ::FindFirstFile(scan_path, &fb); - if (file_handle != INVALID_HANDLE_VALUE && !(fb.dwFileAttributes & (FILE_ATTRIBUTE_TEMPORARY|FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN))) { - - strcpy(fname, fb.cFileName); - FindFileHandle = file_handle; - - return(true); - } - - entry = First; - - if (entry != NULL) { - - while (true) { - - strcpy(scan_path, entry->Path); - strcat(scan_path, fname); - - file_handle = ::FindFirstFile(scan_path, &fb); - if (file_handle != INVALID_HANDLE_VALUE && !(fb.dwFileAttributes & (FILE_ATTRIBUTE_TEMPORARY|FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN))) { - break; - } - - entry = (SearchDriveType *)entry->Next; - if (entry == NULL) { - return(false); - } - } - - strcpy(fname, fb.cFileName); - FindFileHandle = file_handle; - - return(true); - } - } - return(false); -} - - -/// -/// Fetches the next file that matches the search in progress. -/// This routine continues the scan begun by Find_First_File, working through the rest of -/// the matches on whichever drive that routine settled upon. -/// -/// Buffer to fill in with the name of the file found. -/// bool; Was another matching file found? -/// Be sure that the buffer is big enough to hold the filename returned. -bool CDFileClass::Find_Next_File(char *buffer) -{ - WIN32_FIND_DATAA fb; - - if (buffer) { - - if (FindFileHandle != INVALID_HANDLE_VALUE && ::FindNextFile(FindFileHandle, &fb) == TRUE) { - strcpy(buffer, fb.cFileName); - return(true); - } - - buffer[0] = '\0'; - } - return(false); -} - - -/// -/// Closes off the file search that is in progress. -/// Call this routine when the results of a Find_First_File scan are no longer wanted, so -/// that the search handle held on the game's behalf is given back to the system. -/// -void CDFileClass::Find_Close(void) -{ - if (FindFileHandle != INVALID_HANDLE_VALUE) { - FindClose(FindFileHandle); - FindFileHandle = INVALID_HANDLE_VALUE; - } -} diff --git a/code/cdfile.h b/code/cdfile.h index 9f1b34a16..913968b70 100644 --- a/code/cdfile.h +++ b/code/cdfile.h @@ -72,10 +72,6 @@ class CDFileClass : public BufferIOFileClass static void Set_User_Path(char const * path); static char const * User_Path(void); - static bool Find_First_File(char *buffer); - static bool Find_Next_File(char *buffer); - static void Find_Close(void); - private: char const * Capture_Name(char const * filename); diff --git a/code/file.cpp b/code/file.cpp new file mode 100644 index 000000000..d2e727d33 --- /dev/null +++ b/code/file.cpp @@ -0,0 +1,72 @@ +#include "file.h" +#include + +#ifndef _WIN32 +static void Resolve_File_Single(char* fname) +{ + Find_File_Data* ffblk; + + ffblk = Find_File_Data::CreateFindData(); + + if (ffblk == nullptr) { + return; + } + + size_t name_len = strlen(fname); + + if (ffblk->FindFirst(fname) && name_len == strlen(ffblk->GetFullName())) { + strncpy(fname, ffblk->GetFullName(), name_len + 1); + } + + delete ffblk; +} +#endif + +void Resolve_File(char* fname) +{ +#ifndef _WIN32 + // step through each sub-directory before going for the win + char* next = fname; + while (next = strchr(next, '/')) { + *next = '\0'; + Resolve_File_Single(fname); + *next++ = '/'; + } + + Resolve_File_Single(fname); +#endif +} + +bool Find_First(const char* fname, unsigned int mode, Find_File_Data** ffblk) +{ + if (ffblk == nullptr) { + return false; + } + *ffblk = nullptr; + + *ffblk = Find_File_Data::CreateFindData(); + if ((*ffblk)->FindFirst(fname)) { + return true; + } + + delete *ffblk; + *ffblk = nullptr; + + return false; +} + +bool Find_Next(Find_File_Data* ffblk) +{ + if (ffblk == nullptr) { + return false; + } + + return ffblk->FindNext(); +} + +void Find_Close(Find_File_Data* ffblk) +{ + if (ffblk != nullptr) { + delete ffblk; + } +} diff --git a/code/file.h b/code/file.h new file mode 100644 index 000000000..05af6c231 --- /dev/null +++ b/code/file.h @@ -0,0 +1,188 @@ +// +// Copyright 2020 Electronic Arts Inc. +// +// TiberianDawn.DLL and RedAlert.dll and corresponding source code 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. + +// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed +// in the hope that it will be useful, but with permitted additional restrictions +// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT +// distributed with this program. You should have received a copy of the +// GNU General Public License along with permitted additional restrictions +// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection + +/*************************************************************************** + ** C O N F I D E N T I A L --- W E S T W O O D A S S O C I A T E S ** + *************************************************************************** + * * + * Project Name : Library - Filio header stuff. * + * * + * File Name : FILE.H * + * * + * Programmer : Scott K. Bowen * + * * + * Start Date : September 13, 1993 * + * * + * Last Update : April 11, 1994 * + * * + *-------------------------------------------------------------------------* + * Functions: * + * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +#pragma once + +/*=========================================================================*/ +/* File IO system defines and enumerations */ +/*=========================================================================*/ + +#define XMAXPATH 80 + +/* +** These are the Open_File, Read_File, and Seek_File constants. +*/ +#ifndef READ +#define READ 1 // Read access. +#endif +#ifndef WRITE +#define WRITE 2 // Write access. +#endif +#ifndef SEEK_SET +#define SEEK_SET 0 // Seek from start of file. +#define SEEK_CUR 1 // Seek relative from current location. +#define SEEK_END 2 // Seek from end of file. +#endif + +typedef enum +{ + FILEB_PROCESSED = 8, // Was the packed file header of this file processed? + FILEB_PRELOAD, // Scan for and make file resident at WWDOS_Init time? + FILEB_RESIDENT, // Make resident at Open_File time? + FILEB_FLUSH, // Un-resident at Close_File time? + FILEB_PACKED, // Is this file packed? + FILEB_KEEP, // Don't ever flush this resident file? + FILEB_PRIORITY, // Flush this file last? + + FILEB_LAST +} FileFlags_Type; + +#define FILEF_NONE 0 +#define FILEF_PROCESSED (1 << FILEB_PROCESSED) +#define FILEF_PRELOAD (1 << FILEB_PRELOAD) +#define FILEF_RESIDENT (1 << FILEB_RESIDENT) +#define FILEF_FLUSH (1 << FILEB_FLUSH) +#define FILEF_PACKED (1 << FILEB_PACKED) +#define FILEF_KEEP (1 << FILEB_KEEP) +#define FILEF_PRIORITY (1 << FILEB_PRIORITY) + +/* +** These errors are returned by WWDOS_Init(). All errors encountered are +** or'd together so there may be more then one error returned. Not all +** errors are fatal, such as the cache errors. +*/ +typedef enum +{ + FI_SUCCESS = 0x00, + FI_CACHE_TOO_BIG = 0x01, + FI_CACHE_ALREADY_INIT = 0x02, + FI_FILEDATA_FILE_NOT_FOUND = 0x04, + FI_FILEDATA_TOO_BIG = 0x08, + FI_SEARCH_PATH_NOT_FOUND = 0x10, + FI_STARTUP_PATH_NOT_FOUND = 0x20, + FI_NO_CACHE_FOR_PRELOAD = 0x40, + FI_FILETABLE_NOT_INIT = 0x80, +} FileInitErrorType; + +/* +** These are the errors that are detected by the File I/O system and +** passed to the io error routine. +*/ +// lint -strong(AJX,FileErrorType) +typedef enum +{ + CANT_CREATE_FILE, + BAD_OPEN_MODE, + COULD_NOT_OPEN, + TOO_MANY_FILES, + CLOSING_NON_HANDLE, + READING_NON_HANDLE, + WRITING_NON_HANDLE, + SEEKING_NON_HANDLE, + SEEKING_BAD_OFFSET, + WRITING_RESIDENT, + UNKNOWN_INDEX, + DID_NOT_CLOSE, + FATAL_ERROR, + FILE_NOT_LISTED, + FILE_LENGTH_MISMATCH, + INTERNAL_ERROR, + MAKE_RESIDENT_ZERO_SIZE, + RESIDENT_SORT_FAILURE, + + NUMBER_OF_ERRORS /* MAKE SURE THIS IS THE LAST ENTRY */ +} FileErrorType; + +/*=========================================================================*/ +/* File IO system structures */ +/*=========================================================================*/ + +// lint -strong(AJX,FileDataType) +typedef struct +{ + char* Name; // File name (include sub-directory but not volume). + int Size; // File size (0=indeterminate). + void* Ptr; // Resident file pointer. + int Start; // Starting offset in DOS handle file. + unsigned char Disk; // Disk number location. + unsigned char OpenCount; // Count of open locks on resident file. + unsigned short Flag; // File control flags. +} FileDataType; + +/*=========================================================================*/ +/* FIle IO system globals. */ +/*=========================================================================*/ + +// These are cpp errors in funtions declarations JULIO JEREZ + +// extern FileDataType FileData[]; +// extern BYTE ExecPath[XMAXPATH + 1]; +// extern BYTE DataPath[XMAXPATH + 1]; +// extern BYTE StartPath[XMAXPATH + 1]; +// extern BOOL UseCD; + +// The correct syntax is NO TYPE MODIFIER APPLY TO DATA DECLARATIONS +extern FileDataType FileData[]; +extern char ExecPath[XMAXPATH + 1]; +extern char DataPath[XMAXPATH + 1]; +extern char StartPath[XMAXPATH + 1]; + +/*=========================================================================*/ +/* The following prototypes are for the file: file.cpp */ +/*=========================================================================*/ + +void Resolve_File(char* fname); + +class Find_File_Data +{ +public: + static Find_File_Data* CreateFindData(); + + virtual ~Find_File_Data() + { + } + virtual const char* GetName() const = 0; + virtual const char* GetFullName() const + { + return nullptr; + }; + virtual unsigned int GetTime() const = 0; + + virtual bool FindFirst(const char* fname) = 0; + virtual bool FindNext() = 0; + virtual void Close() = 0; +}; + +extern bool Find_First(const char* fname, unsigned int mode, Find_File_Data** ffblk); +extern bool Find_Next(Find_File_Data* ffblk); +extern void Find_Close(Find_File_Data* ffblk); diff --git a/code/file_posix.cpp b/code/file_posix.cpp new file mode 100644 index 000000000..5625dcbf6 --- /dev/null +++ b/code/file_posix.cpp @@ -0,0 +1,133 @@ +#ifndef _WIN32 +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include "file.h" + +#include +#include +#include +#include +#include +#include +#include + +class Find_File_Data_Posix : public Find_File_Data +{ +public: + Find_File_Data_Posix(); + virtual ~Find_File_Data_Posix(); + + virtual const char* GetName() const; + virtual const char* GetFullName() const + { + return DirEntry != nullptr ? FullName : nullptr; + } + virtual unsigned int GetTime() const; + + virtual bool FindFirst(const char* fname); + virtual bool FindNext(); + virtual void Close(); + +private: + DIR* Directory; + struct dirent* DirEntry; + const char* FileFilter; + char FullName[PATH_MAX]; + char DirName[PATH_MAX]; + + bool FindNextWithFilter(); +}; + +Find_File_Data_Posix::Find_File_Data_Posix() + : Directory(nullptr) + , DirEntry(nullptr) +{ +} + +Find_File_Data_Posix::~Find_File_Data_Posix() +{ + Close(); +} + +const char* Find_File_Data_Posix::GetName() const +{ + if (DirEntry == nullptr) { + return nullptr; + } + return DirEntry->d_name; +} + +unsigned int Find_File_Data_Posix::GetTime() const +{ + if (DirEntry == nullptr) { + return 0; + } + struct stat buf = {0}; + if (stat(FullName, &buf) != 0) { + return false; + } + return buf.st_mtime; +} + +bool Find_File_Data_Posix::FindNextWithFilter() +{ + while (true) { + DirEntry = readdir(Directory); + if (DirEntry == nullptr) { + return false; + } + if (fnmatch(FileFilter, DirEntry->d_name, FNM_PATHNAME | FNM_CASEFOLD) == 0) { + strcpy(FullName, DirName); + strcat(FullName, DirEntry->d_name); + break; + } + } + return true; +} + +bool Find_File_Data_Posix::FindFirst(const char* fname) +{ + Close(); + FullName[0] = '\0'; + DirName[0] = '\0'; + + // split directory and file from the path + char* fdir = strrchr((char*)fname, '/'); + if (fdir != nullptr) { + strncat(DirName, fname, (fdir - fname + 1)); + FileFilter = fdir + 1; + Directory = opendir(DirName); + } else { + FileFilter = fname; + Directory = opendir("."); + } + + if (Directory == nullptr) { + return false; + } + + return FindNextWithFilter(); +} + +bool Find_File_Data_Posix::FindNext() +{ + if (Directory == nullptr) { + return false; + } + return FindNextWithFilter(); +} + +void Find_File_Data_Posix::Close() +{ + if (Directory != nullptr) { + closedir(Directory); + Directory = nullptr; + } +} + +Find_File_Data* Find_File_Data::CreateFindData() +{ + return new Find_File_Data_Posix(); +} +#endif diff --git a/code/file_win.cpp b/code/file_win.cpp new file mode 100644 index 000000000..76779ddff --- /dev/null +++ b/code/file_win.cpp @@ -0,0 +1,76 @@ +#ifdef _WIN32 +#include "file.h" + +#include +#include + +class Find_File_Data_Win : public Find_File_Data +{ +public: + Find_File_Data_Win(); + virtual ~Find_File_Data_Win(); + + virtual const char* GetName() const; + virtual unsigned int GetTime() const; + + virtual bool FindFirst(const char* fname); + virtual bool FindNext(); + virtual void Close(); + +private: + HANDLE FindHandle; + WIN32_FIND_DATAA FindData; +}; + +Find_File_Data_Win::Find_File_Data_Win() + : FindHandle(INVALID_HANDLE_VALUE) + , FindData({0}) +{ +} + +Find_File_Data_Win::~Find_File_Data_Win() +{ + Close(); +} + +const char* Find_File_Data_Win::GetName() const +{ + return FindData.cFileName; +} + +unsigned int Find_File_Data_Win::GetTime() const +{ + ULARGE_INTEGER ull; + ull.LowPart = FindData.ftLastWriteTime.dwLowDateTime; + ull.HighPart = FindData.ftLastWriteTime.dwHighDateTime; + return (unsigned int)(ull.QuadPart / 10000000ULL - 11644473600ULL); +} + +bool Find_File_Data_Win::FindFirst(const char* fname) +{ + FindHandle = FindFirstFileA(fname, &FindData); + return (FindHandle != INVALID_HANDLE_VALUE); +} + +bool Find_File_Data_Win::FindNext() +{ + if (FindHandle == INVALID_HANDLE_VALUE) { + return false; + } + + return (FindNextFileA(FindHandle, &FindData) != FALSE); +} + +void Find_File_Data_Win::Close() +{ + if (FindHandle != INVALID_HANDLE_VALUE) { + FindClose(FindHandle); + FindHandle = INVALID_HANDLE_VALUE; + } +} + +Find_File_Data* Find_File_Data::CreateFindData() +{ + return new Find_File_Data_Win(); +} +#endif diff --git a/code/gamedirs.cpp b/code/gamedirs.cpp index d26045e8e..790761aa5 100644 --- a/code/gamedirs.cpp +++ b/code/gamedirs.cpp @@ -14,7 +14,9 @@ #include "cdfile.h" #include "dbgprint.h" +#include #include +#include #include /* @@ -61,7 +63,7 @@ static std::string Terminate_Path(std::string const & path) return(path); default: - return(path + '\\'); + return(path + (char)std::filesystem::path::preferred_separator); } } @@ -279,7 +281,7 @@ std::string Saved_Game_Name(char const * filename) CreateDirectory(folder.c_str(), NULL); - return(folder + '\\' + filename); + return(folder + (char)std::filesystem::path::preferred_separator + filename); } diff --git a/code/ini.cpp b/code/ini.cpp index 4db0ee6f2..7ef526684 100644 --- a/code/ini.cpp +++ b/code/ini.cpp @@ -957,6 +957,8 @@ int INIClass::Get_Int(char const * section, char const * entry, int defvalue) co } +#ifdef _WIN32 + /// /// Fetches a class identifier from the specified section. /// This routine will fetch the printable form of a class identifier from the entry and @@ -1006,6 +1008,7 @@ bool INIClass::Put_CLSID(char const * section, char const * entry, CLSID const & SysFreeString(olestr); return(Put_String(section, entry, buffer)); } +#endif /*********************************************************************************************** diff --git a/code/ini.h b/code/ini.h index a4f33badd..457e0aedc 100644 --- a/code/ini.h +++ b/code/ini.h @@ -34,7 +34,10 @@ #include "crc.h" #include "index.h" +#ifdef _WIN32 #include +#endif + #include #include #include @@ -113,7 +116,9 @@ class INIClass { TPoint3D const Get_Point(char const * section, char const * entry, TPoint3D const & defvalue) const; TPoint2D const Get_Point(char const * section, char const * entry, TPoint2D const & defvalue) const; TPoint3D const Get_Point(char const * section, char const * entry, TPoint3D const & defvalue) const; +#ifdef _WIN32 CLSID const Get_CLSID(char const * section, char const * entry, CLSID defvalue) const; +#endif /* ** Put a data type to the section and entry specified. @@ -130,7 +135,9 @@ class INIClass { bool Put_Point(char const * section, char const * entry, TPoint3D const & value); bool Put_Point(char const * section, char const * entry, TPoint3D const & value); bool Put_Point(char const * section, char const * entry, TPoint2D const & value); +#ifdef _WIN32 bool Put_CLSID(char const * section, char const * entry, CLSID const & value); +#endif // Callers size the buffers they hand to Get_String from this. It does not bound a line // of the file; the reader keeps a line of any length. diff --git a/code/mixfile.cpp b/code/mixfile.cpp index 9a5db0d37..f650c0de9 100644 --- a/code/mixfile.cpp +++ b/code/mixfile.cpp @@ -69,7 +69,7 @@ ** with the mixfile system. */ //template -List MixFileClass::List; +List MixFileClass::MixList; /// template class MixFileClass; @@ -185,7 +185,7 @@ MixFileClass::MixFileClass(char const * filename, PKey const * key) : /* ** Attach to list of mixfiles. */ - List.Add_Tail(this); + MixList.Add_Tail(this); } @@ -303,7 +303,7 @@ void const * MixFileClass::Retrieve(char const * filename) *=============================================================================================*/ MixFileClass * MixFileClass::Finder(char const * filename) { - MixFileClass * ptr = List.First(); + MixFileClass * ptr = MixList.First(); while (ptr->Is_Valid()) { char path[_MAX_PATH]; char name[_MAX_FNAME]; @@ -534,7 +534,10 @@ bool MixFileClass::Offset(char const * filename, void ** realptr, MixFileClass * */ /// Can't call strupr on a const string. - int crc = (CRCEngine()(strupr((char *)filename), strlen(filename))); //Calculate_CRC(strupr((char *)filename), strlen(filename)); + char filename_upper[_MAX_PATH]; + strcpy(filename_upper, filename); + strupr(filename_upper); + int crc = (CRCEngine()(filename_upper, strlen(filename_upper))); //Calculate_CRC(strupr((char *)filename), strlen(filename)); SubBlock key; key.CRC = crc; @@ -542,7 +545,7 @@ bool MixFileClass::Offset(char const * filename, void ** realptr, MixFileClass * /* ** Sweep through all registered mixfiles, trying to find the file in question. */ - ptr = List.First(); + ptr = MixList.First(); while (ptr->Is_Valid()) { SubBlock * block; diff --git a/code/mixfile.h b/code/mixfile.h index 12af50954..b4d0c78eb 100644 --- a/code/mixfile.h +++ b/code/mixfile.h @@ -111,5 +111,5 @@ class MixFileClass : public Node */ void * Data; // Pointer to raw data. - static List List; + static List MixList; }; diff --git a/code/rawfile.cpp b/code/rawfile.cpp index 609822d7b..3520e5724 100644 --- a/code/rawfile.cpp +++ b/code/rawfile.cpp @@ -50,13 +50,24 @@ #include "always.h" #include "rawfile.h" +#include "file.h" #include #include #include #include -#include -#include + +#ifndef _WIN32 +#include +#include +#include +#include +#define _unlink unlink +#define fopen(x, y) fopen(x, y) +#else +#include +#include +#endif /*********************************************************************************************** @@ -78,10 +89,9 @@ RawFileClass::~RawFileClass(void) { Close(); - if (Allocated && Filename) { + if (Filename) { free((char *)Filename); ((char *&)Filename) = 0; - Allocated = false; } } @@ -135,12 +145,13 @@ RawFileClass::RawFileClass(char const * filename) : Rights(0), BiasStart(0), BiasLength(-1), - Handle(NULL_HANDLE), - Filename(filename), + Handle(nullptr), + Filename(nullptr), Date(0), Time(0), - Allocated(false) + LastAccessType(0) { + Set_Name(filename); } @@ -166,10 +177,9 @@ RawFileClass::RawFileClass(char const * filename) : *=============================================================================================*/ char const * RawFileClass::Set_Name(char const * filename) { - if (Filename != NULL && Allocated) { + if (Filename != NULL) { free((char *)Filename); - Filename = NULL; - Allocated = false; + Filename = nullptr; } if (filename == NULL) return(NULL); @@ -181,7 +191,18 @@ char const * RawFileClass::Set_Name(char const * filename) Error(ENOMEM, false, filename); return(NULL); } - Allocated = true; + + /* + ** If we ever save this file, make sure we save it in lowercase but + ** if Resolve_File finds an actual file on-disk we use the real name + ** instead. + */ + _strlwr(Filename); + + /* + ** Try to locate an existing file ignoring case, updates Filename + */ + Resolve_File(Filename); return(Filename); } @@ -236,6 +257,7 @@ int RawFileClass::Open(char const * filename, int rights) int RawFileClass::Open(int rights) { Close(); + LastAccessType = 0; /* ** Verify that there is a filename associated with this file object. If not, then this is a @@ -267,23 +289,24 @@ int RawFileClass::Open(int rights) ** an invalid access code. */ default: + errno = EINVAL; break; case READ: - Handle = CreateFile(Filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); + Handle = fopen(Filename, "rb"); break; case WRITE: - Handle = CreateFile(Filename, GENERIC_WRITE, 0, - NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + Handle = fopen(Filename, "wb"); break; case READ|WRITE: - // SKB 5/13/99 use OPEN_ALWAYS instead of CREATE_ALWAYS so that files + // SKB 5/13/99 try "r+" first before using "w+" so that files // does not get destroyed. - Handle = CreateFile(Filename, GENERIC_READ | GENERIC_WRITE, 0, - NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + Handle = fopen(Filename, "r+b"); + if (Handle == nullptr) { + Handle = fopen(Filename, "w+b"); + } break; } @@ -299,10 +322,10 @@ int RawFileClass::Open(int rights) ** For the case of the file cannot be found, then allow a retry. All other cases ** are fatal. */ - if (Handle == NULL_HANDLE) { + if (Handle == nullptr) { return(false); -// Error(GetLastError(), false, Filename); +// Error(errno, false, Filename); // continue; } break; @@ -354,22 +377,18 @@ bool RawFileClass::Is_Available(int forced) ** CD-ROM, this routine will return a failure condition. In all but the missing file ** condition, go through the normal error recover channels. */ - for (;;) { - Handle = CreateFile(Filename, GENERIC_READ, FILE_SHARE_READ, - NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (Handle == NULL_HANDLE) { - return(false); - } - break; + Handle = fopen(Filename, "r"); + if (Handle == nullptr) { + return(false); } /* ** Since the file could be opened, then close it and return that the file exists. */ - if (!CloseHandle(Handle)) { - Error(GetLastError(), false, Filename); + if (fclose(Handle) != 0) { + Error(errno, false, Filename); } - Handle = NULL_HANDLE; + Handle = nullptr; return(true); } @@ -401,14 +420,20 @@ void RawFileClass::Close(void) ** Try to close the file. If there was an error (who knows what that could be), then ** call the error routine. */ - if (!CloseHandle(Handle)) { - Error(GetLastError(), false, Filename); + if (fclose(Handle) != 0) { + Error(errno, false, Filename); } /* ** At this point the file must have been closed. Mark the file as empty and return. */ - Handle = NULL_HANDLE; + Handle = nullptr; + + /* + ** Clear any positioning information incase class is reused to open another file. + */ + BiasStart = 0; + BiasLength = -1; } } @@ -464,18 +489,24 @@ int RawFileClass::Read(void * buffer, int size) size = size < remainder ? size : remainder; } + if (!opened && LastAccessType != 0 && LastAccessType != READ) { + if (fseek(Handle, ftell(Handle), SEEK_SET) < 0) { + Error(errno, false, Filename); + return(0); + } + } + LastAccessType = READ; + int total = 0; while (size > 0) { - bytesread = 0; - - if (!ReadFile(Handle, buffer, size, &(DWORD &)bytesread, NULL)) { - buffer = (char *)buffer + bytesread; + clearerr(Handle); + bytesread = fread(buffer, 1, size, Handle); + if (ferror(Handle)) { size -= bytesread; total += bytesread; - Error(GetLastError(), true, Filename); + Error(errno, true, Filename); continue; } - buffer = (char *)buffer + bytesread; size -= bytesread; total += bytesread; if (bytesread == 0) break; @@ -526,8 +557,18 @@ int RawFileClass::Write(void const * buffer, int size) opened = true; } - if (!WriteFile(Handle, buffer, size, &(DWORD &)byteswritten, NULL)) { - Error(GetLastError(), false, Filename); + if (!opened && LastAccessType != 0 && LastAccessType != WRITE) { + if (fseek(Handle, ftell(Handle), SEEK_SET) < 0) { + Error(errno, false, Filename); + return(0); + } + } + LastAccessType = WRITE; + + clearerr(Handle); + byteswritten = fwrite(buffer, 1, size, Handle); + if (ferror(Handle)) { + Error(errno, false, Filename); } /* @@ -660,15 +701,30 @@ int RawFileClass::Size(void) */ if (Is_Open()) { - size = GetFileSize(Handle, NULL); - /* - ** If there was in internal error, then call the error function. + ** With stdio we seek to end to obtain the length, then reset the position back. */ - if (size == 0xFFFFFFFF) { - Error(GetLastError(), false, Filename); + clearerr(Handle); + + int position = ftell(Handle); + if (position < 0) { + Error(errno, false, Filename); + return(0); } + if (fseek(Handle, 0, SEEK_END) < 0) { + Error(errno, false, Filename); + return(0); + } + + size = ftell(Handle); + + if (fseek(Handle, position, SEEK_SET) < 0) { + Error(errno, false, Filename); + return(0); + } + LastAccessType = 0; + } else { /* @@ -777,8 +833,8 @@ int RawFileClass::Delete(void) return(false); } - if (!DeleteFile(Filename)) { - Error(GetLastError(), false, Filename); + if (_unlink(Filename) < 0) { + Error(errno, false, Filename); return(false); } break; @@ -809,14 +865,38 @@ int RawFileClass::Delete(void) *=============================================================================================*/ unsigned int RawFileClass::Get_Date_Time(void) { - BY_HANDLE_FILE_INFORMATION info; +#ifdef _WIN32 + if (RawFileClass::Is_Open()) { + BY_HANDLE_FILE_INFORMATION info; + HANDLE osHandle = (HANDLE)_get_osfhandle(_fileno(Handle)); + + if (osHandle != INVALID_HANDLE_VALUE && + GetFileInformationByHandle(osHandle, &info)) { + WORD dosdate; + WORD dostime; + FileTimeToDosDateTime(&info.ftLastWriteTime, &dosdate, &dostime); + return((dosdate << 16) | dostime); + } + } +#else + struct stat statbuf; + + if (stat(Filename, &statbuf) == 0) { + struct tm *parsed_time = localtime(&statbuf.st_mtime); + + if (parsed_time != NULL) { + Date = (((parsed_time->tm_year - 80) & ((1 << 7) - 1)) << 9) | + (((parsed_time->tm_mon + 1) & ((1 << 4) - 1)) << 5) | + (parsed_time->tm_mday & ((1 << 5) - 1)); - if (GetFileInformationByHandle(Handle, &info)) { - WORD dosdate; - WORD dostime; - FileTimeToDosDateTime(&info.ftLastWriteTime, &dosdate, &dostime); - return((dosdate << 16) | dostime); + Time = ((parsed_time->tm_hour & ((1 << 5) - 1)) << 11) | + ((parsed_time->tm_min & ((1 << 6) - 1)) << 5) | + ((parsed_time->tm_sec >> 1) & ((1 << 5) - 1)); + + return(Date << 16 | Time); + } } +#endif return(0); } @@ -838,16 +918,45 @@ unsigned int RawFileClass::Get_Date_Time(void) *=============================================================================================*/ bool RawFileClass::Set_Date_Time(unsigned int datetime) { +#ifdef _WIN32 if (RawFileClass::Is_Open()) { BY_HANDLE_FILE_INFORMATION info; + HANDLE osHandle = (HANDLE)_get_osfhandle(_fileno(Handle)); - if (GetFileInformationByHandle(Handle, &info)) { + if (osHandle != INVALID_HANDLE_VALUE && + GetFileInformationByHandle(osHandle, &info)) { FILETIME filetime; if (DosDateTimeToFileTime((WORD)(datetime >> 16), (WORD)(datetime & 0x0FFFF), &filetime)) { - return(SetFileTime(Handle, &info.ftCreationTime, &filetime, &filetime) != 0); + return(SetFileTime(osHandle, &info.ftCreationTime, &filetime, &filetime) != 0); } } } +#else + struct tm input_time = { 0 }; + time_t unix_time; + + Date = (datetime >> 16) & 0xFFFF; + Time = datetime & 0xFFFF; + + input_time.tm_year = ((Date >> 9) & ((1 << 7) - 1)) + 80; + input_time.tm_mon = ((Date >> 5) & ((1 << 4) - 1)) - 1; + input_time.tm_mday = Date & ((1 << 5) - 1); + + input_time.tm_hour = (Time >> 11) & ((1 << 5) - 1); + input_time.tm_min = (Time >> 5) & ((1 << 6) - 1); + input_time.tm_sec = (Time & ((1 << 5) - 1)) << 1; + + input_time.tm_isdst = -1; + + unix_time = mktime(&input_time); + if (unix_time >= 0) { + struct utimbuf buf = { 0 }; + buf.actime = unix_time; + buf.modtime = unix_time; + + return(utime(Filename, &buf) == 0); + } +#endif return(false); } @@ -923,30 +1032,25 @@ int RawFileClass::Raw_Seek(int pos, int dir) */ if (!Is_Open()) { Error(EBADF, false, Filename); - return(0); - } - - switch (dir) { - case SEEK_SET: - dir = FILE_BEGIN; - break; + } else { - case SEEK_CUR: - dir = FILE_CURRENT; - break; + clearerr(Handle); - case SEEK_END: - dir = FILE_END; - break; - } - pos = SetFilePointer(Handle, pos, NULL, dir); + /* + ** If pos == 0 and dir == SEEK_CUR, fseek should basically do nothing. + ** However, some very bad implementations (like the Nintendo DS's libfat) + ** just goes back to the beginning of the file and iterate it until it + ** finds the current position, which is awful. So instead of doing that, + ** guard this case so that sequential ::Read's do not take too much time. + */ + if (!(pos == 0 && dir == SEEK_CUR)) { + if (fseek(Handle, pos, dir) < 0) { + Error(errno, false, Filename); + } + LastAccessType = 0; + } - /* - ** If there was an error in the seek, then bail with an error condition. - */ - if (pos == 0xFFFFFFFF) { - Error(GetLastError(), false, Filename); - return(0); + pos = ftell(Handle); } /* diff --git a/code/rawfile.h b/code/rawfile.h index a35451f09..52cf7b12a 100644 --- a/code/rawfile.h +++ b/code/rawfile.h @@ -41,10 +41,8 @@ #include #include #include -#include +#include -#define NULL_HANDLE INVALID_HANDLE_VALUE -#define HANDLE_TYPE HANDLE #ifndef WWERROR #define WWERROR -1 #endif @@ -95,7 +93,7 @@ class RawFileClass : public FileClass virtual bool Set_Date_Time(unsigned int datetime); virtual void Error(int error, int canretry = false, char const * filename=NULL) override; void Bias(int start, int length=-1); - HANDLE_TYPE Get_File_Handle(void) { return(Handle); }; + FILE *Get_File_Handle(void) { return(Handle); }; /* ** These bias values enable a sub-portion of a file to appear as if it @@ -118,15 +116,19 @@ class RawFileClass : public FileClass private: /* - ** This is the low level DOS handle. A -1 indicates an empty condition. + ** This is the file handle. A nullptr indicates an empty condition. */ - HANDLE_TYPE Handle; + FILE *Handle; /* - ** This points to the filename as a NULL terminated string. It may point to either a - ** constant or an allocated string as indicated by the "Allocated" flag. + ** This points to a copy of the filename as a NULL terminated string. */ - char const * Filename; + char *Filename; + + /* + ** The type of the last file access operation. Reset by fseek(). + */ + int LastAccessType; // // file date and time are in the following formats: @@ -141,15 +143,6 @@ class RawFileClass : public FileClass // unsigned short Date; unsigned short Time; - - /* - ** Filenames that were assigned as part of the construction process - ** are not allocated. It is assumed that the filename string is a - ** constant in that case and thus making duplication unnecessary. - ** This value will be non-zero if the filename has be allocated - ** (using strdup()). - */ - bool Allocated; }; @@ -195,11 +188,11 @@ inline RawFileClass::RawFileClass(void) : Rights(READ), BiasStart(0), BiasLength(-1), - Handle(INVALID_HANDLE_VALUE), - Filename(0), + Handle(nullptr), + Filename(nullptr), Date(0), Time(0), - Allocated(false) + LastAccessType(0) { } @@ -221,5 +214,5 @@ inline RawFileClass::RawFileClass(void) : *=============================================================================================*/ inline bool RawFileClass::Is_Open(void) const { - return(Handle != INVALID_HANDLE_VALUE); + return(Handle != nullptr); } diff --git a/code/session.cpp b/code/session.cpp index c15304290..7a56972c1 100644 --- a/code/session.cpp +++ b/code/session.cpp @@ -76,6 +76,7 @@ #include #include // for station ID computation #include // for station ID computation +#include // for ntohl /***************************** Globals *************************************/ diff --git a/code/sha.cpp b/code/sha.cpp index df04f76e7..95fca0c32 100644 --- a/code/sha.cpp +++ b/code/sha.cpp @@ -78,7 +78,7 @@ void SHAEngine::Process_Partial(void const * & data, int & length) ** Attach as many bytes as possible from the source data into ** the staging buffer. */ - int add_count = std::min((int)length, SRC_BLOCK_SIZE - PartialCount); + int add_count = std::min((int)length, (int)SRC_BLOCK_SIZE - PartialCount); memcpy(&Partial[PartialCount], data, add_count); data = ((char const *&)data) + add_count; PartialCount += add_count; diff --git a/code/sha.h b/code/sha.h index 2a2887cef..e74f2160d 100644 --- a/code/sha.h +++ b/code/sha.h @@ -35,7 +35,7 @@ #include #include #include -#include +#include /* diff --git a/code/vqa.h b/code/vqa.h index d9b53a738..86337755a 100644 --- a/code/vqa.h +++ b/code/vqa.h @@ -19,6 +19,7 @@ #include "ccfile.h" +#include #include //========================================================================== diff --git a/code/wwfile.h b/code/wwfile.h index 9ac186f90..871c71c6d 100644 --- a/code/wwfile.h +++ b/code/wwfile.h @@ -40,13 +40,6 @@ #include #include -#include - -#ifndef SEEK_SET -#define SEEK_SET 0 // Seek from start of file. -#define SEEK_CUR 1 // Seek relative from current location. -#define SEEK_END 2 // Seek from end of file. -#endif class FileClass diff --git a/tests/deploymentconfig/CMakeLists.txt b/tests/deploymentconfig/CMakeLists.txt index f6d40d8b4..c2f3192b8 100644 --- a/tests/deploymentconfig/CMakeLists.txt +++ b/tests/deploymentconfig/CMakeLists.txt @@ -7,6 +7,9 @@ add_executable(DeploymentConfig "${CMAKE_SOURCE_DIR}/code/_deploymentconfig.cpp" "${CMAKE_SOURCE_DIR}/code/bfiofile.cpp" "${CMAKE_SOURCE_DIR}/code/rawfile.cpp" + "${CMAKE_SOURCE_DIR}/code/file.cpp" + "${CMAKE_SOURCE_DIR}/code/file_posix.cpp" + "${CMAKE_SOURCE_DIR}/code/file_win.cpp" "${CMAKE_SOURCE_DIR}/code/dbgprint.cpp" "${CMAKE_SOURCE_DIR}/code/ini.cpp" "${CMAKE_SOURCE_DIR}/code/utf8.cpp" diff --git a/tests/gamedirs/CMakeLists.txt b/tests/gamedirs/CMakeLists.txt index 97346f40a..26ffe5166 100644 --- a/tests/gamedirs/CMakeLists.txt +++ b/tests/gamedirs/CMakeLists.txt @@ -7,6 +7,9 @@ add_executable(GameDirs "${CMAKE_SOURCE_DIR}/code/cdfile.cpp" "${CMAKE_SOURCE_DIR}/code/bfiofile.cpp" "${CMAKE_SOURCE_DIR}/code/rawfile.cpp" + "${CMAKE_SOURCE_DIR}/code/file.cpp" + "${CMAKE_SOURCE_DIR}/code/file_posix.cpp" + "${CMAKE_SOURCE_DIR}/code/file_win.cpp" "${CMAKE_SOURCE_DIR}/code/dbgprint.cpp" "${CMAKE_SOURCE_DIR}/code/ini.cpp" "${CMAKE_SOURCE_DIR}/code/utf8.cpp" From a4f04eabcacf46d8f1bf51ce3ea9088ab2a54c2a Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 19:36:49 +0100 Subject: [PATCH 031/179] build: guard MSVC-only flags, import libraries and post-build steps --- code/CMakeLists.txt | 100 ++++++++++++++++++++++++++------------------ 1 file changed, 60 insertions(+), 40 deletions(-) diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 93a49be1c..92d5fa3f2 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -91,42 +91,53 @@ set_target_properties(OpenTS PROPERTIES # message(STATUS "${PROJECT_NAME}: Applying compiler flags...") -set(OPENTS_COMPILE_OPTIONS - - # ================= DEBUG ================= - $<$: - - # ---------- C++ ---------- - # /arch:SSE2 -- IEEE-754 single precision, no x87 excess precision. - # /fp:precise -- no reassociation of the engine's accumulations. - # /utf-8 -- sources and literals are UTF-8 whatever the build machine's code page. - $<$: - /Zi /Od /RTC1 /GR $<$:/MP> /EHsc /Oy- /MTd /Zc:__cplusplus /arch:SSE2 /fp:precise /utf-8 - > - - # ---------- C ---------- - $<$: - /Zi /Od /RTC1 $<$:/MP> /Oy- /arch:SSE2 /fp:precise /utf-8 - > - > - - # ================= RELEASE ================= - $<$: - - # ---------- C++ ---------- - # /arch:SSE2 -- IEEE-754 single precision, no x87 excess precision. - # /fp:precise -- no reassociation of the engine's accumulations. - # /utf-8 -- sources and literals are UTF-8 whatever the build machine's code page. - $<$: - /Zi /O2 /GF /GR $<$:/MP> /EHsc /MT /Zc:__cplusplus /arch:SSE2 /fp:precise /utf-8 +if(MSVC OR OPENTS_EXPERIMENTAL_CLANG_CL) + set(OPENTS_COMPILE_OPTIONS + + # ================= DEBUG ================= + $<$: + + # ---------- C++ ---------- + # /arch:SSE2 -- IEEE-754 single precision, no x87 excess precision. + # /fp:precise -- no reassociation of the engine's accumulations. + # /utf-8 -- sources and literals are UTF-8 whatever the build machine's code page. + $<$: + /Zi /Od /RTC1 /GR $<$:/MP> /EHsc /Oy- /MTd /Zc:__cplusplus /arch:SSE2 /fp:precise /utf-8 + > + + # ---------- C ---------- + $<$: + /Zi /Od /RTC1 $<$:/MP> /Oy- /arch:SSE2 /fp:precise /utf-8 + > > - # ---------- C ---------- - $<$: - /Zi /O2 /GF $<$:/MP> /arch:SSE2 /fp:precise /utf-8 + # ================= RELEASE ================= + $<$: + + # ---------- C++ ---------- + # /arch:SSE2 -- IEEE-754 single precision, no x87 excess precision. + # /fp:precise -- no reassociation of the engine's accumulations. + # /utf-8 -- sources and literals are UTF-8 whatever the build machine's code page. + $<$: + /Zi /O2 /GF /GR $<$:/MP> /EHsc /MT /Zc:__cplusplus /arch:SSE2 /fp:precise /utf-8 + > + + # ---------- C ---------- + $<$: + /Zi /O2 /GF $<$:/MP> /arch:SSE2 /fp:precise /utf-8 + > > - > -) + ) +else() + # The native host build carries no MSVC switch spellings. Only the floating-point + # contract matters for simulation determinism, so it is the one that is restated. + set(OPENTS_COMPILE_OPTIONS + -g + -ffp-contract=off + -fno-strict-aliasing + -Wno-everything + ) +endif() target_compile_options(OpenTS PRIVATE ${OPENTS_COMPILE_OPTIONS}) @@ -204,6 +215,10 @@ target_link_libraries(OpenTS PRIVATE bx bimg miniaudio +) + +if(WIN32) + target_link_libraries(OpenTS PRIVATE comctl32 dbghelp iphlpapi @@ -213,7 +228,8 @@ target_link_libraries(OpenTS PRIVATE ws2_32 kernel32 user32 gdi32 winspool comdlg32 advapi32 shell32 ole32 oleaut32 uuid odbc32 odbccp32 -) + ) +endif() if(MSVC) target_link_options(OpenTS PRIVATE @@ -328,11 +344,13 @@ if(MSVC) endif() # Copy the linker-generated .map (sits next to the exe, no GenEx for it) -add_custom_command(TARGET OpenTS POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "$/$.map" - "${TS_RUN_DIR}" -) +if(MSVC) + add_custom_command(TARGET OpenTS POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "$/$.map" + "${TS_RUN_DIR}" + ) +endif() # # --------------------------------------------------------- @@ -347,4 +365,6 @@ set_property(GLOBAL PROPERTY VS_STARTUP_PROJECT OpenTS) set(NATVIS_FILE "${CMAKE_CURRENT_SOURCE_DIR}/sun.natvis") source_group("Natvis Files" FILES ${NATVIS_FILE}) target_sources(OpenTS PRIVATE ${NATVIS_FILE}) -target_link_options(OpenTS PRIVATE "/NATVIS:${NATVIS_FILE}") +if(MSVC) + target_link_options(OpenTS PRIVATE "/NATVIS:${NATVIS_FILE}") +endif() From 47fd5ecd54f1da2316b8291354ae01c974cd4078 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 19:42:39 +0100 Subject: [PATCH 032/179] spike: add a Win32 type shim to measure the native port surface --- code/CMakeLists.txt | 6 ++ spike/win32shim/README.md | 7 ++ spike/win32shim/comdef.h | 24 +++++ spike/win32shim/commctrl.h | 2 + spike/win32shim/conio.h | 3 + spike/win32shim/crtdbg.h | 3 + spike/win32shim/dbghelp.h | 2 + spike/win32shim/direct.h | 3 + spike/win32shim/dos.h | 3 + spike/win32shim/io.h | 3 + spike/win32shim/iphlpapi.h | 2 + spike/win32shim/malloc.h | 3 + spike/win32shim/mmsystem.h | 2 + spike/win32shim/objbase.h | 2 + spike/win32shim/objidl.h | 2 + spike/win32shim/ole2.h | 2 + spike/win32shim/process.h | 2 + spike/win32shim/sal.h | 2 + spike/win32shim/share.h | 3 + spike/win32shim/shellapi.h | 2 + spike/win32shim/sys/timeb.h | 3 + spike/win32shim/tlhelp32.h | 2 + spike/win32shim/unknwn.h | 76 ++++++++++++++++ spike/win32shim/utime.h | 3 + spike/win32shim/winbase.h | 2 + spike/win32shim/windef.h | 2 + spike/win32shim/windows.h | 173 ++++++++++++++++++++++++++++++++++++ spike/win32shim/windowsx.h | 2 + spike/win32shim/wingdi.h | 2 + spike/win32shim/winioctl.h | 2 + spike/win32shim/winnt.h | 2 + spike/win32shim/winres.h | 2 + spike/win32shim/winsock.h | 11 +++ spike/win32shim/winsock2.h | 11 +++ spike/win32shim/winuser.h | 2 + spike/win32shim/ws2tcpip.h | 2 + 36 files changed, 375 insertions(+) create mode 100644 spike/win32shim/README.md create mode 100644 spike/win32shim/comdef.h create mode 100644 spike/win32shim/commctrl.h create mode 100644 spike/win32shim/conio.h create mode 100644 spike/win32shim/crtdbg.h create mode 100644 spike/win32shim/dbghelp.h create mode 100644 spike/win32shim/direct.h create mode 100644 spike/win32shim/dos.h create mode 100644 spike/win32shim/io.h create mode 100644 spike/win32shim/iphlpapi.h create mode 100644 spike/win32shim/malloc.h create mode 100644 spike/win32shim/mmsystem.h create mode 100644 spike/win32shim/objbase.h create mode 100644 spike/win32shim/objidl.h create mode 100644 spike/win32shim/ole2.h create mode 100644 spike/win32shim/process.h create mode 100644 spike/win32shim/sal.h create mode 100644 spike/win32shim/share.h create mode 100644 spike/win32shim/shellapi.h create mode 100644 spike/win32shim/sys/timeb.h create mode 100644 spike/win32shim/tlhelp32.h create mode 100644 spike/win32shim/unknwn.h create mode 100644 spike/win32shim/utime.h create mode 100644 spike/win32shim/winbase.h create mode 100644 spike/win32shim/windef.h create mode 100644 spike/win32shim/windows.h create mode 100644 spike/win32shim/windowsx.h create mode 100644 spike/win32shim/wingdi.h create mode 100644 spike/win32shim/winioctl.h create mode 100644 spike/win32shim/winnt.h create mode 100644 spike/win32shim/winres.h create mode 100644 spike/win32shim/winsock.h create mode 100644 spike/win32shim/winsock2.h create mode 100644 spike/win32shim/winuser.h create mode 100644 spike/win32shim/ws2tcpip.h diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 92d5fa3f2..d648a2296 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -156,6 +156,12 @@ target_include_directories(OpenTS PRIVATE "${OPENTS_GENERATED_DIR}" ) +# Spike scaffolding. The shim only declares types, so a native compile reports the Win32 +# surface the tree still needs instead of stopping at the first missing header. +if(OPENTS_EXPERIMENTAL_NATIVE AND NOT WIN32) + target_include_directories(OpenTS PRIVATE "${CMAKE_SOURCE_DIR}/spike/win32shim") +endif() + # The generated build stamp has to exist before anything compiles. add_dependencies(OpenTS OpenTSBuildStamp) diff --git a/spike/win32shim/README.md b/spike/win32shim/README.md new file mode 100644 index 000000000..e05b661f8 --- /dev/null +++ b/spike/win32shim/README.md @@ -0,0 +1,7 @@ +# Win32 measurement shim + +Throwaway scaffolding for the native arm64 portability spike. The headers here +declare only the types and macros the tree needs to get past `#include`, so a +native compile reports the Win32 API surface as undeclared identifiers instead +of stopping at the first missing header. Nothing here implements Win32, and +nothing here is a portability layer. It exists to size the port. diff --git a/spike/win32shim/comdef.h b/spike/win32shim/comdef.h new file mode 100644 index 000000000..0b00b9e98 --- /dev/null +++ b/spike/win32shim/comdef.h @@ -0,0 +1,24 @@ +#pragma once +#include + +// _com_ptr_t and the __declspec(uuid)/__uuidof machinery have no clang equivalent on a +// non-MSVC target. Declaring the template unconditionally lets the surface be counted. +template class _com_ptr_t { +public: + _com_ptr_t() = default; + T *operator->() const; + operator T *() const; + T **operator&(); +}; +#define _COM_SMARTPTR_TYPEDEF(iface, iid) typedef _com_ptr_t iface##Ptr +class _com_error { +public: + HRESULT Error() const; + const char *ErrorMessage() const; +}; + +_COM_SMARTPTR_TYPEDEF(IUnknown, 0); +_COM_SMARTPTR_TYPEDEF(IStream, 0); +_COM_SMARTPTR_TYPEDEF(IPersistStream, 0); +_COM_SMARTPTR_TYPEDEF(IPropertySetStorage, 0); +_COM_SMARTPTR_TYPEDEF(IClassFactory, 0); diff --git a/spike/win32shim/commctrl.h b/spike/win32shim/commctrl.h new file mode 100644 index 000000000..720e64a9b --- /dev/null +++ b/spike/win32shim/commctrl.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/conio.h b/spike/win32shim/conio.h new file mode 100644 index 000000000..607852b09 --- /dev/null +++ b/spike/win32shim/conio.h @@ -0,0 +1,3 @@ +#pragma once +#include +#include diff --git a/spike/win32shim/crtdbg.h b/spike/win32shim/crtdbg.h new file mode 100644 index 000000000..607852b09 --- /dev/null +++ b/spike/win32shim/crtdbg.h @@ -0,0 +1,3 @@ +#pragma once +#include +#include diff --git a/spike/win32shim/dbghelp.h b/spike/win32shim/dbghelp.h new file mode 100644 index 000000000..720e64a9b --- /dev/null +++ b/spike/win32shim/dbghelp.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/direct.h b/spike/win32shim/direct.h new file mode 100644 index 000000000..607852b09 --- /dev/null +++ b/spike/win32shim/direct.h @@ -0,0 +1,3 @@ +#pragma once +#include +#include diff --git a/spike/win32shim/dos.h b/spike/win32shim/dos.h new file mode 100644 index 000000000..607852b09 --- /dev/null +++ b/spike/win32shim/dos.h @@ -0,0 +1,3 @@ +#pragma once +#include +#include diff --git a/spike/win32shim/io.h b/spike/win32shim/io.h new file mode 100644 index 000000000..607852b09 --- /dev/null +++ b/spike/win32shim/io.h @@ -0,0 +1,3 @@ +#pragma once +#include +#include diff --git a/spike/win32shim/iphlpapi.h b/spike/win32shim/iphlpapi.h new file mode 100644 index 000000000..720e64a9b --- /dev/null +++ b/spike/win32shim/iphlpapi.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/malloc.h b/spike/win32shim/malloc.h new file mode 100644 index 000000000..607852b09 --- /dev/null +++ b/spike/win32shim/malloc.h @@ -0,0 +1,3 @@ +#pragma once +#include +#include diff --git a/spike/win32shim/mmsystem.h b/spike/win32shim/mmsystem.h new file mode 100644 index 000000000..720e64a9b --- /dev/null +++ b/spike/win32shim/mmsystem.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/objbase.h b/spike/win32shim/objbase.h new file mode 100644 index 000000000..a2a5b9a9b --- /dev/null +++ b/spike/win32shim/objbase.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/objidl.h b/spike/win32shim/objidl.h new file mode 100644 index 000000000..a2a5b9a9b --- /dev/null +++ b/spike/win32shim/objidl.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/ole2.h b/spike/win32shim/ole2.h new file mode 100644 index 000000000..a2a5b9a9b --- /dev/null +++ b/spike/win32shim/ole2.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/process.h b/spike/win32shim/process.h new file mode 100644 index 000000000..720e64a9b --- /dev/null +++ b/spike/win32shim/process.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/sal.h b/spike/win32shim/sal.h new file mode 100644 index 000000000..720e64a9b --- /dev/null +++ b/spike/win32shim/sal.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/share.h b/spike/win32shim/share.h new file mode 100644 index 000000000..607852b09 --- /dev/null +++ b/spike/win32shim/share.h @@ -0,0 +1,3 @@ +#pragma once +#include +#include diff --git a/spike/win32shim/shellapi.h b/spike/win32shim/shellapi.h new file mode 100644 index 000000000..720e64a9b --- /dev/null +++ b/spike/win32shim/shellapi.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/sys/timeb.h b/spike/win32shim/sys/timeb.h new file mode 100644 index 000000000..22f0d8503 --- /dev/null +++ b/spike/win32shim/sys/timeb.h @@ -0,0 +1,3 @@ +#pragma once +#include +typedef struct _timeb { long time; unsigned short millitm; short timezone; short dstflag; } _timeb; diff --git a/spike/win32shim/tlhelp32.h b/spike/win32shim/tlhelp32.h new file mode 100644 index 000000000..720e64a9b --- /dev/null +++ b/spike/win32shim/tlhelp32.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/unknwn.h b/spike/win32shim/unknwn.h new file mode 100644 index 000000000..10052c5a5 --- /dev/null +++ b/spike/win32shim/unknwn.h @@ -0,0 +1,76 @@ +#pragma once +#include + +#define STDMETHOD(m) virtual HRESULT m +#define STDMETHOD_(t, m) virtual t m +#define STDMETHODIMP HRESULT +#define STDMETHODIMP_(t) t +#define PURE = 0 +#define DECLARE_INTERFACE(i) struct i +#define DECLARE_INTERFACE_(i, b) struct i : public b +#define THIS_ +#define THIS +#define interface struct + +struct IUnknown { + virtual HRESULT QueryInterface(REFIID riid, void **ppv) = 0; + virtual ULONG AddRef() = 0; + virtual ULONG Release() = 0; +}; +typedef IUnknown *LPUNKNOWN; + +#define EXTERN_C extern "C" +#define STDMETHODCALLTYPE +#define STDAPICALLTYPE +#define STDAPI extern "C" HRESULT +#define STDAPI_(t) extern "C" t +#define DECLSPEC_UUID(x) +#define DECLSPEC_NOVTABLE +#define MIDL_INTERFACE(x) struct +#define EXTERN_GUID(n, ...) extern "C" const GUID n + +typedef struct tagSTATSTG { + LPWSTR pwcsName; DWORD type; ULARGE_INTEGER cbSize; + FILETIME mtime, ctime, atime; + DWORD grfMode, grfLocksSupported; + CLSID clsid; DWORD grfStateBits, reserved; +} STATSTG; + +struct ISequentialStream : public IUnknown { + virtual HRESULT Read(void *pv, ULONG cb, ULONG *pcbRead) = 0; + virtual HRESULT Write(const void *pv, ULONG cb, ULONG *pcbWritten) = 0; +}; + +struct IStream : public ISequentialStream { + virtual HRESULT Seek(LARGE_INTEGER, DWORD, ULARGE_INTEGER *) = 0; + virtual HRESULT SetSize(ULARGE_INTEGER) = 0; + virtual HRESULT CopyTo(IStream *, ULARGE_INTEGER, ULARGE_INTEGER *, ULARGE_INTEGER *) = 0; + virtual HRESULT Commit(DWORD) = 0; + virtual HRESULT Revert() = 0; + virtual HRESULT LockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD) = 0; + virtual HRESULT UnlockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD) = 0; + virtual HRESULT Stat(STATSTG *, DWORD) = 0; + virtual HRESULT Clone(IStream **) = 0; +}; +typedef IStream *LPSTREAM; + +typedef unsigned char boolean; + +struct IPersist : public IUnknown { + virtual HRESULT GetClassID(CLSID *pClassID) = 0; +}; + +struct IPersistStream : public IPersist { + virtual HRESULT IsDirty() = 0; + virtual HRESULT Load(IStream *pStm) = 0; + virtual HRESULT Save(IStream *pStm, BOOL fClearDirty) = 0; + virtual HRESULT GetSizeMax(ULARGE_INTEGER *pcbSize) = 0; +}; + +struct IPropertySetStorage : public IUnknown {}; +struct IClassFactory : public IUnknown { + virtual HRESULT CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv) = 0; + virtual HRESULT LockServer(BOOL fLock) = 0; +}; + + diff --git a/spike/win32shim/utime.h b/spike/win32shim/utime.h new file mode 100644 index 000000000..607852b09 --- /dev/null +++ b/spike/win32shim/utime.h @@ -0,0 +1,3 @@ +#pragma once +#include +#include diff --git a/spike/win32shim/winbase.h b/spike/win32shim/winbase.h new file mode 100644 index 000000000..720e64a9b --- /dev/null +++ b/spike/win32shim/winbase.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/windef.h b/spike/win32shim/windef.h new file mode 100644 index 000000000..720e64a9b --- /dev/null +++ b/spike/win32shim/windef.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/windows.h b/spike/win32shim/windows.h new file mode 100644 index 000000000..048df7a8e --- /dev/null +++ b/spike/win32shim/windows.h @@ -0,0 +1,173 @@ +#pragma once + +#include +#include +#include + +#ifndef _WINDOWS_ +#define _WINDOWS_ +#endif + +#define WINAPI +#define APIENTRY +#define CALLBACK +#define WINAPIV +#define __cdecl +#define PASCAL + +typedef int BOOL; +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef unsigned int DWORD; +typedef long LONG; +typedef unsigned long ULONG; +typedef unsigned int UINT; +typedef int INT; +typedef short SHORT; +typedef unsigned short USHORT; +typedef char CHAR; +typedef unsigned char UCHAR; +typedef wchar_t WCHAR; +typedef float FLOAT; +typedef void VOID; +typedef long long LONGLONG; +typedef unsigned long long ULONGLONG; +typedef std::intptr_t INT_PTR; +typedef std::uintptr_t UINT_PTR; +typedef std::intptr_t LONG_PTR; +typedef std::uintptr_t ULONG_PTR; +typedef ULONG_PTR DWORD_PTR; +typedef std::size_t SIZE_T; + +typedef void *LPVOID; +typedef const void *LPCVOID; +typedef char *LPSTR; +typedef const char *LPCSTR; +typedef wchar_t *LPWSTR; +typedef const wchar_t *LPCWSTR; +typedef char *LPTSTR; +typedef const char *LPCTSTR; +typedef BYTE *LPBYTE; +typedef WORD *LPWORD; +typedef DWORD *LPDWORD; +typedef INT *LPINT; +typedef LONG *LPLONG; +typedef BOOL *LPBOOL; + +typedef void *HANDLE; +#define OPENTS_SHIM_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name +OPENTS_SHIM_HANDLE(HWND); +OPENTS_SHIM_HANDLE(HINSTANCE); +OPENTS_SHIM_HANDLE(HDC); +OPENTS_SHIM_HANDLE(HBITMAP); +OPENTS_SHIM_HANDLE(HPALETTE); +OPENTS_SHIM_HANDLE(HBRUSH); +OPENTS_SHIM_HANDLE(HPEN); +OPENTS_SHIM_HANDLE(HFONT); +OPENTS_SHIM_HANDLE(HRGN); +OPENTS_SHIM_HANDLE(HGDIOBJ); +OPENTS_SHIM_HANDLE(HCURSOR); +OPENTS_SHIM_HANDLE(HICON); +OPENTS_SHIM_HANDLE(HMENU); +OPENTS_SHIM_HANDLE(HKEY); +OPENTS_SHIM_HANDLE(HMODULE); +OPENTS_SHIM_HANDLE(HMONITOR); +OPENTS_SHIM_HANDLE(HGLOBAL); +OPENTS_SHIM_HANDLE(HLOCAL); +OPENTS_SHIM_HANDLE(HACCEL); +typedef HINSTANCE HMODULE_ALIAS; + +typedef UINT_PTR WPARAM; +typedef LONG_PTR LPARAM; +typedef LONG_PTR LRESULT; +typedef LONG HRESULT; +typedef DWORD COLORREF; +typedef DWORD *LPCOLORREF; +typedef WORD ATOM; + +typedef LRESULT (CALLBACK *WNDPROC)(HWND, UINT, WPARAM, LPARAM); +typedef INT_PTR (CALLBACK *DLGPROC)(HWND, UINT, WPARAM, LPARAM); +typedef BOOL (CALLBACK *WNDENUMPROC)(HWND, LPARAM); +typedef void (CALLBACK *TIMERPROC)(HWND, UINT, UINT_PTR, DWORD); +typedef DWORD (WINAPI *LPTHREAD_START_ROUTINE)(LPVOID); +typedef int (CALLBACK *FARPROC)(); +typedef int (CALLBACK *PROC)(); + +typedef struct tagPOINT { LONG x, y; } POINT, *LPPOINT, *PPOINT; +typedef struct tagSIZE { LONG cx, cy; } SIZE, *LPSIZE; +typedef struct tagRECT { LONG left, top, right, bottom; } RECT, *LPRECT, *PRECT; +typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG, *LPMSG; +typedef struct tagPAINTSTRUCT { HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; BYTE rgbReserved[32]; } PAINTSTRUCT, *LPPAINTSTRUCT; +typedef union _LARGE_INTEGER { struct { DWORD LowPart; LONG HighPart; }; LONGLONG QuadPart; } LARGE_INTEGER, *PLARGE_INTEGER; +typedef union _ULARGE_INTEGER { struct { DWORD LowPart; DWORD HighPart; }; ULONGLONG QuadPart; } ULARGE_INTEGER; +typedef struct _FILETIME { DWORD dwLowDateTime, dwHighDateTime; } FILETIME, *LPFILETIME; +typedef struct _SYSTEMTIME { WORD wYear, wMonth, wDayOfWeek, wDay, wHour, wMinute, wSecond, wMilliseconds; } SYSTEMTIME, *LPSYSTEMTIME; +typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; LPVOID lpSecurityDescriptor; BOOL bInheritHandle; } SECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES; +typedef struct _OVERLAPPED { ULONG_PTR Internal, InternalHigh; union { struct { DWORD Offset, OffsetHigh; }; void *Pointer; }; HANDLE hEvent; } OVERLAPPED, *LPOVERLAPPED; +typedef struct _RTL_CRITICAL_SECTION { void *opaque[8]; } CRITICAL_SECTION, *LPCRITICAL_SECTION; +typedef struct _GUID { DWORD Data1; WORD Data2; WORD Data3; BYTE Data4[8]; } GUID, IID, CLSID, UUID; +typedef const GUID &REFGUID; +typedef const GUID &REFIID; +typedef const GUID &REFCLSID; + +#define TRUE 1 +#define FALSE 0 +#ifndef NULL +#define NULL 0 +#endif +#define MAX_PATH 260 +#define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1) +#define S_OK ((HRESULT)0) +#define S_FALSE ((HRESULT)1) +#define E_FAIL ((HRESULT)0x80004005L) +#define E_NOINTERFACE ((HRESULT)0x80004002L) +#define E_OUTOFMEMORY ((HRESULT)0x8007000EL) +#define E_INVALIDARG ((HRESULT)0x80070057L) +#define SUCCEEDED(hr) (((HRESULT)(hr)) >= 0) +#define FAILED(hr) (((HRESULT)(hr)) < 0) + +#define MAKEWORD(a, b) ((WORD)(((BYTE)(a)) | (((WORD)((BYTE)(b))) << 8))) +#define MAKELONG(a, b) ((LONG)(((WORD)(a)) | (((DWORD)((WORD)(b))) << 16))) +#define LOWORD(l) ((WORD)(((DWORD_PTR)(l)) & 0xffff)) +#define HIWORD(l) ((WORD)((((DWORD_PTR)(l)) >> 16) & 0xffff)) +#define LOBYTE(w) ((BYTE)(((DWORD_PTR)(w)) & 0xff)) +#define HIBYTE(w) ((BYTE)((((DWORD_PTR)(w)) >> 8) & 0xff)) +#define RGB(r, g, b) ((COLORREF)(((BYTE)(r)) | (((WORD)((BYTE)(g))) << 8) | (((DWORD)((BYTE)(b))) << 16))) +#define MAKEINTRESOURCE(i) ((LPSTR)((ULONG_PTR)((WORD)(i)))) + +typedef struct _WIN32_FIND_DATAA { + DWORD dwFileAttributes; FILETIME ftCreationTime, ftLastAccessTime, ftLastWriteTime; + DWORD nFileSizeHigh, nFileSizeLow, dwReserved0, dwReserved1; + CHAR cFileName[MAX_PATH]; CHAR cAlternateFileName[14]; +} WIN32_FIND_DATAA, WIN32_FIND_DATA, *LPWIN32_FIND_DATAA, *LPWIN32_FIND_DATA; + +typedef struct tagDRAWITEMSTRUCT { + UINT CtlType, CtlID; UINT itemID, itemAction, itemState; + HWND hwndItem; HDC hDC; RECT rcItem; ULONG_PTR itemData; +} DRAWITEMSTRUCT, *LPDRAWITEMSTRUCT; + +typedef struct tagMEASUREITEMSTRUCT { UINT CtlType, CtlID, itemID, itemWidth, itemHeight; ULONG_PTR itemData; } MEASUREITEMSTRUCT, *LPMEASUREITEMSTRUCT; + +#pragma pack(push, 1) +typedef struct tagBITMAPFILEHEADER { WORD bfType; DWORD bfSize; WORD bfReserved1, bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER, *LPBITMAPFILEHEADER; +#pragma pack(pop) +typedef struct tagBITMAPINFOHEADER { + DWORD biSize; LONG biWidth, biHeight; WORD biPlanes, biBitCount; + DWORD biCompression, biSizeImage; LONG biXPelsPerMeter, biYPelsPerMeter; + DWORD biClrUsed, biClrImportant; +} BITMAPINFOHEADER, *LPBITMAPINFOHEADER; +typedef struct tagRGBQUAD { BYTE rgbBlue, rgbGreen, rgbRed, rgbReserved; } RGBQUAD; +typedef struct tagBITMAPINFO { BITMAPINFOHEADER bmiHeader; RGBQUAD bmiColors[1]; } BITMAPINFO, *LPBITMAPINFO; +typedef struct tagBITMAP { LONG bmType, bmWidth, bmHeight, bmWidthBytes; WORD bmPlanes, bmBitsPixel; LPVOID bmBits; } BITMAP; +typedef struct tagDIBSECTION { BITMAP dsBm; BITMAPINFOHEADER dsBmih; DWORD dsBitfields[3]; HANDLE dshSection; DWORD dsOffset; } DIBSECTION; +typedef struct _ICONINFO { BOOL fIcon; DWORD xHotspot, yHotspot; HBITMAP hbmMask, hbmColor; } ICONINFO; +typedef struct tagWNDCLASSA { UINT style; WNDPROC lpfnWndProc; int cbClsExtra, cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCSTR lpszMenuName, lpszClassName; } WNDCLASSA, WNDCLASS, *LPWNDCLASS; +typedef struct tagMSGBOXPARAMSA { UINT cbSize; HWND hwndOwner; HINSTANCE hInstance; LPCSTR lpszText, lpszCaption; DWORD dwStyle; LPCSTR lpszIcon; DWORD_PTR dwContextHelpId; void *lpfnMsgBoxCallback; DWORD dwLanguageId; } MSGBOXPARAMSA, MSGBOXPARAMS; +typedef struct _devicemodeA { CHAR dmDeviceName[32]; WORD dmSpecVersion, dmDriverVersion, dmSize, dmDriverExtra; DWORD dmFields; DWORD dmPelsWidth, dmPelsHeight, dmBitsPerPel, dmDisplayFrequency; } DEVMODEA, DEVMODE; +typedef struct _RTL_SRWLOCK { void *Ptr; } SRWLOCK; +OPENTS_SHIM_HANDLE(HRSRC); +OPENTS_SHIM_HANDLE(HIMAGELIST); +OPENTS_SHIM_HANDLE(HTREEITEM); +typedef struct _NMTREEVIEWA { void *opaque; } NMTREEVIEWA, *LPNMTREEVIEW; +typedef struct DLGTEMPLATE { DWORD style, dwExtendedStyle; WORD cdit; short x, y, cx, cy; } DLGTEMPLATE, *LPDLGTEMPLATE; +typedef const DLGTEMPLATE *LPCDLGTEMPLATE; diff --git a/spike/win32shim/windowsx.h b/spike/win32shim/windowsx.h new file mode 100644 index 000000000..720e64a9b --- /dev/null +++ b/spike/win32shim/windowsx.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/wingdi.h b/spike/win32shim/wingdi.h new file mode 100644 index 000000000..720e64a9b --- /dev/null +++ b/spike/win32shim/wingdi.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/winioctl.h b/spike/win32shim/winioctl.h new file mode 100644 index 000000000..720e64a9b --- /dev/null +++ b/spike/win32shim/winioctl.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/winnt.h b/spike/win32shim/winnt.h new file mode 100644 index 000000000..720e64a9b --- /dev/null +++ b/spike/win32shim/winnt.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/winres.h b/spike/win32shim/winres.h new file mode 100644 index 000000000..720e64a9b --- /dev/null +++ b/spike/win32shim/winres.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/winsock.h b/spike/win32shim/winsock.h new file mode 100644 index 000000000..498e0be92 --- /dev/null +++ b/spike/win32shim/winsock.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include +#include +#include +#include + +typedef int SOCKET; +#define INVALID_SOCKET (-1) +#define SOCKET_ERROR (-1) +typedef struct WSAData { WORD wVersion; WORD wHighVersion; char szDescription[257]; char szSystemStatus[129]; unsigned short iMaxSockets; unsigned short iMaxUdpDg; char *lpVendorInfo; } WSADATA, *LPWSADATA; diff --git a/spike/win32shim/winsock2.h b/spike/win32shim/winsock2.h new file mode 100644 index 000000000..498e0be92 --- /dev/null +++ b/spike/win32shim/winsock2.h @@ -0,0 +1,11 @@ +#pragma once +#include +#include +#include +#include +#include + +typedef int SOCKET; +#define INVALID_SOCKET (-1) +#define SOCKET_ERROR (-1) +typedef struct WSAData { WORD wVersion; WORD wHighVersion; char szDescription[257]; char szSystemStatus[129]; unsigned short iMaxSockets; unsigned short iMaxUdpDg; char *lpVendorInfo; } WSADATA, *LPWSADATA; diff --git a/spike/win32shim/winuser.h b/spike/win32shim/winuser.h new file mode 100644 index 000000000..720e64a9b --- /dev/null +++ b/spike/win32shim/winuser.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/spike/win32shim/ws2tcpip.h b/spike/win32shim/ws2tcpip.h new file mode 100644 index 000000000..e6faf3a8f --- /dev/null +++ b/spike/win32shim/ws2tcpip.h @@ -0,0 +1,2 @@ +#pragma once +#include From 8ae28965f3d8ba6935fd644d0277214580cdf917 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 19:47:56 +0100 Subject: [PATCH 033/179] spike: extend the Win32 shim to cover COM, GDI and CRT symbols --- spike/win32shim/intrin.h | 10 ++++++++++ spike/win32shim/windows.h | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 spike/win32shim/intrin.h diff --git a/spike/win32shim/intrin.h b/spike/win32shim/intrin.h new file mode 100644 index 000000000..a00449c23 --- /dev/null +++ b/spike/win32shim/intrin.h @@ -0,0 +1,10 @@ +#pragma once +#include + +// x86 intrinsics with no arm64 equivalent. The bodies are placeholders; the point is to +// let the rest of the translation unit be compiled and counted. +static inline unsigned int _rotl(unsigned int v, int s) { s &= 31; return s ? (v << s) | (v >> (32 - s)) : v; } +static inline unsigned int _rotr(unsigned int v, int s) { s &= 31; return s ? (v >> s) | (v << (32 - s)) : v; } +static inline unsigned long long __rdtsc() { return 0ULL; } +static inline void __cpuid(int regs[4], int) { regs[0] = regs[1] = regs[2] = regs[3] = 0; } +static inline void __cpuidex(int regs[4], int, int) { regs[0] = regs[1] = regs[2] = regs[3] = 0; } diff --git a/spike/win32shim/windows.h b/spike/win32shim/windows.h index 048df7a8e..9fa0d476d 100644 --- a/spike/win32shim/windows.h +++ b/spike/win32shim/windows.h @@ -171,3 +171,35 @@ OPENTS_SHIM_HANDLE(HTREEITEM); typedef struct _NMTREEVIEWA { void *opaque; } NMTREEVIEWA, *LPNMTREEVIEW; typedef struct DLGTEMPLATE { DWORD style, dwExtendedStyle; WORD cdit; short x, y, cx, cy; } DLGTEMPLATE, *LPDLGTEMPLATE; typedef const DLGTEMPLATE *LPCDLGTEMPLATE; + +// SAL-style annotations the tree spells out on parameters. +#define IN +#define OUT +#define OPTIONAL +#ifndef CONST +#define CONST const +#endif + +#define E_POINTER ((HRESULT)0x80004003L) +#define E_NOTIMPL ((HRESULT)0x80004001L) +#define CLSCTX_ALL 23 +#define CLSCTX_INPROC_SERVER 1 +#define IDOK 1 +#define IDCANCEL 2 +#define IDABORT 3 +#define IDRETRY 4 +#define IDIGNORE 5 +#define IDYES 6 +#define IDNO 7 +#define MB_OK 0x0 +#define MB_OKCANCEL 0x1 +#define MB_YESNO 0x4 +#define MB_ICONSTOP 0x10 +#define MB_ICONERROR 0x10 +#define MB_ICONQUESTION 0x20 +#define MB_ICONEXCLAMATION 0x30 +#define MB_ICONINFORMATION 0x40 +#define MB_SETFOREGROUND 0x10000 +#define MB_TASKMODAL 0x2000 +#define MB_SYSTEMMODAL 0x1000 +#define MB_APPLMODAL 0x0 From e503a02cae9c9f305d4f2ac4a8e7b06a5e6287ad Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 20:23:10 +0100 Subject: [PATCH 034/179] Pin the packed file and packet structures with size guards The isometric tile record needed explicit bitfield padding and the VQA structures needed fixed-width fields to hold the layout they are read with. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/audio/audiodecode.h | 5 +++++ code/connect.h | 6 ++++++ code/event.h | 12 ++++++++++++ code/iff.h | 11 +++++++++-- code/ipxgconn.h | 6 ++++++ code/isotype.cpp | 3 +++ code/isotype.h | 15 +++++++++++++++ code/mixfile.h | 9 +++++++-- code/netpacket.cpp | 6 ++++++ code/pcx.h | 8 ++++++++ code/rgb.h | 2 ++ code/session.h | 6 ++++++ code/shapeset.h | 3 +++ code/srfcache.cpp | 3 +++ code/target.h | 4 ++++ code/vqalib/cmp.h | 9 +++++---- code/vqalib/loader.cpp | 13 +++++++++---- code/vqalib/vqafile.h | 19 +++++++++++++------ code/vqalib/vqaplayp.h | 21 +++++++++++++++------ 19 files changed, 137 insertions(+), 24 deletions(-) diff --git a/code/audio/audiodecode.h b/code/audio/audiodecode.h index e4c393bfa..661924abf 100644 --- a/code/audio/audiodecode.h +++ b/code/audio/audiodecode.h @@ -40,6 +40,11 @@ struct AUDChunkHeaderType { }; #pragma pack(pop) +static_assert(sizeof(AUDHeaderType) == 12, "AUD file header layout changed"); +static_assert(offsetof(AUDHeaderType, Flags) == 10, "AUD file header layout changed"); +static_assert(sizeof(AUDChunkHeaderType) == 8, "AUD chunk header layout changed"); +static_assert(offsetof(AUDChunkHeaderType, Magic) == 4, "AUD chunk header layout changed"); + enum AudCodecType : uint8_t { AUD_CODEC_PCM = 0, diff --git a/code/connect.h b/code/connect.h index c4edbcb6e..6f76829cb 100644 --- a/code/connect.h +++ b/code/connect.h @@ -99,6 +99,9 @@ #include "combuf.h" #include "netadmit.h" +#include +#include + /* ********************************** Defines ********************************** */ @@ -120,6 +123,9 @@ struct CommHeaderType { }; #pragma pack(pop) +static_assert(sizeof(CommHeaderType) == 7, "Packet header layout changed"); +static_assert(offsetof(CommHeaderType, PacketID) == 3, "Packet header layout changed"); + /* ***************************** Class Declaration ***************************** */ diff --git a/code/event.h b/code/event.h index 5a3aaf575..7ed287000 100644 --- a/code/event.h +++ b/code/event.h @@ -267,3 +267,15 @@ class EventClass static char const * EventNames[LAST_EVENT]; }; #pragma pack(pop) + +// A whole event travels in a network packet and a replay file, so its record size and the +// position of every field the packet reader indexes are fixed by the format. The union is +// 36 bytes wide whatever a pointer measures, so the payload pointer in the Variable arm does +// not change the record size; it does move the Size field that follows it, and netpacket.cpp +// carries the guard for that. +static_assert(sizeof(EventClass) == 46, "Event record layout changed"); +static_assert(offsetof(EventClass, Frame) == 1, "Event record layout changed"); +static_assert(offsetof(EventClass, IsExecuted) == 5, "Event record layout changed"); +static_assert(offsetof(EventClass, ID) == 6, "Event record layout changed"); +static_assert(offsetof(EventClass, Data) == 10, "Event record layout changed"); +static_assert(sizeof(EventClass::Data) == 36, "Event record layout changed"); diff --git a/code/iff.h b/code/iff.h index fc4eec5c0..8cdb39882 100644 --- a/code/iff.h +++ b/code/iff.h @@ -31,6 +31,9 @@ #pragma once +#include +#include + class Buffer; #define LZW_SUPPORTED FALSE @@ -76,11 +79,15 @@ enum CompressionType { struct CompHeaderType { char Method; // Compression method (CompressionType). char pad; // Reserved pad byte (always 0). - int Size; // Size of the uncompressed data. - short Skip; // Number of bytes to skip before data. + std::int32_t Size; // Size of the uncompressed data. + std::int16_t Skip; // Number of bytes to skip before data. }; #pragma pack(pop) +static_assert(sizeof(CompHeaderType) == 8, "Compressed block header layout changed"); +static_assert(offsetof(CompHeaderType, Size) == 2, "Compressed block header layout changed"); +static_assert(offsetof(CompHeaderType, Skip) == 6, "Compressed block header layout changed"); + /*=========================================================================*/ /* The following prototypes are for the file: IFF.CPP */ diff --git a/code/ipxgconn.h b/code/ipxgconn.h index 7d53e4c26..eeb864c1b 100644 --- a/code/ipxgconn.h +++ b/code/ipxgconn.h @@ -74,6 +74,9 @@ #include "ipxconn.h" +#include +#include + #pragma pack(push,1) /* @@ -93,6 +96,9 @@ struct GlobalHeaderType { }; #pragma pack(pop) +static_assert(sizeof(GlobalHeaderType) == 9, "Global packet header layout changed"); +static_assert(offsetof(GlobalHeaderType, ProductID) == 7, "Global packet header layout changed"); + /* ***************************** Class Declaration ***************************** */ diff --git a/code/isotype.cpp b/code/isotype.cpp index 9c1fffcce..de040205f 100644 --- a/code/isotype.cpp +++ b/code/isotype.cpp @@ -1621,6 +1621,9 @@ struct IsoBlitState { }; #pragma pack(pop) +// Held only in memory, so the pointers may be any width, but the field count is fixed. +static_assert(sizeof(IsoBlitState) == 54 + 13 * sizeof(void *), "Isometric blit state layout changed"); + IsoBlitState IsoDrawData; unsigned short _iso_row_offsets[ISO_DRAW_WIDTH*ISO_DRAW_HEIGHT]; diff --git a/code/isotype.h b/code/isotype.h index 3e631371c..d1d0dcdb4 100644 --- a/code/isotype.h +++ b/code/isotype.h @@ -22,6 +22,7 @@ #include "isotype.hh" #include "land.hh" +#include #include class LightConvertClass; @@ -79,6 +80,10 @@ struct IsoTileRecord */ unsigned int IsRandomized:1; + // The flag bits fill a whole 32-bit word on disk. Without this padding a compiler that + // packs a following byte into the same word reads the rest of the record four bytes early. + unsigned int :29; + /* * This is the number of height levels this sub-tile lifts the cell it covers, so that a * tile laid across rising ground raises each of its cells by the right amount. @@ -106,6 +111,11 @@ struct IsoTileRecord }; #pragma pack() +static_assert(sizeof(IsoTileRecord) == 52, "Isometric tile record layout changed"); +static_assert(offsetof(IsoTileRecord, ExtraZOffset) == 16, "Isometric tile record layout changed"); +static_assert(offsetof(IsoTileRecord, Height) == 40, "Isometric tile record layout changed"); +static_assert(offsetof(IsoTileRecord, LowColor) == 43, "Isometric tile record layout changed"); + #pragma pack(4) class IsoTileSet { @@ -166,6 +176,9 @@ class IsoTileSet * the file as offsets from the start of the set and converted in place by the loader. * Reach a record through Fetch_Record_Pointer rather than through the array. */ + // The file stores one 32-bit offset per sub-tile here and the loader converts them to + // pointers in place, so a build whose pointers are not four bytes wide cannot read a + // tile set with more than one sub-tile. IsoTileRecord *Tiles[1]; @@ -179,6 +192,8 @@ class IsoTileSet }; #pragma pack() +static_assert(sizeof(IsoTileSet) == 16 + sizeof(IsoTileRecord *), "Isometric tile set header layout changed"); + /**************************************************************************** ** The tile type objects are controlled by this class. It specifies the form diff --git a/code/mixfile.h b/code/mixfile.h index b4d0c78eb..af937829f 100644 --- a/code/mixfile.h +++ b/code/mixfile.h @@ -20,6 +20,8 @@ #include "listnode.h" #include +#include +#include class PKey; @@ -79,11 +81,14 @@ class MixFileClass : public Node */ #pragma pack(1) struct FileHeader { - short count; - int size; + std::int16_t count; + std::int32_t size; }; #pragma pack() + static_assert(sizeof(FileHeader) == 6, "Mixfile header layout changed"); + static_assert(offsetof(FileHeader, size) == 2, "Mixfile header layout changed"); + /* ** The number of files within the mixfile. */ diff --git a/code/netpacket.cpp b/code/netpacket.cpp index 136309a3d..5a8b62c84 100644 --- a/code/netpacket.cpp +++ b/code/netpacket.cpp @@ -41,6 +41,12 @@ namespace NetPacket constexpr std::size_t MEGAMISSION_WHOM_OFFSET = offsetof(MegaMissionType, Whom); constexpr std::size_t MEGAMISSION_WHOM_SIZE = sizeof(std::declval().Whom); + // A packet places the ADDPLAYER payload size where the sender's own union put it, so + // the field follows the payload pointer that precedes it. The original 32-bit build + // wrote it at offset 4; a build whose pointers are wider reads it further along, and + // the two cannot exchange that event. + static_assert(VARIABLE_SIZE_OFFSET == sizeof(void *), "ADDPLAYER wire offset changed"); + static_assert(std::is_standard_layout_v); static_assert(std::is_trivially_copyable_v); static_assert(EventClass::LAST_EVENT <= (std::numeric_limits::max)()); diff --git a/code/pcx.h b/code/pcx.h index 36a252ba7..4b9b85415 100644 --- a/code/pcx.h +++ b/code/pcx.h @@ -36,6 +36,7 @@ #include "wwfile.h" #include +#include #pragma pack(push,1) struct RGB { @@ -67,6 +68,13 @@ struct PCX_HEADER }; #pragma pack(pop) +static_assert(sizeof(RGB) == 3, "PCX palette entry layout changed"); +static_assert(sizeof(PCX_HEADER) == 128, "PCX file header layout changed"); +static_assert(offsetof(PCX_HEADER, x) == 4, "PCX file header layout changed"); +static_assert(offsetof(PCX_HEADER, ega_palette) == 16, "PCX file header layout changed"); +static_assert(offsetof(PCX_HEADER, byte_per_line) == 66, "PCX file header layout changed"); +static_assert(offsetof(PCX_HEADER, filler) == 74, "PCX file header layout changed"); + bool Read_PCX_Size(FileClass & file, int & width, int & height); Surface * Read_PCX_File(FileClass & file_handle, PaletteClass * palette=NULL, void * buff=NULL, int size=0); bool Write_PCX_File(FileClass & file, Surface & pic, PaletteClass * palette); diff --git a/code/rgb.h b/code/rgb.h index 66f76ab6d..b17332ba7 100644 --- a/code/rgb.h +++ b/code/rgb.h @@ -53,6 +53,8 @@ struct RGBStruct }; #pragma pack() +static_assert(sizeof(RGBStruct) == 3, "Palette entry layout changed"); + /* ** Each color entry is represented by this class. It holds the values for the color diff --git a/code/session.h b/code/session.h index b29202d04..40aa14376 100644 --- a/code/session.h +++ b/code/session.h @@ -385,6 +385,12 @@ struct GlobalPacketType { }; #pragma pack() +// These three travel on the network, so their sizes are fixed by the packet format. +static_assert(sizeof(NodeNameType) == 132, "Lobby node layout changed"); +static_assert(sizeof(RemoteFileTransferType) == 487, "Scenario transfer packet layout changed"); +static_assert(sizeof(GlobalPacketType) == 1059, "Global packet layout changed"); +static_assert(offsetof(GlobalPacketType, Name) == 4, "Global packet layout changed"); + //........................................................................... // For finding sync bugs; filled in by the engine when certain conditions // are met; the pointers allow examination of objects in the debugger. diff --git a/code/shapeset.h b/code/shapeset.h index e74d3b442..b41c8d065 100644 --- a/code/shapeset.h +++ b/code/shapeset.h @@ -187,6 +187,9 @@ class ShapeSet }; #pragma pack(pop) +// A shape file is cast straight onto this header, so its four fields keep their file widths. +static_assert(sizeof(ShapeSet) == 8, "Shape file header layout changed"); + /*********************************************************************************************** * ShapeSet::Get_Data -- Fetches pointer to raw shape data. * diff --git a/code/srfcache.cpp b/code/srfcache.cpp index b62d1e4fc..36d0c09c6 100644 --- a/code/srfcache.cpp +++ b/code/srfcache.cpp @@ -37,6 +37,9 @@ struct MSBitmap }; #pragma pack(pop) +static_assert(sizeof(BITMAPFILEHEADER) == 14, "Bitmap file header layout changed"); +static_assert(offsetof(MSBitmap, info) == 14, "Bitmap image layout changed"); + /// /// Builds a 16-bit pixel from an RGB triple with the channels remapped for the diff --git a/code/target.h b/code/target.h index 79ff24d05..f85862729 100644 --- a/code/target.h +++ b/code/target.h @@ -186,6 +186,10 @@ class TargetClass : public xTargetClass }; #pragma pack(pop) +// A target rides inside the network event union, so it stays two 32-bit fields. +static_assert(sizeof(xTargetClass) == 8, "Target layout changed"); +static_assert(sizeof(TargetClass) == 8, "Target layout changed"); + template class IndexClass; extern IndexClass TargetTracker; diff --git a/code/vqalib/cmp.h b/code/vqalib/cmp.h index 7cdd27fd4..06c064ab9 100644 --- a/code/vqalib/cmp.h +++ b/code/vqalib/cmp.h @@ -19,9 +19,9 @@ #include #include -#if defined(__WATCOMC__) || defined(_MSC_VER) +// The ADPCM decoder state is packed on every compiler rather than only on the two the +// original build used, so one build cannot disagree with another about its layout. #pragma pack(push,1) -#endif struct _VQA_SOS_COMPRESS_INFO @@ -34,6 +34,9 @@ struct _VQA_SOS_COMPRESS_INFO typedef _VQA_SOS_COMPRESS_INFO VQASOS; +static_assert(sizeof(VQASOS) == 12, "ADPCM decoder state layout changed"); +static_assert(offsetof(VQASOS, dwPredicted2) == 6, "ADPCM decoder state layout changed"); + extern "C" { void __cdecl VQA_sosCODECInitStream(_VQA_SOS_COMPRESS_INFO *); void __cdecl VQA_sosCODECDecompressData(void *src, void *dst, unsigned short wBitSize, unsigned short wChannels, uint32_t dwUnCompSize, _VQA_SOS_COMPRESS_INFO *sosinfo); @@ -41,8 +44,6 @@ void __cdecl VQA_sosCODECDecompressData(void *src, void *dst, unsigned short wBi //#define VQA_sosCODECDecompressData sosCODECDecompressData -#if defined(__WATCOMC__) || defined(_MSC_VER) #pragma pack(pop) -#endif #endif //VQACMP_H diff --git a/code/vqalib/loader.cpp b/code/vqalib/loader.cpp index 63200ec69..6c29890fb 100644 --- a/code/vqalib/loader.cpp +++ b/code/vqalib/loader.cpp @@ -301,13 +301,16 @@ long VQA_LoadFrame(VQAHandleP *vqap, long flags) */ #pragma pack(push,1) struct VQASN2J { - short index; - long predicted; - short index2; - long predicted2; + std::int16_t index; + std::int32_t predicted; + std::int16_t index2; + std::int32_t predicted2; }; #pragma pack(pop) +static_assert(sizeof(VQASN2J) == 12, "SN2J chunk layout changed"); +static_assert(offsetof(VQASN2J, predicted2) == 8, "SN2J chunk layout changed"); + long VQA_LoadFrame_Internal(VQAHandleP *vqap, long flags) { @@ -3532,6 +3535,8 @@ long Load_SN2J(VQAHandleP *vqap, unsigned long iffsize) } data; #pragma pack(pop) + static_assert(sizeof(data) == 12, "SN2J chunk layout changed"); + #if(VQAVOC_ON && VQAAUDIO_ON) if (((config->OptionFlags & VQAOPTF_AUDIO) == 0) || (vqap->vocfh != -1) || (audio->Buffer == NULL)) { diff --git a/code/vqalib/vqafile.h b/code/vqalib/vqafile.h index 21e6bd07b..7b1a8927f 100644 --- a/code/vqalib/vqafile.h +++ b/code/vqalib/vqafile.h @@ -38,9 +38,12 @@ #include "iff.h" -#if defined(__WATCOMC__) || defined(_MSC_VER) +#include +#include + +// The structures below name bytes as a .vqa file stores them, so they are packed on every +// compiler rather than only on the two that the original build used. #pragma pack(push,1) -#endif /*--------------------------------------------------------------------------- * STRUCTURE DEFINITIONS AND RELATED DEFINES. @@ -99,7 +102,7 @@ typedef struct _VQAHeader { * expanded size when it is zero, so an old movie that leaves it blank * still allocates correctly. */ - unsigned long MaxCBSize; + std::uint32_t MaxCBSize; /* * Bytes of audio that must be loaded ahead of a seek target to prime the @@ -107,9 +110,15 @@ typedef struct _VQAHeader { * how many frames early to start reading. When the movie carries no * VQAHDF_SNDJUMP flag and this is zero, half a second is assumed. */ - unsigned long AudioPreload; + std::uint32_t AudioPreload; } VQAHeader; +// The VQHD chunk is 42 bytes on disk. MaxCBSize and AudioPreload were written as a 32-bit +// long by the original 32-bit build, so they stay 32 bits wide here. +static_assert(sizeof(VQAHeader) == 42, "VQHD chunk layout changed"); +static_assert(offsetof(VQAHeader, MaxCBSize) == 34, "VQHD chunk layout changed"); +static_assert(offsetof(VQAHeader, AudioPreload) == 38, "VQHD chunk layout changed"); + /* Version type. */ #define VQAHD_VER1 1 #define VQAHD_VER2 2 @@ -236,9 +245,7 @@ typedef struct _VQAHeader { #define ID_VPKZ MAKE_ID('V','P','K','Z') #define ID_VPDZ MAKE_ID('V','P','D','Z') -#if defined(__WATCOMC__) || defined(_MSC_VER) #pragma pack(pop) -#endif #endif /* VQAFILE_H */ diff --git a/code/vqalib/vqaplayp.h b/code/vqalib/vqaplayp.h index 655ae9b98..f156a42c0 100644 --- a/code/vqalib/vqaplayp.h +++ b/code/vqalib/vqaplayp.h @@ -108,10 +108,15 @@ extern char ReqTag[]; * size - Size of chunk. */ typedef struct _ChunkHeader { - unsigned long id; - unsigned long size; + std::uint32_t id; + std::uint32_t size; } ChunkHeader; +// The loader reads a chunk header straight off the file, so its two fields stay the 32-bit +// longs the format was written with. +static_assert(sizeof(ChunkHeader) == 8, "IFF chunk header layout changed"); +static_assert(offsetof(ChunkHeader, size) == 4, "IFF chunk header layout changed"); + /* ZAPHeader: ZAP audio compression header. NOTE: If the uncompressed size * and the compressed size are equal then the audio frame is RAW @@ -121,15 +126,19 @@ typedef struct _ChunkHeader { * CompSize - Compressed size in bytes. */ typedef struct _ZAPHeader { - unsigned short UnCompSize; - unsigned short CompSize; + std::uint16_t UnCompSize; + std::uint16_t CompSize; } ZAPHeader; +static_assert(sizeof(ZAPHeader) == 4, "ZAP audio header layout changed"); + typedef struct _VQAClipper { - unsigned long Width; - unsigned long Height; + std::uint32_t Width; + std::uint32_t Height; } VQAClipper; +static_assert(sizeof(VQAClipper) == 8, "CLIP chunk layout changed"); + /* VQACBNode: A circular list of codebook buffers, used by the load task. * If the data is compressed, it is loaded into the end of the From dfa25a0ec94ce403717215c378b6243c7d461ec3 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 20:25:59 +0100 Subject: [PATCH 035/179] Keep the Windows defines off a native build Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/CMakeLists.txt | 9 +++++++-- code/dropship.cpp | 2 +- code/vqalib/audio.cpp | 4 ++-- spike/win32shim/intrin.h | 3 +-- spike/win32shim/utime.h | 1 + 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index aa995e70d..f1e93a057 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -187,11 +187,16 @@ endif() message(STATUS "${PROJECT_NAME}: Adding compilier definitions...") target_compile_definitions(OpenTS PRIVATE - WIN32 - _WINDOWS NOMINMAX ) +if(WIN32) + target_compile_definitions(OpenTS PRIVATE + WIN32 + _WINDOWS + ) +endif() + # # --------------------------------------------------------- # Windows libraries and linker options diff --git a/code/dropship.cpp b/code/dropship.cpp index 6ab951029..b4e02ed6e 100644 --- a/code/dropship.cpp +++ b/code/dropship.cpp @@ -868,7 +868,7 @@ void Dropship_Screen(void) --j; } - int alpha = std::min(255ul, (_dissolve_rate * Host_Milliseconds() - _dissolve_rate * effect->StartTime) / _dissolve_scale); + int alpha = std::min(255u, (_dissolve_rate * Host_Milliseconds() - _dissolve_rate * effect->StartTime) / _dissolve_scale); if (alpha != effect->Alpha || overlap_drawn) { effect->Alpha = alpha; diff --git a/code/vqalib/audio.cpp b/code/vqalib/audio.cpp index bef6f5520..7d211f6fd 100644 --- a/code/vqalib/audio.cpp +++ b/code/vqalib/audio.cpp @@ -131,8 +131,8 @@ long VQA_OpenAudio(VQAHandleP *vqap) params.SampleRate = vqap->SampleRate; params.Channels = vqap->Channels; params.BitsPerSample = vqap->BitsPerSample; - params.Callback1 = VQA_AudioFillCallback; - params.Callback2 = VQA_AudioDoneCallback; + params.Callback1 = (void *)VQA_AudioFillCallback; + params.Callback2 = (void *)VQA_AudioDoneCallback; rc = vqap->Config.AudioHandler((VQAHandle *)vqap, VQAAUDIO_OPEN, ¶ms, sizeof(params)); if (rc >= VQAERR_OK || rc == VQAERR_NONE) { diff --git a/spike/win32shim/intrin.h b/spike/win32shim/intrin.h index a00449c23..fccc81c79 100644 --- a/spike/win32shim/intrin.h +++ b/spike/win32shim/intrin.h @@ -3,8 +3,7 @@ // x86 intrinsics with no arm64 equivalent. The bodies are placeholders; the point is to // let the rest of the translation unit be compiled and counted. -static inline unsigned int _rotl(unsigned int v, int s) { s &= 31; return s ? (v << s) | (v >> (32 - s)) : v; } -static inline unsigned int _rotr(unsigned int v, int s) { s &= 31; return s ? (v >> s) | (v << (32 - s)) : v; } +// clang supplies _rotl and _rotr as builtins under -fms-extensions. static inline unsigned long long __rdtsc() { return 0ULL; } static inline void __cpuid(int regs[4], int) { regs[0] = regs[1] = regs[2] = regs[3] = 0; } static inline void __cpuidex(int regs[4], int, int) { regs[0] = regs[1] = regs[2] = regs[3] = 0; } diff --git a/spike/win32shim/utime.h b/spike/win32shim/utime.h index 607852b09..3b2a750e0 100644 --- a/spike/win32shim/utime.h +++ b/spike/win32shim/utime.h @@ -1,3 +1,4 @@ #pragma once +#include_next #include #include From 8217b497928fe343eecbc3a60ebdb1cd5d6e514f Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 20:27:40 +0100 Subject: [PATCH 036/179] State the width of the swizzle identity in a save record Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/SAVE-FORMAT.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/SAVE-FORMAT.md b/docs/SAVE-FORMAT.md index b7326224a..ea9ddc950 100644 --- a/docs/SAVE-FORMAT.md +++ b/docs/SAVE-FORMAT.md @@ -73,6 +73,11 @@ names them. An object record is: | 4 | Length of the record body | | | The body: the swizzle identity, then the members the class's `Serialize` names | +The swizzle identity is the object's own address, written at the width a +pointer has in the build that wrote it. Every record therefore grows by four +bytes in a 64-bit build, and a save does not cross between builds of different +pointer widths. + The class identifier is the `ClassID` the object's `Class_ID` reports, the same one registered in `code/startup.cpp` and, for a locomotor, named by the `Locomotor=` key. Its sixteen bytes are those of the COM class identifier the From bb37e30fe11e46ffdcdf052aede4feba76b09fb1 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 20:33:59 +0100 Subject: [PATCH 037/179] Fix the ADDPLAYER payload size at its wire offset Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/event.cpp | 1 + code/event.h | 13 ++++++++----- code/netpacket.cpp | 9 ++++----- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/code/event.cpp b/code/event.cpp index de3ffbba6..62a6d2164 100644 --- a/code/event.cpp +++ b/code/event.cpp @@ -639,6 +639,7 @@ EventClass::EventClass(int index, unsigned char type, void * ptr, unsigned int s if (index >= 0) { ID = index; Type = type; + Data.Variable.Slot = 0; Data.Variable.Pointer = ptr; Data.Variable.Size = size; Frame = ::Frame; diff --git a/code/event.h b/code/event.h index 7ed287000..ca3d4bb2e 100644 --- a/code/event.h +++ b/code/event.h @@ -42,6 +42,7 @@ #include "mph.hh" #include "speed.hh" +#include #include /* @@ -213,8 +214,9 @@ class EventClass ** bloating the size of this union (and thus all other event types). */ struct { + std::uint32_t Slot; + std::uint32_t Size; void * Pointer; - unsigned int Size; } Variable; // @@ -269,13 +271,14 @@ class EventClass #pragma pack(pop) // A whole event travels in a network packet and a replay file, so its record size and the -// position of every field the packet reader indexes are fixed by the format. The union is -// 36 bytes wide whatever a pointer measures, so the payload pointer in the Variable arm does -// not change the record size; it does move the Size field that follows it, and netpacket.cpp -// carries the guard for that. +// position of every field the packet reader indexes are fixed by the format. The Variable arm +// keeps the four-byte payload slot the record has always reserved and carries the live pointer +// past the bytes ADDPLAYER puts on the wire, so Size stays at offset 4 at every pointer width. static_assert(sizeof(EventClass) == 46, "Event record layout changed"); static_assert(offsetof(EventClass, Frame) == 1, "Event record layout changed"); static_assert(offsetof(EventClass, IsExecuted) == 5, "Event record layout changed"); static_assert(offsetof(EventClass, ID) == 6, "Event record layout changed"); static_assert(offsetof(EventClass, Data) == 10, "Event record layout changed"); static_assert(sizeof(EventClass::Data) == 36, "Event record layout changed"); +static_assert(offsetof(EventClass, Data.Variable.Size) == 14, "ADDPLAYER wire offset changed"); +static_assert(offsetof(EventClass, Data.Variable.Pointer) == 18, "ADDPLAYER wire offset changed"); diff --git a/code/netpacket.cpp b/code/netpacket.cpp index 5a8b62c84..e521b1c76 100644 --- a/code/netpacket.cpp +++ b/code/netpacket.cpp @@ -41,11 +41,10 @@ namespace NetPacket constexpr std::size_t MEGAMISSION_WHOM_OFFSET = offsetof(MegaMissionType, Whom); constexpr std::size_t MEGAMISSION_WHOM_SIZE = sizeof(std::declval().Whom); - // A packet places the ADDPLAYER payload size where the sender's own union put it, so - // the field follows the payload pointer that precedes it. The original 32-bit build - // wrote it at offset 4; a build whose pointers are wider reads it further along, and - // the two cannot exchange that event. - static_assert(VARIABLE_SIZE_OFFSET == sizeof(void *), "ADDPLAYER wire offset changed"); + // A packet places the ADDPLAYER payload size where the sender's own union put it, so the + // offset belongs to the wire format rather than to the build. The Variable arm reserves a + // four-byte payload slot ahead of it at every pointer width. + static_assert(VARIABLE_SIZE_OFFSET == 4, "ADDPLAYER wire offset changed"); static_assert(std::is_standard_layout_v); static_assert(std::is_trivially_copyable_v); From d29e555457b123f95db34e26d668d9bcbbdd270b Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 20:37:07 +0100 Subject: [PATCH 038/179] Resolve isometric sub-tile offsets on access The tile set file stores a 32-bit offset per sub-tile. Keeping the array as offsets rather than swizzling it to pointers in place keeps the stride at four bytes at every pointer width. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/isotile.cpp | 7 ++++--- code/isotype.cpp | 39 ++++++++------------------------------- code/isotype.h | 33 +++++++++++++++++++++++---------- 3 files changed, 35 insertions(+), 44 deletions(-) diff --git a/code/isotile.cpp b/code/isotile.cpp index 92ad2b072..8f67c3d9d 100644 --- a/code/isotile.cpp +++ b/code/isotile.cpp @@ -46,12 +46,13 @@ bool IsometricTileClass::Mark(MarkType mark) if (Map.In_Radar(cell)) { CellClass *cptr = &Map[cell]; int subtile = Class->SubTile_Index(x, y); - if (set->Tiles[subtile] != NULL) { + IsoTileRecord const * record = set->Fetch_Record_Pointer_Unsafe(subtile); + if (record != NULL) { if (mark == MARK_UP) { if (cptr->ITType == Class->HeapID && cptr->SubTile == subtile) { cptr->ITType = TILE_NONE; cptr->SubTile = 0; - cptr->Height -= set->Tiles[subtile]->Height; + cptr->Height -= record->Height; } } else if (mark == MARK_DOWN || mark == MARK_DOWN_FORCED) { if (Class->HeapID == TILE_CLEAR) { @@ -113,7 +114,7 @@ bool IsometricTileClass::Mark(MarkType mark) } cptr->Overlay = OVERLAY_NONE; cptr->OverlayData = 0; - cptr->Height += set->Tiles[subtile]->Height; + cptr->Height += record->Height; cptr->Fixup_LAT(); cptr->Adjacent_Cell(FACING_N).Fixup_LAT(); cptr->Adjacent_Cell(FACING_E).Fixup_LAT(); diff --git a/code/isotype.cpp b/code/isotype.cpp index de040205f..e7da02947 100644 --- a/code/isotype.cpp +++ b/code/isotype.cpp @@ -379,7 +379,7 @@ bool IsometricTileTypeClass::Is_Tile_Index_Valid(int tile, bool load) } IsoTileSet const * tileset = (IsoTileSet const *)Get_Image_Data(); - if (tileset != NULL && tile < (tileset->Tile_Count()) && tileset->Tiles[tile] != NULL) { + if (tileset != NULL && tile < (tileset->Tile_Count()) && tileset->Fetch_Record_Pointer_Unsafe(tile) != NULL) { return(true); } return(false); @@ -472,11 +472,10 @@ Cell const * IsometricTileTypeClass::Occupy_List(bool placement) const Cell * ptr; IsoTileSet const * tileset = (IsoTileSet const *)Get_Image_Data(); - unsigned int const * map = (unsigned int const *)tileset->Tiles; ptr = &_occupy[0]; for (int index = 0; index < Width * Height; index++) { - if (*map++ != NULL) { + if (tileset->Fetch_Record_Pointer_Unsafe(index) != NULL) { *ptr++ = Cell(index % Width, index / Width); } } @@ -1118,12 +1117,6 @@ void IsometricTileTypeClass::Read_Control_File(TheaterType theater, bool from_cc if (mixfile_set != NULL) { tile->Width = (unsigned char)mixfile_set->MapWidth; tile->Height = (unsigned char)mixfile_set->MapHeight; - for (j = 0; j < mixfile_set->Tile_Count(); j++) { - IsoTileRecord ** record = &mixfile_set->Tiles[j]; - if (*record != 0 && (unsigned int)*record < (unsigned int)mixfile_set) { - *record = (IsoTileRecord *)((unsigned int)mixfile_set + (unsigned int)*record); - } - } tile->Build_Preview_Tiles(); } else { if (tile_count == 0) { @@ -1180,7 +1173,7 @@ void IsometricTileTypeClass::Read_Control_File(TheaterType theater, bool from_cc if (data.IsIceGrowth) { if (Ice1Set != ISOTILE_INVALID) { for (k = ICE_EDGE; k < ICE1_COUNT; k++) { - IsoTileRecord * record = ((IsoTileSet *)IsometricTileTypes[Ice1Set + k]->Get_Image_Data())->Tiles[0]; + IsoTileRecord * record = ((IsoTileSet *)IsometricTileTypes[Ice1Set + k]->Get_Image_Data())->Fetch_Record_Pointer_Unsafe(0); if (record) { record->TileType = 9; } @@ -1188,7 +1181,7 @@ void IsometricTileTypeClass::Read_Control_File(TheaterType theater, bool from_cc } if (Ice2Set != ISOTILE_INVALID) { for (k = ICE_EDGE; k < ICE2_COUNT; k++) { - IsoTileRecord * record = (IsoTileRecord *)((IsoTileSet *)(IsometricTileTypes[Ice2Set + k])->Get_Image_Data())->Tiles[0]; + IsoTileRecord * record = ((IsoTileSet *)(IsometricTileTypes[Ice2Set + k])->Get_Image_Data())->Fetch_Record_Pointer_Unsafe(0); if (record) { record->TileType = 9; } @@ -1196,7 +1189,7 @@ void IsometricTileTypeClass::Read_Control_File(TheaterType theater, bool from_cc } if (Ice3Set != ISOTILE_INVALID) { for (k = ICE_EDGE; k < ICE3_COUNT; k++) { - IsoTileRecord * record = (IsoTileRecord *)((IsoTileSet *)(IsometricTileTypes[Ice3Set + k])->Get_Image_Data())->Tiles[0]; + IsoTileRecord * record = ((IsoTileSet *)(IsometricTileTypes[Ice3Set + k])->Get_Image_Data())->Fetch_Record_Pointer_Unsafe(0); if (record) { record->TileType = 9; } @@ -1308,22 +1301,6 @@ int IsometricTileTypeClass::Load_Tile_Data(void) Height = ((unsigned char)tileset->Map_Height()); Width = ((unsigned char)tileset->Map_Width()); - /// Fixup pointers to point to actual memory - for (int i = 0; i < (Width * Height); i++) { - - if (tileset->Tiles[i] != NULL) { - unsigned char * ptr = (unsigned char *)ImageData; - /* - * Only fix up pointers that have not been converted already. The file - * has just been read fresh, so none of them ever have been. - */ - if ((void *)tileset->Tiles[i] < ptr) { - tileset->Tiles[i] = (IsoTileRecord *)(ptr + (unsigned int)tileset->Tiles[i]); - } - } - - } - Build_Preview_Tiles(); return(size); @@ -2649,7 +2626,7 @@ int IsometricTileTypeClass::Get_Y_Offset(int tile) { IsoTileSet const * tileset = (IsoTileSet const *)Get_Image_Data(); if (tileset != NULL && tile < tileset->Tile_Count()) { - IsoTileRecord const * record = tileset->Tiles[tile]; + IsoTileRecord const * record = tileset->Fetch_Record_Pointer_Unsafe(tile); if (record != NULL) { if ((record->IsHasExtraData) != 0) { return(record->ExtraY - record->Y); @@ -2895,9 +2872,9 @@ void IsometricTileTypeClass::Build_Preview_Tiles(void) PreviewTiles.Clear(); for (i = 0; i < tileset->Tile_Count(); i++) { - if (tileset->Tiles[i] != NULL) { + IsoTileRecord const * record = tileset->Fetch_Record_Pointer_Unsafe(i); + if (record != NULL) { unsigned short * buffer = new unsigned short[24 + 2]; - IsoTileRecord const * record = tileset->Tiles[i]; PreviewTiles.Add(buffer); diff --git a/code/isotype.h b/code/isotype.h index d1d0dcdb4..e1484c334 100644 --- a/code/isotype.h +++ b/code/isotype.h @@ -23,6 +23,7 @@ #include "land.hh" #include +#include #include class LightConvertClass; @@ -127,11 +128,25 @@ class IsoTileSet IsoTileRecord const * Fetch_Record_Pointer(int index) const { - return(Tiles[index % Tile_Count()]); + return(Fetch_Record_Pointer_Unsafe(index % Tile_Count())); } IsoTileRecord const * Fetch_Record_Pointer_Unsafe(int index) const { - return(Tiles[index]); + return(const_cast(this)->Fetch_Record_Pointer_Unsafe(index)); + } + IsoTileRecord * Fetch_Record_Pointer(int index) + { + return(Fetch_Record_Pointer_Unsafe(index % Tile_Count())); + } + IsoTileRecord * Fetch_Record_Pointer_Unsafe(int index) + { + static_assert(sizeof(IsoTileSet) == 20, "Isometric tile set header layout changed"); + static_assert(offsetof(IsoTileSet, TileOffsets) == 16, "Isometric tile set header layout changed"); + std::uint32_t const offset = TileOffsets[index]; + if (offset == 0) { + return(NULL); + } + return(reinterpret_cast(reinterpret_cast(this) + offset)); } /* @@ -172,14 +187,12 @@ class IsoTileSet int Height; /* - * This is the first of the tile set's image record pointers, one per sub-tile, held in - * the file as offsets from the start of the set and converted in place by the loader. - * Reach a record through Fetch_Record_Pointer rather than through the array. + * This is the first of the tile set's image record offsets, one per sub-tile, each + * counted in bytes from the start of the set. The file supplies them and they stay as + * they are; Fetch_Record_Pointer resolves one to an address on access, so the array + * stride is four bytes at every pointer width. Reach a record through that accessor. */ - // The file stores one 32-bit offset per sub-tile here and the loader converts them to - // pointers in place, so a build whose pointers are not four bytes wide cannot read a - // tile set with more than one sub-tile. - IsoTileRecord *Tiles[1]; + std::uint32_t TileOffsets[1]; /* @@ -192,7 +205,7 @@ class IsoTileSet }; #pragma pack() -static_assert(sizeof(IsoTileSet) == 16 + sizeof(IsoTileRecord *), "Isometric tile set header layout changed"); +static_assert(sizeof(IsoTileSet) == 20, "Isometric tile set header layout changed"); /**************************************************************************** From 8a855466c01bcf248086c170fd0968a13774fca9 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 20:41:58 +0100 Subject: [PATCH 039/179] Supply the MSVC runtime spellings a native build lacks Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/always.h | 40 ++++++++++++++++++++++++++++++++++++++++ code/autosave.cpp | 2 ++ code/scenfile.cpp | 2 ++ code/spawnerconfig.cpp | 2 ++ code/spawnhouse.cpp | 2 ++ code/visualc.h | 8 ++++++++ code/vocini.cpp | 2 ++ code/vqalib/dstream.cpp | 14 ++++++++++++++ 8 files changed, 72 insertions(+) diff --git a/code/always.h b/code/always.h index ae7cc1631..1f3f5fed4 100644 --- a/code/always.h +++ b/code/always.h @@ -108,12 +108,52 @@ #define stricmp strcasecmp #define _stricmp strcasecmp #define strnicmp strncasecmp +#define _strnicmp strncasecmp #define memicmp strncasecmp +#define _memicmp strncasecmp #define __cdecl +#include #include #include #include +#include + +/// The USER32 formatter the game uses for short strings. Windows caps its output at 1024 +/// characters, so the substitute caps it at the same place rather than at the buffer. +inline static int wvsprintf(char* buffer, const char* format, va_list args) +{ + return(vsnprintf(buffer, 1024, format, args)); +} + +inline static int wsprintf(char* buffer, const char* format, ...) +{ + va_list args; + va_start(args, format); + int const result = wvsprintf(buffer, format, args); + va_end(args); + return(result); +} + +inline static long filelength(int handle) +{ + off_t const here = lseek(handle, 0, SEEK_CUR); + if (here < 0) { + return(-1); + } + off_t const end = lseek(handle, 0, SEEK_END); + lseek(handle, here, SEEK_SET); + return((long)end); +} + +inline static int freopen_s(FILE** stream, const char* path, const char* mode, FILE* old) +{ + if (stream == NULL) { + return(-1); + } + *stream = freopen(path, mode, old); + return(*stream != NULL ? 0 : -1); +} inline static void _makepath(char* path, const char* drive, const char* dir, const char* fname, const char* ext) { diff --git a/code/autosave.cpp b/code/autosave.cpp index 5aa53d2f0..c38f85f04 100644 --- a/code/autosave.cpp +++ b/code/autosave.cpp @@ -7,6 +7,8 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ +#include "always.h" + #include "autosave.h" #include diff --git a/code/scenfile.cpp b/code/scenfile.cpp index b5bef6d6e..73c6ad9e1 100644 --- a/code/scenfile.cpp +++ b/code/scenfile.cpp @@ -7,6 +7,8 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ +#include "always.h" + #include "scenfile.h" #include diff --git a/code/spawnerconfig.cpp b/code/spawnerconfig.cpp index 3b16f7d5d..1e33ec1d0 100644 --- a/code/spawnerconfig.cpp +++ b/code/spawnerconfig.cpp @@ -8,6 +8,8 @@ ******************************************************************************/ +#include "always.h" + #include "spawnerconfig.h" #include "crc.h" diff --git a/code/spawnhouse.cpp b/code/spawnhouse.cpp index 200b140ea..d6019e114 100644 --- a/code/spawnhouse.cpp +++ b/code/spawnhouse.cpp @@ -8,6 +8,8 @@ ******************************************************************************/ +#include "always.h" + #include "spawnhouse.h" #include diff --git a/code/visualc.h b/code/visualc.h index ca9c57a9e..f5408a960 100644 --- a/code/visualc.h +++ b/code/visualc.h @@ -118,6 +118,14 @@ #endif +// Spellings no C library supplies, so they are needed whatever the compiler. +#ifndef M_SQRT_2 +#define M_SQRT_2 0.707106781186547524401 +#endif +#ifndef M_FPI +#define M_FPI 3.141592654f +#endif + /* ** Macros to convert between degrees and radians diff --git a/code/vocini.cpp b/code/vocini.cpp index 0854e3350..2ae9215a2 100644 --- a/code/vocini.cpp +++ b/code/vocini.cpp @@ -11,6 +11,8 @@ // type. The grammar follows Yuri's Revenge, with defaults that keep the // shipped Tiberian Sun files playing as they did. +#include "always.h" + #include "voc.h" #include "dbgprint.h" diff --git a/code/vqalib/dstream.cpp b/code/vqalib/dstream.cpp index 03003fe00..09e5668e0 100644 --- a/code/vqalib/dstream.cpp +++ b/code/vqalib/dstream.cpp @@ -56,6 +56,20 @@ #endif #include +#ifndef _WIN32 +/// The MSVC runtime reports a descriptor's length without disturbing its position. +static long filelength(int handle) +{ + off_t const here = lseek(handle, 0, SEEK_CUR); + if (here < 0) { + return(-1); + } + off_t const end = lseek(handle, 0, SEEK_END); + lseek(handle, here, SEEK_SET); + return((long)end); +} +#endif + intptr_t __cdecl Disk_VQA_Stream_Handler(VQAHandle *vqa, long action, void *buffer, long nbytes) { From b661033e832dc83ee05d9c33c7cc6900e3fcea64 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 20:42:50 +0100 Subject: [PATCH 040/179] Wait and measure elapsed time through the host clock Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/actionline.cpp | 3 ++- code/desyncdlg.cpp | 3 ++- code/hostclock.h | 12 ++++++++++++ code/mainloop.cpp | 16 ++++++++-------- code/mpu.cpp | 23 +++++++++++++++++++++++ code/msengine.cpp | 5 +++-- code/savemgr.cpp | 3 ++- code/score.cpp | 5 +++-- code/session.cpp | 2 +- code/taction.cpp | 3 ++- code/vqa.cpp | 3 ++- 11 files changed, 60 insertions(+), 18 deletions(-) diff --git a/code/actionline.cpp b/code/actionline.cpp index 6f4b55a34..c0535dbc3 100644 --- a/code/actionline.cpp +++ b/code/actionline.cpp @@ -7,6 +7,7 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ +#include "hostclock.h" #include "always.h" #include "actionline.h" @@ -74,7 +75,7 @@ void Draw_Action_Line_Segment(Surface & surface, Coord const & start, Coord cons for (int index = 0; index < PATTERN_LENGTH; index++) { pattern[index] = ((index / dash_length) & 1) == 0; } - int offset = (dash_rate > 0) ? ((-(int)timeGetTime() / dash_rate) & (PATTERN_LENGTH - 1)) : (7 * Frame % PATTERN_LENGTH); + int offset = (dash_rate > 0) ? ((-(int)Host_Milliseconds() / dash_rate) & (PATTERN_LENGTH - 1)) : (7 * Frame % PATTERN_LENGTH); // A thick line is two rows; its shadow sits below both. int rows = style.IsThick ? 2 : 1; diff --git a/code/desyncdlg.cpp b/code/desyncdlg.cpp index e35413d10..a2719ca44 100644 --- a/code/desyncdlg.cpp +++ b/code/desyncdlg.cpp @@ -7,6 +7,7 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ +#include "hostclock.h" #include "always.h" #include "desyncdlg.h" @@ -133,7 +134,7 @@ DesyncDialogClass::OutcomeType DesyncDialogClass::Run(void) } Decision = 0; - Sleep(10); + Host_Sleep(10); } } diff --git a/code/hostclock.h b/code/hostclock.h index 07ddae781..396d494a3 100644 --- a/code/hostclock.h +++ b/code/hostclock.h @@ -11,6 +11,7 @@ #include #include +#include // The engine's coarse clock, in milliseconds from a clock that only ever moves forward. // Every caller measures an interval with it and none depends on where it starts. The @@ -22,3 +23,14 @@ inline uint32_t Host_Milliseconds(void) std::chrono::steady_clock::now().time_since_epoch()).count()); } + +// Yields the processor for at least the requested number of milliseconds. A zero wait gives +// up the rest of the current time slice without pausing, which is what the frame loops use. +inline void Host_Sleep(unsigned int milliseconds) +{ + if (milliseconds == 0) { + std::this_thread::yield(); + return; + } + std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); +} diff --git a/code/mainloop.cpp b/code/mainloop.cpp index 90a102dd4..7ec6ef756 100644 --- a/code/mainloop.cpp +++ b/code/mainloop.cpp @@ -161,10 +161,10 @@ static void Check_For_Focus_Loss(void) { while (!GameInFocus) { if (Session.Type == GAME_NORMAL || Session.Type == GAME_SKIRMISH) { - Sleep(500); + Host_Sleep(500); Windows_Message_Handler(); } else { - Sleep(10); + Host_Sleep(10); Windows_Message_Handler(); break; } @@ -209,10 +209,10 @@ bool Main_Loop(void) #else while (!GameInFocus) { if (Session.Type == GAME_NORMAL || Session.Type == GAME_SKIRMISH) { - Sleep(500); + Host_Sleep(500); Windows_Message_Handler(); } else { - Sleep(10); + Host_Sleep(10); Windows_Message_Handler(); break; } @@ -599,13 +599,13 @@ void Sync_Delay(void) TacticalMap->AI(); Map.Render(); } else { - Sleep(0); + Host_Sleep(0); } if (!NetFrameTimer()) { break; } } - Sleep(0); + Host_Sleep(0); } } else { while (FrameTimer) { @@ -622,9 +622,9 @@ void Sync_Delay(void) } } if (GameInFocus || (Session.Type != GAME_NORMAL && Session.Type != GAME_SKIRMISH)) { - Sleep(0); + Host_Sleep(0); } else { - Sleep(16 * FrameTimer); + Host_Sleep(16 * FrameTimer); } } } diff --git a/code/mpu.cpp b/code/mpu.cpp index 316273cb5..67383d095 100644 --- a/code/mpu.cpp +++ b/code/mpu.cpp @@ -39,6 +39,29 @@ #include #include +#ifndef _WIN32 +#include + +/// The monotonic clock stands in for the Windows performance counter. Both are read only as +/// a difference over a fixed frequency, so a nanosecond tick answers the same question. +static BOOL QueryPerformanceFrequency(LARGE_INTEGER* result) +{ + result->QuadPart = 1000000000LL; + return(TRUE); +} + +static BOOL QueryPerformanceCounter(LARGE_INTEGER* result) +{ + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) { + result->QuadPart = 0; + return(FALSE); + } + result->QuadPart = (LONGLONG)now.tv_sec * 1000000000LL + (LONGLONG)now.tv_nsec; + return(TRUE); +} +#endif + typedef union { LARGE_INTEGER LargeInt; struct QuadPart { diff --git a/code/msengine.cpp b/code/msengine.cpp index f5f835255..5d34ccb2c 100644 --- a/code/msengine.cpp +++ b/code/msengine.cpp @@ -7,6 +7,7 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ +#include "hostclock.h" #include "always.h" #include "msengine.h" @@ -377,7 +378,7 @@ void MSEngine::Wait_Delay(int delay) timer.Start(); } - Sleep(0); + Host_Sleep(0); } while (timer.Value() > 0); @@ -406,7 +407,7 @@ void MSEngine::Wait_For_Focus(void) while (!GameInFocus) { DebugString("MSEngine - Sleeping\n"); - Sleep(500); + Host_Sleep(500); Windows_Message_Handler(); } diff --git a/code/savemgr.cpp b/code/savemgr.cpp index ec62640cd..a509a99cd 100644 --- a/code/savemgr.cpp +++ b/code/savemgr.cpp @@ -7,6 +7,7 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ +#include "hostclock.h" #include "always.h" #include "savemgr.h" @@ -609,7 +610,7 @@ void SaveManagerClass::Process_Pending_Load_Game(void) OwnerDraw::Set_Custom_Message_Box_Text(dialog, buffer); } OwnerDraw::Dialog_Message_Handler(); - Sleep(10); + Host_Sleep(10); } if (dialog != 0) { diff --git a/code/score.cpp b/code/score.cpp index a1ddb96f4..e7c48104f 100644 --- a/code/score.cpp +++ b/code/score.cpp @@ -42,6 +42,7 @@ * ScoreClass::Pulse_Bar_Graph -- Pulses the bargraph color. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#include "hostclock.h" #include "always.h" #include "score.h" @@ -1058,7 +1059,7 @@ void ScoreClass::Call_Back_Delay(int time) cd.Start(); } - Sleep(0); + Host_Sleep(0); } while (cd > 0); @@ -1119,7 +1120,7 @@ void ScoreClass::Timing(void) } while (!GameInFocus) { - Sleep(500); + Host_Sleep(500); Windows_Message_Handler(); } diff --git a/code/session.cpp b/code/session.cpp index c6ca56f3b..93f2dc122 100644 --- a/code/session.cpp +++ b/code/session.cpp @@ -1226,7 +1226,7 @@ void SessionClass::Update_Progress(int percent) Call_Back(); while (Ipx.Global_Num_Send() > 5 && timer > 0) { - Sleep(20); + Host_Sleep(20); Windows_Message_Handler(); Call_Back(); } diff --git a/code/taction.cpp b/code/taction.cpp index 55623b3e8..fa9b68a7d 100644 --- a/code/taction.cpp +++ b/code/taction.cpp @@ -40,6 +40,7 @@ * ActionChoiceClass::Draw_It -- Display the action choice as part of a list box. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#include "hostclock.h" #include "always.h" #include "taction.h" @@ -2052,7 +2053,7 @@ bool TActionClass::TAction_ZOOM_IN(HouseClass * , ObjectClass * , TriggerClass * Map.Flag_To_Redraw(); Map.Render(); - Sleep(1000); + Host_Sleep(1000); return(true); } diff --git a/code/vqa.cpp b/code/vqa.cpp index 5687e4635..33041b0c5 100644 --- a/code/vqa.cpp +++ b/code/vqa.cpp @@ -11,6 +11,7 @@ * disclaimers apply; see LICENSE.md. ******************************************************************************/ +#include "hostclock.h" #include "always.h" #include "vqa.h" @@ -529,7 +530,7 @@ int VQAClass::Play_VQA(int last_frame_to_play, bool nobreakout) if (sleeping == true) { if (!GameInFocus) { - Sleep((1000/30)); + Host_Sleep((1000/30)); continue; } else { sleeping = false; From 34de2c15437604acc5dbf27ab0c02ee771cea665 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 20:44:30 +0100 Subject: [PATCH 041/179] Substitute the Win32 runtime services each subsystem uses Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/bgfxbackend.cpp | 45 ++++++++++++++++++++++++++++++++++++++ code/saveload.cpp | 22 ++++++++++++++++++- code/syncreport.cpp | 52 +++++++++++++++++++++++++++++++++++++++++--- code/utf8.cpp | 8 +++++++ 4 files changed, 123 insertions(+), 4 deletions(-) diff --git a/code/bgfxbackend.cpp b/code/bgfxbackend.cpp index af72c26a9..40ca82e2d 100644 --- a/code/bgfxbackend.cpp +++ b/code/bgfxbackend.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -79,6 +80,50 @@ struct BackendVertex // bgfx reports lost devices and shader failures through this rather than a return code, // so the engine would otherwise present to a black window with no explanation. +#ifndef _WIN32 +// The renderer reaches for two Win32 services that have no POSIX spelling. Trace output goes +// to the standard error stream, and the aligned allocator keeps the raw block address and the +// usable size in the two words ahead of the address it hands back. +static void OutputDebugString(char const * text) +{ + fputs(text != NULL ? text : "", stderr); +} + +static void _aligned_free(void * ptr) +{ + if (ptr != NULL) { + std::free(((void **)ptr)[-2]); + } +} + +static void * _aligned_realloc(void * ptr, std::size_t size, std::size_t alignment) +{ + if (alignment < sizeof(void *)) { + alignment = sizeof(void *); + } + + std::size_t const header = 2 * sizeof(void *); + void * raw = std::malloc(size + alignment + header); + if (raw == NULL) { + return(NULL); + } + + std::uintptr_t const base = (std::uintptr_t)raw + header; + void * aligned = (void *)((base + alignment - 1) & ~(std::uintptr_t)(alignment - 1)); + ((void **)aligned)[-2] = raw; + ((std::size_t *)aligned)[-1] = size; + + if (ptr != NULL) { + std::size_t const previous = ((std::size_t *)ptr)[-1]; + std::memcpy(aligned, ptr, previous < size ? previous : size); + _aligned_free(ptr); + } + + return(aligned); +} +#endif + + class BackendCallback : public bgfx::CallbackI { public: diff --git a/code/saveload.cpp b/code/saveload.cpp index 1a8901a0c..4253dc0f5 100644 --- a/code/saveload.cpp +++ b/code/saveload.cpp @@ -145,6 +145,26 @@ #include #include +#include +#include + +/// The save record stamps its times in the Windows epoch: hundred nanosecond ticks since the +/// start of 1601. The host clock counts from 1970, so the difference between the two is added. +static void Fetch_System_File_Time(FILETIME* result) +{ +#ifdef _WIN32 + GetSystemTimeAsFileTime(result); +#else + unsigned long long const epoch_difference = 116444736000000000ULL; + unsigned long long const now = (unsigned long long)std::chrono::duration_cast>>( + std::chrono::system_clock::now().time_since_epoch()).count(); + unsigned long long const ticks = now + epoch_difference; + result->dwLowDateTime = (DWORD)(ticks & 0xFFFFFFFFULL); + result->dwHighDateTime = (DWORD)(ticks >> 32); +#endif +} + + //#define SAVE_BLOCK_SIZE 512 #define SAVE_BLOCK_SIZE 4096 //#define SAVE_BLOCK_SIZE 1024 @@ -1016,7 +1036,7 @@ bool Save_Game(const char *file_name, char const * descr) info.Set_Game_Type(Session.Type); FILETIME FileTime; - GetSystemTimeAsFileTime(&FileTime); + Fetch_System_File_Time(&FileTime); info.Set_Last_Time(FileTime); info.Set_Start_Time(FileTime); info.Set_Play_Time(FileTime); diff --git a/code/syncreport.cpp b/code/syncreport.cpp index 626ffbf2b..fa32e7164 100644 --- a/code/syncreport.cpp +++ b/code/syncreport.cpp @@ -93,6 +93,52 @@ #include #include +#ifndef _WIN32 +#include +#include +#endif + +/// The report stamps its filename with the local wall clock. +static void Fetch_Local_Time(SYSTEMTIME* result) +{ +#ifdef _WIN32 + GetLocalTime(result); +#else + time_t const seconds = time(NULL); + struct tm local; + localtime_r(&seconds, &local); + result->wYear = (WORD)(local.tm_year + 1900); + result->wMonth = (WORD)(local.tm_mon + 1); + result->wDayOfWeek = (WORD)local.tm_wday; + result->wDay = (WORD)local.tm_mday; + result->wHour = (WORD)local.tm_hour; + result->wMinute = (WORD)local.tm_min; + result->wSecond = (WORD)local.tm_sec; + result->wMilliseconds = 0; +#endif +} + +/// The report records the x87 control word so two machines can be compared. Nothing outside +/// x86 has one, so the report says zero rather than inventing a reading. +static unsigned Fetch_FPU_Control_Word(void) +{ +#ifdef _WIN32 + return((unsigned)_controlfp(0, 0)); +#else + return(0); +#endif +} + +static DWORD Fetch_Last_Error(void) +{ +#ifdef _WIN32 + return(GetLastError()); +#else + return((DWORD)errno); +#endif +} + + namespace { int LastReportFrame = -1; @@ -197,7 +243,7 @@ void Print_CRCs(EventClass const * events, int count, unsigned const * crc_ring, char const * debug_dir = Debug_Directory(); if (debug_dir != NULL && debug_dir[0] != '\0') { SYSTEMTIME now; - GetLocalTime(&now); + Fetch_Local_Time(&now); Delete_Files_Older_Than(debug_dir, "SYNC_*.LOG", SYNC_REPORT_MAX_AGE_DAYS); snprintf(filename, sizeof(filename), "%s\\SYNC_H%d_%02u-%02u-%04u_%02u-%02u-%02u_F%d.LOG", debug_dir, PlayerPtr->HeapID, @@ -211,7 +257,7 @@ void Print_CRCs(EventClass const * events, int count, unsigned const * crc_ring, fp = fopen(filename,"wt"); if (fp==NULL) { - DWORD const error = GetLastError(); + DWORD const error = Fetch_Last_Error(); DebugString("Failed to open the out-of-sync report %s. Error %d - %s\n", filename, error, Last_Error_Text(error)); return; } @@ -234,7 +280,7 @@ void Print_CRCs(EventClass const * events, int count, unsigned const * crc_ring, } fprintf(fp, "Seed: %08x\n", Seed); fprintf(fp, "Session type: %d\n", Session.Type); - fprintf(fp, "FPU control word: %x\n", _controlfp(0, 0)); + fprintf(fp, "FPU control word: %x\n", Fetch_FPU_Control_Word()); int cpu_type = PROC_PENTIUM_PRO; char vendor[32]; diff --git a/code/utf8.cpp b/code/utf8.cpp index b3aab4473..53cea240a 100644 --- a/code/utf8.cpp +++ b/code/utf8.cpp @@ -115,7 +115,15 @@ int Best_Fit_Index(unsigned page, short * cache, char32_t code) wchar_t wide = (wchar_t)code; char narrow = 0; BOOL defaulted = FALSE; +#ifdef _WIN32 int written = WideCharToMultiByte(page, 0, &wide, 1, &narrow, 1, NULL, &defaulted); +#else + // No host code page service outside Windows, so nothing above ASCII maps and the + // caller falls back to its own substitute glyph. + (void)page; + (void)wide; + int written = 0; +#endif unsigned char byte = (unsigned char)narrow; slot = (written == 1 && !defaulted && byte >= 0x20 && byte != 0x7F) ? (short)byte : (short)-1; } From 4d793450ef189871863ca60eb87e7fdcbce28e5a Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 20:46:33 +0100 Subject: [PATCH 042/179] Read and write the save container through standard file calls Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/gamedirs.cpp | 43 +++++++++++++++++------- code/savefile.cpp | 85 +++++++++++++++++++++++++++++------------------ 2 files changed, 84 insertions(+), 44 deletions(-) diff --git a/code/gamedirs.cpp b/code/gamedirs.cpp index 790761aa5..d9555ccee 100644 --- a/code/gamedirs.cpp +++ b/code/gamedirs.cpp @@ -7,6 +7,7 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ +#include "file.h" #include "always.h" #include "gamedirs.h" @@ -16,6 +17,7 @@ #include #include +#include #include #include @@ -122,9 +124,21 @@ char const * Game_Directory_Error(void) static bool Is_Directory(std::string const & path) { - DWORD attributes = GetFileAttributes(path.c_str()); + std::error_code error; - return(attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0); + return(std::filesystem::is_directory(path, error)); +} + + +/// +/// Makes a directory, reporting success when it is already there. +/// +static bool Make_Directory(std::string const & path) +{ + std::error_code error; + std::filesystem::create_directory(path, error); + + return(!error || Is_Directory(path)); } @@ -222,7 +236,7 @@ std::vector Parse_Search_Folders(char const * list) bool Apply_Game_Directories(void) { if (!UserDirectory.empty()) { - if (!Is_Directory(UserDirectory) && !CreateDirectory(UserDirectory.c_str(), NULL)) { + if (!Is_Directory(UserDirectory) && !Make_Directory(UserDirectory)) { Report_Directory_Error("user", UserDirectory); return(false); } @@ -279,7 +293,7 @@ std::string Saved_Game_Name(char const * filename) { std::string const folder = UserDirectory + SavedGamesFolder; - CreateDirectory(folder.c_str(), NULL); + Make_Directory(folder); return(folder + (char)std::filesystem::path::preferred_separator + filename); } @@ -289,31 +303,36 @@ static void Scan_Folder(char const * prefix, char const * pattern, std::vectorGetName(); + if (found == NULL) { + continue; + } + + std::error_code error; + if (std::filesystem::is_directory(std::string(prefix) + found, error)) { continue; } bool present = false; for (std::string const & existing : names) { - if (Is_Same_Path(existing, block.cFileName)) { + if (Is_Same_Path(existing, found)) { present = true; break; } } if (!present) { - names.push_back(block.cFileName); + names.push_back(found); } - } while (FindNextFile(handle, &block)); + } while (Find_Next(block)); - FindClose(handle); + Find_Close(block); } diff --git a/code/savefile.cpp b/code/savefile.cpp index baaf6ac98..e8e3738f5 100644 --- a/code/savefile.cpp +++ b/code/savefile.cpp @@ -11,7 +11,10 @@ #include +#include #include +#include +#include #include #include @@ -78,37 +81,51 @@ bool Reserve(std::vector & buffer, std::size_t length) } -bool Read_Range(HANDLE file, void * into, unsigned int length) +bool Read_Range(std::FILE * file, void * into, unsigned int length) { unsigned char * cursor = (unsigned char *)into; while (length > 0) { - DWORD got = 0; - if (!ReadFile(file, cursor, length, &got, NULL) || got == 0) return(false); + std::size_t const got = std::fread(cursor, 1, length, file); + if (got == 0) return(false); cursor += got; - length -= got; + length -= (unsigned int)got; } return(true); } -bool Write_Range(HANDLE file, void const * data, unsigned int length) +bool Write_Range(std::FILE * file, void const * data, unsigned int length) { unsigned char const * cursor = (unsigned char const *)data; while (length > 0) { - DWORD const block = (length > 0x100000) ? 0x100000 : length; - DWORD written = 0; - if (!WriteFile(file, cursor, block, &written, NULL) || written != block) return(false); + unsigned int const block = (length > 0x100000) ? 0x100000 : length; + std::size_t const written = std::fwrite(cursor, 1, block, file); + if (written != block) return(false); cursor += written; - length -= written; + length -= block; } return(true); } +/// +/// Reports the length of an open file without moving the caller's read position. +/// +/// The length in bytes, or -1. +long File_Length(std::FILE * file) +{ + long const here = std::ftell(file); + if (here < 0 || std::fseek(file, 0, SEEK_END) != 0) return(-1); + long const end = std::ftell(file); + std::fseek(file, here, SEEK_SET); + return(end); +} + + struct HeaderType { unsigned int Version; unsigned int Flags; @@ -413,18 +430,22 @@ SaveFileClass::ResultType SaveFileClass::Write(char const * path) const std::string const temporary = std::string(path) + ".tmp"; - HANDLE const file = CreateFileA(temporary.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, NULL); - if (file == INVALID_HANDLE_VALUE) return(RESULT_WRITE_FAILED); + std::FILE * const file = std::fopen(temporary.c_str(), "wb"); + if (file == NULL) return(RESULT_WRITE_FAILED); bool ok = Write_Range(file, image.data(), (unsigned int)image.size()); - if (ok) ok = (FlushFileBuffers(file) != FALSE); - if (!CloseHandle(file)) ok = false; + if (ok) ok = (std::fflush(file) == 0); + if (std::fclose(file) != 0) ok = false; - if (ok) ok = (MoveFileExA(temporary.c_str(), path, MOVEFILE_REPLACE_EXISTING) != FALSE); + if (ok) { + std::error_code error; + std::filesystem::rename(temporary, path, error); + ok = !error; + } if (!ok) { - DeleteFileA(temporary.c_str()); + std::error_code error; + std::filesystem::remove(temporary, error); return(RESULT_WRITE_FAILED); } @@ -439,22 +460,22 @@ SaveFileClass::ResultType SaveFileClass::Read(char const * path) if (path == NULL) return(RESULT_MISSING); - HANDLE const file = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, NULL); - if (file == INVALID_HANDLE_VALUE) return(RESULT_MISSING); + std::FILE * const file = std::fopen(path, "rb"); + if (file == NULL) return(RESULT_MISSING); // The header is judged before anything the file's size could ask for is allocated. unsigned char head[HEADER_SIZE]; - DWORD got = 0; - bool const ok = (ReadFile(file, head, HEADER_SIZE, &got, NULL) != FALSE); + unsigned int const got = (unsigned int)std::fread(head, 1, HEADER_SIZE, file); + bool const ok = !std::ferror(file); HeaderType header; ResultType result = ok ? Parse_Header(head, got, header) : RESULT_CORRUPT; std::vector image; if (result == RESULT_OK) { - DWORD const size = GetFileSize(file, NULL); - if (size == INVALID_FILE_SIZE || size != header.ContentOffset + header.StoredLength) { + long const length = File_Length(file); + unsigned int const size = (unsigned int)length; + if (length < 0 || size != header.ContentOffset + header.StoredLength) { result = RESULT_CORRUPT; } else if (!Reserve(image, size)) { result = RESULT_NO_MEMORY; @@ -463,7 +484,7 @@ SaveFileClass::ResultType SaveFileClass::Read(char const * path) if (!Read_Range(file, image.data() + HEADER_SIZE, size - HEADER_SIZE)) result = RESULT_CORRUPT; } } - CloseHandle(file); + std::fclose(file); if (result != RESULT_OK) return(result); if (Header_CRC(image.data(), image.data() + HEADER_SIZE, header.TableLength) != header.HeaderCRC) { @@ -519,21 +540,21 @@ SaveFileClass::ResultType SaveFileClass::Read_Fields(char const * path) if (path == NULL) return(RESULT_MISSING); - HANDLE const file = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, NULL); - if (file == INVALID_HANDLE_VALUE) return(RESULT_MISSING); + std::FILE * const file = std::fopen(path, "rb"); + if (file == NULL) return(RESULT_MISSING); unsigned char head[HEADER_SIZE]; - DWORD got = 0; - bool ok = (ReadFile(file, head, HEADER_SIZE, &got, NULL) != FALSE); + unsigned int const got = (unsigned int)std::fread(head, 1, HEADER_SIZE, file); + bool ok = !std::ferror(file); HeaderType header; ResultType result = ok ? Parse_Header(head, got, header) : RESULT_CORRUPT; std::vector table; if (result == RESULT_OK && header.TableLength > 0) { - DWORD const size = GetFileSize(file, NULL); - if (size == INVALID_FILE_SIZE || header.TableLength > size - HEADER_SIZE) { + long const length = File_Length(file); + unsigned int const size = (unsigned int)length; + if (length < 0 || header.TableLength > size - HEADER_SIZE) { result = RESULT_CORRUPT; } else if (!Reserve(table, header.TableLength)) { result = RESULT_NO_MEMORY; @@ -541,7 +562,7 @@ SaveFileClass::ResultType SaveFileClass::Read_Fields(char const * path) if (!Read_Range(file, table.data(), header.TableLength)) result = RESULT_CORRUPT; } } - CloseHandle(file); + std::fclose(file); if (result != RESULT_OK) return(result); if (Header_CRC(head, table.data(), (unsigned int)table.size()) != header.HeaderCRC) return(RESULT_CORRUPT); From b182f97213325023e52b73b1bc711a9bba083c2c Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 20:47:25 +0100 Subject: [PATCH 043/179] Scan the game directories through the portable file layer Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/gamedirs.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/code/gamedirs.cpp b/code/gamedirs.cpp index d9555ccee..321507d6d 100644 --- a/code/gamedirs.cpp +++ b/code/gamedirs.cpp @@ -7,7 +7,6 @@ * See LICENSE.md for applicable additional terms and warranty disclaimers. ******************************************************************************/ -#include "file.h" #include "always.h" #include "gamedirs.h" @@ -15,6 +14,10 @@ #include "cdfile.h" #include "dbgprint.h" +// Included after the file classes: it defines READ and WRITE as macros that would otherwise +// swallow the identically named enumerators in wwfile.h. +#include "file.h" + #include #include #include From 8cf39117e50136cb5d0b663ea0738b00bd6d772e Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 20:49:38 +0100 Subject: [PATCH 044/179] Name the running program without the module handle Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/stats.cpp | 86 +++++++++++++++++++++++++++++++++++++++--------- code/version.cpp | 49 ++++++++++++++++++++++++++- code/version.h | 1 + 3 files changed, 119 insertions(+), 17 deletions(-) diff --git a/code/stats.cpp b/code/stats.cpp index 45628b66e..3f5604e2b 100644 --- a/code/stats.cpp +++ b/code/stats.cpp @@ -62,6 +62,71 @@ int WestwoodOnline_PortNumber = 1234; #include "unittype.h" #include "win.h" +#include "version.h" + +#ifndef _WIN32 +#include +#include +#include +#endif + +namespace { + +/// +/// Reports the machine's installed memory, which the report sends as a byte count. +/// +unsigned long long Physical_Memory_Bytes(void) +{ +#ifdef _WIN32 + MEMORYSTATUS mem_info; + mem_info.dwLength = sizeof(mem_info); + GlobalMemoryStatus(&mem_info); + return((unsigned long long)mem_info.dwTotalPhys); +#elif defined(__APPLE__) + int name[2] = { CTL_HW, HW_MEMSIZE }; + unsigned long long total = 0; + size_t length = sizeof(total); + if (sysctl(name, 2, &total, &length, NULL, 0) == 0) { + return(total); + } + return(0); +#else + long const pages = sysconf(_SC_PHYS_PAGES); + long const page_size = sysconf(_SC_PAGESIZE); + if (pages > 0 && page_size > 0) { + return((unsigned long long)pages * (unsigned long long)page_size); + } + return(0); +#endif +} + +/// +/// Reads a file's last write time in the Windows epoch the report field carries. +/// +/// bool; Was a time read? +bool Program_Write_Time(char const * path, FILETIME & result) +{ +#ifdef _WIN32 + RawFileClass file; + file.Set_Name(path); + file.Open(); + HANDLE handle = file.Get_File_Handle(); + return(handle != INVALID_HANDLE_VALUE && GetFileTime(handle, NULL, NULL, &result) != FALSE); +#else + struct stat info; + if (stat(path, &info) != 0) { + return(false); + } + unsigned long long const ticks = (unsigned long long)info.st_mtime * 10000000ULL + 116444736000000000ULL; + result.dwLowDateTime = (DWORD)(ticks & 0xFFFFFFFFULL); + result.dwHighDateTime = (DWORD)(ticks >> 32); + return(true); +#endif +} + +} + + #define FIELD_GAME_ID "IDNO" #define FIELD_START_CREDITS "CRED" #define FIELD_BASES "BASE" @@ -402,10 +467,7 @@ void Send_Statistics_Packet(void) /* ** Memory */ - MEMORYSTATUS mem_info; - mem_info.dwLength=sizeof(mem_info); - GlobalMemoryStatus(&mem_info); - stats.Add_Field (FIELD_MEMORY, (int)mem_info.dwTotalPhys); + stats.Add_Field (FIELD_MEMORY, (int)Physical_Memory_Bytes()); /* ** Game speed setting. @@ -422,18 +484,10 @@ void Send_Statistics_Packet(void) char path_to_exe[280]; FILETIME write_time; //File time is 64 bits - GetModuleFileName (ProgramInstance, path_to_exe, sizeof(path_to_exe)); - RawFileClass file; - file.Set_Name(path_to_exe); - file.Open(); - HANDLE handle = file.Get_File_Handle(); - - if (handle != INVALID_HANDLE_VALUE) { - if (GetFileTime (handle, NULL, NULL, &write_time)){ - write_time.dwLowDateTime = htonl (write_time.dwLowDateTime); - write_time.dwHighDateTime = htonl (write_time.dwHighDateTime); - stats.Add_Field (FIELD_GAME_BUILD_DATE, (void*)&write_time, sizeof (write_time)); - } + if (Program_File_Name(path_to_exe, sizeof(path_to_exe)) && Program_Write_Time(path_to_exe, write_time)) { + write_time.dwLowDateTime = htonl (write_time.dwLowDateTime); + write_time.dwHighDateTime = htonl (write_time.dwHighDateTime); + stats.Add_Field (FIELD_GAME_BUILD_DATE, (void*)&write_time, sizeof (write_time)); } /* diff --git a/code/version.cpp b/code/version.cpp index 04d9c3295..a751babd8 100644 --- a/code/version.cpp +++ b/code/version.cpp @@ -40,6 +40,14 @@ * VersionClass::Max_Version -- returns highest version # to connect to * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#include "opents_build.h" + +#ifdef __APPLE__ +#include +#elif !defined(_WIN32) +#include +#endif + #include "always.h" #include "version.h" @@ -516,7 +524,14 @@ char const * Version_Name(void) empty = false; - if (GetModuleFileName(ProgramInstance, filename, sizeof(filename)) > 0) { +#ifndef _WIN32 + // No version resource outside Windows, so the generated build stamp answers instead. + (void)size; (void)block; (void)translate_len; (void)handle; (void)translate; + (void)query; (void)filename; + strncpy(buffer, OPENTS_VERSION_DISPLAY, sizeof(buffer) - 1); + buffer[sizeof(buffer) - 1] = '\0'; +#else + if (Program_File_Name(filename, sizeof(filename))) { handle = 1; size = GetFileVersionInfoSize(filename, &handle); if (size > 0) { @@ -535,7 +550,39 @@ char const * Version_Name(void) delete [] block; } } +#endif } return(buffer); } + + +/// +/// Names the file the running program was loaded from. +/// +/// bool; Was a path written into the buffer? +bool Program_File_Name(char * buffer, unsigned int length) +{ + if (buffer == NULL || length == 0) { + return(false); + } + +#ifdef _WIN32 + return(GetModuleFileName(ProgramInstance, buffer, length) > 0); +#elif defined(__APPLE__) + uint32_t size = (uint32_t)length; + if (_NSGetExecutablePath(buffer, &size) != 0) { + buffer[0] = '\0'; + return(false); + } + return(true); +#else + ssize_t const written = readlink("/proc/self/exe", buffer, length - 1); + if (written <= 0) { + buffer[0] = '\0'; + return(false); + } + buffer[written] = '\0'; + return(true); +#endif +} diff --git a/code/version.h b/code/version.h index 2dc230e84..e205dd4d9 100644 --- a/code/version.h +++ b/code/version.h @@ -154,5 +154,6 @@ class VersionClass { }; char const * Version_Name(void); +bool Program_File_Name(char * buffer, unsigned int length); /************************** end of version.h *******************************/ From 975ef351e03ed737e3e2aaa29da410964be8ec8f Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 20:50:22 +0100 Subject: [PATCH 045/179] Report no display modes where the host cannot enumerate them Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/video.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/code/video.cpp b/code/video.cpp index 576026469..bbce57854 100644 --- a/code/video.cpp +++ b/code/video.cpp @@ -341,6 +341,12 @@ static int __cdecl Compare_Modes(void const * left, void const * right) /// when nothing matched. int * EnumDisplayModes(int minwidth, int minheight, int maxwidth, int maxheight) { +#ifndef _WIN32 + // No host mode enumeration on this platform yet. Reporting nothing is already a supported + // answer, and the caller falls back to the sizes it knows. + (void)minwidth; (void)minheight; (void)maxwidth; (void)maxheight; + return(NULL); +#else DEVMODE devmode; int count = 0; int capacity = 0; @@ -404,4 +410,5 @@ int * EnumDisplayModes(int minwidth, int minheight, int maxwidth, int maxheight) modes[unique * 2] = 0; modes[unique * 2 + 1] = 0; return(modes); +#endif } From 35079ffb02a5e81ea49ddc6bce002b79d029f9b8 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 20:52:41 +0100 Subject: [PATCH 046/179] Measure free disk space through the standard filesystem library Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/conquer.cpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/code/conquer.cpp b/code/conquer.cpp index 31d52bf7f..2d1fbacc1 100644 --- a/code/conquer.cpp +++ b/code/conquer.cpp @@ -62,6 +62,9 @@ * Is_Aftermath_Installed -- Function to determine the availability of the AM expansion. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#include +#include + #include "always.h" #include "conquer.h" @@ -355,6 +358,7 @@ void Main_Game(int argc, char * argv[]) int ret = Init_Game(argc, argv); if (ret) { if (ret < 0) { +#ifdef _WIN32 MSGBOXPARAMS params; params.cbSize = sizeof(MSGBOXPARAMS); params.hwndOwner = MainWindow; @@ -367,6 +371,10 @@ void Main_Game(int argc, char * argv[]) params.lpfnMsgBoxCallback = NULL; params.dwLanguageId = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); MessageBoxIndirect(¶ms); +#else + // No host message box yet, so the failure is reported where the launch happened. + fprintf(stderr, "%s: %s\n", Fetch_String(TXT_SHORT_TITLE), Fetch_String(TXT_INITGAME_FAILED)); +#endif } return; } @@ -1203,25 +1211,26 @@ TechnoTypeClass const * Fetch_Techno_Type(RTTIType type, int id) *=========================================================================*/ unsigned int Disk_Space_Available(void) { - ULARGE_INTEGER freebytecount; // Free bytes on disk available to caller (caller may not have access to entire disk). - DebugString("Checking available disk space\n"); /* * Measured where the game's saved games will actually go, which is not the current * directory once a player has one of their own. */ - std::string const user_directory = User_File_Write_Name(""); - LPCTSTR const disk = user_directory.empty() ? NULL : user_directory.c_str(); + std::string user_directory = User_File_Write_Name(""); + if (user_directory.empty()) { + user_directory = "."; + } - if (!GetDiskFreeSpaceEx(disk, &freebytecount, NULL, NULL)) { - DWORD const error = GetLastError(); - DebugString("GetDiskFreeSpaceEx failed with error code %d - %s\n", error, Last_Error_Text(error)); + std::error_code error; + std::filesystem::space_info const space = std::filesystem::space(user_directory, error); + if (error) { + DebugString("Free disk space unreadable - %s\n", error.message().c_str()); return(0); } // The kilobyte count saturates rather than wrapping. - unsigned int const diskspace = (unsigned int)std::min(freebytecount.QuadPart / 1024, UINT_MAX); + unsigned int const diskspace = (unsigned int)std::min((unsigned long long)space.available / 1024, UINT_MAX); DebugString("Free disk space is %u Mb\n", diskspace / 1024); return(diskspace); } From 818e05f26963009b84256b0eb86c6bc8751d3b08 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 21:09:32 +0100 Subject: [PATCH 047/179] Add SDL3 as the non-Windows window and event backend Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- .gitmodules | 3 ++ .../win32compat/include}/comdef.h | 0 .../win32compat/include}/commctrl.h | 0 .../win32compat/include}/conio.h | 0 .../win32compat/include}/crtdbg.h | 0 .../win32compat/include}/dbghelp.h | 0 .../win32compat/include}/direct.h | 0 .../win32compat/include}/dos.h | 0 .../win32compat/include}/intrin.h | 0 .../win32compat/include}/io.h | 0 .../win32compat/include}/iphlpapi.h | 0 .../win32compat/include}/malloc.h | 0 .../win32compat/include}/mmsystem.h | 0 .../win32compat/include}/objbase.h | 0 .../win32compat/include}/objidl.h | 0 .../win32compat/include}/ole2.h | 0 .../win32compat/include}/process.h | 0 .../win32compat/include}/sal.h | 0 .../win32compat/include}/share.h | 0 .../win32compat/include}/shellapi.h | 0 .../win32compat/include}/sys/timeb.h | 0 .../win32compat/include}/tlhelp32.h | 0 .../win32compat/include}/unknwn.h | 0 .../win32compat/include}/utime.h | 0 .../win32compat/include}/winbase.h | 0 .../win32compat/include}/windef.h | 0 .../win32compat/include}/windows.h | 0 .../win32compat/include}/windowsx.h | 0 .../win32compat/include}/wingdi.h | 0 .../win32compat/include}/winioctl.h | 0 .../win32compat/include}/winnt.h | 0 .../win32compat/include}/winres.h | 0 .../win32compat/include}/winsock.h | 0 .../win32compat/include}/winsock2.h | 0 .../win32compat/include}/winuser.h | 0 .../win32compat/include}/ws2tcpip.h | 0 spike/win32shim/README.md | 7 ---- thirdparty/CMakeLists.txt | 33 +++++++++++++++++++ thirdparty/SDL | 1 + 39 files changed, 37 insertions(+), 7 deletions(-) rename {spike/win32shim => platform/win32compat/include}/comdef.h (100%) rename {spike/win32shim => platform/win32compat/include}/commctrl.h (100%) rename {spike/win32shim => platform/win32compat/include}/conio.h (100%) rename {spike/win32shim => platform/win32compat/include}/crtdbg.h (100%) rename {spike/win32shim => platform/win32compat/include}/dbghelp.h (100%) rename {spike/win32shim => platform/win32compat/include}/direct.h (100%) rename {spike/win32shim => platform/win32compat/include}/dos.h (100%) rename {spike/win32shim => platform/win32compat/include}/intrin.h (100%) rename {spike/win32shim => platform/win32compat/include}/io.h (100%) rename {spike/win32shim => platform/win32compat/include}/iphlpapi.h (100%) rename {spike/win32shim => platform/win32compat/include}/malloc.h (100%) rename {spike/win32shim => platform/win32compat/include}/mmsystem.h (100%) rename {spike/win32shim => platform/win32compat/include}/objbase.h (100%) rename {spike/win32shim => platform/win32compat/include}/objidl.h (100%) rename {spike/win32shim => platform/win32compat/include}/ole2.h (100%) rename {spike/win32shim => platform/win32compat/include}/process.h (100%) rename {spike/win32shim => platform/win32compat/include}/sal.h (100%) rename {spike/win32shim => platform/win32compat/include}/share.h (100%) rename {spike/win32shim => platform/win32compat/include}/shellapi.h (100%) rename {spike/win32shim => platform/win32compat/include}/sys/timeb.h (100%) rename {spike/win32shim => platform/win32compat/include}/tlhelp32.h (100%) rename {spike/win32shim => platform/win32compat/include}/unknwn.h (100%) rename {spike/win32shim => platform/win32compat/include}/utime.h (100%) rename {spike/win32shim => platform/win32compat/include}/winbase.h (100%) rename {spike/win32shim => platform/win32compat/include}/windef.h (100%) rename {spike/win32shim => platform/win32compat/include}/windows.h (100%) rename {spike/win32shim => platform/win32compat/include}/windowsx.h (100%) rename {spike/win32shim => platform/win32compat/include}/wingdi.h (100%) rename {spike/win32shim => platform/win32compat/include}/winioctl.h (100%) rename {spike/win32shim => platform/win32compat/include}/winnt.h (100%) rename {spike/win32shim => platform/win32compat/include}/winres.h (100%) rename {spike/win32shim => platform/win32compat/include}/winsock.h (100%) rename {spike/win32shim => platform/win32compat/include}/winsock2.h (100%) rename {spike/win32shim => platform/win32compat/include}/winuser.h (100%) rename {spike/win32shim => platform/win32compat/include}/ws2tcpip.h (100%) delete mode 100644 spike/win32shim/README.md create mode 160000 thirdparty/SDL diff --git a/.gitmodules b/.gitmodules index 0af59cec6..43da1b1f3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "thirdparty/miniaudio"] path = thirdparty/miniaudio url = https://github.com/mackron/miniaudio.git +[submodule "thirdparty/SDL"] + path = thirdparty/SDL + url = https://github.com/libsdl-org/SDL.git diff --git a/spike/win32shim/comdef.h b/platform/win32compat/include/comdef.h similarity index 100% rename from spike/win32shim/comdef.h rename to platform/win32compat/include/comdef.h diff --git a/spike/win32shim/commctrl.h b/platform/win32compat/include/commctrl.h similarity index 100% rename from spike/win32shim/commctrl.h rename to platform/win32compat/include/commctrl.h diff --git a/spike/win32shim/conio.h b/platform/win32compat/include/conio.h similarity index 100% rename from spike/win32shim/conio.h rename to platform/win32compat/include/conio.h diff --git a/spike/win32shim/crtdbg.h b/platform/win32compat/include/crtdbg.h similarity index 100% rename from spike/win32shim/crtdbg.h rename to platform/win32compat/include/crtdbg.h diff --git a/spike/win32shim/dbghelp.h b/platform/win32compat/include/dbghelp.h similarity index 100% rename from spike/win32shim/dbghelp.h rename to platform/win32compat/include/dbghelp.h diff --git a/spike/win32shim/direct.h b/platform/win32compat/include/direct.h similarity index 100% rename from spike/win32shim/direct.h rename to platform/win32compat/include/direct.h diff --git a/spike/win32shim/dos.h b/platform/win32compat/include/dos.h similarity index 100% rename from spike/win32shim/dos.h rename to platform/win32compat/include/dos.h diff --git a/spike/win32shim/intrin.h b/platform/win32compat/include/intrin.h similarity index 100% rename from spike/win32shim/intrin.h rename to platform/win32compat/include/intrin.h diff --git a/spike/win32shim/io.h b/platform/win32compat/include/io.h similarity index 100% rename from spike/win32shim/io.h rename to platform/win32compat/include/io.h diff --git a/spike/win32shim/iphlpapi.h b/platform/win32compat/include/iphlpapi.h similarity index 100% rename from spike/win32shim/iphlpapi.h rename to platform/win32compat/include/iphlpapi.h diff --git a/spike/win32shim/malloc.h b/platform/win32compat/include/malloc.h similarity index 100% rename from spike/win32shim/malloc.h rename to platform/win32compat/include/malloc.h diff --git a/spike/win32shim/mmsystem.h b/platform/win32compat/include/mmsystem.h similarity index 100% rename from spike/win32shim/mmsystem.h rename to platform/win32compat/include/mmsystem.h diff --git a/spike/win32shim/objbase.h b/platform/win32compat/include/objbase.h similarity index 100% rename from spike/win32shim/objbase.h rename to platform/win32compat/include/objbase.h diff --git a/spike/win32shim/objidl.h b/platform/win32compat/include/objidl.h similarity index 100% rename from spike/win32shim/objidl.h rename to platform/win32compat/include/objidl.h diff --git a/spike/win32shim/ole2.h b/platform/win32compat/include/ole2.h similarity index 100% rename from spike/win32shim/ole2.h rename to platform/win32compat/include/ole2.h diff --git a/spike/win32shim/process.h b/platform/win32compat/include/process.h similarity index 100% rename from spike/win32shim/process.h rename to platform/win32compat/include/process.h diff --git a/spike/win32shim/sal.h b/platform/win32compat/include/sal.h similarity index 100% rename from spike/win32shim/sal.h rename to platform/win32compat/include/sal.h diff --git a/spike/win32shim/share.h b/platform/win32compat/include/share.h similarity index 100% rename from spike/win32shim/share.h rename to platform/win32compat/include/share.h diff --git a/spike/win32shim/shellapi.h b/platform/win32compat/include/shellapi.h similarity index 100% rename from spike/win32shim/shellapi.h rename to platform/win32compat/include/shellapi.h diff --git a/spike/win32shim/sys/timeb.h b/platform/win32compat/include/sys/timeb.h similarity index 100% rename from spike/win32shim/sys/timeb.h rename to platform/win32compat/include/sys/timeb.h diff --git a/spike/win32shim/tlhelp32.h b/platform/win32compat/include/tlhelp32.h similarity index 100% rename from spike/win32shim/tlhelp32.h rename to platform/win32compat/include/tlhelp32.h diff --git a/spike/win32shim/unknwn.h b/platform/win32compat/include/unknwn.h similarity index 100% rename from spike/win32shim/unknwn.h rename to platform/win32compat/include/unknwn.h diff --git a/spike/win32shim/utime.h b/platform/win32compat/include/utime.h similarity index 100% rename from spike/win32shim/utime.h rename to platform/win32compat/include/utime.h diff --git a/spike/win32shim/winbase.h b/platform/win32compat/include/winbase.h similarity index 100% rename from spike/win32shim/winbase.h rename to platform/win32compat/include/winbase.h diff --git a/spike/win32shim/windef.h b/platform/win32compat/include/windef.h similarity index 100% rename from spike/win32shim/windef.h rename to platform/win32compat/include/windef.h diff --git a/spike/win32shim/windows.h b/platform/win32compat/include/windows.h similarity index 100% rename from spike/win32shim/windows.h rename to platform/win32compat/include/windows.h diff --git a/spike/win32shim/windowsx.h b/platform/win32compat/include/windowsx.h similarity index 100% rename from spike/win32shim/windowsx.h rename to platform/win32compat/include/windowsx.h diff --git a/spike/win32shim/wingdi.h b/platform/win32compat/include/wingdi.h similarity index 100% rename from spike/win32shim/wingdi.h rename to platform/win32compat/include/wingdi.h diff --git a/spike/win32shim/winioctl.h b/platform/win32compat/include/winioctl.h similarity index 100% rename from spike/win32shim/winioctl.h rename to platform/win32compat/include/winioctl.h diff --git a/spike/win32shim/winnt.h b/platform/win32compat/include/winnt.h similarity index 100% rename from spike/win32shim/winnt.h rename to platform/win32compat/include/winnt.h diff --git a/spike/win32shim/winres.h b/platform/win32compat/include/winres.h similarity index 100% rename from spike/win32shim/winres.h rename to platform/win32compat/include/winres.h diff --git a/spike/win32shim/winsock.h b/platform/win32compat/include/winsock.h similarity index 100% rename from spike/win32shim/winsock.h rename to platform/win32compat/include/winsock.h diff --git a/spike/win32shim/winsock2.h b/platform/win32compat/include/winsock2.h similarity index 100% rename from spike/win32shim/winsock2.h rename to platform/win32compat/include/winsock2.h diff --git a/spike/win32shim/winuser.h b/platform/win32compat/include/winuser.h similarity index 100% rename from spike/win32shim/winuser.h rename to platform/win32compat/include/winuser.h diff --git a/spike/win32shim/ws2tcpip.h b/platform/win32compat/include/ws2tcpip.h similarity index 100% rename from spike/win32shim/ws2tcpip.h rename to platform/win32compat/include/ws2tcpip.h diff --git a/spike/win32shim/README.md b/spike/win32shim/README.md deleted file mode 100644 index e05b661f8..000000000 --- a/spike/win32shim/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Win32 measurement shim - -Throwaway scaffolding for the native arm64 portability spike. The headers here -declare only the types and macros the tree needs to get past `#include`, so a -native compile reports the Win32 API surface as undeclared identifiers instead -of stopping at the first missing header. Nothing here implements Win32, and -nothing here is a portability layer. It exists to size the port. diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index a47cd2a3e..18423863b 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -103,3 +103,36 @@ target_include_directories(lzo PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/lzo/include" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/lzo/src" ) + +# +# --------------------------------------------------------- +# SDL3 (window, event loop, cursor, keyboard) -- non-Windows only +# --------------------------------------------------------- +# +# Windows supplies the window, the message loop and the cursor itself, so the supported +# build links none of this. Every other target needs one library that also reaches iOS, +# which rules out a desktop-only toolkit and rules out writing AppKit and UIKit twice. +# Audio stays on miniaudio; only the video, event and keyboard subsystems are built. +if(NOT WIN32) + if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/SDL/CMakeLists.txt") + message(FATAL_ERROR + "thirdparty/SDL is empty. Fetch the submodules with:\n" + " git submodule update --init --recursive") + endif() + + set(SDL_SHARED OFF CACHE BOOL "" FORCE) + set(SDL_STATIC ON CACHE BOOL "" FORCE) + set(SDL_TEST_LIBRARY OFF CACHE BOOL "" FORCE) + set(SDL_TESTS OFF CACHE BOOL "" FORCE) + set(SDL_EXAMPLES OFF CACHE BOOL "" FORCE) + set(SDL_INSTALL OFF CACHE BOOL "" FORCE) + set(SDL_AUDIO OFF CACHE BOOL "" FORCE) + set(SDL_RENDER OFF CACHE BOOL "" FORCE) + set(SDL_CAMERA OFF CACHE BOOL "" FORCE) + set(SDL_HAPTIC OFF CACHE BOOL "" FORCE) + set(SDL_SENSOR OFF CACHE BOOL "" FORCE) + set(SDL_POWER OFF CACHE BOOL "" FORCE) + set(SDL_DIALOG OFF CACHE BOOL "" FORCE) + + add_subdirectory(SDL) +endif() diff --git a/thirdparty/SDL b/thirdparty/SDL new file mode 160000 index 000000000..fa2c02bb6 --- /dev/null +++ b/thirdparty/SDL @@ -0,0 +1 @@ +Subproject commit fa2c02bb6e21974a89ea9824bc53c9932abe5f9c From 3b29294d4cc2acb8c0361275d7f3d116865570c6 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 21:09:38 +0100 Subject: [PATCH 048/179] Declare the Win32 window surface the native build has to supply Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/always.h | 2 + code/except.h | 4 + code/init.cpp | 3 +- platform/win32compat/include/commctrl.h | 111 ++++ platform/win32compat/include/conio.h | 1 + platform/win32compat/include/direct.h | 4 + platform/win32compat/include/sys/timeb.h | 6 +- platform/win32compat/include/windows.h | 766 ++++++++++++++++++++++- platform/win32compat/include/windowsx.h | 44 ++ platform/win32compat/include/winnt.h | 43 ++ 10 files changed, 976 insertions(+), 8 deletions(-) diff --git a/code/always.h b/code/always.h index 1f3f5fed4..e35c58582 100644 --- a/code/always.h +++ b/code/always.h @@ -98,6 +98,8 @@ */ #ifndef _WIN32 +#define _MAX_DRIVE 3 +#define _MAX_DIR 256 #define _MAX_FNAME 255 #define _MAX_EXT 8 #define _MAX_PATH 512 diff --git a/code/except.h b/code/except.h index fefa2eb89..f309bc927 100644 --- a/code/except.h +++ b/code/except.h @@ -57,6 +57,10 @@ #define _Printf_format_string_ +// The window procedure switches on this message whether or not the handler that posts it +// was built, so the two branches have to agree on its value. +#define WM_EXCEPTION_TEST (WM_APP + 0x54) + #endif void Install_Exception_Handler(void); diff --git a/code/init.cpp b/code/init.cpp index 6af0b3f65..19e772b7e 100644 --- a/code/init.cpp +++ b/code/init.cpp @@ -1916,7 +1916,8 @@ void Init_Random(void) */ if (Session.Type == GAME_NORMAL || Session.Type == GAME_SKIRMISH) { - #ifdef WIN32 + // The alternative is the DOS build's timer, which no target this tree builds for has. + #ifndef __DOS__ /* ** Gather some "random" bits from the system timer. Actually, only the ** low order millisecond bits are secure. The other bits could be diff --git a/platform/win32compat/include/commctrl.h b/platform/win32compat/include/commctrl.h index 720e64a9b..53c29ba77 100644 --- a/platform/win32compat/include/commctrl.h +++ b/platform/win32compat/include/commctrl.h @@ -1,2 +1,113 @@ #pragma once #include + +// Common controls exist only in the legacy dialog layer, which UI_DESIGN step 13 replaces. +// The window class names are declared so that a template that names one still compiles. +#define TRACKBAR_CLASS "msctls_trackbar32" +#define PROGRESS_CLASS "msctls_progress32" +#define HOTKEY_CLASS "msctls_hotkey32" +#define WC_TREEVIEW "SysTreeView32" +#define WC_LISTVIEW "SysListView32" +#define WC_TABCONTROL "SysTabControl32" + +#define TBM_GETPOS (WM_USER) +#define TBM_GETRANGEMIN (WM_USER + 1) +#define TBM_GETRANGEMAX (WM_USER + 2) +#define TBM_SETPOS (WM_USER + 5) +#define TBM_SETRANGE (WM_USER + 6) +#define TB_LINEUP 0 +#define TB_LINEDOWN 1 +#define TB_THUMBPOSITION 4 +#define TB_THUMBTRACK 5 +#define TB_ENDTRACK 8 + +#define PBM_SETRANGE (WM_USER + 1) +#define PBM_SETPOS (WM_USER + 2) + +#define HKM_SETHOTKEY (WM_USER + 1) +#define HKM_GETHOTKEY (WM_USER + 2) + +#define TV_FIRST 0x1100 +#define TVM_SELECTITEM (TV_FIRST + 11) +#define TVM_GETNEXTITEM (TV_FIRST + 10) +#define TVM_GETINDENT (TV_FIRST + 6) +#define TVM_GETEDITCONTROL (TV_FIRST + 15) +#define TVGN_ROOT 0 +#define TVGN_NEXT 1 +#define TVGN_PREVIOUS 2 +#define TVGN_CARET 9 +#define TVGN_FIRSTVISIBLE 5 +#define TVGN_NEXTVISIBLE 6 +#define TVGN_PREVIOUSVISIBLE 7 +#define TVGN_DROPHILITE 8 +#define TVE_COLLAPSE 0x0001 +#define TVE_EXPAND 0x0002 +#define TVIF_TEXT 0x0001 +#define TVIF_IMAGE 0x0002 +#define TVIF_PARAM 0x0004 +#define TVIF_STATE 0x0008 +#define TVIF_HANDLE 0x0010 +#define TVIF_SELECTEDIMAGE 0x0020 +#define TVIS_EXPANDED 0x0020 +#define TVIS_SELECTED 0x0002 + +typedef struct tagTVITEMA { + UINT mask; HTREEITEM hItem; UINT state, stateMask; + LPSTR pszText; int cchTextMax, iImage, iSelectedImage, cChildren; LPARAM lParam; +} TVITEMA, TVITEM, TV_ITEM; + +typedef struct tagNMHDR { HWND hwndFrom; UINT_PTR idFrom; UINT code; } NMHDR; +typedef struct tagTVDISPINFOA { NMHDR hdr; TVITEMA item; } NMTVDISPINFOA, NMTVDISPINFO; + +#define TreeView_SelectItem(hwnd, item) ((BOOL)SendMessage((hwnd), TVM_SELECTITEM, TVGN_CARET, (LPARAM)(HTREEITEM)(item))) +#define TreeView_SelectDropTarget(hwnd, item) ((BOOL)SendMessage((hwnd), TVM_SELECTITEM, TVGN_DROPHILITE, (LPARAM)(HTREEITEM)(item))) +#define TreeView_SelectSetFirstVisible(hwnd, item) ((BOOL)SendMessage((hwnd), TVM_SELECTITEM, TVGN_FIRSTVISIBLE, (LPARAM)(HTREEITEM)(item))) +#define TreeView_GetNextItem(hwnd, item, code) ((HTREEITEM)SendMessage((hwnd), TVM_GETNEXTITEM, (WPARAM)(code), (LPARAM)(HTREEITEM)(item))) +#define TreeView_GetRoot(hwnd) TreeView_GetNextItem((hwnd), NULL, TVGN_ROOT) +#define TreeView_GetNextSibling(hwnd, item) TreeView_GetNextItem((hwnd), (item), TVGN_NEXT) +#define TreeView_GetFirstVisible(hwnd) TreeView_GetNextItem((hwnd), NULL, TVGN_FIRSTVISIBLE) +#define TreeView_GetNextVisible(hwnd, item) TreeView_GetNextItem((hwnd), (item), TVGN_NEXTVISIBLE) +#define TreeView_GetPrevVisible(hwnd, item) TreeView_GetNextItem((hwnd), (item), TVGN_PREVIOUSVISIBLE) +#define TreeView_GetIndent(hwnd) ((int)SendMessage((hwnd), TVM_GETINDENT, 0, 0)) +#define TreeView_GetEditControl(hwnd) ((HWND)SendMessage((hwnd), TVM_GETEDITCONTROL, 0, 0)) + +#define LVM_FIRST 0x1000 +#define LVM_GETCOLUMNWIDTH (LVM_FIRST + 29) +#define LVM_SETCOLUMNWIDTH (LVM_FIRST + 30) +#define ListView_GetColumnWidth(hwnd, index) ((int)SendMessage((hwnd), LVM_GETCOLUMNWIDTH, (WPARAM)(int)(index), 0)) +#define ListView_SetColumnWidth(hwnd, index, width) ((BOOL)SendMessage((hwnd), LVM_SETCOLUMNWIDTH, (WPARAM)(int)(index), MAKELPARAM((width), 0))) + +#define TCM_FIRST 0x1300 +#define TCM_GETITEMCOUNT (TCM_FIRST + 4) +#define TCM_GETCURSEL (TCM_FIRST + 11) +#define TCM_GETITEMRECT (TCM_FIRST + 10) +#define TCM_SETITEMSIZE (TCM_FIRST + 41) +#define TCIF_TEXT 0x0001 +typedef struct tagTCITEMA { UINT mask; DWORD dwState, dwStateMask; LPSTR pszText; int cchTextMax, iImage; LPARAM lParam; } TCITEMA, TC_ITEM; +#define TabCtrl_GetItemCount(hwnd) ((int)SendMessage((hwnd), TCM_GETITEMCOUNT, 0, 0)) +#define TabCtrl_GetCurSel(hwnd) ((int)SendMessage((hwnd), TCM_GETCURSEL, 0, 0)) +#define TabCtrl_GetItemRect(hwnd, index, rect) ((BOOL)SendMessage((hwnd), TCM_GETITEMRECT, (WPARAM)(int)(index), (LPARAM)(RECT *)(rect))) + +extern "C" { +void InitCommonControls(void); +BOOL ImageList_BeginDrag(HIMAGELIST list, int image, int x, int y); +BOOL ImageList_DragEnter(HWND lock, int x, int y); +BOOL ImageList_DragMove(int x, int y); +BOOL ImageList_DragShowNolock(BOOL show); +void ImageList_EndDrag(void); +BOOL ImageList_Destroy(HIMAGELIST list); +} + +#define TVM_GETITEM (TV_FIRST + 12) +#define TVM_SETITEM (TV_FIRST + 13) +#define TVM_EXPAND (TV_FIRST + 2) +#define TVM_GETITEMRECT (TV_FIRST + 4) +#define TVM_CREATEDRAGIMAGE (TV_FIRST + 18) +#define TreeView_GetItem(hwnd, item) ((BOOL)SendMessage((hwnd), TVM_GETITEM, 0, (LPARAM)(TVITEM *)(item))) +#define TreeView_SetItem(hwnd, item) ((BOOL)SendMessage((hwnd), TVM_SETITEM, 0, (LPARAM)(TVITEM const *)(item))) +#define TreeView_Expand(hwnd, item, code) ((BOOL)SendMessage((hwnd), TVM_EXPAND, (WPARAM)(code), (LPARAM)(HTREEITEM)(item))) +#define TreeView_GetItemRect(hwnd, item, rect, partial) (*(HTREEITEM *)(rect) = (item), (BOOL)SendMessage((hwnd), TVM_GETITEMRECT, (WPARAM)(BOOL)(partial), (LPARAM)(RECT *)(rect))) +#define TreeView_CreateDragImage(hwnd, item) ((HIMAGELIST)SendMessage((hwnd), TVM_CREATEDRAGIMAGE, 0, (LPARAM)(HTREEITEM)(item))) + +#define TCM_GETITEM (TCM_FIRST + 5) +#define TabCtrl_GetItem(hwnd, index, item) ((BOOL)SendMessage((hwnd), TCM_GETITEM, (WPARAM)(int)(index), (LPARAM)(TC_ITEM *)(item))) diff --git a/platform/win32compat/include/conio.h b/platform/win32compat/include/conio.h index 607852b09..c9f1f4d1c 100644 --- a/platform/win32compat/include/conio.h +++ b/platform/win32compat/include/conio.h @@ -1,3 +1,4 @@ #pragma once #include #include +extern "C" int _getch(void); diff --git a/platform/win32compat/include/direct.h b/platform/win32compat/include/direct.h index 607852b09..9da0e4dc5 100644 --- a/platform/win32compat/include/direct.h +++ b/platform/win32compat/include/direct.h @@ -1,3 +1,7 @@ #pragma once #include #include +#define _MAX_DRIVE 3 +#define _MAX_DIR 256 +#define _MAX_FNAME 256 +#define _MAX_EXT 256 diff --git a/platform/win32compat/include/sys/timeb.h b/platform/win32compat/include/sys/timeb.h index 22f0d8503..586cfdef2 100644 --- a/platform/win32compat/include/sys/timeb.h +++ b/platform/win32compat/include/sys/timeb.h @@ -1,3 +1,7 @@ #pragma once #include -typedef struct _timeb { long time; unsigned short millitm; short timezone; short dstflag; } _timeb; + +struct _timeb { long time; unsigned short millitm; short timezone; short dstflag; }; +#define timeb _timeb + +extern "C" void _ftime(struct _timeb * time); diff --git a/platform/win32compat/include/windows.h b/platform/win32compat/include/windows.h index 9fa0d476d..c708634ca 100644 --- a/platform/win32compat/include/windows.h +++ b/platform/win32compat/include/windows.h @@ -65,17 +65,16 @@ OPENTS_SHIM_HANDLE(HBRUSH); OPENTS_SHIM_HANDLE(HPEN); OPENTS_SHIM_HANDLE(HFONT); OPENTS_SHIM_HANDLE(HRGN); -OPENTS_SHIM_HANDLE(HGDIOBJ); OPENTS_SHIM_HANDLE(HCURSOR); OPENTS_SHIM_HANDLE(HICON); OPENTS_SHIM_HANDLE(HMENU); OPENTS_SHIM_HANDLE(HKEY); -OPENTS_SHIM_HANDLE(HMODULE); OPENTS_SHIM_HANDLE(HMONITOR); -OPENTS_SHIM_HANDLE(HGLOBAL); -OPENTS_SHIM_HANDLE(HLOCAL); OPENTS_SHIM_HANDLE(HACCEL); -typedef HINSTANCE HMODULE_ALIAS; +typedef void *HGDIOBJ; +typedef HINSTANCE HMODULE; +typedef HANDLE HGLOBAL; +typedef HANDLE HLOCAL; typedef UINT_PTR WPARAM; typedef LONG_PTR LPARAM; @@ -168,7 +167,9 @@ typedef struct _RTL_SRWLOCK { void *Ptr; } SRWLOCK; OPENTS_SHIM_HANDLE(HRSRC); OPENTS_SHIM_HANDLE(HIMAGELIST); OPENTS_SHIM_HANDLE(HTREEITEM); -typedef struct _NMTREEVIEWA { void *opaque; } NMTREEVIEWA, *LPNMTREEVIEW; +typedef struct _NMHDR_SHIM { HWND hwndFrom; UINT_PTR idFrom; UINT code; } NMHDR_SHIM; +typedef struct _TVITEM_SHIM { UINT mask; HTREEITEM hItem; UINT state, stateMask; LPSTR pszText; int cchTextMax, iImage, iSelectedImage, cChildren; LPARAM lParam; } TVITEM_SHIM; +typedef struct _NMTREEVIEWA { NMHDR_SHIM hdr; UINT action; TVITEM_SHIM itemOld, itemNew; POINT ptDrag; } NMTREEVIEWA, NMTREEVIEW, *LPNMTREEVIEW; typedef struct DLGTEMPLATE { DWORD style, dwExtendedStyle; WORD cdit; short x, y, cx, cy; } DLGTEMPLATE, *LPDLGTEMPLATE; typedef const DLGTEMPLATE *LPCDLGTEMPLATE; @@ -203,3 +204,756 @@ typedef const DLGTEMPLATE *LPCDLGTEMPLATE; #define MB_TASKMODAL 0x2000 #define MB_SYSTEMMODAL 0x1000 #define MB_APPLMODAL 0x0 + +// --------------------------------------------------------------------------- +// Window messages +// --------------------------------------------------------------------------- +#define WM_NULL 0x0000 +#define WM_CREATE 0x0001 +#define WM_DESTROY 0x0002 +#define WM_MOVE 0x0003 +#define WM_SIZE 0x0005 +#define WM_ACTIVATE 0x0006 +#define WM_SETFOCUS 0x0007 +#define WM_KILLFOCUS 0x0008 +#define WM_ENABLE 0x000A +#define WM_SETREDRAW 0x000B +#define WM_SETTEXT 0x000C +#define WM_GETTEXT 0x000D +#define WM_GETTEXTLENGTH 0x000E +#define WM_PAINT 0x000F +#define WM_CLOSE 0x0010 +#define WM_QUIT 0x0012 +#define WM_ERASEBKGND 0x0014 +#define WM_SHOWWINDOW 0x0018 +#define WM_ACTIVATEAPP 0x001C +#define WM_SETCURSOR 0x0020 +#define WM_MOUSEACTIVATE 0x0021 +#define WM_GETMINMAXINFO 0x0024 +#define WM_SETFONT 0x0030 +#define WM_GETFONT 0x0031 +#define WM_WINDOWPOSCHANGING 0x0046 +#define WM_WINDOWPOSCHANGED 0x0047 +#define WM_CONTEXTMENU 0x007B +#define WM_DISPLAYCHANGE 0x007E +#define WM_NCDESTROY 0x0082 +#define WM_NCHITTEST 0x0084 +#define WM_NCPAINT 0x0085 +#define WM_GETDLGCODE 0x0087 +#define WM_NCMOUSEMOVE 0x00A0 +#define WM_KEYDOWN 0x0100 +#define WM_KEYUP 0x0101 +#define WM_CHAR 0x0102 +#define WM_DEADCHAR 0x0103 +#define WM_SYSKEYDOWN 0x0104 +#define WM_SYSKEYUP 0x0105 +#define WM_SYSCHAR 0x0106 +#define WM_SYSDEADCHAR 0x0107 +#define WM_KEYLAST 0x0109 +#define WM_INITDIALOG 0x0110 +#define WM_COMMAND 0x0111 +#define WM_SYSCOMMAND 0x0112 +#define WM_TIMER 0x0113 +#define WM_HSCROLL 0x0114 +#define WM_VSCROLL 0x0115 +#define WM_CTLCOLORMSGBOX 0x0132 +#define WM_CTLCOLOREDIT 0x0133 +#define WM_CTLCOLORLISTBOX 0x0134 +#define WM_CTLCOLORBTN 0x0135 +#define WM_CTLCOLORDLG 0x0136 +#define WM_CTLCOLORSCROLLBAR 0x0137 +#define WM_CTLCOLORSTATIC 0x0138 +#define WM_MOUSEMOVE 0x0200 +#define WM_LBUTTONDOWN 0x0201 +#define WM_LBUTTONUP 0x0202 +#define WM_LBUTTONDBLCLK 0x0203 +#define WM_RBUTTONDOWN 0x0204 +#define WM_RBUTTONUP 0x0205 +#define WM_RBUTTONDBLCLK 0x0206 +#define WM_MBUTTONDOWN 0x0207 +#define WM_MBUTTONUP 0x0208 +#define WM_MBUTTONDBLCLK 0x0209 +#define WM_MOUSEWHEEL 0x020A +#define WM_XBUTTONDOWN 0x020B +#define WM_XBUTTONUP 0x020C +#define WM_XBUTTONDBLCLK 0x020D +#define WM_MOUSELAST 0x020E +#define WM_MOVING 0x0216 +#define WM_CAPTURECHANGED 0x0215 +#define WM_DRAWITEM 0x002B +#define WM_MEASUREITEM 0x002C +#define WM_DELETEITEM 0x002D +#define WM_COMPAREITEM 0x0039 +#define WM_HELP 0x0053 +#define WM_NOTIFY 0x004E +#define WM_USER 0x0400 +#define WM_APP 0x8000 + +#define HTTRANSPARENT (-1) +#define HTNOWHERE 0 +#define HTCLIENT 1 +#define HTCAPTION 2 + +#define SIZE_RESTORED 0 +#define SIZE_MINIMIZED 1 +#define SIZE_MAXIMIZED 2 + +#define MK_LBUTTON 0x0001 +#define MK_RBUTTON 0x0002 +#define MK_SHIFT 0x0004 +#define MK_CONTROL 0x0008 +#define MK_MBUTTON 0x0010 + +#define SC_SIZE 0xF000 +#define SC_CLOSE 0xF060 +#define SC_SCREENSAVE 0xF140 +#define SC_MONITORPOWER 0xF170 +#define MF_BYCOMMAND 0x00000000 +#define MF_BYPOSITION 0x00000400 +#define MF_GRAYED 0x00000001 + +// --------------------------------------------------------------------------- +// Window and class styles +// --------------------------------------------------------------------------- +#define CS_VREDRAW 0x0001 +#define CS_HREDRAW 0x0002 +#define CS_DBLCLKS 0x0008 +#define CS_OWNDC 0x0020 + +#define WS_OVERLAPPED 0x00000000L +#define WS_POPUP 0x80000000L +#define WS_CHILD 0x40000000L +#define WS_MINIMIZE 0x20000000L +#define WS_VISIBLE 0x10000000L +#define WS_DISABLED 0x08000000L +#define WS_CLIPSIBLINGS 0x04000000L +#define WS_CLIPCHILDREN 0x02000000L +#define WS_MAXIMIZE 0x01000000L +#define WS_CAPTION 0x00C00000L +#define WS_BORDER 0x00800000L +#define WS_DLGFRAME 0x00400000L +#define WS_VSCROLL 0x00200000L +#define WS_HSCROLL 0x00100000L +#define WS_SYSMENU 0x00080000L +#define WS_THICKFRAME 0x00040000L +#define WS_GROUP 0x00020000L +#define WS_TABSTOP 0x00010000L +#define WS_MINIMIZEBOX 0x00020000L +#define WS_MAXIMIZEBOX 0x00010000L +#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_THICKFRAME|WS_MINIMIZEBOX|WS_MAXIMIZEBOX) +#define WS_EX_TOPMOST 0x00000008L +#define WS_EX_TOOLWINDOW 0x00000080L + +#define BS_PUSHBUTTON 0x00000000L +#define BS_CHECKBOX 0x00000002L +#define BS_AUTOCHECKBOX 0x00000003L +#define BS_RADIOBUTTON 0x00000004L +#define BS_GROUPBOX 0x00000007L +#define BS_OWNERDRAW 0x0000000BL +#define ES_MULTILINE 0x0004L +#define ES_PASSWORD 0x0020L +#define SS_CENTER 0x00000001L +#define SS_RIGHT 0x00000002L +#define LBS_NOTIFY 0x0001L +#define LBS_NOSEL 0x4000L +#define LBS_MULTIPLESEL 0x0008L + +#define GWL_STYLE (-16) +#define GWL_EXSTYLE (-20) +#define GWL_ID (-12) +#define GWL_USERDATA (-21) +#define GWL_WNDPROC (-4) +#define GWL_HINSTANCE (-6) +#define GWL_HWNDPARENT (-8) +#define GWLP_WNDPROC (-4) +#define GWLP_HINSTANCE (-6) +#define GWLP_HWNDPARENT (-8) +#define GWLP_USERDATA (-21) +#define GWLP_ID (-12) +#define DWLP_MSGRESULT 0 +#define DWLP_DLGPROC (DWLP_MSGRESULT + sizeof(LRESULT)) +#define DWLP_USER (DWLP_DLGPROC + sizeof(DLGPROC)) + +#define SW_HIDE 0 +#define SW_SHOWNORMAL 1 +#define SW_NORMAL 1 +#define SW_SHOWMINIMIZED 2 +#define SW_SHOWMAXIMIZED 3 +#define SW_SHOW 5 +#define SW_MINIMIZE 6 +#define SW_RESTORE 9 + +#define SWP_NOSIZE 0x0001 +#define SWP_NOMOVE 0x0002 +#define SWP_NOZORDER 0x0004 +#define SWP_NOACTIVATE 0x0010 +#define SWP_SHOWWINDOW 0x0040 +#define SWP_NOOWNERZORDER 0x0200 + +#define GW_HWNDFIRST 0 +#define GW_HWNDLAST 1 +#define GW_HWNDNEXT 2 +#define GW_HWNDPREV 3 +#define GW_OWNER 4 +#define GW_CHILD 5 + +#define HWND_DESKTOP ((HWND)0) +#define HWND_TOP ((HWND)0) +#define HWND_TOPMOST ((HWND)-1) + +#define RDW_INVALIDATE 0x0001 +#define RDW_INTERNALPAINT 0x0002 +#define RDW_ERASE 0x0004 +#define RDW_UPDATENOW 0x0100 +#define RDW_FRAME 0x0400 +#define RDW_ALLCHILDREN 0x0080 + +#define PM_NOREMOVE 0x0000 +#define PM_REMOVE 0x0001 +#define PM_NOYIELD 0x0002 + +#define SM_CXSCREEN 0 +#define SM_CYSCREEN 1 +#define SM_CXBORDER 5 +#define SM_CYBORDER 6 +#define SM_CXFULLSCREEN 16 +#define SM_CYFULLSCREEN 17 +#define SM_SWAPBUTTON 23 +#define SM_CXDRAG 68 +#define SM_CYDRAG 69 + +#define MOD_ALT 0x0001 +#define MOD_CONTROL 0x0002 +#define MOD_SHIFT 0x0004 + +#define IDC_ARROW ((LPCSTR)(ULONG_PTR)32512) +#define IDC_NO ((LPCSTR)(ULONG_PTR)32648) +#define IDI_APPLICATION ((LPCSTR)(ULONG_PTR)32512) +#define RT_DIALOG ((LPCSTR)(ULONG_PTR)5) +#define MB_ICONWARNING 0x30 + +#define HELP_CONTEXTMENU 0x000a +#define HELP_CONTEXTPOPUP 0x0026 + +#define MONITOR_DEFAULTTONULL 0x0 +#define MONITOR_DEFAULTTOPRIMARY 0x1 +#define MONITOR_DEFAULTTONEAREST 0x2 + +// Button, edit, list box, combo box and scroll bar messages +#define BM_GETCHECK 0x00F0 +#define BM_SETCHECK 0x00F1 +#define BM_GETSTATE 0x00F2 +#define BM_SETSTATE 0x00F3 +#define BST_UNCHECKED 0x0000 +#define BST_CHECKED 0x0001 +#define BN_CLICKED 0 +#define BN_DBLCLK 5 +#define ODT_MENU 1 +#define ODT_LISTBOX 2 +#define ODT_COMBOBOX 3 +#define ODT_BUTTON 4 +#define ODT_STATIC 5 + +#define EM_GETSEL 0x00B0 +#define EM_SETSEL 0x00B1 +#define EM_POSFROMCHAR 0x00D6 +#define EM_SETLIMITTEXT 0x00C5 +#define EN_SETFOCUS 0x0100 +#define EN_KILLFOCUS 0x0200 +#define EN_CHANGE 0x0300 +#define EN_MAXTEXT 0x0501 + +#define LB_ADDSTRING 0x0180 +#define LB_INSERTSTRING 0x0181 +#define LB_DELETESTRING 0x0182 +#define LB_RESETCONTENT 0x0184 +#define LB_SETSEL 0x0185 +#define LB_SETCURSEL 0x0186 +#define LB_GETSEL 0x0187 +#define LB_GETCURSEL 0x0188 +#define LB_GETTEXT 0x0189 +#define LB_GETTEXTLEN 0x018A +#define LB_GETCOUNT 0x018B +#define LB_SELECTSTRING 0x018C +#define LB_FINDSTRING 0x018F +#define LB_SELITEMRANGE 0x019B +#define LB_GETSELCOUNT 0x0190 +#define LB_GETSELITEMS 0x0191 +#define LB_SETITEMDATA 0x019A +#define LB_GETITEMDATA 0x0199 +#define LB_GETITEMRECT 0x0198 +#define LB_SETTOPINDEX 0x0197 +#define LB_GETTOPINDEX 0x018E +#define LB_FINDSTRINGEXACT 0x01A2 +#define LB_SETITEMHEIGHT 0x01A0 +#define LB_GETITEMHEIGHT 0x01A1 +#define LB_ERR (-1) +#define LBN_SELCHANGE 1 +#define LBN_DBLCLK 2 + +#define CB_GETEDITSEL 0x0140 +#define CB_ADDSTRING 0x0143 +#define CB_DELETESTRING 0x0144 +#define CB_GETCOUNT 0x0146 +#define CB_GETCURSEL 0x0147 +#define CB_GETLBTEXT 0x0148 +#define CB_INSERTSTRING 0x014A +#define CB_RESETCONTENT 0x014B +#define CB_FINDSTRING 0x014C +#define CB_SETCURSEL 0x014E +#define CB_SHOWDROPDOWN 0x014F +#define CB_GETITEMDATA 0x0150 +#define CB_SETITEMDATA 0x0151 +#define CB_GETDROPPEDCONTROLRECT 0x0152 +#define CB_SETITEMHEIGHT 0x0153 +#define CB_GETITEMHEIGHT 0x0154 +#define CB_GETDROPPEDSTATE 0x0157 +#define CB_GETTOPINDEX 0x015b +#define CB_SETTOPINDEX 0x015c +#define CB_ERR (-1) +#define CBN_SELCHANGE 1 + +#define SBM_SETPOS 0x00E0 +#define SBM_GETPOS 0x00E1 +#define SBM_SETRANGE 0x00E2 +#define SBM_SETSCROLLINFO 0x00E9 +#define SB_LINEUP 0 +#define SB_LINEDOWN 1 +#define SB_THUMBPOSITION 4 +#define SB_THUMBTRACK 5 +#define SB_ENDSCROLL 8 +#define SIF_RANGE 0x0001 +#define SIF_PAGE 0x0002 +#define SIF_POS 0x0004 +#define SIF_ALL 0x0017 + +typedef struct tagSCROLLINFO { UINT cbSize, fMask; int nMin, nMax; UINT nPage; int nPos, nTrackPos; } SCROLLINFO, *LPSCROLLINFO; +typedef struct tagPOINTS { SHORT x, y; } POINTS; +typedef struct tagWINDOWPOS { HWND hwnd, hwndInsertAfter; int x, y, cx, cy; UINT flags; } WINDOWPOS, *LPWINDOWPOS; +typedef struct tagMONITORINFO { DWORD cbSize; RECT rcMonitor, rcWork; DWORD dwFlags; } MONITORINFO, *LPMONITORINFO; +typedef struct tagHELPINFO { UINT cbSize; int iContextType, iCtrlId; HANDLE hItemHandle; DWORD_PTR dwContextId; POINT MousePos; } HELPINFO, *LPHELPINFO; + +#define MAKEPOINTS(l) (*((POINTS *)&(l))) +#define MAKEWPARAM(l, h) ((WPARAM)(DWORD)MAKELONG(l, h)) +#define MAKELPARAM(l, h) ((LPARAM)(DWORD)MAKELONG(l, h)) +#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp)) +#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp)) +#define IS_SURROGATE_PAIR(hi, lo) ((hi) >= 0xd800 && (hi) <= 0xdbff && (lo) >= 0xdc00 && (lo) <= 0xdfff) +#ifndef TEXT +#define TEXT(s) s +#endif + +// --------------------------------------------------------------------------- +// Window management +// --------------------------------------------------------------------------- +extern "C" { +ATOM RegisterClass(WNDCLASS const * cls); +HWND CreateWindowEx(DWORD exstyle, LPCSTR classname, LPCSTR windowname, DWORD style, int x, int y, int width, int height, HWND parent, HMENU menu, HINSTANCE instance, LPVOID param); +BOOL DestroyWindow(HWND window); +BOOL ShowWindow(HWND window, int command); +BOOL ShowWindowAsync(HWND window, int command); +BOOL UpdateWindow(HWND window); +BOOL MoveWindow(HWND window, int x, int y, int width, int height, BOOL repaint); +BOOL SetWindowPos(HWND window, HWND after, int x, int y, int cx, int cy, UINT flags); +BOOL GetClientRect(HWND window, LPRECT rect); +BOOL GetWindowRect(HWND window, LPRECT rect); +BOOL ClientToScreen(HWND window, LPPOINT point); +BOOL ScreenToClient(HWND window, LPPOINT point); +int MapWindowPoints(HWND from, HWND to, LPPOINT points, UINT count); +BOOL AdjustWindowRectEx(LPRECT rect, DWORD style, BOOL menu, DWORD exstyle); +LONG_PTR GetWindowLong(HWND window, int index); +LONG_PTR SetWindowLong(HWND window, int index, LONG_PTR value); +LONG_PTR GetWindowLongPtr(HWND window, int index); +LONG_PTR SetWindowLongPtr(HWND window, int index, LONG_PTR value); +BOOL SetWindowText(HWND window, LPCSTR text); +int GetWindowText(HWND window, LPSTR text, int max); +int GetWindowTextLength(HWND window); +int GetClassName(HWND window, LPSTR name, int max); +BOOL IsWindow(HWND window); +BOOL IsWindowVisible(HWND window); +BOOL IsWindowEnabled(HWND window); +BOOL IsChild(HWND parent, HWND child); +BOOL EnableWindow(HWND window, BOOL enable); +HWND GetParent(HWND window); +HWND GetWindow(HWND window, UINT command); +HWND GetTopWindow(HWND window); +HWND GetDesktopWindow(void); +HWND GetActiveWindow(void); +HWND SetActiveWindow(HWND window); +HWND GetFocus(void); +HWND SetFocus(HWND window); +BOOL SetForegroundWindow(HWND window); +BOOL BringWindowToTop(HWND window); +HWND FindWindow(LPCSTR classname, LPCSTR windowname); +HWND WindowFromPoint(POINT point); +HWND ChildWindowFromPoint(HWND parent, POINT point); +BOOL EnumChildWindows(HWND parent, WNDENUMPROC proc, LPARAM param); +BOOL InvalidateRect(HWND window, RECT const * rect, BOOL erase); +BOOL ValidateRect(HWND window, RECT const * rect); +BOOL GetUpdateRect(HWND window, LPRECT rect, BOOL erase); +BOOL RedrawWindow(HWND window, RECT const * rect, HRGN region, UINT flags); +BOOL CloseWindow(HWND window); +HMENU GetMenu(HWND window); +HMENU GetSystemMenu(HWND window, BOOL revert); +BOOL EnableMenuItem(HMENU menu, UINT item, UINT enable); +BOOL RegisterHotKey(HWND window, int id, UINT modifiers, UINT key); +BOOL SetRect(LPRECT rect, int left, int top, int right, int bottom); +BOOL IntersectRect(LPRECT dest, RECT const * a, RECT const * b); +BOOL PtInRect(RECT const * rect, POINT point); +int GetSystemMetrics(int index); +HMONITOR MonitorFromWindow(HWND window, DWORD flags); +BOOL GetMonitorInfo(HMONITOR monitor, LPMONITORINFO info); +int GetWindowContextHelpId(HWND window); +int MessageBox(HWND window, LPCSTR text, LPCSTR caption, UINT type); +int MessageBoxIndirect(MSGBOXPARAMS const * params); + +// --------------------------------------------------------------------------- +// Messages +// --------------------------------------------------------------------------- +BOOL GetMessage(LPMSG msg, HWND window, UINT filtermin, UINT filtermax); +BOOL PeekMessage(LPMSG msg, HWND window, UINT filtermin, UINT filtermax, UINT remove); +BOOL TranslateMessage(MSG const * msg); +LRESULT DispatchMessage(MSG const * msg); +BOOL PostMessage(HWND window, UINT message, WPARAM wparam, LPARAM lparam); +LRESULT SendMessage(HWND window, UINT message, WPARAM wparam, LPARAM lparam); +void PostQuitMessage(int code); +LRESULT DefWindowProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); +LRESULT CallWindowProc(WNDPROC proc, HWND window, UINT message, WPARAM wparam, LPARAM lparam); +int TranslateAccelerator(HWND window, HACCEL table, LPMSG msg); +UINT_PTR SetTimer(HWND window, UINT_PTR id, UINT elapse, TIMERPROC proc); +BOOL KillTimer(HWND window, UINT_PTR id); + +// --------------------------------------------------------------------------- +// Cursor, keyboard and capture +// --------------------------------------------------------------------------- +BOOL GetCursorPos(LPPOINT point); +BOOL SetCursorPos(int x, int y); +HCURSOR SetCursor(HCURSOR cursor); +int ShowCursor(BOOL show); +BOOL ClipCursor(RECT const * rect); +HWND SetCapture(HWND window); +BOOL ReleaseCapture(void); +HWND GetCapture(void); +SHORT GetAsyncKeyState(int key); +SHORT GetKeyState(int key); +UINT MapVirtualKey(UINT code, UINT type); +int GetKeyNameText(LONG param, LPSTR name, int size); +HCURSOR LoadCursor(HINSTANCE instance, LPCSTR name); +HICON LoadIcon(HINSTANCE instance, LPCSTR name); +HCURSOR CreateIconIndirect(ICONINFO * info); +BOOL DestroyCursor(HCURSOR cursor); +BOOL DestroyIcon(HICON icon); +int LoadString(HINSTANCE instance, UINT id, LPSTR buffer, int max); + +// --------------------------------------------------------------------------- +// Dialogs and controls. Every dialog in the tree is a Win32 resource template, which +// UI_DESIGN step 13 replaces; these report failure so that a caller takes its own +// no-dialog path rather than believing in a window that was never created. +// --------------------------------------------------------------------------- +HWND CreateDialogIndirectParam(HINSTANCE instance, LPCDLGTEMPLATE templ, HWND parent, DLGPROC proc, LPARAM param); +HWND CreateDialogParam(HINSTANCE instance, LPCSTR name, HWND parent, DLGPROC proc, LPARAM param); +INT_PTR DialogBoxParam(HINSTANCE instance, LPCSTR name, HWND parent, DLGPROC proc, LPARAM param); +BOOL EndDialog(HWND dialog, INT_PTR result); +BOOL IsDialogMessage(HWND dialog, LPMSG msg); +HWND GetDlgItem(HWND dialog, int id); +int GetDlgCtrlID(HWND control); +BOOL SetDlgItemText(HWND dialog, int id, LPCSTR text); +UINT GetDlgItemText(HWND dialog, int id, LPSTR text, int max); +BOOL CheckDlgButton(HWND dialog, int id, UINT check); +UINT IsDlgButtonChecked(HWND dialog, int id); +LRESULT SendDlgItemMessage(HWND dialog, int id, UINT message, WPARAM wparam, LPARAM lparam); +} + +#define SetWindowTextA SetWindowText +#define GetWindowTextA GetWindowText +#define LoadStringA LoadString +#define MessageBoxA MessageBox +#define GetClassNameA GetClassName +#define FindWindowA FindWindow + +// --------------------------------------------------------------------------- +// GDI. The engine draws its own frame into system-memory surfaces and presents it +// through bgfx, so the only GDI users left are the legacy dialog layer and the +// tactical font path. Nothing here draws. +// --------------------------------------------------------------------------- +#define BI_RGB 0 +#define BI_BITFIELDS 3 +#define DIB_RGB_COLORS 0 +#define DIB_PAL_COLORS 1 +#define SRCCOPY 0x00CC0020 +#define BLACKNESS 0x00000042 +#define COLORONCOLOR 3 +#define HALFTONE 4 +#define TRANSPARENT 1 +#define OPAQUE 2 +#define TA_LEFT 0 +#define TA_RIGHT 2 +#define TA_CENTER 6 +#define TA_TOP 0 +#define DT_LEFT 0x00000000 +#define DT_CENTER 0x00000001 +#define DT_VCENTER 0x00000004 +#define DT_SINGLELINE 0x00000020 +#define GM_COMPATIBLE 1 +#define GM_ADVANCED 2 +#define MWT_IDENTITY 1 +#define VREFRESH 116 +#define BITSPIXEL 12 +#define WHITE_BRUSH 0 +#define BLACK_BRUSH 4 +#define NULL_BRUSH 5 +#define SYSTEM_FONT 13 +#define FW_NORMAL 400 +#define FW_BOLD 700 +#define ANSI_CHARSET 0 +#define DEFAULT_CHARSET 1 +#define OUT_DEFAULT_PRECIS 0 +#define OUT_RASTER_PRECIS 6 +#define CLIP_DEFAULT_PRECIS 0 +#define DEFAULT_QUALITY 0 +#define PROOF_QUALITY 2 +#define DEFAULT_PITCH 0 +#define FF_DONTCARE 0 +#define FF_SWISS 32 + +typedef struct tagLOGFONTA { + LONG lfHeight, lfWidth, lfEscapement, lfOrientation, lfWeight; + BYTE lfItalic, lfUnderline, lfStrikeOut, lfCharSet, lfOutPrecision, lfClipPrecision, lfQuality, lfPitchAndFamily; + CHAR lfFaceName[32]; +} LOGFONTA, LOGFONT, *LPLOGFONT; + +typedef struct tagTEXTMETRICA { + LONG tmHeight, tmAscent, tmDescent, tmInternalLeading, tmExternalLeading; + LONG tmAveCharWidth, tmMaxCharWidth, tmWeight, tmOverhang, tmDigitizedAspectX, tmDigitizedAspectY; + CHAR tmFirstChar, tmLastChar, tmDefaultChar, tmBreakChar; + BYTE tmItalic, tmUnderlined, tmStruckOut, tmPitchAndFamily, tmCharSet; +} TEXTMETRICA, TEXTMETRIC, *LPTEXTMETRIC; + +extern "C" { +HDC GetDC(HWND window); +int ReleaseDC(HWND window, HDC dc); +HDC CreateCompatibleDC(HDC dc); +BOOL DeleteDC(HDC dc); +int SaveDC(HDC dc); +BOOL RestoreDC(HDC dc, int state); +HGDIOBJ SelectObject(HDC dc, HGDIOBJ object); +BOOL DeleteObject(HGDIOBJ object); +int GetObject(HGDIOBJ object, int size, LPVOID buffer); +HGDIOBJ GetStockObject(int index); +HBRUSH CreateSolidBrush(COLORREF color); +HBITMAP CreateBitmap(int width, int height, UINT planes, UINT bits, void const * data); +HBITMAP CreateDIBSection(HDC dc, BITMAPINFO const * info, UINT usage, void ** bits, HANDLE section, DWORD offset); +HFONT CreateFont(int height, int width, int escapement, int orientation, int weight, DWORD italic, DWORD underline, DWORD strikeout, DWORD charset, DWORD outprecision, DWORD clipprecision, DWORD quality, DWORD pitch, LPCSTR face); +HFONT CreateFontIndirect(LOGFONT const * font); +int GetDeviceCaps(HDC dc, int index); +BOOL BitBlt(HDC dest, int x, int y, int width, int height, HDC source, int sx, int sy, DWORD rop); +BOOL StretchBlt(HDC dest, int x, int y, int width, int height, HDC source, int sx, int sy, int swidth, int sheight, DWORD rop); +int SetStretchBltMode(HDC dc, int mode); +int SetDIBitsToDevice(HDC dc, int x, int y, DWORD width, DWORD height, int sx, int sy, UINT start, UINT lines, void const * bits, BITMAPINFO const * info, UINT usage); +BOOL TextOut(HDC dc, int x, int y, LPCSTR text, int length); +int DrawText(HDC dc, LPCSTR text, int length, LPRECT rect, UINT format); +BOOL GetTextExtentPoint32(HDC dc, LPCSTR text, int length, LPSIZE size); +BOOL GetTextMetrics(HDC dc, LPTEXTMETRIC metrics); +UINT SetTextAlign(HDC dc, UINT align); +COLORREF SetTextColor(HDC dc, COLORREF color); +COLORREF GetTextColor(HDC dc); +COLORREF SetBkColor(HDC dc, COLORREF color); +COLORREF GetBkColor(HDC dc); +int SetBkMode(HDC dc, int mode); +int GetBkMode(HDC dc); +int SetGraphicsMode(HDC dc, int mode); +BOOL SetViewportOrgEx(HDC dc, int x, int y, LPPOINT previous); +BOOL SetWindowOrgEx(HDC dc, int x, int y, LPPOINT previous); +BOOL DPtoLP(HDC dc, LPPOINT points, int count); +BOOL FillRect(HDC dc, RECT const * rect, HBRUSH brush); +BOOL PatBlt(HDC dc, int x, int y, int width, int height, DWORD rop); +void GdiFlush(void); +BOOL EnumDisplaySettings(LPCSTR device, DWORD mode, DEVMODE * settings); +} + +// --------------------------------------------------------------------------- +// Kernel services +// --------------------------------------------------------------------------- +#define ERROR_SUCCESS 0L +#define ERROR_ALREADY_EXISTS 183L +#define ERROR_FILE_NOT_FOUND 2L +#define WAIT_OBJECT_0 0L +#define WAIT_TIMEOUT 258L +#define WAIT_FAILED 0xFFFFFFFFL +#define MUTEX_ALL_ACCESS 0x1F0001L +#define INFINITE 0xFFFFFFFFL +#define GENERIC_READ 0x80000000L +#define GENERIC_WRITE 0x40000000L +#define FILE_SHARE_READ 0x00000001L +#define FILE_SHARE_WRITE 0x00000002L +#define CREATE_NEW 1 +#define CREATE_ALWAYS 2 +#define OPEN_EXISTING 3 +#define OPEN_ALWAYS 4 +#define FILE_BEGIN 0 +#define FILE_CURRENT 1 +#define FILE_END 2 +#define INVALID_SET_FILE_POINTER 0xFFFFFFFFL +#define INVALID_FILE_ATTRIBUTES 0xFFFFFFFFL +#define FILE_ATTRIBUTE_READONLY 0x00000001L +#define FILE_ATTRIBUTE_HIDDEN 0x00000002L +#define FILE_ATTRIBUTE_SYSTEM 0x00000004L +#define FILE_ATTRIBUTE_DIRECTORY 0x00000010L +#define FILE_ATTRIBUTE_ARCHIVE 0x00000020L +#define FILE_ATTRIBUTE_NORMAL 0x00000080L +#define FILE_ATTRIBUTE_TEMPORARY 0x00000100L +#define CP_ACP 0 +#define CP_OEMCP 1 +#define CP_UTF8 65001 +#define STD_INPUT_HANDLE ((DWORD)-10) +#define STD_OUTPUT_HANDLE ((DWORD)-11) +#define STD_ERROR_HANDLE ((DWORD)-12) +#define FORMAT_MESSAGE_FROM_SYSTEM 0x00001000 +#define FORMAT_MESSAGE_ALLOCATE_BUFFER 0x00000100 +#define FORMAT_MESSAGE_IGNORE_INSERTS 0x00000200 +#define LANG_NEUTRAL 0x00 +#define SUBLANG_DEFAULT 0x01 +#define LANG_USER_DEFAULT 0x0400 +#define MAKELANGID(p, s) ((((WORD)(s)) << 10) | (WORD)(p)) +#define TIME_NOSECONDS 0x0002 +#define TIME_NOMINUTESORSECONDS 0x0001 +#define HKEY_LOCAL_MACHINE ((HKEY)(ULONG_PTR)0x80000002) +#define KEY_READ 0x20019 +#define SRWLOCK_INIT { NULL } + +typedef struct _COORD { SHORT X, Y; } COORD; +typedef struct _SMALL_RECT { SHORT Left, Top, Right, Bottom; } SMALL_RECT; +typedef struct _CONSOLE_SCREEN_BUFFER_INFO { COORD dwSize, dwCursorPosition; WORD wAttributes; SMALL_RECT srWindow; COORD dwMaximumWindowSize; } CONSOLE_SCREEN_BUFFER_INFO; +typedef struct _RTL_OSVERSIONINFOW { ULONG dwOSVersionInfoSize, dwMajorVersion, dwMinorVersion, dwBuildNumber, dwPlatformId; WCHAR szCSDVersion[128]; } RTL_OSVERSIONINFOW, *PRTL_OSVERSIONINFOW; +typedef BYTE *PBYTE; + +extern "C" { +DWORD GetLastError(void); +void SetLastError(DWORD code); +HANDLE CreateMutex(LPSECURITY_ATTRIBUTES attributes, BOOL owner, LPCSTR name); +HANDLE OpenMutex(DWORD access, BOOL inherit, LPCSTR name); +DWORD WaitForSingleObject(HANDLE object, DWORD milliseconds); +BOOL ReleaseMutex(HANDLE mutex); +BOOL CloseHandle(HANDLE object); +DWORD GetCurrentProcessId(void); +DWORD GetCurrentThreadId(void); +BOOL IsDebuggerPresent(void); +void OutputDebugString(LPCSTR text); +void Sleep(DWORD milliseconds); +HMODULE GetModuleHandle(LPCSTR name); +HMODULE LoadLibrary(LPCSTR name); +BOOL FreeLibrary(HMODULE module); +FARPROC GetProcAddress(HMODULE module, LPCSTR name); +DWORD GetModuleFileName(HMODULE module, LPSTR name, DWORD size); +HLOCAL LocalFree(HLOCAL memory); +LPWSTR GetCommandLineW(void); +LPWSTR * CommandLineToArgvW(LPCWSTR commandline, int * count); +UINT GetACP(void); +UINT GetOEMCP(void); + +HANDLE CreateFileA(LPCSTR name, DWORD access, DWORD share, LPSECURITY_ATTRIBUTES attributes, DWORD disposition, DWORD flags, HANDLE templatefile); +BOOL ReadFile(HANDLE file, LPVOID buffer, DWORD size, LPDWORD read, LPOVERLAPPED overlapped); +BOOL WriteFile(HANDLE file, LPCVOID buffer, DWORD size, LPDWORD written, LPOVERLAPPED overlapped); +DWORD SetFilePointer(HANDLE file, LONG distance, LONG * distancehigh, DWORD method); +BOOL DeleteFileA(LPCSTR name); +BOOL CopyFile(LPCSTR from, LPCSTR to, BOOL failifexists); +BOOL CreateDirectory(LPCSTR path, LPSECURITY_ATTRIBUTES attributes); +BOOL SetCurrentDirectory(LPCSTR path); +DWORD GetFileAttributesA(LPCSTR name); +HANDLE FindFirstFile(LPCSTR name, LPWIN32_FIND_DATA data); +BOOL FindNextFile(HANDLE find, LPWIN32_FIND_DATA data); +BOOL FindClose(HANDLE find); +LONG CompareFileTime(FILETIME const * a, FILETIME const * b); +BOOL FileTimeToLocalFileTime(FILETIME const * file, LPFILETIME local); +BOOL FileTimeToSystemTime(FILETIME const * file, LPSYSTEMTIME system); +BOOL SystemTimeToFileTime(SYSTEMTIME const * system, LPFILETIME file); +void GetSystemTime(LPSYSTEMTIME system); + +BOOL AllocConsole(void); +HWND GetConsoleWindow(void); +HANDLE GetStdHandle(DWORD which); +BOOL SetConsoleTitle(LPCSTR title); +BOOL WriteConsole(HANDLE console, void const * buffer, DWORD length, LPDWORD written, LPVOID reserved); +BOOL GetConsoleScreenBufferInfo(HANDLE console, CONSOLE_SCREEN_BUFFER_INFO * info); + +void InitializeSRWLock(SRWLOCK * lock); +void AcquireSRWLockExclusive(SRWLOCK * lock); +void ReleaseSRWLockExclusive(SRWLOCK * lock); + +DWORD GetFileVersionInfoSize(LPCSTR name, LPDWORD handle); +BOOL GetFileVersionInfo(LPCSTR name, DWORD handle, DWORD length, LPVOID data); +BOOL VerQueryValue(LPCVOID block, LPCSTR path, LPVOID * buffer, UINT * length); +HRSRC FindResource(HMODULE module, LPCSTR name, LPCSTR type); +HGLOBAL LoadResource(HMODULE module, HRSRC resource); +LPVOID LockResource(HGLOBAL resource); +DWORD SizeofResource(HMODULE module, HRSRC resource); +LONG RegOpenKeyEx(HKEY key, LPCSTR subkey, DWORD options, DWORD access, HKEY * result); +LONG RegQueryValueEx(HKEY key, LPCSTR name, LPDWORD reserved, LPDWORD type, LPBYTE data, LPDWORD size); +LONG RegCloseKey(HKEY key); +} + +#define DeleteFile DeleteFileA +#define GetFileAttributes GetFileAttributesA +#define CreateFile CreateFileA +#define GetFileVersionInfoSizeA GetFileVersionInfoSize +#define GetFileVersionInfoA GetFileVersionInfo +#define VerQueryValueA VerQueryValue +#define GetModuleFileNameA GetModuleFileName +#define LoadLibraryA LoadLibrary +#define GetModuleHandleA GetModuleHandle +#define OutputDebugStringA OutputDebugString +#define SetConsoleTitleA SetConsoleTitle +#define WriteConsoleA WriteConsole +#define RegOpenKeyExA RegOpenKeyEx +#define RegQueryValueExA RegQueryValueEx +#define CreateMutexA CreateMutex +#define OpenMutexA OpenMutex +#define FindFirstFileA FindFirstFile +#define FindNextFileA FindNextFile +#define CreateDirectoryA CreateDirectory +#define SetCurrentDirectoryA SetCurrentDirectory +#define CopyFileA CopyFile + +// --------------------------------------------------------------------------- +// The remaining entry points the tree names, grouped with the ones above by role. +// --------------------------------------------------------------------------- +typedef struct _XFORM { FLOAT eM11, eM12, eM21, eM22, eDx, eDy; } XFORM; + +extern "C" { +int MultiByteToWideChar(UINT codepage, DWORD flags, LPCSTR source, int sourcelength, LPWSTR dest, int destlength); +int WideCharToMultiByte(UINT codepage, DWORD flags, LPCWSTR source, int sourcelength, LPSTR dest, int destlength, LPCSTR defaultchar, LPBOOL useddefault); +void GetLocalTime(LPSYSTEMTIME time); +int GetTimeFormat(DWORD locale, DWORD flags, SYSTEMTIME const * time, LPCSTR format, LPSTR buffer, int size); +int GetDateFormat(DWORD locale, DWORD flags, SYSTEMTIME const * time, LPCSTR format, LPSTR buffer, int size); +DWORD FormatMessage(DWORD flags, LPCVOID source, DWORD id, DWORD language, LPSTR buffer, DWORD size, void * arguments); +BOOL SetStdHandle(DWORD which, HANDLE handle); +BOOL SetConsoleCP(UINT codepage); +BOOL SetConsoleOutputCP(UINT codepage); +BOOL SetConsoleScreenBufferSize(HANDLE console, COORD size); +BOOL DeleteMenu(HMENU menu, UINT position, UINT flags); +BOOL WinHelp(HWND window, LPCSTR help, UINT command, ULONG_PTR data); +int ToUnicode(UINT key, UINT scan, BYTE const * state, LPWSTR buffer, int size, UINT flags); +HWND GetNextDlgTabItem(HWND dialog, HWND control, BOOL previous); +BOOL ModifyWorldTransform(HDC dc, XFORM const * transform, DWORD mode); +} + +#define SNDMSG SendMessage +#define SendMessageA SendMessage +#define PostMessageA PostMessage +#define GetWindowLongPtrA GetWindowLongPtr +#define SetWindowLongPtrA SetWindowLongPtr +#define GetWindowLongA GetWindowLong +#define SetWindowLongA SetWindowLong +#define MultiByteToWideCharA MultiByteToWideChar +#define GetTimeFormatA GetTimeFormat +#define GetDateFormatA GetDateFormat +#define FormatMessageA FormatMessage +#define WinHelpA WinHelp +#define GetKeyNameTextA GetKeyNameText +#define SetDlgItemTextA SetDlgItemText +#define GetDlgItemTextA GetDlgItemText +#define CreateFontA CreateFont +#define TextOutA TextOut +#define DrawTextA DrawText +#define GetTextExtentPoint32A GetTextExtentPoint32 +#define EnumDisplaySettingsA EnumDisplaySettings diff --git a/platform/win32compat/include/windowsx.h b/platform/win32compat/include/windowsx.h index 720e64a9b..85f7a1f04 100644 --- a/platform/win32compat/include/windowsx.h +++ b/platform/win32compat/include/windowsx.h @@ -1,2 +1,46 @@ #pragma once #include + +// The control wrappers Windows spells as macros over SendMessage. Keeping them as macros +// keeps the call sites identical to the supported build. +#define Button_GetCheck(hwnd) ((int)SendMessage((hwnd), BM_GETCHECK, 0, 0)) +#define Button_SetCheck(hwnd, check) ((void)SendMessage((hwnd), BM_SETCHECK, (WPARAM)(int)(check), 0)) +#define Button_Enable(hwnd, enable) EnableWindow((hwnd), (BOOL)(enable)) +#define Button_SetText(hwnd, text) SetWindowText((hwnd), (text)) +#define Static_SetText(hwnd, text) SetWindowText((hwnd), (text)) +#define Edit_SetText(hwnd, text) SetWindowText((hwnd), (text)) +#define Edit_GetText(hwnd, text, max) GetWindowText((hwnd), (text), (max)) + +#define ListBox_AddString(hwnd, text) ((int)SendMessage((hwnd), LB_ADDSTRING, 0, (LPARAM)(LPCSTR)(text))) +#define ListBox_InsertString(hwnd, index, text) ((int)SendMessage((hwnd), LB_INSERTSTRING, (WPARAM)(int)(index), (LPARAM)(LPCSTR)(text))) +#define ListBox_DeleteString(hwnd, index) ((int)SendMessage((hwnd), LB_DELETESTRING, (WPARAM)(int)(index), 0)) +#define ListBox_ResetContent(hwnd) ((BOOL)SendMessage((hwnd), LB_RESETCONTENT, 0, 0)) +#define ListBox_GetCount(hwnd) ((int)SendMessage((hwnd), LB_GETCOUNT, 0, 0)) +#define ListBox_GetCurSel(hwnd) ((int)SendMessage((hwnd), LB_GETCURSEL, 0, 0)) +#define ListBox_SetCurSel(hwnd, index) ((int)SendMessage((hwnd), LB_SETCURSEL, (WPARAM)(int)(index), 0)) +#define ListBox_GetSel(hwnd, index) ((int)SendMessage((hwnd), LB_GETSEL, (WPARAM)(int)(index), 0)) +#define ListBox_SetSel(hwnd, select, index) ((int)SendMessage((hwnd), LB_SETSEL, (WPARAM)(BOOL)(select), (LPARAM)(int)(index))) +#define ListBox_GetText(hwnd, index, text) ((int)SendMessage((hwnd), LB_GETTEXT, (WPARAM)(int)(index), (LPARAM)(LPCSTR)(text))) +#define ListBox_GetItemData(hwnd, index) ((LRESULT)SendMessage((hwnd), LB_GETITEMDATA, (WPARAM)(int)(index), 0)) +#define ListBox_SetItemData(hwnd, index, data) ((int)SendMessage((hwnd), LB_SETITEMDATA, (WPARAM)(int)(index), (LPARAM)(data))) +#define ListBox_SetTopIndex(hwnd, index) ((int)SendMessage((hwnd), LB_SETTOPINDEX, (WPARAM)(int)(index), 0)) +#define ListBox_GetTopIndex(hwnd) ((int)SendMessage((hwnd), LB_GETTOPINDEX, 0, 0)) +#define ListBox_FindStringExact(hwnd, start, text) ((int)SendMessage((hwnd), LB_FINDSTRINGEXACT, (WPARAM)(int)(start), (LPARAM)(LPCSTR)(text))) +#define ListBox_SetItemHeight(hwnd, index, height) ((int)SendMessage((hwnd), LB_SETITEMHEIGHT, (WPARAM)(int)(index), MAKELPARAM((height), 0))) +#define ListBox_GetItemHeight(hwnd, index) ((int)SendMessage((hwnd), LB_GETITEMHEIGHT, (WPARAM)(int)(index), 0)) + +#define ComboBox_AddString(hwnd, text) ((int)SendMessage((hwnd), CB_ADDSTRING, 0, (LPARAM)(LPCSTR)(text))) +#define ComboBox_InsertString(hwnd, index, text) ((int)SendMessage((hwnd), CB_INSERTSTRING, (WPARAM)(int)(index), (LPARAM)(LPCSTR)(text))) +#define ComboBox_DeleteString(hwnd, index) ((int)SendMessage((hwnd), CB_DELETESTRING, (WPARAM)(int)(index), 0)) +#define ComboBox_ResetContent(hwnd) ((int)SendMessage((hwnd), CB_RESETCONTENT, 0, 0)) +#define ComboBox_GetCount(hwnd) ((int)SendMessage((hwnd), CB_GETCOUNT, 0, 0)) +#define ComboBox_GetCurSel(hwnd) ((int)SendMessage((hwnd), CB_GETCURSEL, 0, 0)) +#define ComboBox_SetCurSel(hwnd, index) ((int)SendMessage((hwnd), CB_SETCURSEL, (WPARAM)(int)(index), 0)) +#define ComboBox_FindString(hwnd, start, text) ((int)SendMessage((hwnd), CB_FINDSTRING, (WPARAM)(int)(start), (LPARAM)(LPCSTR)(text))) +#define ComboBox_GetItemData(hwnd, index) ((LRESULT)SendMessage((hwnd), CB_GETITEMDATA, (WPARAM)(int)(index), 0)) +#define ComboBox_SetItemData(hwnd, index, data) ((int)SendMessage((hwnd), CB_SETITEMDATA, (WPARAM)(int)(index), (LPARAM)(data))) +#define ComboBox_GetLBText(hwnd, index, text) ((int)SendMessage((hwnd), CB_GETLBTEXT, (WPARAM)(int)(index), (LPARAM)(LPCSTR)(text))) +#define ComboBox_GetDroppedControlRect(hwnd, rect) ((void)SendMessage((hwnd), CB_GETDROPPEDCONTROLRECT, 0, (LPARAM)(RECT *)(rect))) +#define ComboBox_GetDroppedState(hwnd) ((BOOL)SendMessage((hwnd), CB_GETDROPPEDSTATE, 0, 0)) +#define ComboBox_ShowDropdown(hwnd, show) ((BOOL)SendMessage((hwnd), CB_SHOWDROPDOWN, (WPARAM)(BOOL)(show), 0)) +#define Edit_SetSel(hwnd, start, end) ((void)SendMessage((hwnd), EM_SETSEL, (WPARAM)(int)(start), (LPARAM)(int)(end))) diff --git a/platform/win32compat/include/winnt.h b/platform/win32compat/include/winnt.h index 720e64a9b..079a447ae 100644 --- a/platform/win32compat/include/winnt.h +++ b/platform/win32compat/include/winnt.h @@ -1,2 +1,45 @@ #pragma once #include + +// The sync record hook reads its own module's PE headers to recover the build's +// symbol layout. Nothing outside Windows has a PE image to read; the declarations +// exist so the file still compiles and its reader reports that it found nothing. +#define IMAGE_DOS_SIGNATURE 0x5A4D +#define IMAGE_NT_SIGNATURE 0x00004550 +#define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10B +#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16 + +typedef struct _IMAGE_DOS_HEADER { + WORD e_magic, e_cblp, e_cp, e_crlc, e_cparhdr, e_minalloc, e_maxalloc; + WORD e_ss, e_sp, e_csum, e_ip, e_cs, e_lfarlc, e_ovno, e_res[4]; + WORD e_oemid, e_oeminfo, e_res2[10]; + LONG e_lfanew; +} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; + +typedef struct _IMAGE_FILE_HEADER { + WORD Machine, NumberOfSections; + DWORD TimeDateStamp, PointerToSymbolTable, NumberOfSymbols; + WORD SizeOfOptionalHeader, Characteristics; +} IMAGE_FILE_HEADER; + +typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress, Size; } IMAGE_DATA_DIRECTORY; + +typedef struct _IMAGE_OPTIONAL_HEADER { + WORD Magic; BYTE MajorLinkerVersion, MinorLinkerVersion; + DWORD SizeOfCode, SizeOfInitializedData, SizeOfUninitializedData; + DWORD AddressOfEntryPoint, BaseOfCode, BaseOfData, ImageBase; + DWORD SectionAlignment, FileAlignment; + WORD MajorOperatingSystemVersion, MinorOperatingSystemVersion; + WORD MajorImageVersion, MinorImageVersion, MajorSubsystemVersion, MinorSubsystemVersion; + DWORD Win32VersionValue, SizeOfImage, SizeOfHeaders, CheckSum; + WORD Subsystem, DllCharacteristics; + DWORD SizeOfStackReserve, SizeOfStackCommit, SizeOfHeapReserve, SizeOfHeapCommit; + DWORD LoaderFlags, NumberOfRvaAndSizes; + IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; +} IMAGE_OPTIONAL_HEADER32; + +typedef struct _IMAGE_NT_HEADERS { + DWORD Signature; + IMAGE_FILE_HEADER FileHeader; + IMAGE_OPTIONAL_HEADER32 OptionalHeader; +} IMAGE_NT_HEADERS32, IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS; From acd72545ca036e15955787c1d47038de1e74c04a Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 21:17:24 +0100 Subject: [PATCH 049/179] Supply the window, message loop and cursor from SDL Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- CMakeLists.txt | 5 + code/CMakeLists.txt | 8 +- code/winstub.cpp | 16 + platform/CMakeLists.txt | 33 ++ platform/win32compat/src/dialog.cpp | 105 ++++ platform/win32compat/src/entry.cpp | 34 ++ platform/win32compat/src/gdi.cpp | 76 +++ platform/win32compat/src/input.cpp | 329 +++++++++++ platform/win32compat/src/kernel.cpp | 770 ++++++++++++++++++++++++ platform/win32compat/src/message.cpp | 411 +++++++++++++ platform/win32compat/src/win32compat.h | 46 ++ platform/win32compat/src/window.cpp | 780 +++++++++++++++++++++++++ 12 files changed, 2609 insertions(+), 4 deletions(-) create mode 100644 platform/CMakeLists.txt create mode 100644 platform/win32compat/src/dialog.cpp create mode 100644 platform/win32compat/src/entry.cpp create mode 100644 platform/win32compat/src/gdi.cpp create mode 100644 platform/win32compat/src/input.cpp create mode 100644 platform/win32compat/src/kernel.cpp create mode 100644 platform/win32compat/src/message.cpp create mode 100644 platform/win32compat/src/win32compat.h create mode 100644 platform/win32compat/src/window.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d7f902f1d..2fae28873 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,11 @@ add_custom_target(OpenTSBuildStamp ALL ) add_subdirectory(thirdparty) + +if(NOT WIN32) + add_subdirectory(platform) +endif() + add_subdirectory(code) add_subdirectory(code/language) diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index f1e93a057..4feb36e0c 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -156,10 +156,10 @@ target_include_directories(OpenTS PRIVATE "${OPENTS_GENERATED_DIR}" ) -# Spike scaffolding. The shim only declares types, so a native compile reports the Win32 -# surface the tree still needs instead of stopping at the first missing header. -if(OPENTS_EXPERIMENTAL_NATIVE AND NOT WIN32) - target_include_directories(OpenTS PRIVATE "${CMAKE_SOURCE_DIR}/spike/win32shim") +# Windows supplies the window, the message loop and the cursor itself. Every other target +# gets them from the compatibility layer, which carries the headers as well as the code. +if(NOT WIN32) + target_link_libraries(OpenTS PRIVATE win32compat) endif() # The generated build stamp has to exist before anything compiles. diff --git a/code/winstub.cpp b/code/winstub.cpp index e11e6fc62..b79b2b424 100644 --- a/code/winstub.cpp +++ b/code/winstub.cpp @@ -355,9 +355,21 @@ LRESULT CALLBACK /*_export*/ Windows_Procedure(HWND hwnd, UINT message, WPARAM w } +#ifndef _WIN32 +// The host toolkit owns the surface the renderer presents into, and hands over the layer +// it created for it. Windows presents into the window handle itself. +extern "C" void * Win32Compat_Native_Window_Handle(HWND window); +extern "C" int Win32Compat_Window_Refresh_Rate(HWND window); +#endif + + NativeWindow Win_Native_Window(HWND window) { +#ifdef _WIN32 return(NativeWindow{ NATIVE_WINDOW_DEFAULT, nullptr, window }); +#else + return(NativeWindow{ NATIVE_WINDOW_DEFAULT, nullptr, Win32Compat_Native_Window_Handle(window) }); +#endif } @@ -377,6 +389,9 @@ bool Win_Window_Drawable_Size(HWND window, int & width, int & height) int Win_Window_Refresh_Rate(HWND window) { +#ifndef _WIN32 + return(Win32Compat_Window_Refresh_Rate(window)); +#else int refreshrate = 0; HDC dc = GetDC(window); @@ -386,6 +401,7 @@ int Win_Window_Refresh_Rate(HWND window) } return(refreshrate); +#endif } diff --git a/platform/CMakeLists.txt b/platform/CMakeLists.txt new file mode 100644 index 000000000..2f9e39d2c --- /dev/null +++ b/platform/CMakeLists.txt @@ -0,0 +1,33 @@ +# +# --------------------------------------------------------- +# Win32 compatibility layer (non-Windows targets) +# --------------------------------------------------------- +# +# The engine is written against the Win32 window, message, cursor and GDI surface: the +# window procedure, the keyboard queue, the scroll handler and every dialog driver switch +# on WM_ values. Rather than teaching those call sites a second event model, this library +# keeps the surface and supplies it from SDL, so both builds dispatch the same messages. +# +# What it implements is the window, the event loop, the cursor and the keyboard. What it +# refuses is the dialog and GDI surface, which docs/UI_DESIGN.md step 13 replaces rather +# than ports: those entry points report failure so a caller takes its own no-dialog path. +add_library(win32compat STATIC + "${CMAKE_CURRENT_SOURCE_DIR}/win32compat/src/dialog.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/win32compat/src/entry.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/win32compat/src/gdi.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/win32compat/src/input.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/win32compat/src/kernel.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/win32compat/src/message.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/win32compat/src/window.cpp" +) + +target_compile_features(win32compat PRIVATE cxx_std_20) + +# Consumers include and the rest of the surface by their Windows spellings. +target_include_directories(win32compat PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/win32compat/include") + +target_link_libraries(win32compat PUBLIC SDL3::SDL3-static) + +if(APPLE) + target_link_libraries(win32compat PUBLIC "-framework QuartzCore") +endif() diff --git a/platform/win32compat/src/dialog.cpp b/platform/win32compat/src/dialog.cpp new file mode 100644 index 000000000..a3fdfe541 --- /dev/null +++ b/platform/win32compat/src/dialog.cpp @@ -0,0 +1,105 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#include "win32compat.h" + +#include + +// Every dialog in the tree is a Win32 resource template driven by OwnerDraw, and +// docs/UI_DESIGN.md step 13 replaces that layer rather than porting it. Creating a dialog +// therefore fails here, and each driver takes the path it already has for a dialog that +// could not be created. Nothing below draws or measures anything: a control that was +// never created answers no message. + +extern "C" HWND CreateDialogIndirectParam(HINSTANCE instance, LPCDLGTEMPLATE templ, HWND parent, + DLGPROC proc, LPARAM param) +{ + (void)instance; + (void)templ; + (void)parent; + (void)proc; + (void)param; + return(NULL); +} + + +extern "C" HWND CreateDialogParam(HINSTANCE instance, LPCSTR name, HWND parent, DLGPROC proc, LPARAM param) +{ + (void)instance; + (void)name; + (void)parent; + (void)proc; + (void)param; + return(NULL); +} + + +extern "C" INT_PTR DialogBoxParam(HINSTANCE instance, LPCSTR name, HWND parent, DLGPROC proc, LPARAM param) +{ + (void)instance; + (void)name; + (void)parent; + (void)proc; + (void)param; + return(IDCANCEL); +} + + +extern "C" BOOL EndDialog(HWND dialog, INT_PTR result) { (void)dialog; (void)result; return(TRUE); } +extern "C" BOOL IsDialogMessage(HWND dialog, LPMSG msg) { (void)dialog; (void)msg; return(FALSE); } +extern "C" HWND GetDlgItem(HWND dialog, int id) { (void)dialog; (void)id; return(NULL); } +extern "C" int GetDlgCtrlID(HWND control) { (void)control; return(0); } +extern "C" HWND GetNextDlgTabItem(HWND dialog, HWND control, BOOL previous) { (void)dialog; (void)control; (void)previous; return(NULL); } +extern "C" BOOL SetDlgItemText(HWND dialog, int id, LPCSTR text) { (void)dialog; (void)id; (void)text; return(FALSE); } +extern "C" BOOL CheckDlgButton(HWND dialog, int id, UINT check) { (void)dialog; (void)id; (void)check; return(FALSE); } +extern "C" UINT IsDlgButtonChecked(HWND dialog, int id) { (void)dialog; (void)id; return(BST_UNCHECKED); } + + +extern "C" UINT GetDlgItemText(HWND dialog, int id, LPSTR text, int max) +{ + (void)dialog; + (void)id; + + if (text != NULL && max > 0) { + text[0] = '\0'; + } + + return(0); +} + + +extern "C" LRESULT SendDlgItemMessage(HWND dialog, int id, UINT message, WPARAM wparam, LPARAM lparam) +{ + return(SendMessage(GetDlgItem(dialog, id), message, wparam, lparam)); +} + + +extern "C" void InitCommonControls(void) {} +extern "C" BOOL ImageList_BeginDrag(HIMAGELIST list, int image, int x, int y) { (void)list; (void)image; (void)x; (void)y; return(FALSE); } +extern "C" BOOL ImageList_DragEnter(HWND lock, int x, int y) { (void)lock; (void)x; (void)y; return(FALSE); } +extern "C" BOOL ImageList_DragMove(int x, int y) { (void)x; (void)y; return(FALSE); } +extern "C" BOOL ImageList_DragShowNolock(BOOL show) { (void)show; return(FALSE); } +extern "C" void ImageList_EndDrag(void) {} +extern "C" BOOL ImageList_Destroy(HIMAGELIST list) { (void)list; return(FALSE); } + + +// The string table lives in the Windows resource script. The engine's own loader reports +// a missing string by returning an empty one, which is what a caller here receives. +extern "C" int LoadString(HINSTANCE instance, UINT id, LPSTR buffer, int max) +{ + (void)instance; + (void)id; + + if (buffer == NULL || max <= 0) { + return(0); + } + + buffer[0] = '\0'; + return(0); +} diff --git a/platform/win32compat/src/entry.cpp b/platform/win32compat/src/entry.cpp new file mode 100644 index 000000000..6c657725d --- /dev/null +++ b/platform/win32compat/src/entry.cpp @@ -0,0 +1,34 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#include "win32compat.h" + +// Windows enters the game at WinMain. Nothing else does, so the host's entry point records +// the arguments the shell handed over and hands control to the same function the supported +// build starts in. + +int CALLBACK WinMain(HINSTANCE instance, HINSTANCE previous, char * commandline, int show); + +void Win32_Record_Arguments(int argc, char ** argv); + + +int main(int argc, char ** argv) +{ + Win32_Record_Arguments(argc, argv); + + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { + SDL_Log("SDL could not start: %s", SDL_GetError()); + return(1); + } + + int const result = WinMain((HINSTANCE)(ULONG_PTR)1, NULL, NULL, 1); + + SDL_Quit(); + return(result); +} diff --git a/platform/win32compat/src/gdi.cpp b/platform/win32compat/src/gdi.cpp new file mode 100644 index 000000000..bd1c63073 --- /dev/null +++ b/platform/win32compat/src/gdi.cpp @@ -0,0 +1,76 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#include "win32compat.h" + +// The game composes its own frame in system memory and presents it through bgfx, so GDI +// is reached only by the legacy dialog layer and by the tactical map's text, both of which +// draw with the host font on Windows. Nothing here draws: a device context that does not +// exist refuses every call, and the callers fall back to the engine's own font path. + +extern "C" HDC GetDC(HWND window) { (void)window; return(NULL); } +extern "C" int ReleaseDC(HWND window, HDC dc) { (void)window; (void)dc; return(0); } +extern "C" HDC CreateCompatibleDC(HDC dc) { (void)dc; return(NULL); } +extern "C" BOOL DeleteDC(HDC dc) { (void)dc; return(FALSE); } +extern "C" int SaveDC(HDC dc) { (void)dc; return(0); } +extern "C" BOOL RestoreDC(HDC dc, int state) { (void)dc; (void)state; return(FALSE); } +extern "C" HGDIOBJ SelectObject(HDC dc, HGDIOBJ object) { (void)dc; (void)object; return(NULL); } +extern "C" BOOL DeleteObject(HGDIOBJ object) { (void)object; return(FALSE); } +extern "C" int GetObject(HGDIOBJ object, int size, LPVOID buffer) { (void)object; (void)size; (void)buffer; return(0); } +extern "C" HGDIOBJ GetStockObject(int index) { (void)index; return(NULL); } +extern "C" HBRUSH CreateSolidBrush(COLORREF color) { (void)color; return(NULL); } +extern "C" HBITMAP CreateBitmap(int width, int height, UINT planes, UINT bits, void const * data) { (void)width; (void)height; (void)planes; (void)bits; (void)data; return(NULL); } +extern "C" HBITMAP CreateDIBSection(HDC dc, BITMAPINFO const * info, UINT usage, void ** bits, HANDLE section, DWORD offset) { (void)dc; (void)info; (void)usage; (void)section; (void)offset; if (bits != NULL) *bits = NULL; return(NULL); } +extern "C" HFONT CreateFont(int height, int width, int escapement, int orientation, int weight, DWORD italic, DWORD underline, DWORD strikeout, DWORD charset, DWORD outprecision, DWORD clipprecision, DWORD quality, DWORD pitch, LPCSTR face) { (void)height; (void)width; (void)escapement; (void)orientation; (void)weight; (void)italic; (void)underline; (void)strikeout; (void)charset; (void)outprecision; (void)clipprecision; (void)quality; (void)pitch; (void)face; return(NULL); } +extern "C" HFONT CreateFontIndirect(LOGFONT const * font) { (void)font; return(NULL); } +extern "C" BOOL BitBlt(HDC dest, int x, int y, int width, int height, HDC source, int sx, int sy, DWORD rop) { (void)dest; (void)x; (void)y; (void)width; (void)height; (void)source; (void)sx; (void)sy; (void)rop; return(FALSE); } +extern "C" BOOL StretchBlt(HDC dest, int x, int y, int width, int height, HDC source, int sx, int sy, int swidth, int sheight, DWORD rop) { (void)dest; (void)x; (void)y; (void)width; (void)height; (void)source; (void)sx; (void)sy; (void)swidth; (void)sheight; (void)rop; return(FALSE); } +extern "C" int SetStretchBltMode(HDC dc, int mode) { (void)dc; (void)mode; return(0); } +extern "C" int SetDIBitsToDevice(HDC dc, int x, int y, DWORD width, DWORD height, int sx, int sy, UINT start, UINT lines, void const * bits, BITMAPINFO const * info, UINT usage) { (void)dc; (void)x; (void)y; (void)width; (void)height; (void)sx; (void)sy; (void)start; (void)lines; (void)bits; (void)info; (void)usage; return(0); } +extern "C" BOOL TextOut(HDC dc, int x, int y, LPCSTR text, int length) { (void)dc; (void)x; (void)y; (void)text; (void)length; return(FALSE); } +extern "C" int DrawText(HDC dc, LPCSTR text, int length, LPRECT rect, UINT format) { (void)dc; (void)text; (void)length; (void)rect; (void)format; return(0); } +extern "C" BOOL GetTextMetrics(HDC dc, LPTEXTMETRIC metrics) { (void)dc; (void)metrics; return(FALSE); } +extern "C" UINT SetTextAlign(HDC dc, UINT align) { (void)dc; (void)align; return(0); } +extern "C" COLORREF SetTextColor(HDC dc, COLORREF color) { (void)dc; (void)color; return(0); } +extern "C" COLORREF GetTextColor(HDC dc) { (void)dc; return(0); } +extern "C" COLORREF SetBkColor(HDC dc, COLORREF color) { (void)dc; (void)color; return(0); } +extern "C" COLORREF GetBkColor(HDC dc) { (void)dc; return(0); } +extern "C" int SetBkMode(HDC dc, int mode) { (void)dc; (void)mode; return(0); } +extern "C" int GetBkMode(HDC dc) { (void)dc; return(0); } +extern "C" int SetGraphicsMode(HDC dc, int mode) { (void)dc; (void)mode; return(0); } +extern "C" BOOL SetViewportOrgEx(HDC dc, int x, int y, LPPOINT previous) { (void)dc; (void)x; (void)y; (void)previous; return(FALSE); } +extern "C" BOOL SetWindowOrgEx(HDC dc, int x, int y, LPPOINT previous) { (void)dc; (void)x; (void)y; (void)previous; return(FALSE); } +extern "C" BOOL DPtoLP(HDC dc, LPPOINT points, int count) { (void)dc; (void)points; (void)count; return(FALSE); } +extern "C" BOOL FillRect(HDC dc, RECT const * rect, HBRUSH brush) { (void)dc; (void)rect; (void)brush; return(FALSE); } +extern "C" BOOL PatBlt(HDC dc, int x, int y, int width, int height, DWORD rop) { (void)dc; (void)x; (void)y; (void)width; (void)height; (void)rop; return(FALSE); } +extern "C" BOOL ModifyWorldTransform(HDC dc, XFORM const * transform, DWORD mode) { (void)dc; (void)transform; (void)mode; return(FALSE); } +extern "C" void GdiFlush(void) {} + + +extern "C" BOOL GetTextExtentPoint32(HDC dc, LPCSTR text, int length, LPSIZE size) +{ + (void)dc; + (void)text; + (void)length; + + if (size != NULL) { + size->cx = 0; + size->cy = 0; + } + + return(FALSE); +} + + +extern "C" int GetDeviceCaps(HDC dc, int index) +{ + (void)dc; + (void)index; + return(0); +} diff --git a/platform/win32compat/src/input.cpp b/platform/win32compat/src/input.cpp new file mode 100644 index 000000000..ea2f40136 --- /dev/null +++ b/platform/win32compat/src/input.cpp @@ -0,0 +1,329 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#include "win32compat.h" + +#include + +// The virtual key codes the engine's keyboard queue is written against. They are stated +// as numbers rather than taken from the engine's own header so that this layer does not +// depend on the engine it serves. +static int const VK_LBUTTON_CODE = 0x01; +static int const VK_RBUTTON_CODE = 0x02; +static int const VK_MBUTTON_CODE = 0x04; + +struct KeyMapping +{ + SDL_Scancode Scancode; + int VirtualKey; +}; + +static KeyMapping const _Keys[] = { + { SDL_SCANCODE_BACKSPACE, 0x08 }, { SDL_SCANCODE_TAB, 0x09 }, + { SDL_SCANCODE_RETURN, 0x0D }, { SDL_SCANCODE_ESCAPE, 0x1B }, + { SDL_SCANCODE_SPACE, 0x20 }, { SDL_SCANCODE_PAGEUP, 0x21 }, + { SDL_SCANCODE_PAGEDOWN, 0x22 }, { SDL_SCANCODE_END, 0x23 }, + { SDL_SCANCODE_HOME, 0x24 }, { SDL_SCANCODE_LEFT, 0x25 }, + { SDL_SCANCODE_UP, 0x26 }, { SDL_SCANCODE_RIGHT, 0x27 }, + { SDL_SCANCODE_DOWN, 0x28 }, { SDL_SCANCODE_INSERT, 0x2D }, + { SDL_SCANCODE_DELETE, 0x2E }, + { SDL_SCANCODE_LSHIFT, 0x10 }, { SDL_SCANCODE_RSHIFT, 0x10 }, + { SDL_SCANCODE_LCTRL, 0x11 }, { SDL_SCANCODE_RCTRL, 0x11 }, + { SDL_SCANCODE_LALT, 0x12 }, { SDL_SCANCODE_RALT, 0x12 }, + { SDL_SCANCODE_CAPSLOCK, 0x14 }, { SDL_SCANCODE_PAUSE, 0x13 }, + { SDL_SCANCODE_PRINTSCREEN, 0x2C }, { SDL_SCANCODE_SCROLLLOCK, 0x91 }, + { SDL_SCANCODE_NUMLOCKCLEAR, 0x90 }, + { SDL_SCANCODE_KP_0, 0x60 }, { SDL_SCANCODE_KP_1, 0x61 }, { SDL_SCANCODE_KP_2, 0x62 }, + { SDL_SCANCODE_KP_3, 0x63 }, { SDL_SCANCODE_KP_4, 0x64 }, { SDL_SCANCODE_KP_5, 0x65 }, + { SDL_SCANCODE_KP_6, 0x66 }, { SDL_SCANCODE_KP_7, 0x67 }, { SDL_SCANCODE_KP_8, 0x68 }, + { SDL_SCANCODE_KP_9, 0x69 }, { SDL_SCANCODE_KP_MULTIPLY, 0x6A }, + { SDL_SCANCODE_KP_PLUS, 0x6B }, { SDL_SCANCODE_KP_MINUS, 0x6D }, + { SDL_SCANCODE_KP_PERIOD, 0x6E }, { SDL_SCANCODE_KP_DIVIDE, 0x6F }, + { SDL_SCANCODE_KP_ENTER, 0x0D }, + { SDL_SCANCODE_F1, 0x70 }, { SDL_SCANCODE_F2, 0x71 }, { SDL_SCANCODE_F3, 0x72 }, + { SDL_SCANCODE_F4, 0x73 }, { SDL_SCANCODE_F5, 0x74 }, { SDL_SCANCODE_F6, 0x75 }, + { SDL_SCANCODE_F7, 0x76 }, { SDL_SCANCODE_F8, 0x77 }, { SDL_SCANCODE_F9, 0x78 }, + { SDL_SCANCODE_F10, 0x79 }, { SDL_SCANCODE_F11, 0x7A }, { SDL_SCANCODE_F12, 0x7B }, + { SDL_SCANCODE_SEMICOLON, 0xBA }, { SDL_SCANCODE_EQUALS, 0xBB }, + { SDL_SCANCODE_COMMA, 0xBC }, { SDL_SCANCODE_MINUS, 0xBD }, + { SDL_SCANCODE_PERIOD, 0xBE }, { SDL_SCANCODE_SLASH, 0xBF }, + { SDL_SCANCODE_GRAVE, 0xC0 }, { SDL_SCANCODE_LEFTBRACKET, 0xDB }, + { SDL_SCANCODE_BACKSLASH, 0xDC }, { SDL_SCANCODE_RIGHTBRACKET, 0xDD }, + { SDL_SCANCODE_APOSTROPHE, 0xDE }, +}; + + +int Win32_Virtual_Key(SDL_Scancode scancode, SDL_Keycode keycode) +{ + if (scancode >= SDL_SCANCODE_A && scancode <= SDL_SCANCODE_Z) { + return('A' + (scancode - SDL_SCANCODE_A)); + } + + if (scancode == SDL_SCANCODE_0) { + return('0'); + } + + if (scancode >= SDL_SCANCODE_1 && scancode <= SDL_SCANCODE_9) { + return('1' + (scancode - SDL_SCANCODE_1)); + } + + for (KeyMapping const & mapping : _Keys) { + if (mapping.Scancode == scancode) { + return(mapping.VirtualKey); + } + } + + if (keycode > 0 && keycode < 128) { + return(SDL_toupper((int)keycode)); + } + + return(0); +} + + +static SDL_Scancode Scancode_For_Virtual_Key(int key) +{ + if (key >= 'A' && key <= 'Z') { + return((SDL_Scancode)(SDL_SCANCODE_A + (key - 'A'))); + } + + if (key == '0') { + return(SDL_SCANCODE_0); + } + + if (key > '0' && key <= '9') { + return((SDL_Scancode)(SDL_SCANCODE_1 + (key - '1'))); + } + + for (KeyMapping const & mapping : _Keys) { + if (mapping.VirtualKey == key) { + return(mapping.Scancode); + } + } + + return(SDL_SCANCODE_UNKNOWN); +} + + +// The engine polls held keys through this rather than through the message queue, so it has +// to answer from the host's live keyboard state and not from anything this layer buffers. +extern "C" SHORT GetAsyncKeyState(int key) +{ + SDL_MouseButtonFlags const buttons = SDL_GetMouseState(NULL, NULL); + + switch (key) { + case VK_LBUTTON_CODE: return((buttons & SDL_BUTTON_LMASK) != 0 ? (SHORT)0x8000 : 0); + case VK_RBUTTON_CODE: return((buttons & SDL_BUTTON_RMASK) != 0 ? (SHORT)0x8000 : 0); + case VK_MBUTTON_CODE: return((buttons & SDL_BUTTON_MMASK) != 0 ? (SHORT)0x8000 : 0); + default: break; + } + + SDL_Scancode const scancode = Scancode_For_Virtual_Key(key); + + if (scancode == SDL_SCANCODE_UNKNOWN) { + return(0); + } + + int count = 0; + bool const * state = SDL_GetKeyboardState(&count); + + if (state == NULL || (int)scancode >= count) { + return(0); + } + + return(state[scancode] ? (SHORT)0x8000 : 0); +} + + +extern "C" SHORT GetKeyState(int key) +{ + SHORT const held = GetAsyncKeyState(key); + + // Only the toggling keys carry a low bit, and the host reports those as modifiers. + SDL_Keymod const modifiers = SDL_GetModState(); + + if (key == 0x14) { + return(held | ((modifiers & SDL_KMOD_CAPS) != 0 ? 1 : 0)); + } + + if (key == 0x90) { + return(held | ((modifiers & SDL_KMOD_NUM) != 0 ? 1 : 0)); + } + + return(held); +} + + +extern "C" UINT MapVirtualKey(UINT code, UINT type) +{ + switch (type) { + // A virtual key to a scan code, and back. + case 0: return((UINT)Scancode_For_Virtual_Key((int)code)); + case 1: return((UINT)Win32_Virtual_Key((SDL_Scancode)code, 0)); + + // A virtual key to the character it types unshifted. + case 2: { + SDL_Scancode const scancode = Scancode_For_Virtual_Key((int)code); + SDL_Keycode const keycode = SDL_GetKeyFromScancode(scancode, SDL_KMOD_NONE, false); + return(keycode < 128 ? (UINT)SDL_toupper((int)keycode) : 0); + } + + default: + return(0); + } +} + + +extern "C" int GetKeyNameText(LONG param, LPSTR name, int size) +{ + if (name == NULL || size <= 0) { + return(0); + } + + name[0] = '\0'; + + SDL_Scancode const scancode = (SDL_Scancode)((param >> 16) & 0xFF); + char const * text = SDL_GetScancodeName(scancode); + + if (text == NULL) { + return(0); + } + + SDL_strlcpy(name, text, (size_t)size); + return((int)strlen(name)); +} + + +extern "C" int ToUnicode(UINT key, UINT scan, BYTE const * state, LPWSTR buffer, int size, UINT flags) +{ + (void)scan; + (void)state; + (void)flags; + + if (buffer == NULL || size <= 0) { + return(0); + } + + SDL_Scancode const scancode = Scancode_For_Virtual_Key((int)key); + SDL_Keymod const modifiers = SDL_GetModState(); + SDL_Keycode const keycode = SDL_GetKeyFromScancode(scancode, modifiers, false); + + if (keycode == 0 || keycode > 0xFFFF) { + return(0); + } + + buffer[0] = (wchar_t)keycode; + return(1); +} + + +extern "C" BOOL GetCursorPos(LPPOINT point) +{ + if (point == NULL) { + return(FALSE); + } + + float x = 0.0f; + float y = 0.0f; + SDL_GetGlobalMouseState(&x, &y); + + float const density = Win32_Pixel_Density(); + point->x = (LONG)(x * density); + point->y = (LONG)(y * density); + return(TRUE); +} + + +extern "C" BOOL SetCursorPos(int x, int y) +{ + float const density = Win32_Pixel_Density(); + return(SDL_WarpMouseGlobal(x / density, y / density) ? TRUE : FALSE); +} + + +static bool _CursorVisible = true; +static int _CursorCount; +static HWND _Capture; + + +extern "C" HCURSOR SetCursor(HCURSOR cursor) +{ + // The game draws its own pointer from its own shapes, so the host's pointer is only + // ever shown or hidden and never replaced. + if (cursor == NULL) { + SDL_HideCursor(); + } else { + SDL_ShowCursor(); + } + + return(NULL); +} + + +extern "C" int ShowCursor(BOOL show) +{ + _CursorCount += show ? 1 : -1; + + bool const visible = _CursorCount >= 0; + if (visible != _CursorVisible) { + _CursorVisible = visible; + if (visible) { + SDL_ShowCursor(); + } else { + SDL_HideCursor(); + } + } + + return(_CursorCount); +} + + +// Confining the pointer is what the game does at the window's edge while scrolling. SDL +// confines to a window rather than to a desktop rectangle, so the request is honoured at +// window granularity and a rectangle smaller than the window is not. +extern "C" BOOL ClipCursor(RECT const * rect) +{ + Win32Window * main = Win32_Lookup(Win32_Main_Window()); + + if (main == NULL || main->Handle == NULL) { + return(FALSE); + } + + return(SDL_SetWindowMouseGrab(main->Handle, rect != NULL) ? TRUE : FALSE); +} + + +extern "C" HWND SetCapture(HWND window) +{ + HWND const previous = _Capture; + _Capture = window; + SDL_CaptureMouse(true); + return(previous); +} + + +extern "C" BOOL ReleaseCapture(void) +{ + _Capture = NULL; + SDL_CaptureMouse(false); + return(TRUE); +} + + +extern "C" HWND GetCapture(void) +{ + return(_Capture); +} + + +extern "C" HCURSOR LoadCursor(HINSTANCE instance, LPCSTR name) { (void)instance; (void)name; return(NULL); } +extern "C" HICON LoadIcon(HINSTANCE instance, LPCSTR name) { (void)instance; (void)name; return(NULL); } +extern "C" HCURSOR CreateIconIndirect(ICONINFO * info) { (void)info; return(NULL); } +extern "C" BOOL DestroyCursor(HCURSOR cursor) { (void)cursor; return(TRUE); } +extern "C" BOOL DestroyIcon(HICON icon) { (void)icon; return(TRUE); } diff --git a/platform/win32compat/src/kernel.cpp b/platform/win32compat/src/kernel.cpp new file mode 100644 index 000000000..ce9c56fe1 --- /dev/null +++ b/platform/win32compat/src/kernel.cpp @@ -0,0 +1,770 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#include "win32compat.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static DWORD _LastError; + + +extern "C" DWORD GetLastError(void) { return(_LastError); } +extern "C" void SetLastError(DWORD code) { _LastError = code; } + + +// The mutexes exist to keep a second copy of the game and the installer's autoplay from +// running at once. A process-local handle answers both callers correctly for a single run +// and refuses nothing, which is the behaviour a first native launch needs. +extern "C" HANDLE CreateMutex(LPSECURITY_ATTRIBUTES attributes, BOOL owner, LPCSTR name) +{ + (void)attributes; + (void)owner; + (void)name; + _LastError = ERROR_SUCCESS; + return((HANDLE)new std::recursive_mutex()); +} + + +extern "C" HANDLE OpenMutex(DWORD access, BOOL inherit, LPCSTR name) +{ + (void)access; + (void)inherit; + (void)name; + _LastError = ERROR_FILE_NOT_FOUND; + return(NULL); +} + + +extern "C" DWORD WaitForSingleObject(HANDLE object, DWORD milliseconds) +{ + (void)object; + (void)milliseconds; + return(WAIT_OBJECT_0); +} + + +extern "C" BOOL ReleaseMutex(HANDLE mutex) { (void)mutex; return(TRUE); } + + +extern "C" DWORD GetCurrentProcessId(void) { return((DWORD)getpid()); } +extern "C" DWORD GetCurrentThreadId(void) { return((DWORD)SDL_GetCurrentThreadID()); } +extern "C" BOOL IsDebuggerPresent(void) { return(FALSE); } +extern "C" void Sleep(DWORD milliseconds) { SDL_Delay(milliseconds); } + + +extern "C" void OutputDebugString(LPCSTR text) +{ + if (text != NULL) { + std::fputs(text, stderr); + } +} + + +// Handles stand in for modules so that a caller can tell "the running program" from +// "some other module"; nothing here loads code. +extern "C" HMODULE GetModuleHandle(LPCSTR name) { (void)name; return((HMODULE)(ULONG_PTR)1); } +extern "C" HMODULE LoadLibrary(LPCSTR name) { (void)name; return(NULL); } +extern "C" BOOL FreeLibrary(HMODULE module) { (void)module; return(TRUE); } +extern "C" FARPROC GetProcAddress(HMODULE module, LPCSTR name) { (void)module; (void)name; return(NULL); } + + +extern "C" DWORD GetModuleFileName(HMODULE module, LPSTR name, DWORD size) +{ + (void)module; + + if (name == NULL || size == 0) { + return(0); + } + + name[0] = '\0'; + + uint32_t length = size; + if (_NSGetExecutablePath(name, &length) != 0) { + return(0); + } + + return((DWORD)strlen(name)); +} + + +// The engine asks the shell for its command line and then splits it. The real argument +// vector is captured at entry, so the wide line handed back is built from that and split +// back into the same arguments rather than re-parsed by a quoting rule this host lacks. +static std::vector _Arguments; +static std::wstring _CommandLine; + +void Win32_Record_Arguments(int argc, char ** argv) +{ + _Arguments.clear(); + _CommandLine.clear(); + + for (int index = 0; index < argc; index++) { + std::string const argument(argv[index] != NULL ? argv[index] : ""); + _Arguments.push_back(std::wstring(argument.begin(), argument.end())); + + if (index > 0) { + _CommandLine += L' '; + } + _CommandLine += _Arguments.back(); + } +} + + +extern "C" LPWSTR GetCommandLineW(void) +{ + return(const_cast(_CommandLine.c_str())); +} + + +extern "C" LPWSTR * CommandLineToArgvW(LPCWSTR commandline, int * count) +{ + (void)commandline; + + static std::vector pointers; + pointers.clear(); + + for (std::wstring & argument : _Arguments) { + pointers.push_back(const_cast(argument.c_str())); + } + + if (count != NULL) { + *count = (int)pointers.size(); + } + + return(pointers.empty() ? NULL : pointers.data()); +} + + +extern "C" HLOCAL LocalFree(HLOCAL memory) { (void)memory; return(NULL); } + + +// The host has no legacy single-byte code page, so both conversions are UTF-8 only. A +// character with no UTF-8 spelling does not exist, so nothing is lost in that direction; +// the other direction is what the engine's own substitute-glyph path already covers. +extern "C" UINT GetACP(void) { return(CP_UTF8); } +extern "C" UINT GetOEMCP(void) { return(CP_UTF8); } + + +extern "C" int MultiByteToWideChar(UINT codepage, DWORD flags, LPCSTR source, int sourcelength, + LPWSTR dest, int destlength) +{ + (void)codepage; + (void)flags; + + if (source == NULL) { + return(0); + } + + size_t const length = sourcelength < 0 ? strlen(source) + 1 : (size_t)sourcelength; + + if (dest == NULL || destlength == 0) { + return((int)length); + } + + size_t const copied = length < (size_t)destlength ? length : (size_t)destlength; + for (size_t index = 0; index < copied; index++) { + dest[index] = (wchar_t)(unsigned char)source[index]; + } + + return((int)copied); +} + + +extern "C" int WideCharToMultiByte(UINT codepage, DWORD flags, LPCWSTR source, int sourcelength, + LPSTR dest, int destlength, LPCSTR defaultchar, LPBOOL useddefault) +{ + (void)codepage; + (void)flags; + (void)defaultchar; + + if (useddefault != NULL) { + *useddefault = FALSE; + } + + if (source == NULL) { + return(0); + } + + size_t length = 0; + if (sourcelength < 0) { + while (source[length] != 0) length++; + length++; + } else { + length = (size_t)sourcelength; + } + + if (dest == NULL || destlength == 0) { + return((int)length); + } + + size_t const copied = length < (size_t)destlength ? length : (size_t)destlength; + for (size_t index = 0; index < copied; index++) { + dest[index] = source[index] < 128 ? (char)source[index] : '?'; + } + + return((int)copied); +} + + +static FILETIME File_Time_From_Unix(time_t seconds) +{ + // The Windows epoch is 1601, and the count is in hundred-nanosecond units. + unsigned long long const ticks = (unsigned long long)seconds * 10000000ULL + 116444736000000000ULL; + FILETIME time; + time.dwLowDateTime = (DWORD)(ticks & 0xFFFFFFFFULL); + time.dwHighDateTime = (DWORD)(ticks >> 32); + return(time); +} + + +static unsigned long long Ticks_From_File_Time(FILETIME const & time) +{ + return(((unsigned long long)time.dwHighDateTime << 32) | (unsigned long long)time.dwLowDateTime); +} + + +extern "C" void GetSystemTime(LPSYSTEMTIME system) +{ + if (system == NULL) { + return; + } + + timeval now; + gettimeofday(&now, NULL); + + tm parts; + gmtime_r(&now.tv_sec, &parts); + + system->wYear = (WORD)(parts.tm_year + 1900); + system->wMonth = (WORD)(parts.tm_mon + 1); + system->wDayOfWeek = (WORD)parts.tm_wday; + system->wDay = (WORD)parts.tm_mday; + system->wHour = (WORD)parts.tm_hour; + system->wMinute = (WORD)parts.tm_min; + system->wSecond = (WORD)parts.tm_sec; + system->wMilliseconds = (WORD)(now.tv_usec / 1000); +} + + +extern "C" void GetLocalTime(LPSYSTEMTIME system) +{ + if (system == NULL) { + return; + } + + timeval now; + gettimeofday(&now, NULL); + + tm parts; + localtime_r(&now.tv_sec, &parts); + + system->wYear = (WORD)(parts.tm_year + 1900); + system->wMonth = (WORD)(parts.tm_mon + 1); + system->wDayOfWeek = (WORD)parts.tm_wday; + system->wDay = (WORD)parts.tm_mday; + system->wHour = (WORD)parts.tm_hour; + system->wMinute = (WORD)parts.tm_min; + system->wSecond = (WORD)parts.tm_sec; + system->wMilliseconds = (WORD)(now.tv_usec / 1000); +} + + +extern "C" BOOL SystemTimeToFileTime(SYSTEMTIME const * system, LPFILETIME file) +{ + if (system == NULL || file == NULL) { + return(FALSE); + } + + tm parts = {}; + parts.tm_year = system->wYear - 1900; + parts.tm_mon = system->wMonth - 1; + parts.tm_mday = system->wDay; + parts.tm_hour = system->wHour; + parts.tm_min = system->wMinute; + parts.tm_sec = system->wSecond; + + *file = File_Time_From_Unix(timegm(&parts)); + return(TRUE); +} + + +extern "C" BOOL FileTimeToSystemTime(FILETIME const * file, LPSYSTEMTIME system) +{ + if (file == NULL || system == NULL) { + return(FALSE); + } + + time_t const seconds = (time_t)((Ticks_From_File_Time(*file) - 116444736000000000ULL) / 10000000ULL); + + tm parts; + gmtime_r(&seconds, &parts); + + system->wYear = (WORD)(parts.tm_year + 1900); + system->wMonth = (WORD)(parts.tm_mon + 1); + system->wDayOfWeek = (WORD)parts.tm_wday; + system->wDay = (WORD)parts.tm_mday; + system->wHour = (WORD)parts.tm_hour; + system->wMinute = (WORD)parts.tm_min; + system->wSecond = (WORD)parts.tm_sec; + system->wMilliseconds = 0; + return(TRUE); +} + + +extern "C" BOOL FileTimeToLocalFileTime(FILETIME const * file, LPFILETIME local) +{ + if (file == NULL || local == NULL) { + return(FALSE); + } + + *local = *file; + return(TRUE); +} + + +extern "C" LONG CompareFileTime(FILETIME const * a, FILETIME const * b) +{ + if (a == NULL || b == NULL) { + return(0); + } + + unsigned long long const left = Ticks_From_File_Time(*a); + unsigned long long const right = Ticks_From_File_Time(*b); + return(left < right ? -1 : (left > right ? 1 : 0)); +} + + +extern "C" void _ftime(struct _timeb * time) +{ + if (time == NULL) { + return; + } + + timeval now; + gettimeofday(&now, NULL); + time->time = (long)now.tv_sec; + time->millitm = (unsigned short)(now.tv_usec / 1000); + time->timezone = 0; + time->dstflag = 0; +} + + +extern "C" int _getch(void) +{ + return(std::getchar()); +} + + +extern "C" int GetTimeFormat(DWORD locale, DWORD flags, SYSTEMTIME const * time, LPCSTR format, + LPSTR buffer, int size) +{ + (void)locale; + (void)format; + + if (buffer == NULL || size <= 0 || time == NULL) { + return(0); + } + + if ((flags & TIME_NOMINUTESORSECONDS) != 0) { + snprintf(buffer, (size_t)size, "%02u", time->wHour); + } else if ((flags & TIME_NOSECONDS) != 0) { + snprintf(buffer, (size_t)size, "%02u:%02u", time->wHour, time->wMinute); + } else { + snprintf(buffer, (size_t)size, "%02u:%02u:%02u", time->wHour, time->wMinute, time->wSecond); + } + + return((int)strlen(buffer) + 1); +} + + +extern "C" int GetDateFormat(DWORD locale, DWORD flags, SYSTEMTIME const * time, LPCSTR format, + LPSTR buffer, int size) +{ + (void)locale; + (void)flags; + (void)format; + + if (buffer == NULL || size <= 0 || time == NULL) { + return(0); + } + + snprintf(buffer, (size_t)size, "%04u-%02u-%02u", time->wYear, time->wMonth, time->wDay); + return((int)strlen(buffer) + 1); +} + + +extern "C" DWORD FormatMessage(DWORD flags, LPCVOID source, DWORD id, DWORD language, + LPSTR buffer, DWORD size, void * arguments) +{ + (void)flags; + (void)source; + (void)language; + (void)arguments; + + if (buffer == NULL || size == 0) { + return(0); + } + + snprintf(buffer, size, "%s", strerror((int)id)); + return((DWORD)strlen(buffer)); +} + + +// +// --------------------------------------------------------- +// Files +// --------------------------------------------------------- +// +extern "C" HANDLE CreateFileA(LPCSTR name, DWORD access, DWORD share, LPSECURITY_ATTRIBUTES attributes, + DWORD disposition, DWORD flags, HANDLE templatefile) +{ + (void)share; + (void)attributes; + (void)flags; + (void)templatefile; + + if (name == NULL) { + return(INVALID_HANDLE_VALUE); + } + + char const * mode = "rb"; + if ((access & GENERIC_WRITE) != 0) { + mode = disposition == OPEN_EXISTING ? "r+b" : "w+b"; + } + + std::FILE * file = std::fopen(name, mode); + + if (file == NULL) { + _LastError = (DWORD)errno; + return(INVALID_HANDLE_VALUE); + } + + return((HANDLE)file); +} + + +extern "C" BOOL ReadFile(HANDLE handle, LPVOID buffer, DWORD size, LPDWORD read, LPOVERLAPPED overlapped) +{ + (void)overlapped; + + if (handle == INVALID_HANDLE_VALUE || handle == NULL) { + return(FALSE); + } + + size_t const got = std::fread(buffer, 1, size, (std::FILE *)handle); + + if (read != NULL) { + *read = (DWORD)got; + } + + return(TRUE); +} + + +extern "C" BOOL WriteFile(HANDLE handle, LPCVOID buffer, DWORD size, LPDWORD written, LPOVERLAPPED overlapped) +{ + (void)overlapped; + + if (handle == INVALID_HANDLE_VALUE || handle == NULL) { + return(FALSE); + } + + size_t const put = std::fwrite(buffer, 1, size, (std::FILE *)handle); + + if (written != NULL) { + *written = (DWORD)put; + } + + return(TRUE); +} + + +extern "C" DWORD SetFilePointer(HANDLE handle, LONG distance, LONG * distancehigh, DWORD method) +{ + (void)distancehigh; + + if (handle == INVALID_HANDLE_VALUE || handle == NULL) { + return(INVALID_SET_FILE_POINTER); + } + + int const origin = method == FILE_BEGIN ? SEEK_SET : (method == FILE_END ? SEEK_END : SEEK_CUR); + + if (std::fseek((std::FILE *)handle, distance, origin) != 0) { + return(INVALID_SET_FILE_POINTER); + } + + return((DWORD)std::ftell((std::FILE *)handle)); +} + + +extern "C" BOOL CloseHandle(HANDLE handle) +{ + if (handle == NULL || handle == INVALID_HANDLE_VALUE) { + return(FALSE); + } + + std::fclose((std::FILE *)handle); + return(TRUE); +} + + +extern "C" BOOL DeleteFileA(LPCSTR name) +{ + return(name != NULL && std::remove(name) == 0 ? TRUE : FALSE); +} + + +extern "C" BOOL CopyFile(LPCSTR from, LPCSTR to, BOOL failifexists) +{ + if (from == NULL || to == NULL) { + return(FALSE); + } + + std::error_code error; + auto const options = failifexists + ? std::filesystem::copy_options::none + : std::filesystem::copy_options::overwrite_existing; + std::filesystem::copy_file(from, to, options, error); + return(error ? FALSE : TRUE); +} + + +extern "C" BOOL CreateDirectory(LPCSTR path, LPSECURITY_ATTRIBUTES attributes) +{ + (void)attributes; + + if (path == NULL) { + return(FALSE); + } + + std::error_code error; + return(std::filesystem::create_directory(path, error) ? TRUE : FALSE); +} + + +extern "C" BOOL SetCurrentDirectory(LPCSTR path) +{ + return(path != NULL && chdir(path) == 0 ? TRUE : FALSE); +} + + +extern "C" DWORD GetFileAttributesA(LPCSTR name) +{ + struct stat status; + + if (name == NULL || stat(name, &status) != 0) { + return(INVALID_FILE_ATTRIBUTES); + } + + DWORD attributes = FILE_ATTRIBUTE_NORMAL; + if (S_ISDIR(status.st_mode)) attributes = FILE_ATTRIBUTE_DIRECTORY; + if ((status.st_mode & S_IWUSR) == 0) attributes |= FILE_ATTRIBUTE_READONLY; + return(attributes); +} + + +// Directory enumeration keeps the shape the callers expect: one handle that walks a +// directory and matches each entry against the pattern the caller supplied. +struct Win32Find +{ + DIR * Directory; + std::string Path; + std::string Pattern; +}; + + +static bool Fill_Find_Data(Win32Find * find, LPWIN32_FIND_DATA data) +{ + dirent * entry = NULL; + + while ((entry = readdir(find->Directory)) != NULL) { + if (fnmatch(find->Pattern.c_str(), entry->d_name, FNM_CASEFOLD) != 0) { + continue; + } + + std::string const full = find->Path + "/" + entry->d_name; + + struct stat status; + if (stat(full.c_str(), &status) != 0) { + continue; + } + + memset(data, 0, sizeof(*data)); + data->dwFileAttributes = S_ISDIR(status.st_mode) ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL; + data->nFileSizeLow = (DWORD)status.st_size; + data->nFileSizeHigh = (DWORD)((unsigned long long)status.st_size >> 32); + data->ftLastWriteTime = File_Time_From_Unix(status.st_mtime); + data->ftCreationTime = data->ftLastWriteTime; + data->ftLastAccessTime = data->ftLastWriteTime; + strncpy(data->cFileName, entry->d_name, sizeof(data->cFileName) - 1); + return(true); + } + + return(false); +} + + +extern "C" HANDLE FindFirstFile(LPCSTR name, LPWIN32_FIND_DATA data) +{ + if (name == NULL || data == NULL) { + return(INVALID_HANDLE_VALUE); + } + + std::string full(name); + std::string::size_type const slash = full.find_last_of("/\\"); + std::string const directory = slash == std::string::npos ? std::string(".") : full.substr(0, slash); + std::string const pattern = slash == std::string::npos ? full : full.substr(slash + 1); + + DIR * handle = opendir(directory.c_str()); + + if (handle == NULL) { + _LastError = (DWORD)errno; + return(INVALID_HANDLE_VALUE); + } + + Win32Find * find = new Win32Find(); + find->Directory = handle; + find->Path = directory; + find->Pattern = pattern; + + if (!Fill_Find_Data(find, data)) { + closedir(handle); + delete find; + return(INVALID_HANDLE_VALUE); + } + + return((HANDLE)find); +} + + +extern "C" BOOL FindNextFile(HANDLE handle, LPWIN32_FIND_DATA data) +{ + if (handle == INVALID_HANDLE_VALUE || handle == NULL || data == NULL) { + return(FALSE); + } + + return(Fill_Find_Data((Win32Find *)handle, data) ? TRUE : FALSE); +} + + +extern "C" BOOL FindClose(HANDLE handle) +{ + if (handle == INVALID_HANDLE_VALUE || handle == NULL) { + return(FALSE); + } + + Win32Find * find = (Win32Find *)handle; + closedir(find->Directory); + delete find; + return(TRUE); +} + + +// +// --------------------------------------------------------- +// Console, locks and the services with no host equivalent +// --------------------------------------------------------- +// +extern "C" BOOL AllocConsole(void) { return(FALSE); } +extern "C" HWND GetConsoleWindow(void) { return(NULL); } +extern "C" HANDLE GetStdHandle(DWORD which) +{ + switch (which) { + case STD_INPUT_HANDLE: return((HANDLE)stdin); + case STD_OUTPUT_HANDLE: return((HANDLE)stdout); + default: return((HANDLE)stderr); + } +} +extern "C" BOOL SetStdHandle(DWORD which, HANDLE handle) { (void)which; (void)handle; return(TRUE); } +extern "C" BOOL SetConsoleTitle(LPCSTR title) { (void)title; return(TRUE); } +extern "C" BOOL SetConsoleCP(UINT codepage) { (void)codepage; return(TRUE); } +extern "C" BOOL SetConsoleOutputCP(UINT codepage) { (void)codepage; return(TRUE); } +extern "C" BOOL SetConsoleScreenBufferSize(HANDLE console, COORD size) { (void)console; (void)size; return(TRUE); } +extern "C" BOOL GetConsoleScreenBufferInfo(HANDLE console, CONSOLE_SCREEN_BUFFER_INFO * info) +{ + (void)console; + (void)info; + return(FALSE); +} + + +extern "C" BOOL WriteConsole(HANDLE console, void const * buffer, DWORD length, LPDWORD written, LPVOID reserved) +{ + (void)reserved; + + std::FILE * stream = console == (HANDLE)stdout ? stdout : stderr; + size_t const put = std::fwrite(buffer, 1, length, stream); + + if (written != NULL) { + *written = (DWORD)put; + } + + return(TRUE); +} + + +// The debug log's lock is a plain mutex; nothing in the tree takes it for reading only. +extern "C" void InitializeSRWLock(SRWLOCK * lock) +{ + if (lock != NULL) { + lock->Ptr = new std::recursive_mutex(); + } +} + + +extern "C" void AcquireSRWLockExclusive(SRWLOCK * lock) +{ + if (lock == NULL) { + return; + } + + if (lock->Ptr == NULL) { + InitializeSRWLock(lock); + } + + ((std::recursive_mutex *)lock->Ptr)->lock(); +} + + +extern "C" void ReleaseSRWLockExclusive(SRWLOCK * lock) +{ + if (lock != NULL && lock->Ptr != NULL) { + ((std::recursive_mutex *)lock->Ptr)->unlock(); + } +} + + +// There is no version resource and no PE image to read outside Windows. Reporting nothing +// is what the callers already handle; inventing a value would be worse. +extern "C" DWORD GetFileVersionInfoSize(LPCSTR name, LPDWORD handle) { (void)name; (void)handle; return(0); } +extern "C" BOOL GetFileVersionInfo(LPCSTR name, DWORD handle, DWORD length, LPVOID data) { (void)name; (void)handle; (void)length; (void)data; return(FALSE); } +extern "C" BOOL VerQueryValue(LPCVOID block, LPCSTR path, LPVOID * buffer, UINT * length) { (void)block; (void)path; (void)buffer; (void)length; return(FALSE); } +extern "C" HRSRC FindResource(HMODULE module, LPCSTR name, LPCSTR type) { (void)module; (void)name; (void)type; return(NULL); } +extern "C" HGLOBAL LoadResource(HMODULE module, HRSRC resource) { (void)module; (void)resource; return(NULL); } +extern "C" LPVOID LockResource(HGLOBAL resource) { (void)resource; return(NULL); } +extern "C" DWORD SizeofResource(HMODULE module, HRSRC resource) { (void)module; (void)resource; return(0); } +extern "C" LONG RegOpenKeyEx(HKEY key, LPCSTR subkey, DWORD options, DWORD access, HKEY * result) { (void)key; (void)subkey; (void)options; (void)access; if (result != NULL) *result = NULL; return(1); } +extern "C" LONG RegQueryValueEx(HKEY key, LPCSTR name, LPDWORD reserved, LPDWORD type, LPBYTE data, LPDWORD size) { (void)key; (void)name; (void)reserved; (void)type; (void)data; (void)size; return(1); } +extern "C" LONG RegCloseKey(HKEY key) { (void)key; return(0); } diff --git a/platform/win32compat/src/message.cpp b/platform/win32compat/src/message.cpp new file mode 100644 index 000000000..7265e7ac2 --- /dev/null +++ b/platform/win32compat/src/message.cpp @@ -0,0 +1,411 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#include "win32compat.h" + +#include +#include + +// The engine speaks Win32 messages everywhere: the window procedure, the keyboard queue, +// the scroll handler and the tooltip timer all switch on WM_ values, and so do the dialog +// drivers. Translating host events into those messages leaves every one of those call +// sites as it is on Windows, and leaves both builds dispatching the same vocabulary. + +static std::deque _Queue; +static bool _Quitting; +static int _QuitCode; + +struct Win32Timer +{ + HWND Window; + UINT_PTR Id; + UINT Interval; + Uint64 Due; + TIMERPROC Procedure; +}; + +static std::vector _Timers; + + +void Win32_Post_Message(HWND window, UINT message, WPARAM wparam, LPARAM lparam) +{ + MSG msg = {}; + msg.hwnd = window; + msg.message = message; + msg.wParam = wparam; + msg.lParam = lparam; + msg.time = (DWORD)SDL_GetTicks(); + _Queue.push_back(msg); +} + + +// The host reports mouse positions in its own logical coordinates; the engine measures its +// client area in physical pixels, so a position crosses the density before it is packed +// into the message the way Windows packs it. +static LPARAM Point_To_LParam(float x, float y) +{ + float const density = Win32_Pixel_Density(); + int const px = (int)(x * density); + int const py = (int)(y * density); + return(MAKELPARAM((short)px, (short)py)); +} + + +static WPARAM Mouse_Key_State(void) +{ + SDL_MouseButtonFlags const buttons = SDL_GetMouseState(NULL, NULL); + SDL_Keymod const modifiers = SDL_GetModState(); + + WPARAM state = 0; + if ((buttons & SDL_BUTTON_LMASK) != 0) state |= MK_LBUTTON; + if ((buttons & SDL_BUTTON_RMASK) != 0) state |= MK_RBUTTON; + if ((buttons & SDL_BUTTON_MMASK) != 0) state |= MK_MBUTTON; + if ((modifiers & SDL_KMOD_SHIFT) != 0) state |= MK_SHIFT; + if ((modifiers & SDL_KMOD_CTRL) != 0) state |= MK_CONTROL; + return(state); +} + + +extern int Win32_Virtual_Key(SDL_Scancode scancode, SDL_Keycode keycode); + + +static void Translate_Event(SDL_Event const & event) +{ + HWND const main = Win32_Main_Window(); + + if (main == NULL) { + return; + } + + switch (event.type) { + case SDL_EVENT_QUIT: + Win32_Post_Message(main, WM_CLOSE, 0, 0); + break; + + case SDL_EVENT_WINDOW_CLOSE_REQUESTED: + Win32_Post_Message(main, WM_CLOSE, 0, 0); + break; + + case SDL_EVENT_WINDOW_FOCUS_GAINED: + Win32_Post_Message(main, WM_ACTIVATEAPP, 1, 0); + Win32_Post_Message(main, WM_SETFOCUS, 0, 0); + break; + + case SDL_EVENT_WINDOW_FOCUS_LOST: + Win32_Post_Message(main, WM_ACTIVATEAPP, 0, 0); + Win32_Post_Message(main, WM_KILLFOCUS, 0, 0); + break; + + case SDL_EVENT_WINDOW_SHOWN: + Win32_Post_Message(main, WM_SHOWWINDOW, 1, 0); + break; + + case SDL_EVENT_WINDOW_HIDDEN: + Win32_Post_Message(main, WM_SHOWWINDOW, 0, 0); + break; + + case SDL_EVENT_WINDOW_MINIMIZED: + Win32_Post_Message(main, WM_SIZE, SIZE_MINIMIZED, 0); + break; + + case SDL_EVENT_WINDOW_RESTORED: + Win32_Post_Message(main, WM_SIZE, SIZE_RESTORED, 0); + break; + + case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: + Win32_Post_Message(main, WM_SIZE, SIZE_RESTORED, + MAKELPARAM((short)event.window.data1, (short)event.window.data2)); + break; + + case SDL_EVENT_WINDOW_MOVED: + Win32_Post_Message(main, WM_MOVE, 0, MAKELPARAM((short)event.window.data1, (short)event.window.data2)); + break; + + case SDL_EVENT_WINDOW_EXPOSED: + Win32_Post_Message(main, WM_PAINT, 0, 0); + break; + + case SDL_EVENT_MOUSE_MOTION: + Win32_Post_Message(main, WM_MOUSEMOVE, Mouse_Key_State(), + Point_To_LParam(event.motion.x, event.motion.y)); + break; + + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: { + bool const down = event.type == SDL_EVENT_MOUSE_BUTTON_DOWN; + bool const doubled = down && event.button.clicks >= 2; + UINT message = 0; + + switch (event.button.button) { + case SDL_BUTTON_LEFT: + message = down ? (doubled ? WM_LBUTTONDBLCLK : WM_LBUTTONDOWN) : WM_LBUTTONUP; + break; + case SDL_BUTTON_RIGHT: + message = down ? (doubled ? WM_RBUTTONDBLCLK : WM_RBUTTONDOWN) : WM_RBUTTONUP; + break; + case SDL_BUTTON_MIDDLE: + message = down ? (doubled ? WM_MBUTTONDBLCLK : WM_MBUTTONDOWN) : WM_MBUTTONUP; + break; + default: + return; + } + + Win32_Post_Message(main, message, Mouse_Key_State(), Point_To_LParam(event.button.x, event.button.y)); + break; + } + + case SDL_EVENT_MOUSE_WHEEL: { + // Windows reports the wheel in notch multiples in the high word, and the + // position in screen rather than client coordinates. + int const notches = (int)(event.wheel.y * 120.0f); + float mx = 0.0f; + float my = 0.0f; + SDL_GetGlobalMouseState(&mx, &my); + Win32_Post_Message(main, WM_MOUSEWHEEL, + MAKEWPARAM((WORD)Mouse_Key_State(), (WORD)(short)notches), Point_To_LParam(mx, my)); + break; + } + + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: { + int const key = Win32_Virtual_Key(event.key.scancode, event.key.key); + if (key == 0) { + return; + } + + bool const down = event.type == SDL_EVENT_KEY_DOWN; + bool const system = (event.key.mod & SDL_KMOD_ALT) != 0; + UINT const message = down + ? (system ? WM_SYSKEYDOWN : WM_KEYDOWN) + : (system ? WM_SYSKEYUP : WM_KEYUP); + + // The repeat count, the scan code and the transition bit occupy the same + // places in the parameter that Windows puts them in. + LPARAM lparam = 1; + lparam |= (LPARAM)(event.key.scancode & 0xFF) << 16; + if (!down) lparam |= (LPARAM)3 << 30; + + Win32_Post_Message(main, message, (WPARAM)key, lparam); + break; + } + + case SDL_EVENT_TEXT_INPUT: { + for (char const * cursor = event.text.text; cursor != NULL && *cursor != '\0'; cursor++) { + Win32_Post_Message(main, WM_CHAR, (WPARAM)(unsigned char)*cursor, 1); + } + break; + } + + default: + break; + } +} + + +static void Service_Timers(void) +{ + Uint64 const now = SDL_GetTicks(); + + for (Win32Timer & timer : _Timers) { + if (now >= timer.Due) { + timer.Due = now + timer.Interval; + Win32_Post_Message(timer.Window, WM_TIMER, (WPARAM)timer.Id, (LPARAM)timer.Procedure); + } + } +} + + +void Win32_Pump_Host_Events(void) +{ + if (SDL_WasInit(SDL_INIT_VIDEO) == 0) { + return; + } + + SDL_Event event; + while (SDL_PollEvent(&event)) { + Translate_Event(event); + } + + Service_Timers(); +} + + +static bool Matches_Filter(MSG const & msg, HWND window, UINT filtermin, UINT filtermax) +{ + if (window != NULL && msg.hwnd != window) { + return(false); + } + + if (filtermin == 0 && filtermax == 0) { + return(true); + } + + return(msg.message >= filtermin && msg.message <= filtermax); +} + + +extern "C" BOOL PeekMessage(LPMSG msg, HWND window, UINT filtermin, UINT filtermax, UINT remove) +{ + Win32_Pump_Host_Events(); + + for (auto it = _Queue.begin(); it != _Queue.end(); ++it) { + if (!Matches_Filter(*it, window, filtermin, filtermax)) { + continue; + } + + if (msg != NULL) { + *msg = *it; + } + + if ((remove & PM_REMOVE) != 0) { + _Queue.erase(it); + } + + return(TRUE); + } + + return(FALSE); +} + + +// The engine drives its own frame, so a wait for a message must never block: every caller +// reaches this from inside a loop that also has drawing and simulation to do. +extern "C" BOOL GetMessage(LPMSG msg, HWND window, UINT filtermin, UINT filtermax) +{ + if (!PeekMessage(msg, window, filtermin, filtermax, PM_REMOVE)) { + return(FALSE); + } + + return(msg != NULL && msg->message == WM_QUIT ? FALSE : TRUE); +} + + +extern "C" BOOL TranslateMessage(MSG const * msg) +{ + // Character messages arrive from the host's own text input, so a key message needs no + // second pass to produce one. + (void)msg; + return(FALSE); +} + + +extern "C" LRESULT DispatchMessage(MSG const * msg) +{ + if (msg == NULL) { + return(0); + } + + Win32Window * window = Win32_Lookup(msg->hwnd); + + if (window == NULL || window->Procedure == NULL) { + return(0); + } + + return(window->Procedure(msg->hwnd, msg->message, msg->wParam, msg->lParam)); +} + + +extern "C" BOOL PostMessage(HWND window, UINT message, WPARAM wparam, LPARAM lparam) +{ + Win32_Post_Message(window, message, wparam, lparam); + return(TRUE); +} + + +extern "C" LRESULT SendMessage(HWND window, UINT message, WPARAM wparam, LPARAM lparam) +{ + Win32Window * record = Win32_Lookup(window); + + if (record == NULL || record->Procedure == NULL) { + return(0); + } + + return(record->Procedure(window, message, wparam, lparam)); +} + + +extern "C" void PostQuitMessage(int code) +{ + _Quitting = true; + _QuitCode = code; + Win32_Post_Message(Win32_Main_Window(), WM_QUIT, (WPARAM)code, 0); +} + + +extern "C" LRESULT DefWindowProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) +{ + (void)window; + (void)wparam; + (void)lparam; + + switch (message) { + case WM_NCHITTEST: + return(HTCLIENT); + + case WM_SETCURSOR: + return(TRUE); + + default: + return(0); + } +} + + +extern "C" LRESULT CallWindowProc(WNDPROC proc, HWND window, UINT message, WPARAM wparam, LPARAM lparam) +{ + if (proc == NULL) { + return(DefWindowProc(window, message, wparam, lparam)); + } + + return(proc(window, message, wparam, lparam)); +} + + +extern "C" int TranslateAccelerator(HWND window, HACCEL table, LPMSG msg) +{ + (void)window; + (void)table; + (void)msg; + return(0); +} + + +extern "C" UINT_PTR SetTimer(HWND window, UINT_PTR id, UINT elapse, TIMERPROC proc) +{ + for (Win32Timer & timer : _Timers) { + if (timer.Window == window && timer.Id == id) { + timer.Interval = elapse; + timer.Due = SDL_GetTicks() + elapse; + timer.Procedure = proc; + return(id); + } + } + + Win32Timer timer; + timer.Window = window; + timer.Id = id; + timer.Interval = elapse; + timer.Due = SDL_GetTicks() + elapse; + timer.Procedure = proc; + _Timers.push_back(timer); + return(id); +} + + +extern "C" BOOL KillTimer(HWND window, UINT_PTR id) +{ + for (auto it = _Timers.begin(); it != _Timers.end(); ++it) { + if (it->Window == window && it->Id == id) { + _Timers.erase(it); + return(TRUE); + } + } + + return(FALSE); +} diff --git a/platform/win32compat/src/win32compat.h b/platform/win32compat/src/win32compat.h new file mode 100644 index 000000000..4bed6bce6 --- /dev/null +++ b/platform/win32compat/src/win32compat.h @@ -0,0 +1,46 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#pragma once + +#include + +#include + +// One window record per HWND the engine asks for. Only the main window is backed by an +// SDL window; everything the legacy dialog layer would have created is refused, so a +// record without a Handle never exists. +struct Win32Window +{ + SDL_Window * Handle; + WNDPROC Procedure; + char ClassName[64]; + char Title[128]; + DWORD Style; + DWORD ExStyle; + bool Visible; + bool Enabled; +}; + +Win32Window * Win32_Lookup(HWND window); +HWND Win32_Main_Window(void); +WNDPROC Win32_Class_Procedure(char const * name); + +// Drains the host's event queue into the engine's message queue. Every entry point that +// waits for a message calls this, so the engine keeps its own pump and its own frame pace. +void Win32_Pump_Host_Events(void); +void Win32_Post_Message(HWND window, UINT message, WPARAM wparam, LPARAM lparam); + +// Converts between the host's coordinates and the physical client pixels the engine +// measures its frame in. The two agree on a display whose pixel density is one. +float Win32_Pixel_Density(void); + +// The layer bgfx presents into, which SDL owns and this layer only hands over. +extern "C" void * Win32Compat_Native_Window_Handle(HWND window); +extern "C" int Win32Compat_Window_Refresh_Rate(HWND window); diff --git a/platform/win32compat/src/window.cpp b/platform/win32compat/src/window.cpp new file mode 100644 index 000000000..8c5dbe2a8 --- /dev/null +++ b/platform/win32compat/src/window.cpp @@ -0,0 +1,780 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#include "win32compat.h" + +#include + +#include +#include +#include +#include + +static std::vector _Windows; +static std::unordered_map _Classes; +static HWND _MainWindow; +static SDL_MetalView _MetalView; + + +Win32Window * Win32_Lookup(HWND window) +{ + if (window == NULL) { + return(NULL); + } + + for (Win32Window * candidate : _Windows) { + if ((HWND)candidate == window) { + return(candidate); + } + } + + return(NULL); +} + + +HWND Win32_Main_Window(void) +{ + return(_MainWindow); +} + + +WNDPROC Win32_Class_Procedure(char const * name) +{ + if (name == NULL) { + return(NULL); + } + + auto found = _Classes.find(name); + return(found == _Classes.end() ? NULL : found->second); +} + + +float Win32_Pixel_Density(void) +{ + Win32Window * main = Win32_Lookup(_MainWindow); + + if (main == NULL || main->Handle == NULL) { + return(1.0f); + } + + float const density = SDL_GetWindowPixelDensity(main->Handle); + return(density > 0.0f ? density : 1.0f); +} + + +extern "C" ATOM RegisterClass(WNDCLASS const * cls) +{ + if (cls == NULL || cls->lpszClassName == NULL) { + return(0); + } + + _Classes[cls->lpszClassName] = cls->lpfnWndProc; + return(1); +} + + +extern "C" HWND CreateWindowEx(DWORD exstyle, LPCSTR classname, LPCSTR windowname, DWORD style, + int x, int y, int width, int height, HWND parent, HMENU menu, HINSTANCE instance, LPVOID param) +{ + (void)parent; + (void)menu; + (void)instance; + (void)param; + + if (!SDL_InitSubSystem(SDL_INIT_VIDEO)) { + return(NULL); + } + + Win32Window * window = new Win32Window(); + window->Procedure = Win32_Class_Procedure(classname); + window->Style = style; + window->ExStyle = exstyle; + window->Enabled = true; + SDL_strlcpy(window->ClassName, classname != NULL ? classname : "", sizeof(window->ClassName)); + SDL_strlcpy(window->Title, windowname != NULL ? windowname : "", sizeof(window->Title)); + + // A zero size is what the windowed path asks for before it measures the frame it wants, + // so the window opens at a size SDL accepts and is moved to the real one afterwards. + int const openwidth = width > 0 ? width : 640; + int const openheight = height > 0 ? height : 480; + + SDL_WindowFlags flags = SDL_WINDOW_METAL | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY; + if ((style & WS_POPUP) != 0) { + flags |= SDL_WINDOW_BORDERLESS; + } + + window->Handle = SDL_CreateWindow(window->Title, openwidth, openheight, flags); + + if (window->Handle == NULL) { + delete window; + return(NULL); + } + + _Windows.push_back(window); + + if (_MainWindow == NULL) { + _MainWindow = (HWND)window; + } + + if (window->Procedure != NULL) { + window->Procedure((HWND)window, WM_CREATE, 0, 0); + } + + return((HWND)window); +} + + +extern "C" BOOL DestroyWindow(HWND handle) +{ + Win32Window * window = Win32_Lookup(handle); + + if (window == NULL) { + return(FALSE); + } + + if (window->Procedure != NULL) { + window->Procedure(handle, WM_DESTROY, 0, 0); + } + + if (window->Handle != NULL) { + SDL_DestroyWindow(window->Handle); + } + + for (auto it = _Windows.begin(); it != _Windows.end(); ++it) { + if (*it == window) { + _Windows.erase(it); + break; + } + } + + if (_MainWindow == handle) { + _MainWindow = NULL; + } + + delete window; + return(TRUE); +} + + +extern "C" BOOL ShowWindow(HWND handle, int command) +{ + Win32Window * window = Win32_Lookup(handle); + + if (window == NULL || window->Handle == NULL) { + return(FALSE); + } + + bool const previous = window->Visible; + + switch (command) { + case SW_HIDE: + SDL_HideWindow(window->Handle); + window->Visible = false; + break; + + case SW_MINIMIZE: + case SW_SHOWMINIMIZED: + SDL_MinimizeWindow(window->Handle); + break; + + default: + SDL_ShowWindow(window->Handle); + SDL_RaiseWindow(window->Handle); + window->Visible = true; + break; + } + + return(previous ? TRUE : FALSE); +} + + +extern "C" BOOL ShowWindowAsync(HWND handle, int command) +{ + return(ShowWindow(handle, command)); +} + + +extern "C" BOOL UpdateWindow(HWND handle) +{ + Win32Window * window = Win32_Lookup(handle); + + if (window == NULL) { + return(FALSE); + } + + if (window->Procedure != NULL) { + window->Procedure(handle, WM_PAINT, 0, 0); + } + + return(TRUE); +} + + +extern "C" BOOL MoveWindow(HWND handle, int x, int y, int width, int height, BOOL repaint) +{ + Win32Window * window = Win32_Lookup(handle); + + if (window == NULL || window->Handle == NULL) { + return(FALSE); + } + + SDL_SetWindowPosition(window->Handle, x, y); + SDL_SetWindowSize(window->Handle, width, height); + + if (repaint) { + UpdateWindow(handle); + } + + return(TRUE); +} + + +extern "C" BOOL SetWindowPos(HWND handle, HWND after, int x, int y, int cx, int cy, UINT flags) +{ + (void)after; + + Win32Window * window = Win32_Lookup(handle); + + if (window == NULL || window->Handle == NULL) { + return(FALSE); + } + + if ((flags & SWP_NOMOVE) == 0) { + SDL_SetWindowPosition(window->Handle, x, y); + } + + if ((flags & SWP_NOSIZE) == 0) { + SDL_SetWindowSize(window->Handle, cx, cy); + } + + return(TRUE); +} + + +// The frame is measured in physical pixels because the engine scales it to the drawable +// area itself, so the client rectangle reports pixels and every position the layer +// reports elsewhere is converted to match. +extern "C" BOOL GetClientRect(HWND handle, LPRECT rect) +{ + if (rect == NULL) { + return(FALSE); + } + + rect->left = 0; + rect->top = 0; + rect->right = 0; + rect->bottom = 0; + + Win32Window * window = Win32_Lookup(handle); + + if (window == NULL || window->Handle == NULL) { + return(FALSE); + } + + int width = 0; + int height = 0; + if (!SDL_GetWindowSizeInPixels(window->Handle, &width, &height)) { + return(FALSE); + } + + rect->right = width; + rect->bottom = height; + return(TRUE); +} + + +extern "C" BOOL GetWindowRect(HWND handle, LPRECT rect) +{ + if (rect == NULL) { + return(FALSE); + } + + rect->left = 0; + rect->top = 0; + rect->right = 0; + rect->bottom = 0; + + Win32Window * window = Win32_Lookup(handle); + + if (window == NULL || window->Handle == NULL) { + return(FALSE); + } + + int x = 0; + int y = 0; + int width = 0; + int height = 0; + SDL_GetWindowPosition(window->Handle, &x, &y); + SDL_GetWindowSizeInPixels(window->Handle, &width, &height); + + float const density = Win32_Pixel_Density(); + rect->left = (LONG)(x * density); + rect->top = (LONG)(y * density); + rect->right = rect->left + width; + rect->bottom = rect->top + height; + return(TRUE); +} + + +extern "C" BOOL ClientToScreen(HWND handle, LPPOINT point) +{ + if (point == NULL) { + return(FALSE); + } + + RECT rect; + if (!GetWindowRect(handle, &rect)) { + return(FALSE); + } + + point->x += rect.left; + point->y += rect.top; + return(TRUE); +} + + +extern "C" BOOL ScreenToClient(HWND handle, LPPOINT point) +{ + if (point == NULL) { + return(FALSE); + } + + RECT rect; + if (!GetWindowRect(handle, &rect)) { + return(FALSE); + } + + point->x -= rect.left; + point->y -= rect.top; + return(TRUE); +} + + +extern "C" int MapWindowPoints(HWND from, HWND to, LPPOINT points, UINT count) +{ + for (UINT index = 0; index < count; index++) { + if (from != NULL) ClientToScreen(from, &points[index]); + if (to != NULL) ScreenToClient(to, &points[index]); + } + + return(0); +} + + +// The window is borderless or resizable but never has a client area smaller than the +// frame, so the adjustment the engine asks for is the identity. +extern "C" BOOL AdjustWindowRectEx(LPRECT rect, DWORD style, BOOL menu, DWORD exstyle) +{ + (void)style; + (void)menu; + (void)exstyle; + return(rect != NULL); +} + + +extern "C" LONG_PTR GetWindowLongPtr(HWND handle, int index) +{ + Win32Window * window = Win32_Lookup(handle); + + if (window == NULL) { + return(0); + } + + switch (index) { + case GWL_STYLE: return((LONG_PTR)window->Style); + case GWL_EXSTYLE: return((LONG_PTR)window->ExStyle); + case GWLP_WNDPROC: return((LONG_PTR)window->Procedure); + default: return(0); + } +} + + +extern "C" LONG_PTR SetWindowLongPtr(HWND handle, int index, LONG_PTR value) +{ + Win32Window * window = Win32_Lookup(handle); + + if (window == NULL) { + return(0); + } + + LONG_PTR const previous = GetWindowLongPtr(handle, index); + + switch (index) { + case GWL_STYLE: window->Style = (DWORD)value; break; + case GWL_EXSTYLE: window->ExStyle = (DWORD)value; break; + case GWLP_WNDPROC: window->Procedure = (WNDPROC)value; break; + default: break; + } + + return(previous); +} + + +extern "C" LONG_PTR GetWindowLong(HWND handle, int index) +{ + return(GetWindowLongPtr(handle, index)); +} + + +extern "C" LONG_PTR SetWindowLong(HWND handle, int index, LONG_PTR value) +{ + return(SetWindowLongPtr(handle, index, value)); +} + + +extern "C" BOOL SetWindowText(HWND handle, LPCSTR text) +{ + Win32Window * window = Win32_Lookup(handle); + + if (window == NULL || text == NULL) { + return(FALSE); + } + + SDL_strlcpy(window->Title, text, sizeof(window->Title)); + + if (window->Handle != NULL) { + SDL_SetWindowTitle(window->Handle, window->Title); + } + + return(TRUE); +} + + +extern "C" int GetWindowText(HWND handle, LPSTR text, int max) +{ + if (text == NULL || max <= 0) { + return(0); + } + + text[0] = '\0'; + + Win32Window * window = Win32_Lookup(handle); + + if (window == NULL) { + return(0); + } + + SDL_strlcpy(text, window->Title, (size_t)max); + return((int)strlen(text)); +} + + +extern "C" int GetWindowTextLength(HWND handle) +{ + Win32Window * window = Win32_Lookup(handle); + return(window == NULL ? 0 : (int)strlen(window->Title)); +} + + +extern "C" int GetClassName(HWND handle, LPSTR name, int max) +{ + if (name == NULL || max <= 0) { + return(0); + } + + name[0] = '\0'; + + Win32Window * window = Win32_Lookup(handle); + + if (window == NULL) { + return(0); + } + + SDL_strlcpy(name, window->ClassName, (size_t)max); + return((int)strlen(name)); +} + + +extern "C" BOOL IsWindow(HWND handle) +{ + return(Win32_Lookup(handle) != NULL); +} + + +extern "C" BOOL IsWindowVisible(HWND handle) +{ + Win32Window * window = Win32_Lookup(handle); + return(window != NULL && window->Visible); +} + + +extern "C" BOOL IsWindowEnabled(HWND handle) +{ + Win32Window * window = Win32_Lookup(handle); + return(window != NULL && window->Enabled); +} + + +extern "C" BOOL IsChild(HWND parent, HWND child) +{ + (void)parent; + (void)child; + return(FALSE); +} + + +extern "C" BOOL EnableWindow(HWND handle, BOOL enable) +{ + Win32Window * window = Win32_Lookup(handle); + + if (window == NULL) { + return(FALSE); + } + + BOOL const previous = window->Enabled ? FALSE : TRUE; + window->Enabled = enable != FALSE; + return(previous); +} + + +extern "C" HWND GetParent(HWND handle) { (void)handle; return(NULL); } +extern "C" HWND GetWindow(HWND handle, UINT command) { (void)handle; (void)command; return(NULL); } +extern "C" HWND GetTopWindow(HWND handle) { (void)handle; return(NULL); } +extern "C" HWND GetDesktopWindow(void) { return(NULL); } +extern "C" HWND GetActiveWindow(void) { return(_MainWindow); } +extern "C" HWND SetActiveWindow(HWND handle) { (void)handle; return(_MainWindow); } +extern "C" HWND GetFocus(void) { return(_MainWindow); } +extern "C" HWND SetFocus(HWND handle) { (void)handle; return(_MainWindow); } +extern "C" HWND WindowFromPoint(POINT point) { (void)point; return(_MainWindow); } +extern "C" HWND ChildWindowFromPoint(HWND parent, POINT point) { (void)parent; (void)point; return(NULL); } +extern "C" BOOL EnumChildWindows(HWND parent, WNDENUMPROC proc, LPARAM param) { (void)parent; (void)proc; (void)param; return(TRUE); } +extern "C" HMENU GetMenu(HWND handle) { (void)handle; return(NULL); } +extern "C" HMENU GetSystemMenu(HWND handle, BOOL revert) { (void)handle; (void)revert; return(NULL); } +extern "C" BOOL EnableMenuItem(HMENU menu, UINT item, UINT enable) { (void)menu; (void)item; (void)enable; return(FALSE); } +extern "C" BOOL DeleteMenu(HMENU menu, UINT position, UINT flags) { (void)menu; (void)position; (void)flags; return(FALSE); } +extern "C" BOOL RegisterHotKey(HWND handle, int id, UINT modifiers, UINT key) { (void)handle; (void)id; (void)modifiers; (void)key; return(FALSE); } +extern "C" int GetWindowContextHelpId(HWND handle) { (void)handle; return(0); } +extern "C" BOOL WinHelp(HWND handle, LPCSTR help, UINT command, ULONG_PTR data) { (void)handle; (void)help; (void)command; (void)data; return(FALSE); } + + +extern "C" BOOL SetForegroundWindow(HWND handle) +{ + Win32Window * window = Win32_Lookup(handle); + + if (window == NULL || window->Handle == NULL) { + return(FALSE); + } + + SDL_RaiseWindow(window->Handle); + return(TRUE); +} + + +extern "C" BOOL BringWindowToTop(HWND handle) +{ + return(SetForegroundWindow(handle)); +} + + +extern "C" HWND FindWindow(LPCSTR classname, LPCSTR windowname) +{ + (void)windowname; + + for (Win32Window * candidate : _Windows) { + if (classname == NULL || strcmp(candidate->ClassName, classname) == 0) { + return((HWND)candidate); + } + } + + return(NULL); +} + + +extern "C" BOOL CloseWindow(HWND handle) +{ + return(ShowWindow(handle, SW_MINIMIZE)); +} + + +// The frame is presented every time it changes rather than in answer to a paint request, +// so an invalidation has nothing to record and an update rectangle is always empty. +extern "C" BOOL InvalidateRect(HWND handle, RECT const * rect, BOOL erase) { (void)handle; (void)rect; (void)erase; return(TRUE); } +extern "C" BOOL ValidateRect(HWND handle, RECT const * rect) { (void)handle; (void)rect; return(TRUE); } +extern "C" BOOL RedrawWindow(HWND handle, RECT const * rect, HRGN region, UINT flags) { (void)handle; (void)rect; (void)region; (void)flags; return(TRUE); } + + +extern "C" BOOL GetUpdateRect(HWND handle, LPRECT rect, BOOL erase) +{ + (void)handle; + (void)erase; + + if (rect != NULL) { + rect->left = rect->top = rect->right = rect->bottom = 0; + } + + return(FALSE); +} + + +extern "C" BOOL SetRect(LPRECT rect, int left, int top, int right, int bottom) +{ + if (rect == NULL) { + return(FALSE); + } + + rect->left = left; + rect->top = top; + rect->right = right; + rect->bottom = bottom; + return(TRUE); +} + + +extern "C" BOOL IntersectRect(LPRECT dest, RECT const * a, RECT const * b) +{ + if (dest == NULL || a == NULL || b == NULL) { + return(FALSE); + } + + dest->left = a->left > b->left ? a->left : b->left; + dest->top = a->top > b->top ? a->top : b->top; + dest->right = a->right < b->right ? a->right : b->right; + dest->bottom = a->bottom < b->bottom ? a->bottom : b->bottom; + + if (dest->right <= dest->left || dest->bottom <= dest->top) { + dest->left = dest->top = dest->right = dest->bottom = 0; + return(FALSE); + } + + return(TRUE); +} + + +extern "C" BOOL PtInRect(RECT const * rect, POINT point) +{ + if (rect == NULL) { + return(FALSE); + } + + return(point.x >= rect->left && point.x < rect->right && point.y >= rect->top && point.y < rect->bottom); +} + + +extern "C" int GetSystemMetrics(int index) +{ + SDL_DisplayID const display = SDL_GetPrimaryDisplay(); + SDL_DisplayMode const * mode = SDL_GetDesktopDisplayMode(display); + float const density = Win32_Pixel_Density(); + + switch (index) { + case SM_CXSCREEN: + case SM_CXFULLSCREEN: + return(mode != NULL ? (int)(mode->w * density) : 640); + + case SM_CYSCREEN: + case SM_CYFULLSCREEN: + return(mode != NULL ? (int)(mode->h * density) : 480); + + case SM_CXBORDER: + case SM_CYBORDER: + return(1); + + case SM_CXDRAG: + case SM_CYDRAG: + return(4); + + case SM_SWAPBUTTON: + return(0); + + default: + return(0); + } +} + + +extern "C" HMONITOR MonitorFromWindow(HWND handle, DWORD flags) +{ + (void)handle; + (void)flags; + return((HMONITOR)(ULONG_PTR)SDL_GetPrimaryDisplay()); +} + + +extern "C" BOOL GetMonitorInfo(HMONITOR monitor, LPMONITORINFO info) +{ + (void)monitor; + + if (info == NULL) { + return(FALSE); + } + + SetRect(&info->rcMonitor, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)); + info->rcWork = info->rcMonitor; + info->dwFlags = 0; + return(TRUE); +} + + +extern "C" BOOL EnumDisplaySettings(LPCSTR device, DWORD mode, DEVMODE * settings) +{ + (void)device; + (void)mode; + (void)settings; + return(FALSE); +} + + +// A message box has no native equivalent that can be shown from inside the engine's own +// loop without a second event source, so the text is reported where a headless run sees +// it and the caller is told the default button was chosen. +extern "C" int MessageBox(HWND handle, LPCSTR text, LPCSTR caption, UINT type) +{ + (void)handle; + + SDL_Log("%s: %s", caption != NULL ? caption : "OpenTS", text != NULL ? text : ""); + + if ((type & MB_YESNO) == MB_YESNO) { + return(IDYES); + } + + return(IDOK); +} + + +extern "C" int MessageBoxIndirect(MSGBOXPARAMS const * params) +{ + if (params == NULL) { + return(IDOK); + } + + return(MessageBox(params->hwndOwner, params->lpszText, params->lpszCaption, params->dwStyle)); +} + + +extern "C" void * Win32Compat_Native_Window_Handle(HWND handle) +{ + Win32Window * window = Win32_Lookup(handle); + + if (window == NULL || window->Handle == NULL) { + return(NULL); + } + + if (_MetalView == NULL) { + _MetalView = SDL_Metal_CreateView(window->Handle); + } + + if (_MetalView == NULL) { + return(NULL); + } + + return(SDL_Metal_GetLayer(_MetalView)); +} + + +extern "C" int Win32Compat_Window_Refresh_Rate(HWND handle) +{ + Win32Window * window = Win32_Lookup(handle); + + if (window == NULL || window->Handle == NULL) { + return(0); + } + + SDL_DisplayMode const * mode = SDL_GetCurrentDisplayMode(SDL_GetDisplayForWindow(window->Handle)); + return(mode != NULL ? (int)(mode->refresh_rate + 0.5f) : 0); +} From 4bac245d9e4b4ae7dcdc31c7127cd5ec7656a072 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 21:27:07 +0100 Subject: [PATCH 050/179] Load a named module under the host's own library naming Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- platform/win32compat/src/kernel.cpp | 79 ++++++++++++++++++++++++++--- platform/win32compat/src/window.cpp | 5 +- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/platform/win32compat/src/kernel.cpp b/platform/win32compat/src/kernel.cpp index ce9c56fe1..676877d08 100644 --- a/platform/win32compat/src/kernel.cpp +++ b/platform/win32compat/src/kernel.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -82,12 +83,78 @@ extern "C" void OutputDebugString(LPCSTR text) } -// Handles stand in for modules so that a caller can tell "the running program" from -// "some other module"; nothing here loads code. -extern "C" HMODULE GetModuleHandle(LPCSTR name) { (void)name; return((HMODULE)(ULONG_PTR)1); } -extern "C" HMODULE LoadLibrary(LPCSTR name) { (void)name; return(NULL); } -extern "C" BOOL FreeLibrary(HMODULE module) { (void)module; return(TRUE); } -extern "C" FARPROC GetProcAddress(HMODULE module, LPCSTR name) { (void)module; (void)name; return(NULL); } +// A module named without a path is looked for beside the executable, under the host's own +// library naming: the game asks for "Language.dll" and the build produces +// "libLanguage.dylib" in the same directory. +static std::string Host_Library_Name(char const * name) +{ + std::string stem(name != NULL ? name : ""); + std::string::size_type const dot = stem.find_last_of('.'); + + if (dot != std::string::npos) { + stem = stem.substr(0, dot); + } + +#ifdef __APPLE__ + return("lib" + stem + ".dylib"); +#else + return("lib" + stem + ".so"); +#endif +} + + +// A null name asks for the running program, which is the handle a caller compares against +// rather than one it loads anything from. +extern "C" HMODULE GetModuleHandle(LPCSTR name) +{ + if (name == NULL) { + return((HMODULE)(ULONG_PTR)1); + } + + return((HMODULE)dlopen(Host_Library_Name(name).c_str(), RTLD_LAZY | RTLD_NOLOAD)); +} + + +extern "C" HMODULE LoadLibrary(LPCSTR name) +{ + if (name == NULL) { + return(NULL); + } + + std::string const library = Host_Library_Name(name); + + char executable[MAX_PATH]; + if (GetModuleFileName(NULL, executable, sizeof(executable)) != 0) { + std::filesystem::path beside(executable); + beside.replace_filename(library); + + if (void * handle = dlopen(beside.c_str(), RTLD_LAZY)) { + return((HMODULE)handle); + } + } + + return((HMODULE)dlopen(library.c_str(), RTLD_LAZY)); +} + + +extern "C" BOOL FreeLibrary(HMODULE module) +{ + if (module == NULL || module == (HMODULE)(ULONG_PTR)1) { + return(TRUE); + } + + return(dlclose((void *)module) == 0 ? TRUE : FALSE); +} + + +extern "C" FARPROC GetProcAddress(HMODULE module, LPCSTR name) +{ + if (module == NULL || module == (HMODULE)(ULONG_PTR)1 || name == NULL) { + return(NULL); + } + + return((FARPROC)dlsym((void *)module, name)); +} extern "C" DWORD GetModuleFileName(HMODULE module, LPSTR name, DWORD size) diff --git a/platform/win32compat/src/window.cpp b/platform/win32compat/src/window.cpp index 8c5dbe2a8..648130a7d 100644 --- a/platform/win32compat/src/window.cpp +++ b/platform/win32compat/src/window.cpp @@ -11,6 +11,7 @@ #include +#include #include #include #include @@ -727,7 +728,9 @@ extern "C" int MessageBox(HWND handle, LPCSTR text, LPCSTR caption, UINT type) { (void)handle; - SDL_Log("%s: %s", caption != NULL ? caption : "OpenTS", text != NULL ? text : ""); + // Written with the C runtime rather than through the host's logger, which drops a + // message that is not valid UTF-8, and several of these carry legacy code page bytes. + std::fprintf(stderr, "%s: %s\n", caption != NULL ? caption : "OpenTS", text != NULL ? text : ""); if ((type & MB_YESNO) == MB_YESNO) { return(IDYES); From f3cd5774d14b716780c2373ff3fb83cfaaf63e95 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 21:27:07 +0100 Subject: [PATCH 051/179] Back a surface with plain memory where there is no GDI Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/dsurface.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/code/dsurface.cpp b/code/dsurface.cpp index 350895110..44d97177e 100644 --- a/code/dsurface.cpp +++ b/code/dsurface.cpp @@ -55,7 +55,9 @@ #include "video.h" #include +#include #include +#include #include extern bool GameInFocus; @@ -111,6 +113,16 @@ DSurface::DSurface(int width, int height) : GDIBuffer(NULL), Pitch(0) { +#ifndef _WIN32 + /* + * The DIB section exists so that GDI can draw into the same pixels the software + * blitter does. Where there is no GDI, the pixels are ordinary memory: the rows are + * kept four-byte aligned so that the blitter sees the pitch it does on Windows. + */ + Pitch = ((width * 2) + 3) & ~3; + GDIBuffer = new(std::nothrow) unsigned char[(std::size_t)Pitch * (std::size_t)height](); + return; +#else /* * BITMAPINFO carries room for a single color entry, but a bitfields bitmap is * described by three masks following the header, so the header is declared with @@ -159,6 +171,7 @@ DSurface::DSurface(int width, int height) : } else { Pitch = width * 2; } +#endif } @@ -178,6 +191,11 @@ DSurface::DSurface(int width, int height) : *=============================================================================================*/ DSurface::~DSurface(void) { +#ifndef _WIN32 + delete[] (unsigned char *)GDIBuffer; + GDIBuffer = NULL; + return; +#else /* * GDI will not free a bitmap that is still selected into a context, so the one the * context started with has to go back first. @@ -197,6 +215,7 @@ DSurface::~DSurface(void) } GDIBuffer = NULL; +#endif } From 0a5fa45e96fb9dd6c0049609271045edc5a4c43d Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 21:29:00 +0100 Subject: [PATCH 052/179] Report window positions and sizes in physical pixels throughout Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- platform/win32compat/src/kernel.cpp | 11 +++++++++- platform/win32compat/src/window.cpp | 32 +++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/platform/win32compat/src/kernel.cpp b/platform/win32compat/src/kernel.cpp index 676877d08..0e9d757a9 100644 --- a/platform/win32compat/src/kernel.cpp +++ b/platform/win32compat/src/kernel.cpp @@ -627,7 +627,16 @@ extern "C" BOOL CreateDirectory(LPCSTR path, LPSECURITY_ATTRIBUTES attributes) } std::error_code error; - return(std::filesystem::create_directory(path, error) ? TRUE : FALSE); + + if (std::filesystem::create_directory(path, error)) { + _LastError = ERROR_SUCCESS; + return(TRUE); + } + + // A caller distinguishes "it is already there" from a real failure through the last + // error rather than through the result, so the two cases must not look alike. + _LastError = std::filesystem::is_directory(path, error) ? ERROR_ALREADY_EXISTS : ERROR_FILE_NOT_FOUND; + return(FALSE); } diff --git a/platform/win32compat/src/window.cpp b/platform/win32compat/src/window.cpp index 648130a7d..9d28b730c 100644 --- a/platform/win32compat/src/window.cpp +++ b/platform/win32compat/src/window.cpp @@ -56,16 +56,26 @@ WNDPROC Win32_Class_Procedure(char const * name) } +// Every position and size this layer reports is in physical pixels, because that is what +// the engine measures its frame and its client area in. The host works in logical points, +// so one density converts between them. It is read from the display rather than from the +// window so that it answers the same before and after the window exists. float Win32_Pixel_Density(void) { + SDL_DisplayID display = SDL_GetPrimaryDisplay(); Win32Window * main = Win32_Lookup(_MainWindow); - if (main == NULL || main->Handle == NULL) { + if (main != NULL && main->Handle != NULL) { + display = SDL_GetDisplayForWindow(main->Handle); + } + + SDL_DisplayMode const * mode = SDL_GetDesktopDisplayMode(display); + + if (mode == NULL || mode->pixel_density <= 0.0f) { return(1.0f); } - float const density = SDL_GetWindowPixelDensity(main->Handle); - return(density > 0.0f ? density : 1.0f); + return(mode->pixel_density); } @@ -102,8 +112,9 @@ extern "C" HWND CreateWindowEx(DWORD exstyle, LPCSTR classname, LPCSTR windownam // A zero size is what the windowed path asks for before it measures the frame it wants, // so the window opens at a size SDL accepts and is moved to the real one afterwards. - int const openwidth = width > 0 ? width : 640; - int const openheight = height > 0 ? height : 480; + float const density = Win32_Pixel_Density(); + int const openwidth = width > 0 ? (int)(width / density) : 640; + int const openheight = height > 0 ? (int)(height / density) : 480; SDL_WindowFlags flags = SDL_WINDOW_METAL | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY; if ((style & WS_POPUP) != 0) { @@ -225,8 +236,9 @@ extern "C" BOOL MoveWindow(HWND handle, int x, int y, int width, int height, BOO return(FALSE); } - SDL_SetWindowPosition(window->Handle, x, y); - SDL_SetWindowSize(window->Handle, width, height); + float const density = Win32_Pixel_Density(); + SDL_SetWindowPosition(window->Handle, (int)(x / density), (int)(y / density)); + SDL_SetWindowSize(window->Handle, (int)(width / density), (int)(height / density)); if (repaint) { UpdateWindow(handle); @@ -246,12 +258,14 @@ extern "C" BOOL SetWindowPos(HWND handle, HWND after, int x, int y, int cx, int return(FALSE); } + float const density = Win32_Pixel_Density(); + if ((flags & SWP_NOMOVE) == 0) { - SDL_SetWindowPosition(window->Handle, x, y); + SDL_SetWindowPosition(window->Handle, (int)(x / density), (int)(y / density)); } if ((flags & SWP_NOSIZE) == 0) { - SDL_SetWindowSize(window->Handle, cx, cy); + SDL_SetWindowSize(window->Handle, (int)(cx / density), (int)(cy / density)); } return(TRUE); From d1a407a1c408264da66e014223a525838d2199a0 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 21:29:21 +0100 Subject: [PATCH 053/179] Record where a non-Windows build gets its window Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/BUILDING.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/BUILDING.md b/docs/BUILDING.md index c0c901b96..e1e16bccc 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -117,6 +117,14 @@ cmake -S . -B build/native -G Ninja \ cmake --build build/native ``` +Windows supplies the window, the message loop and the cursor itself. Every +other target gets them from `platform/win32compat`, which keeps the Win32 +surface the engine is written against and supplies it from +[SDL](https://github.com/libsdl-org/SDL), pinned as `thirdparty/SDL` and built +with its audio, render and camera subsystems off. Audio stays on miniaudio. +That library is added to the build only when the target is not Windows, so a +Windows configure neither builds nor links it. + ## Build from Visual Studio Code With the recommended extensions installed, the repository provides: From 35e1757368d7fc16b6af858d8995b65e69ae1117 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 21:39:23 +0100 Subject: [PATCH 054/179] Generate the string table where no resource compiler builds it Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- cmake/StringTable.cmake | 154 ++++++++++++++++++++ code/language/CMakeLists.txt | 33 +++++ platform/CMakeLists.txt | 1 + platform/win32compat/src/dialog.cpp | 15 -- platform/win32compat/src/strings.cpp | 204 +++++++++++++++++++++++++++ 5 files changed, 392 insertions(+), 15 deletions(-) create mode 100644 cmake/StringTable.cmake create mode 100644 platform/win32compat/src/strings.cpp diff --git a/cmake/StringTable.cmake b/cmake/StringTable.cmake new file mode 100644 index 000000000..ebfcf8f20 --- /dev/null +++ b/cmake/StringTable.cmake @@ -0,0 +1,154 @@ +# Builds the portable string table from the Windows resource script. +# +# The resource script is the only place the strings live, and only the RC compiler turns it +# into a module resource. A host without an RC compiler still needs the same strings, so this +# script reads the STRINGTABLE blocks out of the script, resolves each symbolic identifier +# through the header the script itself includes, and writes a flat data file the game reads +# at runtime. The Windows build is untouched and keeps using the compiled resource. +# +# Run with: +# cmake -DRC_FILE= -DHEADER_FILE= -DOUTPUT= -P StringTable.cmake +# +# Encoding: the resource script carries "#pragma code_page(65001)", so its bytes are already +# UTF-8 and are copied through unchanged. The data file is UTF-8 for the same reason. + +if(NOT DEFINED RC_FILE OR NOT DEFINED HEADER_FILE OR NOT DEFINED OUTPUT) + message(FATAL_ERROR "StringTable.cmake needs RC_FILE, HEADER_FILE and OUTPUT") +endif() + +# A semicolon separates list elements everywhere in this language, and at least one string +# contains one. It is carried as a marker through every step that treats text as a list and +# put back only when the record is written. No string contains "@", which is what makes the +# marker safe to pick. +set(SEMICOLON_MARK "@OPENTS_SEMI@") +set(BACKSLASH_MARK "@OPENTS_BSLASH@") + +# --------------------------------------------------------------------------------------------- +# The identifiers. The resource script names its strings symbolically and the header gives each +# name its number. +# --------------------------------------------------------------------------------------------- + +file(READ "${HEADER_FILE}" HEADER_TEXT) +string(REGEX MATCHALL "#define[ \t]+[A-Za-z_][A-Za-z0-9_]*[ \t]+[0-9]+" HEADER_DEFINES "${HEADER_TEXT}") + +foreach(define IN LISTS HEADER_DEFINES) + string(REGEX MATCH "#define[ \t]+([A-Za-z_][A-Za-z0-9_]*)[ \t]+([0-9]+)" ignored "${define}") + set("ID_${CMAKE_MATCH_1}" "${CMAKE_MATCH_2}") +endforeach() + +list(LENGTH HEADER_DEFINES DEFINE_COUNT) + +# --------------------------------------------------------------------------------------------- +# The strings. Only the STRINGTABLE blocks are read; the dialog templates in the same script +# hold quoted text that is not a string resource. +# --------------------------------------------------------------------------------------------- + +file(READ "${RC_FILE}" RC_TEXT) +string(REPLACE ";" "${SEMICOLON_MARK}" RC_TEXT "${RC_TEXT}") +string(REPLACE "\r" "" RC_TEXT "${RC_TEXT}") +string(REPLACE "\n" ";" RC_LINES "${RC_TEXT}") + +set(IN_TABLE FALSE) +set(IN_BODY FALSE) +set(PENDING_NAME "") +set(RECORD_COUNT 0) +set(RECORDS "") +set(MISSING "") + +foreach(line IN LISTS RC_LINES) + if(NOT IN_TABLE) + if(line MATCHES "^STRINGTABLE([ \t]|$)") + set(IN_TABLE TRUE) + endif() + continue() + endif() + + if(NOT IN_BODY) + if(line MATCHES "^BEGIN[ \t]*$") + set(IN_BODY TRUE) + endif() + continue() + endif() + + if(line MATCHES "^END[ \t]*$") + set(IN_TABLE FALSE) + set(IN_BODY FALSE) + set(PENDING_NAME "") + continue() + endif() + + set(name "") + set(raw "") + set(have_string FALSE) + + if(line MATCHES "^[ \t]*([A-Za-z_][A-Za-z0-9_]*)[ \t]+\"(.*)\"[ \t]*$") + set(name "${CMAKE_MATCH_1}") + set(raw "${CMAKE_MATCH_2}") + set(have_string TRUE) + elseif(line MATCHES "^[ \t]*([A-Za-z_][A-Za-z0-9_]*)[ \t]*$") + set(PENDING_NAME "${CMAKE_MATCH_1}") + continue() + elseif(line MATCHES "^[ \t]*\"(.*)\"[ \t]*$") + set(name "${PENDING_NAME}") + set(raw "${CMAKE_MATCH_1}") + set(have_string TRUE) + set(PENDING_NAME "") + elseif(line MATCHES "^[ \t]*$") + continue() + else() + message(FATAL_ERROR "StringTable.cmake did not understand a STRINGTABLE line: ${line}") + endif() + + if(NOT have_string) + continue() + endif() + + if(name STREQUAL "") + message(FATAL_ERROR "StringTable.cmake found a string with no identifier: ${line}") + endif() + + if(NOT DEFINED "ID_${name}") + list(APPEND MISSING "${name}") + continue() + endif() + + # The escapes the resource compiler understands, in the order that keeps an escaped + # backslash from being read twice. + string(REPLACE "\\\\" "${BACKSLASH_MARK}" raw "${raw}") + string(REPLACE "\\n" "\n" raw "${raw}") + string(REPLACE "\\r" "\r" raw "${raw}") + string(REPLACE "\\t" "\t" raw "${raw}") + string(REPLACE "\\\"" "\"" raw "${raw}") + string(REPLACE "\"\"" "\"" raw "${raw}") + string(REPLACE "${BACKSLASH_MARK}" "\\" raw "${raw}") + string(REPLACE "${SEMICOLON_MARK}" ";" raw "${raw}") + + string(LENGTH "${raw}" length) + + # Length-prefixed, so a string that contains a newline needs no escaping of its own. + string(APPEND RECORDS "${ID_${name}} ${length}\n${raw}\n") + math(EXPR RECORD_COUNT "${RECORD_COUNT} + 1") +endforeach() + +if(IN_TABLE) + message(FATAL_ERROR "StringTable.cmake reached the end of ${RC_FILE} inside a STRINGTABLE") +endif() + +if(MISSING) + list(REMOVE_DUPLICATES MISSING) + string(REPLACE ";" ", " MISSING_TEXT "${MISSING}") + message(FATAL_ERROR "StringTable.cmake found no identifier in ${HEADER_FILE} for: ${MISSING_TEXT}") +endif() + +if(RECORD_COUNT EQUAL 0) + message(FATAL_ERROR "StringTable.cmake found no strings in ${RC_FILE}") +endif() + +get_filename_component(OUTPUT_DIR "${OUTPUT}" DIRECTORY) +if(OUTPUT_DIR) + file(MAKE_DIRECTORY "${OUTPUT_DIR}") +endif() + +file(WRITE "${OUTPUT}" "OPENTS-STRINGS 1\n${RECORD_COUNT}\n${RECORDS}") + +message(STATUS "String table: ${RECORD_COUNT} strings from ${DEFINE_COUNT} identifiers -> ${OUTPUT}") diff --git a/code/language/CMakeLists.txt b/code/language/CMakeLists.txt index 7f5e68a7f..d5bbe0950 100644 --- a/code/language/CMakeLists.txt +++ b/code/language/CMakeLists.txt @@ -26,3 +26,36 @@ add_custom_command(TARGET Language POST_BUILD "$" "${TS_RUN_DIR}/$" ) + +# Only the resource compiler turns the string table in language.rc into a module resource, so a +# host without one reads the same strings out of a flat data file generated from the same +# script. The Windows build neither generates nor ships it and keeps using LoadString. +if(NOT WIN32) + set(OPENTS_STRING_TABLE "${OPENTS_GENERATED_DIR}/Language.dat") + + add_custom_command( + OUTPUT "${OPENTS_STRING_TABLE}" + COMMAND ${CMAKE_COMMAND} + "-DRC_FILE=${CMAKE_CURRENT_SOURCE_DIR}/language.rc" + "-DHEADER_FILE=${CMAKE_CURRENT_SOURCE_DIR}/language.h" + "-DOUTPUT=${OPENTS_STRING_TABLE}" + -P "${CMAKE_SOURCE_DIR}/cmake/StringTable.cmake" + DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/language.rc" + "${CMAKE_CURRENT_SOURCE_DIR}/language.h" + "${CMAKE_SOURCE_DIR}/cmake/StringTable.cmake" + COMMENT "Generating the portable string table from language.rc" + VERBATIM + ) + + add_custom_target(OpenTSStringTable ALL + DEPENDS "${OPENTS_STRING_TABLE}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${TS_RUN_DIR}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${OPENTS_STRING_TABLE}" + "${TS_RUN_DIR}/Language.dat" + VERBATIM + ) + + add_dependencies(OpenTS OpenTSStringTable) +endif() diff --git a/platform/CMakeLists.txt b/platform/CMakeLists.txt index 2f9e39d2c..2e3aee964 100644 --- a/platform/CMakeLists.txt +++ b/platform/CMakeLists.txt @@ -18,6 +18,7 @@ add_library(win32compat STATIC "${CMAKE_CURRENT_SOURCE_DIR}/win32compat/src/input.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/win32compat/src/kernel.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/win32compat/src/message.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/win32compat/src/strings.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/win32compat/src/window.cpp" ) diff --git a/platform/win32compat/src/dialog.cpp b/platform/win32compat/src/dialog.cpp index a3fdfe541..a5d0c8abd 100644 --- a/platform/win32compat/src/dialog.cpp +++ b/platform/win32compat/src/dialog.cpp @@ -88,18 +88,3 @@ extern "C" BOOL ImageList_DragShowNolock(BOOL show) { (void)show; return(FALSE); extern "C" void ImageList_EndDrag(void) {} extern "C" BOOL ImageList_Destroy(HIMAGELIST list) { (void)list; return(FALSE); } - -// The string table lives in the Windows resource script. The engine's own loader reports -// a missing string by returning an empty one, which is what a caller here receives. -extern "C" int LoadString(HINSTANCE instance, UINT id, LPSTR buffer, int max) -{ - (void)instance; - (void)id; - - if (buffer == NULL || max <= 0) { - return(0); - } - - buffer[0] = '\0'; - return(0); -} diff --git a/platform/win32compat/src/strings.cpp b/platform/win32compat/src/strings.cpp new file mode 100644 index 000000000..e482acb2c --- /dev/null +++ b/platform/win32compat/src/strings.cpp @@ -0,0 +1,204 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#include "win32compat.h" + +#include +#include +#include +#include +#include +#include +#include + +// Every string a player reads comes through Fetch_String, which asks LoadString for a +// resource compiled from language.rc. Only the resource compiler builds that resource, so +// on a host without one the same strings arrive as a flat data file generated from the same +// script at build time by cmake/StringTable.cmake. The file is UTF-8, because the resource +// script declares code page 65001 and the generator copies its bytes through unchanged. +// +// The format is length-prefixed so that a string containing a newline needs no escaping: +// +// OPENTS-STRINGS 1\n +// \n +// \n\n (repeated times) + +namespace +{ + +char const * const STRING_TABLE_FILE = "Language.dat"; +char const * const STRING_TABLE_MAGIC = "OPENTS-STRINGS 1"; + +typedef std::unordered_map StringMap; + + +// The table sits beside the executable, which is where the language library it replaces is +// looked for as well. A run from elsewhere still finds it through the working directory. +std::vector Table_Candidates(void) +{ + std::vector candidates; + + char executable[MAX_PATH]; + if (GetModuleFileName(NULL, executable, sizeof(executable)) != 0) { + std::filesystem::path beside(executable); + beside.replace_filename(STRING_TABLE_FILE); + candidates.push_back(beside); + } + + candidates.push_back(std::filesystem::path(STRING_TABLE_FILE)); + return(candidates); +} + + +bool Read_Whole_File(std::filesystem::path const & path, std::string & content) +{ + std::FILE * file = std::fopen(path.c_str(), "rb"); + if (file == NULL) { + return(false); + } + + content.clear(); + + char buffer[8192]; + std::size_t got; + while ((got = std::fread(buffer, 1, sizeof(buffer), file)) > 0) { + content.append(buffer, got); + } + + bool const ok = (std::ferror(file) == 0); + std::fclose(file); + return(ok); +} + + +// Reads one line and leaves the cursor past its terminator. A record's text is never read +// this way, since only its declared length says where it ends. +bool Next_Line(std::string const & content, std::size_t & cursor, std::string & line) +{ + if (cursor >= content.size()) { + return(false); + } + + std::size_t const end = content.find('\n', cursor); + if (end == std::string::npos) { + return(false); + } + + line.assign(content, cursor, end - cursor); + cursor = end + 1; + return(true); +} + + +void Load_String_Table(StringMap & strings) +{ + std::string content; + bool found = false; + for (std::filesystem::path const & candidate : Table_Candidates()) { + if (Read_Whole_File(candidate, content)) { + found = true; + break; + } + } + + if (!found) { + return; + } + + std::size_t cursor = 0; + std::string line; + + if (!Next_Line(content, cursor, line) || line != STRING_TABLE_MAGIC) { + return; + } + + if (!Next_Line(content, cursor, line)) { + return; + } + + long const count = std::strtol(line.c_str(), NULL, 10); + + for (long record = 0; record < count; record++) { + if (!Next_Line(content, cursor, line)) { + break; + } + + char * after = NULL; + unsigned long const id = std::strtoul(line.c_str(), &after, 10); + if (after == line.c_str()) { + break; + } + + long const length = std::strtol(after, NULL, 10); + if (length < 0 || cursor + (std::size_t)length > content.size()) { + break; + } + + strings[(unsigned int)id] = content.substr(cursor, (std::size_t)length); + cursor += (std::size_t)length + 1; + } +} + + +// A global constructor in the engine asks for a string before this translation unit's own +// statics would have been built, so the table is built on the first request. It is never torn +// down either, because a string can just as easily be asked for from a static destructor. +StringMap const & Table(void) +{ + static StringMap * strings = NULL; + if (strings == NULL) { + strings = new StringMap(); + Load_String_Table(*strings); + } + + return(*strings); +} + + +// A buffer too small to hold the whole string truncates it, as the Windows call does. The +// text is UTF-8, so the cut is pulled back off a continuation byte rather than left to +// split a code point. +std::size_t Whole_Code_Points(char const * text, std::size_t length) +{ + while (length > 0 && ((unsigned char)text[length] & 0xC0) == 0x80) { + length--; + } + + return(length); +} + +} + + +extern "C" int LoadString(HINSTANCE instance, UINT id, LPSTR buffer, int max) +{ + (void)instance; + + if (buffer == NULL || max <= 0) { + return(0); + } + + buffer[0] = '\0'; + + StringMap const & strings = Table(); + + StringMap::const_iterator const entry = strings.find(id); + if (entry == strings.end()) { + return(0); + } + + std::size_t length = entry->second.size(); + if (length > (std::size_t)(max - 1)) { + length = Whole_Code_Points(entry->second.c_str(), (std::size_t)(max - 1)); + } + + std::memcpy(buffer, entry->second.c_str(), length); + buffer[length] = '\0'; + return((int)length); +} From 7349a438cebe596b3f8bcb9358921f4cb8bdda6c Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 21:41:53 +0100 Subject: [PATCH 055/179] Fill every path component _splitpath was asked for Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/always.h | 79 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 8 deletions(-) diff --git a/code/always.h b/code/always.h index e35c58582..932856832 100644 --- a/code/always.h +++ b/code/always.h @@ -159,26 +159,89 @@ inline static int freopen_s(FILE** stream, const char* path, const char* mode, F inline static void _makepath(char* path, const char* drive, const char* dir, const char* fname, const char* ext) { - if (!path || !fname || !ext) { + if (!path) { return; } - sprintf(path, "%s%s%s", fname, (ext[0] == '.' ? "" : "."), ext); + path[0] = '\0'; + + if (drive && drive[0] != '\0') { + sprintf(path + strlen(path), "%c:", drive[0]); + } + + if (dir && dir[0] != '\0') { + char const last = dir[strlen(dir) - 1]; + sprintf(path + strlen(path), "%s%s", dir, (last == '/' || last == '\\') ? "" : "/"); + } + + if (fname && fname[0] != '\0') { + sprintf(path + strlen(path), "%s", fname); + } + + if (ext && ext[0] != '\0') { + sprintf(path + strlen(path), "%s%s", (ext[0] == '.' ? "" : "."), ext); + } } +/// +/// Splits a path into the components the caller asked for. +/// Every non-null component is written, empty where the path has nothing to put in it, so a +/// caller may ask for any subset. Each buffer must hold its documented _MAX_ size, and the +/// extension carries its leading dot as the Microsoft routine's does. +/// inline static void _splitpath(const char* path, char* drive, char* dir, char* fname, char* ext) { - if (!path || !ext) { + if (drive) drive[0] = '\0'; + if (dir) dir[0] = '\0'; + if (fname) fname[0] = '\0'; + if (ext) ext[0] = '\0'; + + if (!path) { return; } - while (*path != '\0') { - if (*path == '.') { - strcpy(ext, path + 1); - break; + // A path read out of a game file was written on Windows, so both separators and a drive + // letter are recognised whatever the host uses. + const char* start = path; + if (path[0] != '\0' && path[1] == ':') { + if (drive) { + drive[0] = path[0]; + drive[1] = ':'; + drive[2] = '\0'; } + start = path + 2; + } + + const char* slash = NULL; + for (const char* scan = start; *scan != '\0'; ++scan) { + if (*scan == '/' || *scan == '\\') { + slash = scan; + } + } + + const char* base = (slash != NULL) ? slash + 1 : start; + + if (dir && slash != NULL) { + size_t length = (size_t)(base - start); + if (length > _MAX_DIR - 1) length = _MAX_DIR - 1; + memcpy(dir, start, length); + dir[length] = '\0'; + } + + const char* dot = strrchr(base, '.'); + + if (fname) { + size_t length = (dot != NULL) ? (size_t)(dot - base) : strlen(base); + if (length > _MAX_FNAME - 1) length = _MAX_FNAME - 1; + memcpy(fname, base, length); + fname[length] = '\0'; + } - ++path; + if (ext && dot != NULL) { + size_t length = strlen(dot); + if (length > _MAX_EXT - 1) length = _MAX_EXT - 1; + memcpy(ext, dot, length); + ext[length] = '\0'; } } From 24b4f861ac3897cf91e743b132fbe2b7e96aafaf Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 21:41:53 +0100 Subject: [PATCH 056/179] Put the debug log inside its directory rather than beside it Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/dbgprint.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/dbgprint.cpp b/code/dbgprint.cpp index 41bcbb097..c629d2cc9 100644 --- a/code/dbgprint.cpp +++ b/code/dbgprint.cpp @@ -262,13 +262,13 @@ static void Init_Locked(void) Delete_Files_Older_Than(DebugDirectory, "DEBUG_*.LOG", DEBUG_LOG_MAX_AGE_DAYS); - snprintf(DebugFileName, sizeof(DebugFileName), "%s\\DEBUG_%s.LOG", DebugDirectory, timestamp); + snprintf(DebugFileName, sizeof(DebugFileName), "%s/DEBUG_%s.LOG", DebugDirectory, timestamp); DebugFile = CreateFile(DebugFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); // A second process started in the same second must not disturb the first one's log. if (DebugFile == INVALID_HANDLE_VALUE) { - snprintf(DebugFileName, sizeof(DebugFileName), "%s\\DEBUG_%s_%lu.LOG", + snprintf(DebugFileName, sizeof(DebugFileName), "%s/DEBUG_%s_%lu.LOG", DebugDirectory, timestamp, GetCurrentProcessId()); DebugFile = CreateFile(DebugFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); From 176e4875e9a14966b274e33f73c4c0dee192224d Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 21:43:59 +0100 Subject: [PATCH 057/179] Probe for the deployment file with a portable separator Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/deploymentconfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/deploymentconfig.cpp b/code/deploymentconfig.cpp index 614bb5af5..4f8caf97d 100644 --- a/code/deploymentconfig.cpp +++ b/code/deploymentconfig.cpp @@ -20,7 +20,7 @@ static char const * const ConfigName = "OPENTS.INI"; /* * The folders the file itself is looked for in, relative to the data directory. */ -static char const * const ConfigProbes[] = {"", "INI\\", "MIX\\"}; +static char const * const ConfigProbes[] = {"", "INI/", "MIX/"}; void DeploymentConfigClass::Read_INI(INIClass const & ini) From 0bcbcab47922d776b8adc9806b2c323703531c5f Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 21:47:16 +0100 Subject: [PATCH 058/179] build: add a Steam asset fetcher for the game data OpenTS supplies the engine, not the game data. This pulls Tiberian Sun and Firestorm from a Steam account that owns it (app 2229880, Windows depot -- the archives are platform independent) and places them in Run/, which is where the manual's game-data page says the engine looks. It verifies the archives startup refuses to run without rather than leaving the user to discover them one at a time at 'Failed to bootstrap!', and it fails fast when the Steam desktop client is running, because steamcmd shares Steam's data directory and would otherwise hang forever after 'Verifying installation...' with no error at all. That check matches the client binary itself: quitting Steam leaves helpers such as ipcserver running, and a broader pattern reports a client that no amount of quitting will clear. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- scripts/get-assets.sh | 121 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100755 scripts/get-assets.sh diff --git a/scripts/get-assets.sh b/scripts/get-assets.sh new file mode 100755 index 000000000..7b9499bda --- /dev/null +++ b/scripts/get-assets.sh @@ -0,0 +1,121 @@ +#!/bin/bash +# Download Tiberian Sun + Firestorm game data from your own Steam account into Run/. +# +# OpenTS supplies the engine, not the game data. This fetches the data files from +# a copy of the game you already own and places them where the engine looks for +# them, which the manual describes under "Game data". +# +# Usage: ./scripts/get-assets.sh +# Steam Guard: you will be prompted for the code on first login. +set -euo pipefail + +if [[ $# -lt 1 ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +STEAM_USER="$1" +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +DEST="${TS_RUN_DIR:-$REPO_ROOT/Run}" +TMP_DIR="$REPO_ROOT/build/.steamcmd_ts" + +# App 2229880 = "Command & Conquer Tiberian Sun and Firestorm". The depot is +# Windows-only, which does not matter: the archives this copies are data, and the +# Windows executables are excluded below because OpenTS replaces them. +STEAM_APP_ID=2229880 + +if ! command -v steamcmd >/dev/null 2>&1; then + echo "Error: steamcmd is not installed." >&2 + echo " macOS: brew install --cask steamcmd" >&2 + echo " Linux: install the steamcmd package for your distribution" >&2 + exit 1 +fi + +mkdir -p "$TMP_DIR" "$DEST" + +# macOS Gatekeeper quarantines steamcmd's unnotarized bundled frameworks when +# Homebrew installs the cask. On first run that pops a blocking "Apple could not +# verify ... malware" dialog which stops steamcmd dead. Clear the flag up front. +if [[ "$(uname)" == "Darwin" ]] && command -v brew >/dev/null 2>&1; then + STEAMCMD_CASK="$(brew --prefix)/Caskroom/steamcmd" + [[ -d "$STEAMCMD_CASK" ]] && xattr -dr com.apple.quarantine "$STEAMCMD_CASK" 2>/dev/null || true +fi + +# steamcmd and the Steam desktop client share one data directory. A running +# client holds a single-instance lock, so steamcmd stalls forever right after +# "Verifying installation..." with no error at all. Fail fast and say why. +# +# Match the client binary itself, not anything living under the Steam bundle. +# Quitting Steam leaves helpers such as ipcserver and steamwebhelper running for +# a while, and they hold no lock — a broader pattern reports the client as +# running when it is not, and no amount of quitting Steam clears it. +if pgrep -x steam_osx >/dev/null 2>&1 \ + || pgrep -x steam >/dev/null 2>&1; then + echo "Error: the Steam desktop client is running." >&2 + echo "steamcmd shares Steam's data directory, and the running client locks it —" >&2 + echo 'steamcmd would hang forever after "Verifying installation...".' >&2 + echo "Quit Steam completely (Steam > Quit Steam, or Cmd-Q), then re-run this script." >&2 + exit 1 +fi + +echo "==> Downloading app $STEAM_APP_ID from Steam as '$STEAM_USER'" +steamcmd \ + +@sSteamCmdForcePlatformType windows \ + +force_install_dir "$TMP_DIR" \ + +login "$STEAM_USER" \ + +app_update "$STEAM_APP_ID" validate \ + +quit + +echo "==> Copying game data into $DEST" +# Data only. OpenTS replaces the game's own executables, and copying them in +# would leave two things called Game in the same directory. +rsync -a \ + --exclude="*.exe" --exclude="*.dll" --exclude="*.pdb" \ + --exclude="_CommonRedist/" --exclude="installscript.vdf" \ + "$TMP_DIR/" "$DEST/" + +# The manual's MIX archive page lists what startup actually requires: CACHE.MIX +# first, then CONQUER.MIX, SOUNDS.MIX, SCORES.MIX, a movie archive, and +# SOUNDS01.MIX where the expansion is present. Everything else is mounted when +# found and passed over when not, so check only what the engine refuses to start +# without, and check it case-insensitively because the depot's casing varies. +echo "==> Verifying the archives startup requires" +missing=0 +for archive in CACHE.MIX TIBSUN.MIX LOCAL.MIX CONQUER.MIX SOUNDS.MIX SCORES.MIX; do + if ! find "$DEST" -maxdepth 2 -iname "$archive" -print -quit | grep -q .; then + echo " MISSING: $archive" >&2 + missing=1 + else + echo " found: $archive" + fi +done + +# A movie archive is required, but its name varies by release and by disc. +if find "$DEST" -maxdepth 2 -iname "MOVIES*.MIX" -print -quit | grep -q .; then + echo " found: $(find "$DEST" -maxdepth 2 -iname 'MOVIES*.MIX' -exec basename {} \; | tr '\n' ' ')" +else + echo " MISSING: a MOVIES*.MIX archive" >&2 + missing=1 +fi + +# Firestorm's speech archive is required only where Firestorm is installed, +# which FIRESTRM.INI identifies. +if find "$DEST" -maxdepth 2 -iname "FIRESTRM.INI" -print -quit | grep -q .; then + if find "$DEST" -maxdepth 2 -iname "SOUNDS01.MIX" -print -quit | grep -q .; then + echo " found: SOUNDS01.MIX (Firestorm)" + else + echo " MISSING: SOUNDS01.MIX, and FIRESTRM.INI says Firestorm is installed" >&2 + missing=1 + fi +fi + +if [[ $missing -ne 0 ]]; then + echo >&2 + echo "Error: the download completed but the archives the engine needs are not present." >&2 + echo "Check that the Steam account owns Tiberian Sun and that app $STEAM_APP_ID installed cleanly." >&2 + exit 1 +fi + +echo +echo "Done. Game data is in $DEST" +echo "Run the engine with: $DEST/Game" From 638a9e9176bc32b4e89af6e8c3394fa72ba64b42 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 21:51:40 +0100 Subject: [PATCH 059/179] fix: verify only the archives that must be their own files CACHE.MIX, LOCAL.MIX, CONQUER.MIX and SOUNDS.MIX are not loose files in this release. Init_Bootstrap_Mixfiles mounts TIBSUN.MIX before it opens them, so they are read through it. Checking for them beside the executable reported a complete download as a broken one. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- scripts/get-assets.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/get-assets.sh b/scripts/get-assets.sh index 7b9499bda..dcc09c1af 100755 --- a/scripts/get-assets.sh +++ b/scripts/get-assets.sh @@ -74,14 +74,14 @@ rsync -a \ --exclude="_CommonRedist/" --exclude="installscript.vdf" \ "$TMP_DIR/" "$DEST/" -# The manual's MIX archive page lists what startup actually requires: CACHE.MIX -# first, then CONQUER.MIX, SOUNDS.MIX, SCORES.MIX, a movie archive, and -# SOUNDS01.MIX where the expansion is present. Everything else is mounted when -# found and passed over when not, so check only what the engine refuses to start -# without, and check it case-insensitively because the depot's casing varies. +# Only check archives that must exist as their own files. CACHE.MIX, LOCAL.MIX, +# CONQUER.MIX and SOUNDS.MIX are NOT among them: Init_Bootstrap_Mixfiles mounts +# TIBSUN.MIX first and then opens them through it, so in this release they live +# inside TIBSUN.MIX rather than beside it. Checking for them as loose files +# reports a complete download as broken. echo "==> Verifying the archives startup requires" missing=0 -for archive in CACHE.MIX TIBSUN.MIX LOCAL.MIX CONQUER.MIX SOUNDS.MIX SCORES.MIX; do +for archive in TIBSUN.MIX SCORES.MIX; do if ! find "$DEST" -maxdepth 2 -iname "$archive" -print -quit | grep -q .; then echo " MISSING: $archive" >&2 missing=1 From 4bd241a221ffff5fea171274329e02a0728f7aa2 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 21:59:35 +0100 Subject: [PATCH 060/179] fix: hold the SHA digest in 32-bit words SHAEngine's digest union declared its five accumulator words as `unsigned long`, which is eight bytes on LP64. sizeof(SHADigest) was therefore 40 rather than 20, so the engine hashed in 64-bit words, Result() reversed ten words over a five-word union, and it copied 40 bytes into every caller's 20-byte digest buffer. Measured against the standard vectors: SHA-1("abc") came back as a9993e36 00000000 4706816a 01000000 ba3e2571 -- the correct words interleaved with padding -- and is now a9993e364706816aba3e25717850c26c9cd0d89d. This is what stopped the game reaching a menu. MixFileClass::Cache verifies an attached message digest as it caches, so every archive carrying one refused to cache: CACHE.MIX read its 168752 bytes correctly out of the nested TIBSUN.MIX and then compared a computed digest of zeroes against the 6ffc2d75... the file carries. Guarded with a static_assert so the digest cannot silently change width again. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/sha.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/code/sha.h b/code/sha.h index e74f2160d..416fca674 100644 --- a/code/sha.h +++ b/code/sha.h @@ -32,6 +32,7 @@ #pragma once +#include #include #include #include @@ -68,10 +69,17 @@ class SHAEngine private: typedef union { - unsigned long Long[5]; + std::uint32_t Long[5]; unsigned char Char[20]; } SHADigest; + /* + ** The digest is a 160 bit value laid out as five 32 bit words, and every + ** routine below indexes it as such. A wider word would silently change the + ** size of the digest, the size of the accumulator and the stride of both. + */ + static_assert(sizeof(SHADigest) == 20, "SHA digest must be 160 bits"); + /* ** This holds the calculated final result. It is cached ** here to avoid the overhead of recalculating it over From 3d30e0a90cc0828d59dce8ec1bf257d5e910e249 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 22:20:31 +0100 Subject: [PATCH 061/179] fix: write a debug log record straight through WriteFile went to a buffered std::FILE, so the last several seconds of a run stayed in the standard library's buffer and were lost whenever the process was killed rather than closed. A Windows file handle is not buffered, and the debug log is read after a crash precisely because of that, so flush each record as it is written. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- platform/win32compat/src/kernel.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/platform/win32compat/src/kernel.cpp b/platform/win32compat/src/kernel.cpp index 0e9d757a9..50d375263 100644 --- a/platform/win32compat/src/kernel.cpp +++ b/platform/win32compat/src/kernel.cpp @@ -560,6 +560,13 @@ extern "C" BOOL WriteFile(HANDLE handle, LPCVOID buffer, DWORD size, LPDWORD wri size_t const put = std::fwrite(buffer, 1, size, (std::FILE *)handle); + /* + * A Windows file handle is not buffered, so a log written through this call + * survives a crash. Match that rather than leaving the last records of a run + * in a standard library buffer that is never drained. + */ + std::fflush((std::FILE *)handle); + if (written != NULL) { *written = (DWORD)put; } From bc83714a4326d45047ca8f1ee6b5d9194744107f Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 22:48:11 +0100 Subject: [PATCH 062/179] fix: walk the module headers only where a PE image has them Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/syncrechook.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/code/syncrechook.cpp b/code/syncrechook.cpp index 6469adb0f..7ff67a04f 100644 --- a/code/syncrechook.cpp +++ b/code/syncrechook.cpp @@ -246,9 +246,14 @@ void Sync_Recorder_Arm(void) bool const network = (Session.Type == GAME_IPX || Session.Type == GAME_INTERNET); SyncRecorder.Set_Recording(network || Session.Record || Session.Play); - ModuleBase = (uintptr_t)GetModuleHandle(nullptr); + ModuleBase = 0; ModuleSize = 0; MapImageBase = 0; + +#ifdef _WIN32 + // Only a PE image carries the headers this walks, and a caller address is reported as an + // absolute address wherever it does not. + ModuleBase = (uintptr_t)GetModuleHandle(nullptr); if (ModuleBase != 0) { IMAGE_DOS_HEADER const * dos = (IMAGE_DOS_HEADER const *)ModuleBase; if (dos->e_magic == IMAGE_DOS_SIGNATURE) { @@ -258,6 +263,7 @@ void Sync_Recorder_Arm(void) } } } +#endif MapImageBase = Sync_Preferred_Image_Base(); From e6c2584d4114b5aeae755ef39979b8ae2f392ac2 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 22:48:11 +0100 Subject: [PATCH 063/179] fix: hold the isometric blit source addresses at pointer width Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/isotype.cpp | 82 ++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/code/isotype.cpp b/code/isotype.cpp index e7da02947..a29dbc318 100644 --- a/code/isotype.cpp +++ b/code/isotype.cpp @@ -1568,8 +1568,8 @@ Cell const * IsometricTileTypeClass::Shadow_Caster_List(void) const /// Fill constants: FillDepth (depth-only / fill passes), FogColor (shroud fog), HalfbrightMask #pragma pack(push, 1) struct IsoBlitState { - int SrcPixel; - int SrcDepth; + unsigned char *SrcPixel; + unsigned char *SrcDepth; unsigned short *PixelTranslate; unsigned short *DepthPtr; unsigned short *AlphaPtr; @@ -1599,7 +1599,7 @@ struct IsoBlitState { #pragma pack(pop) // Held only in memory, so the pointers may be any width, but the field count is fixed. -static_assert(sizeof(IsoBlitState) == 54 + 13 * sizeof(void *), "Isometric blit state layout changed"); +static_assert(sizeof(IsoBlitState) == 46 + 15 * sizeof(void *), "Isometric blit state layout changed"); IsoBlitState IsoDrawData; @@ -1892,7 +1892,7 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, arow = (unsigned short *)AlphaBuffer->Get_Buffer_Offset(Point2D(lock_x, work.Y - TacticalRect.Y)); IsoDrawData.AlphaPtr = arow; IsoDrawData.AlphaWidth = AlphaBuffer->Get_Buffer_Width(); - IsoDrawData.SrcPixel = (int)(record + 1); + IsoDrawData.SrcPixel = (unsigned char *)(record + 1); drawer = (LightConvertClass *)IsoDrawData.SrcPixel; if (fill || fog) { IsoDrawData.FillDepth = 0; @@ -1901,14 +1901,14 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, } IsoDrawData.FogColor = IsoDrawData.HalfbrightMask & (fog_color >> 1); if (use_z && record->IsHasZData) { - IsoDrawData.SrcDepth = (int)record + record->ZDataOffset; + IsoDrawData.SrcDepth = (unsigned char *)record + record->ZDataOffset; } IsoDrawData.ImageBase = (unsigned char *)(record + 1); IsoDrawData.DepthBase = (unsigned char *)IsoDrawData.SrcDepth; if (use_z) { zrow = IsoDrawData.DepthPtr; - if ((unsigned int)&IsoDrawData.DepthPtr[IsoDrawData.SpanWidth + 2 + IsoDrawData.SpanHeight * DepthBuffer->BufferWidth] >= DepthBuffer->Get_Buffer_End()) { + if ((uintptr_t)&IsoDrawData.DepthPtr[IsoDrawData.SpanWidth + 2 + IsoDrawData.SpanHeight * DepthBuffer->BufferWidth] >= DepthBuffer->Get_Buffer_End()) { if (fill) { /* @@ -1922,7 +1922,7 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, IsoDrawData.RowDepth = zrow; IsoDrawData.DestPtr = (unsigned short *)((char *)destrow + start); IsoDrawData.DepthPtr = (unsigned short *)((char *)zrow + start); - IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)IsoDrawData.DepthPtr); + IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.DepthPtr); int run = *IsoDrawData.RowRunLength; if (run > 0) { do { @@ -1930,7 +1930,7 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, *IsoDrawData.DestPtr = (unsigned short)IsoDrawData.FogColor; ++IsoDrawData.DestPtr; ++IsoDrawData.DepthPtr; - IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)IsoDrawData.DepthPtr); + IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.DepthPtr); --run; } while (run != 0); } @@ -1950,13 +1950,13 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, for (int row = 0; row < IsoDrawData.SpanHeight; ++row) { IsoDrawData.RowDepth = zrow; IsoDrawData.DepthPtr = &zrow[*IsoDrawData.RowStartCol]; - IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)IsoDrawData.DepthPtr); + IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.DepthPtr); int run = *IsoDrawData.RowRunLength; if (run > 0) { do { *IsoDrawData.DepthPtr = IsoDrawData.FillDepth; ++IsoDrawData.DepthPtr; - IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)IsoDrawData.DepthPtr); + IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.DepthPtr); --run; } while (run != 0); } @@ -1974,13 +1974,13 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, unsigned char * destrow = (unsigned char *)IsoDrawData.DestPtr; for (int row = 0; row < IsoDrawData.SpanHeight; ++row) { unsigned short rowoff = *IsoDrawData.RowSrcOffset; - IsoDrawData.SrcPixel = (int)IsoDrawData.ImageBase + rowoff; + IsoDrawData.SrcPixel = IsoDrawData.ImageBase + rowoff; int start = *IsoDrawData.RowStartCol * 2; IsoDrawData.RowDest = (unsigned short *)destrow; IsoDrawData.RowDepth = zrow; IsoDrawData.DestPtr = (unsigned short *)&destrow[start]; IsoDrawData.DepthPtr = (unsigned short *)((char *)zrow + start); - IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)IsoDrawData.DepthPtr); + IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.DepthPtr); int checker = ((unsigned char *)IsoDrawData.SrcPixel - (unsigned char *)drawer + row + IsoDrawData.ClipTop) & 1; int run = *IsoDrawData.RowRunLength; if (run > 0) { @@ -1993,7 +1993,7 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, ++IsoDrawData.SrcPixel; ++IsoDrawData.DestPtr; ++IsoDrawData.DepthPtr; - IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)IsoDrawData.DepthPtr); + IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.DepthPtr); --run; } while (run != 0); } @@ -2012,13 +2012,13 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, */ unsigned char * destrow = (unsigned char *)IsoDrawData.DestPtr; for (int row = 0; row < IsoDrawData.SpanHeight; ++row) { - IsoDrawData.SrcPixel = (int)IsoDrawData.ImageBase + *IsoDrawData.RowSrcOffset; + IsoDrawData.SrcPixel = IsoDrawData.ImageBase + *IsoDrawData.RowSrcOffset; int start = *IsoDrawData.RowStartCol * 2; IsoDrawData.RowDest = (unsigned short *)destrow; IsoDrawData.RowDepth = zrow; IsoDrawData.DestPtr = (unsigned short *)&destrow[start]; IsoDrawData.DepthPtr = (unsigned short *)((char *)zrow + start); - IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)IsoDrawData.DepthPtr); + IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.DepthPtr); int run = *IsoDrawData.RowRunLength; if (run > 0) { do { @@ -2029,7 +2029,7 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, ++IsoDrawData.SrcPixel; ++IsoDrawData.DestPtr; ++IsoDrawData.DepthPtr; - IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)IsoDrawData.DepthPtr); + IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.DepthPtr); --run; } while (run != 0); } @@ -2051,17 +2051,17 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, unsigned char * destrow = (unsigned char *)IsoDrawData.DestPtr; for (int row = 0; row < IsoDrawData.SpanHeight; ++row) { unsigned short rowoff = *IsoDrawData.RowSrcOffset; - IsoDrawData.SrcPixel = (int)IsoDrawData.ImageBase + rowoff; - IsoDrawData.SrcDepth = (int)IsoDrawData.DepthBase + rowoff; + IsoDrawData.SrcPixel = IsoDrawData.ImageBase + rowoff; + IsoDrawData.SrcDepth = IsoDrawData.DepthBase + rowoff; int startcol = *IsoDrawData.RowStartCol * 2; IsoDrawData.RowDest = (unsigned short *)destrow; IsoDrawData.RowDepth = zrow; IsoDrawData.DepthPtr = (unsigned short *)((char *)zrow + startcol); IsoDrawData.DestPtr = (unsigned short *)((char *)destrow + startcol); - IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)IsoDrawData.DepthPtr); + IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.DepthPtr); IsoDrawData.RowAlpha = arow; IsoDrawData.AlphaPtr = (unsigned short *)((char *)arow + startcol); - IsoDrawData.AlphaPtr = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)IsoDrawData.AlphaPtr); + IsoDrawData.AlphaPtr = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.AlphaPtr); int run = *IsoDrawData.RowRunLength; if (run > 0) { do { @@ -2074,9 +2074,9 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, ++IsoDrawData.DepthPtr; ++IsoDrawData.SrcPixel; ++IsoDrawData.DestPtr; - IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)IsoDrawData.DepthPtr); + IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.DepthPtr); ++IsoDrawData.AlphaPtr; - IsoDrawData.AlphaPtr = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)IsoDrawData.AlphaPtr); + IsoDrawData.AlphaPtr = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.AlphaPtr); --run; } while (run != 0); } @@ -2138,7 +2138,7 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, unsigned char * destrow = (unsigned char *)IsoDrawData.DestPtr; for (int row = 0; row < IsoDrawData.SpanHeight; ++row) { unsigned short rowoff = *IsoDrawData.RowSrcOffset; - IsoDrawData.SrcPixel = (int)IsoDrawData.ImageBase + rowoff; + IsoDrawData.SrcPixel = IsoDrawData.ImageBase + rowoff; int start = *IsoDrawData.RowStartCol; IsoDrawData.RowDest = (unsigned short *)destrow; IsoDrawData.RowDepth = zrow; @@ -2172,7 +2172,7 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, } else { unsigned char * destrow = (unsigned char *)IsoDrawData.DestPtr; for (int row = 0; row < IsoDrawData.SpanHeight; ++row) { - IsoDrawData.SrcPixel = (int)IsoDrawData.ImageBase + *IsoDrawData.RowSrcOffset; + IsoDrawData.SrcPixel = IsoDrawData.ImageBase + *IsoDrawData.RowSrcOffset; int start = *IsoDrawData.RowStartCol; IsoDrawData.RowDest = (unsigned short *)destrow; IsoDrawData.RowDepth = zrow; @@ -2203,8 +2203,8 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, unsigned char * destrow = (unsigned char *)IsoDrawData.DestPtr; for (int row = 0; row < IsoDrawData.SpanHeight; ++row) { unsigned short rowoff = *IsoDrawData.RowSrcOffset; - IsoDrawData.SrcPixel = (int)IsoDrawData.ImageBase + rowoff; - IsoDrawData.SrcDepth = (int)IsoDrawData.DepthBase + rowoff; + IsoDrawData.SrcPixel = IsoDrawData.ImageBase + rowoff; + IsoDrawData.SrcDepth = IsoDrawData.DepthBase + rowoff; int start = *IsoDrawData.RowStartCol * 2; IsoDrawData.RowDest = (unsigned short *)destrow; IsoDrawData.RowDepth = zrow; @@ -2241,16 +2241,16 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, } } } else { - if ((unsigned int)&arow[IsoDrawData.SpanWidth + 2 + IsoDrawData.SpanHeight * AlphaBuffer->Get_Buffer_Width()] >= AlphaBuffer->Get_Buffer_End()) { + if ((uintptr_t)&arow[IsoDrawData.SpanWidth + 2 + IsoDrawData.SpanHeight * AlphaBuffer->Get_Buffer_Width()] >= AlphaBuffer->Get_Buffer_End()) { unsigned char * destrow = (unsigned char *)IsoDrawData.DestPtr; for (int row = 0; row < IsoDrawData.SpanHeight; ++row) { - IsoDrawData.SrcPixel = (int)IsoDrawData.ImageBase + *IsoDrawData.RowSrcOffset; + IsoDrawData.SrcPixel = IsoDrawData.ImageBase + *IsoDrawData.RowSrcOffset; int startcol = *IsoDrawData.RowStartCol * 2; IsoDrawData.RowDest = (unsigned short *)destrow; IsoDrawData.RowAlpha = arow; IsoDrawData.DestPtr = (unsigned short *)&destrow[startcol]; IsoDrawData.AlphaPtr = (unsigned short *)((char *)arow + startcol); - IsoDrawData.AlphaPtr = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)IsoDrawData.AlphaPtr); + IsoDrawData.AlphaPtr = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.AlphaPtr); int run = *IsoDrawData.RowRunLength; if (run > 0) { do { @@ -2258,7 +2258,7 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, ++IsoDrawData.DestPtr; ++IsoDrawData.SrcPixel; ++IsoDrawData.AlphaPtr; - IsoDrawData.AlphaPtr = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)IsoDrawData.AlphaPtr); + IsoDrawData.AlphaPtr = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.AlphaPtr); --run; } while (run != 0); } @@ -2273,7 +2273,7 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, } else { unsigned char * destrow = (unsigned char *)IsoDrawData.DestPtr; for (int row = 0; row < IsoDrawData.SpanHeight; ++row) { - IsoDrawData.SrcPixel = (int)IsoDrawData.ImageBase + *IsoDrawData.RowSrcOffset; + IsoDrawData.SrcPixel = IsoDrawData.ImageBase + *IsoDrawData.RowSrcOffset; int startcol = *IsoDrawData.RowStartCol * 2; IsoDrawData.RowDest = (unsigned short *)destrow; IsoDrawData.RowAlpha = arow; @@ -2352,12 +2352,12 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, IsoDrawData.AlphaWidth = AlphaBuffer->Get_Buffer_Width() - IsoDrawData.SpanWidth; IsoDrawData.DestPtr = (unsigned short *)surface.Lock(Point2D(ex, ey)); if (IsoDrawData.DestPtr != NULL) { - IsoDrawData.SrcPixel = (int)record + record->ExtraOffset + IsoDrawData.ClipLeft; + IsoDrawData.SrcPixel = (unsigned char *)record + record->ExtraOffset + IsoDrawData.ClipLeft; IsoDrawData.SrcPixel = IsoDrawData.ClipTop * record->ExtraWidth + IsoDrawData.SrcPixel; if (use_z) { - IsoDrawData.SrcDepth = (int)record + record->ExtraZOffset + IsoDrawData.ClipLeft; + IsoDrawData.SrcDepth = (unsigned char *)record + record->ExtraZOffset + IsoDrawData.ClipLeft; IsoDrawData.SrcDepth = IsoDrawData.ClipTop * record->ExtraWidth + IsoDrawData.SrcDepth; - if ((unsigned int)&IsoDrawData.DepthPtr[IsoDrawData.SpanWidth + 2 + IsoDrawData.SpanHeight * DepthBuffer->BufferWidth] >= DepthBuffer->Get_Buffer_End()) { + if ((uintptr_t)&IsoDrawData.DepthPtr[IsoDrawData.SpanWidth + 2 + IsoDrawData.SpanHeight * DepthBuffer->BufferWidth] >= DepthBuffer->Get_Buffer_End()) { /* * Depth-tested extra image, Z buffer wrapping. @@ -2374,17 +2374,17 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, ++IsoDrawData.SrcPixel; ++IsoDrawData.SrcDepth; ++IsoDrawData.DepthPtr; - IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)IsoDrawData.DepthPtr); + IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.DepthPtr); ++IsoDrawData.AlphaPtr; - IsoDrawData.AlphaPtr = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)IsoDrawData.AlphaPtr); + IsoDrawData.AlphaPtr = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.AlphaPtr); } IsoDrawData.DestPtr = (unsigned short *)((char *)IsoDrawData.DestPtr + IsoDrawData.SurfacePitch); IsoDrawData.SrcPixel += IsoDrawData.ImageRowStep; IsoDrawData.SrcDepth += IsoDrawData.ImageRowStep; IsoDrawData.DepthPtr = &IsoDrawData.DepthPtr[IsoDrawData.DepthWidth]; - IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((unsigned int)IsoDrawData.DepthPtr); + IsoDrawData.DepthPtr = (unsigned short *)DepthBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.DepthPtr); IsoDrawData.AlphaPtr = &IsoDrawData.AlphaPtr[IsoDrawData.AlphaWidth]; - IsoDrawData.AlphaPtr = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)IsoDrawData.AlphaPtr); + IsoDrawData.AlphaPtr = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.AlphaPtr); } } else { @@ -2412,7 +2412,7 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, IsoDrawData.AlphaPtr = &IsoDrawData.AlphaPtr[IsoDrawData.AlphaWidth]; } } - } else if ((unsigned int)&IsoDrawData.AlphaPtr[IsoDrawData.SpanWidth + 2 + IsoDrawData.SpanHeight * AlphaBuffer->Get_Buffer_Width()] >= AlphaBuffer->Get_Buffer_End()) { + } else if ((uintptr_t)&IsoDrawData.AlphaPtr[IsoDrawData.SpanWidth + 2 + IsoDrawData.SpanHeight * AlphaBuffer->Get_Buffer_Width()] >= AlphaBuffer->Get_Buffer_End()) { /* * Plain extra image (no depth), alpha buffer wrapping. @@ -2424,12 +2424,12 @@ void IsometricTileTypeClass::Draw_Tile(LightConvertClass * drawer, int subtile, } ++IsoDrawData.DestPtr; ++IsoDrawData.AlphaPtr; - IsoDrawData.AlphaPtr = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)IsoDrawData.AlphaPtr); + IsoDrawData.AlphaPtr = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.AlphaPtr); ++IsoDrawData.SrcPixel; } IsoDrawData.DestPtr = (unsigned short *)((char *)IsoDrawData.DestPtr + IsoDrawData.SurfacePitch); IsoDrawData.AlphaPtr = &IsoDrawData.AlphaPtr[IsoDrawData.AlphaWidth]; - IsoDrawData.AlphaPtr = (unsigned short *)AlphaBuffer->Wrap_Overflow((unsigned int)IsoDrawData.AlphaPtr); + IsoDrawData.AlphaPtr = (unsigned short *)AlphaBuffer->Wrap_Overflow((uintptr_t)IsoDrawData.AlphaPtr); IsoDrawData.SrcPixel += IsoDrawData.ImageRowStep; } } else { From a0d04725e96761d54ea8215559d1f88540d9ec32 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 22:48:11 +0100 Subject: [PATCH 064/179] build: keep the Windows defines off a native VQA player build Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/vqalib/CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/code/vqalib/CMakeLists.txt b/code/vqalib/CMakeLists.txt index 5a77067fc..8a162b337 100644 --- a/code/vqalib/CMakeLists.txt +++ b/code/vqalib/CMakeLists.txt @@ -26,11 +26,11 @@ set_target_properties(VQALib PROPERTIES # binary and have to agree on the runtime library and the floating-point model. target_compile_options(VQALib PRIVATE ${OPENTS_COMPILE_OPTIONS}) -target_compile_definitions(VQALib PRIVATE - WIN32 - _WINDOWS - NOMINMAX -) +target_compile_definitions(VQALib PRIVATE NOMINMAX) + +if(WIN32) + target_compile_definitions(VQALib PRIVATE WIN32 _WINDOWS) +endif() # Callers reach the vqaplay.h family through the library, while the player itself reaches # back into the engine for ahandle.h, which owns the sound handle it plays through. From 2351d521c1ba9f337ba4d8d11dcdaae6cce1a018 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 23:16:41 +0100 Subject: [PATCH 065/179] Build a host cursor from the icon the game hands over Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- platform/win32compat/src/gdi.cpp | 123 ++++++++++++++++++- platform/win32compat/src/input.cpp | 159 +++++++++++++++++++++++-- platform/win32compat/src/win32compat.h | 14 +++ 3 files changed, 285 insertions(+), 11 deletions(-) diff --git a/platform/win32compat/src/gdi.cpp b/platform/win32compat/src/gdi.cpp index bd1c63073..cf38d79d1 100644 --- a/platform/win32compat/src/gdi.cpp +++ b/platform/win32compat/src/gdi.cpp @@ -9,10 +9,19 @@ #include "win32compat.h" +#include +#include +#include +#include + // The game composes its own frame in system memory and presents it through bgfx, so GDI // is reached only by the legacy dialog layer and by the tactical map's text, both of which // draw with the host font on Windows. Nothing here draws: a device context that does not // exist refuses every call, and the callers fall back to the engine's own font path. +// +// Bitmaps are the exception. The game builds its mouse cursor by drawing one of its own +// shape frames into a bitmap and handing it to CreateIconIndirect, so a bitmap has to hold +// real pixels for the cursor to exist at all. extern "C" HDC GetDC(HWND window) { (void)window; return(NULL); } extern "C" int ReleaseDC(HWND window, HDC dc) { (void)window; (void)dc; return(0); } @@ -21,12 +30,9 @@ extern "C" BOOL DeleteDC(HDC dc) { (void)dc; return(FALSE); } extern "C" int SaveDC(HDC dc) { (void)dc; return(0); } extern "C" BOOL RestoreDC(HDC dc, int state) { (void)dc; (void)state; return(FALSE); } extern "C" HGDIOBJ SelectObject(HDC dc, HGDIOBJ object) { (void)dc; (void)object; return(NULL); } -extern "C" BOOL DeleteObject(HGDIOBJ object) { (void)object; return(FALSE); } extern "C" int GetObject(HGDIOBJ object, int size, LPVOID buffer) { (void)object; (void)size; (void)buffer; return(0); } extern "C" HGDIOBJ GetStockObject(int index) { (void)index; return(NULL); } extern "C" HBRUSH CreateSolidBrush(COLORREF color) { (void)color; return(NULL); } -extern "C" HBITMAP CreateBitmap(int width, int height, UINT planes, UINT bits, void const * data) { (void)width; (void)height; (void)planes; (void)bits; (void)data; return(NULL); } -extern "C" HBITMAP CreateDIBSection(HDC dc, BITMAPINFO const * info, UINT usage, void ** bits, HANDLE section, DWORD offset) { (void)dc; (void)info; (void)usage; (void)section; (void)offset; if (bits != NULL) *bits = NULL; return(NULL); } extern "C" HFONT CreateFont(int height, int width, int escapement, int orientation, int weight, DWORD italic, DWORD underline, DWORD strikeout, DWORD charset, DWORD outprecision, DWORD clipprecision, DWORD quality, DWORD pitch, LPCSTR face) { (void)height; (void)width; (void)escapement; (void)orientation; (void)weight; (void)italic; (void)underline; (void)strikeout; (void)charset; (void)outprecision; (void)clipprecision; (void)quality; (void)pitch; (void)face; return(NULL); } extern "C" HFONT CreateFontIndirect(LOGFONT const * font) { (void)font; return(NULL); } extern "C" BOOL BitBlt(HDC dest, int x, int y, int width, int height, HDC source, int sx, int sy, DWORD rop) { (void)dest; (void)x; (void)y; (void)width; (void)height; (void)source; (void)sx; (void)sy; (void)rop; return(FALSE); } @@ -74,3 +80,114 @@ extern "C" int GetDeviceCaps(HDC dc, int index) (void)index; return(0); } + + +static std::vector _Bitmaps; + + +Win32Bitmap * Win32_Lookup_Bitmap(HBITMAP bitmap) +{ + for (Win32Bitmap * record : _Bitmaps) { + if ((HBITMAP)record == bitmap) { + return(record); + } + } + + return(NULL); +} + + +static Win32Bitmap * Make_Bitmap(int width, int height, int bitcount, int alignment, bool topdown) +{ + if (width <= 0 || height <= 0) { + return(NULL); + } + + // A device-dependent bitmap aligns its scan lines to a word and a device-independent + // one to a double word, and the caller sizes the pixels it hands over to match. + int const bits = alignment * 8; + int const pitch = (((width * bitcount) + bits - 1) / bits) * alignment; + + Win32Bitmap * record = new(std::nothrow) Win32Bitmap; + + if (record == NULL) { + return(NULL); + } + + record->Width = width; + record->Height = height; + record->BitCount = bitcount; + record->Pitch = pitch; + record->TopDown = topdown; + record->Bits = new(std::nothrow) unsigned char[(std::size_t)pitch * (std::size_t)height](); + + if (record->Bits == NULL) { + delete record; + return(NULL); + } + + _Bitmaps.push_back(record); + return(record); +} + + +extern "C" HBITMAP CreateBitmap(int width, int height, UINT planes, UINT bits, void const * data) +{ + if (planes != 1 || (bits != 1 && bits != 32)) { + return(NULL); + } + + Win32Bitmap * record = Make_Bitmap(width, height, (int)bits, 2, true); + + if (record != NULL && data != NULL) { + memcpy(record->Bits, data, (std::size_t)record->Pitch * (std::size_t)record->Height); + } + + return((HBITMAP)record); +} + + +// Only the layout the cursor is drawn in is offered: an uncompressed 32-bit image whose +// rows the caller then writes itself. Anything else is refused the way the rest of GDI is. +extern "C" HBITMAP CreateDIBSection(HDC dc, BITMAPINFO const * info, UINT usage, void ** bits, HANDLE section, DWORD offset) +{ + (void)dc; + (void)usage; + + if (bits != NULL) { + *bits = NULL; + } + + if (info == NULL || section != NULL || offset != 0) { + return(NULL); + } + + if (info->bmiHeader.biPlanes != 1 || info->bmiHeader.biBitCount != 32 || info->bmiHeader.biCompression != BI_RGB) { + return(NULL); + } + + LONG const rawheight = info->bmiHeader.biHeight; + Win32Bitmap * record = Make_Bitmap((int)info->bmiHeader.biWidth, + (int)(rawheight < 0 ? -rawheight : rawheight), 32, 4, rawheight < 0); + + if (record != NULL && bits != NULL) { + *bits = record->Bits; + } + + return((HBITMAP)record); +} + + +extern "C" BOOL DeleteObject(HGDIOBJ object) +{ + for (auto it = _Bitmaps.begin(); it != _Bitmaps.end(); ++it) { + if ((HGDIOBJ)*it == object) { + delete [] (*it)->Bits; + delete *it; + _Bitmaps.erase(it); + return(TRUE); + } + } + + return(FALSE); +} diff --git a/platform/win32compat/src/input.cpp b/platform/win32compat/src/input.cpp index ea2f40136..4df33a2ba 100644 --- a/platform/win32compat/src/input.cpp +++ b/platform/win32compat/src/input.cpp @@ -9,7 +9,10 @@ #include "win32compat.h" +#include #include +#include +#include // The virtual key codes the engine's keyboard queue is written against. They are stated // as numbers rather than taken from the engine's own header so that this layer does not @@ -252,17 +255,45 @@ static int _CursorCount; static HWND _Capture; +// One record per cursor the game builds out of its own shape art. The game keeps the +// handles and reselects them as the pointer changes shape, so a record lives until the +// game destroys it. +struct Win32Cursor +{ + SDL_Cursor * Cursor; +}; + +static std::vector _Cursors; +static HCURSOR _CurrentCursor; + + +static Win32Cursor * Lookup_Cursor(HCURSOR cursor) +{ + for (Win32Cursor * record : _Cursors) { + if ((HCURSOR)record == cursor) { + return(record); + } + } + + return(NULL); +} + + extern "C" HCURSOR SetCursor(HCURSOR cursor) { - // The game draws its own pointer from its own shapes, so the host's pointer is only - // ever shown or hidden and never replaced. - if (cursor == NULL) { - SDL_HideCursor(); - } else { + HCURSOR const previous = _CurrentCursor; + Win32Cursor * record = Lookup_Cursor(cursor); + + _CurrentCursor = record != NULL ? cursor : NULL; + + if (record != NULL) { + SDL_SetCursor(record->Cursor); SDL_ShowCursor(); + } else { + SDL_HideCursor(); } - return(NULL); + return(previous); } @@ -324,6 +355,118 @@ extern "C" HWND GetCapture(void) extern "C" HCURSOR LoadCursor(HINSTANCE instance, LPCSTR name) { (void)instance; (void)name; return(NULL); } extern "C" HICON LoadIcon(HINSTANCE instance, LPCSTR name) { (void)instance; (void)name; return(NULL); } -extern "C" HCURSOR CreateIconIndirect(ICONINFO * info) { (void)info; return(NULL); } -extern "C" BOOL DestroyCursor(HCURSOR cursor) { (void)cursor; return(TRUE); } extern "C" BOOL DestroyIcon(HICON icon) { (void)icon; return(TRUE); } + + +// A 32-bit device-independent bitmap holds its pixels as blue, green, red and alpha in +// memory order, which is what the host calls ARGB8888 on a little-endian machine. +static SDL_Surface * Surface_From_Bitmap(Win32Bitmap const * bitmap) +{ + SDL_Surface * surface = SDL_CreateSurface(bitmap->Width, bitmap->Height, SDL_PIXELFORMAT_ARGB8888); + + if (surface == NULL) { + return(NULL); + } + + for (int y = 0; y < bitmap->Height; y++) { + int const source = bitmap->TopDown ? y : bitmap->Height - 1 - y; + memcpy((unsigned char *)surface->pixels + (std::size_t)y * (std::size_t)surface->pitch, + bitmap->Bits + (std::size_t)source * (std::size_t)bitmap->Pitch, + (std::size_t)bitmap->Width * 4); + } + + return(surface); +} + + +/* + * The game draws its pointer at the scale its frame is presented at, which is measured in + * physical pixels, while the host lays a cursor out in the points its display uses. The + * image is offered at the point size that matches, with the pixels the game drew carried + * alongside it so a dense display still shows all of them. + */ +extern "C" HCURSOR CreateIconIndirect(ICONINFO * info) +{ + if (info == NULL) { + return(NULL); + } + + Win32Bitmap const * color = Win32_Lookup_Bitmap(info->hbmColor); + + if (color == NULL || color->BitCount != 32) { + return(NULL); + } + + SDL_Surface * pixels = Surface_From_Bitmap(color); + + if (pixels == NULL) { + return(NULL); + } + + float const density = Win32_Pixel_Density(); + SDL_Surface * image = pixels; + int hotx = (int)info->xHotspot; + int hoty = (int)info->yHotspot; + + if (density > 1.0f) { + int const width = (int)(pixels->w / density); + int const height = (int)(pixels->h / density); + SDL_Surface * scaled = width > 0 && height > 0 + ? SDL_ScaleSurface(pixels, width, height, SDL_SCALEMODE_NEAREST) : NULL; + + if (scaled != NULL && SDL_AddSurfaceAlternateImage(scaled, pixels)) { + image = scaled; + hotx = (int)(hotx / density); + hoty = (int)(hoty / density); + } else if (scaled != NULL) { + SDL_DestroySurface(scaled); + } + } + + if (hotx >= image->w) hotx = image->w - 1; + if (hoty >= image->h) hoty = image->h - 1; + if (hotx < 0) hotx = 0; + if (hoty < 0) hoty = 0; + + SDL_Cursor * cursor = SDL_CreateColorCursor(image, hotx, hoty); + + if (image != pixels) { + SDL_DestroySurface(image); + } + SDL_DestroySurface(pixels); + + if (cursor == NULL) { + return(NULL); + } + + Win32Cursor * record = new(std::nothrow) Win32Cursor; + + if (record == NULL) { + SDL_DestroyCursor(cursor); + return(NULL); + } + + record->Cursor = cursor; + _Cursors.push_back(record); + return((HCURSOR)record); +} + + +extern "C" BOOL DestroyCursor(HCURSOR cursor) +{ + for (auto it = _Cursors.begin(); it != _Cursors.end(); ++it) { + if ((HCURSOR)*it == cursor) { + if (_CurrentCursor == cursor) { + _CurrentCursor = NULL; + SDL_SetCursor(SDL_GetDefaultCursor()); + } + + SDL_DestroyCursor((*it)->Cursor); + delete *it; + _Cursors.erase(it); + return(TRUE); + } + } + + return(FALSE); +} diff --git a/platform/win32compat/src/win32compat.h b/platform/win32compat/src/win32compat.h index 4bed6bce6..7d0bab01c 100644 --- a/platform/win32compat/src/win32compat.h +++ b/platform/win32compat/src/win32compat.h @@ -28,6 +28,20 @@ struct Win32Window bool Enabled; }; +// The one bitmap a native build creates is the canvas the game draws a mouse cursor onto, +// so a bitmap object carries only what a cursor is built from. +struct Win32Bitmap +{ + int Width; + int Height; + int BitCount; + int Pitch; + bool TopDown; + unsigned char * Bits; +}; + +Win32Bitmap * Win32_Lookup_Bitmap(HBITMAP bitmap); + Win32Window * Win32_Lookup(HWND window); HWND Win32_Main_Window(void); WNDPROC Win32_Class_Procedure(char const * name); From caa7c30e0af0bef92723ed0adb21c483a8b77ba7 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 23:16:56 +0100 Subject: [PATCH 066/179] Hold the cursor pixels at 32 bits and decode a compressed shape Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/wincursor.cpp | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/code/wincursor.cpp b/code/wincursor.cpp index 1f4a36975..cdb91cd1a 100644 --- a/code/wincursor.cpp +++ b/code/wincursor.cpp @@ -21,6 +21,7 @@ #include "win.h" #include "xmouse.h" +#include #include @@ -120,26 +121,43 @@ static HCURSOR Build_Cursor(ShapeSet const * shape, int frame, int hotx, int hot // what turns one into the other. unsigned short const * table = (unsigned short const *)MouseDrawer->Get_Translate_Table(); + bool const compressed = shape->Is_RLE_Compressed(frame); + unsigned char const * line = data; + for (int y = 0; y < rect.Height; y++) { - for (int x = 0; x < rect.Width; x++) { - unsigned char index = data[y * rect.Width + x]; + // A compressed line starts with its own byte length and then runs of pixels, where + // a zero introduces a count of transparent ones. + unsigned char const * source = compressed ? line + sizeof(unsigned short) : data + y * rect.Width; + int x = 0; + + while (x < rect.Width) { + + unsigned char index = *source++; + if (index == 0) { + x += compressed ? *source++ : 1; continue; } unsigned short pixel = table[index]; - unsigned long red = ((pixel >> 11) & 0x1F) << 3; - unsigned long green = ((pixel >> 5) & 0x3F) << 2; - unsigned long blue = (pixel & 0x1F) << 3; - unsigned long argb = 0xFF000000UL | (red << 16) | (green << 8) | blue; + std::uint32_t red = ((pixel >> 11) & 0x1F) << 3; + std::uint32_t green = ((pixel >> 5) & 0x3F) << 2; + std::uint32_t blue = (pixel & 0x1F) << 3; + std::uint32_t argb = 0xFF000000U | (red << 16) | (green << 8) | blue; for (int suby = 0; suby < scale; suby++) { - unsigned long * row = (unsigned long *)bits + ((rect.Y + y) * scale + suby) * width + (rect.X + x) * scale; + std::uint32_t * row = (std::uint32_t *)bits + ((rect.Y + y) * scale + suby) * width + (rect.X + x) * scale; for (int subx = 0; subx < scale; subx++) { row[subx] = argb; } } + + x++; + } + + if (compressed) { + line += *(unsigned short const *)line; } } From 9d13d8cb0c2907386fc560a614b7330ae2d0778e Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 23:19:57 +0100 Subject: [PATCH 067/179] End the program when the window is asked to close Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/winstub.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/code/winstub.cpp b/code/winstub.cpp index b79b2b424..6ecfa5c54 100644 --- a/code/winstub.cpp +++ b/code/winstub.cpp @@ -254,6 +254,15 @@ LRESULT CALLBACK /*_export*/ Windows_Procedure(HWND hwnd, UINT message, WPARAM w break; case WM_CLOSE: +#ifndef _WIN32 + /* + ** Windows answers a close request by destroying the window and leaving the + ** program to notice. There is no front end here to notice it and no dialog + ** layer to confirm through, so the request ends the program itself. + */ + Emergency_Exit(); + exit(EXIT_SUCCESS); +#endif break; case WM_CREATE: From ce28f804536799dab8eabc84785b86da671c6b24 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 23:23:02 +0100 Subject: [PATCH 068/179] Show a pointer while the game has the mouse released Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- platform/win32compat/src/input.cpp | 43 +++++++++++++++++------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/platform/win32compat/src/input.cpp b/platform/win32compat/src/input.cpp index 4df33a2ba..6a15564e4 100644 --- a/platform/win32compat/src/input.cpp +++ b/platform/win32compat/src/input.cpp @@ -250,7 +250,7 @@ extern "C" BOOL SetCursorPos(int x, int y) } -static bool _CursorVisible = true; +static bool _CursorShown = true; static int _CursorCount; static HWND _Capture; @@ -279,20 +279,32 @@ static Win32Cursor * Lookup_Cursor(HCURSOR cursor) } -extern "C" HCURSOR SetCursor(HCURSOR cursor) +// The pointer the game selects and the counter this API keeps both decide whether anything +// is on screen, so they are applied together. +static void Apply_Cursor(void) { - HCURSOR const previous = _CurrentCursor; - Win32Cursor * record = Lookup_Cursor(cursor); - - _CurrentCursor = record != NULL ? cursor : NULL; + Win32Cursor * record = Lookup_Cursor(_CurrentCursor); - if (record != NULL) { - SDL_SetCursor(record->Cursor); - SDL_ShowCursor(); - } else { + if (!_CursorShown || _CursorCount < 0) { SDL_HideCursor(); + return; } + // While the mouse is released the game selects no shape of its own, and Windows would + // be drawing the window class's cursor, so the host's own pointer stands in for it. + SDL_SetCursor(record != NULL ? record->Cursor : SDL_GetDefaultCursor()); + SDL_ShowCursor(); +} + + +extern "C" HCURSOR SetCursor(HCURSOR cursor) +{ + HCURSOR const previous = _CurrentCursor; + + _CurrentCursor = Lookup_Cursor(cursor) != NULL ? cursor : NULL; + _CursorShown = _CurrentCursor != NULL; + + Apply_Cursor(); return(previous); } @@ -301,16 +313,11 @@ extern "C" int ShowCursor(BOOL show) { _CursorCount += show ? 1 : -1; - bool const visible = _CursorCount >= 0; - if (visible != _CursorVisible) { - _CursorVisible = visible; - if (visible) { - SDL_ShowCursor(); - } else { - SDL_HideCursor(); - } + if (show) { + _CursorShown = true; } + Apply_Cursor(); return(_CursorCount); } From c0ba2ef7635fbae262243ec45416c5905fed9772 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 23:34:04 +0100 Subject: [PATCH 069/179] build: vendor RmlUi, FreeType and Dear ImGui Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- .github/workflows/engine-build.yml | 4 ++ .gitmodules | 9 ++++ THIRD_PARTY_NOTICES.md | 4 ++ docs/BUILDING.md | 17 +++++++ thirdparty/CMakeLists.txt | 80 +++++++++++++++++++++++++++++- thirdparty/RmlUi | 1 + thirdparty/freetype | 1 + thirdparty/imgui | 1 + thirdparty/licenses/zlib.txt | 24 +++++++++ 9 files changed, 139 insertions(+), 2 deletions(-) create mode 160000 thirdparty/RmlUi create mode 160000 thirdparty/freetype create mode 160000 thirdparty/imgui create mode 100644 thirdparty/licenses/zlib.txt diff --git a/.github/workflows/engine-build.yml b/.github/workflows/engine-build.yml index 75102b394..900cb1a19 100644 --- a/.github/workflows/engine-build.yml +++ b/.github/workflows/engine-build.yml @@ -85,6 +85,10 @@ jobs: cp thirdparty/licenses/khronos-opengl.txt artifact/OpenTS_THIRD_PARTY_LICENSES/khronos-opengl.txt cp thirdparty/miniaudio/LICENSE artifact/OpenTS_THIRD_PARTY_LICENSES/miniaudio.txt cp thirdparty/licenses/stb-vorbis.txt artifact/OpenTS_THIRD_PARTY_LICENSES/stb-vorbis.txt + cp thirdparty/RmlUi/LICENSE.txt artifact/OpenTS_THIRD_PARTY_LICENSES/rmlui.txt + cp thirdparty/freetype/docs/FTL.TXT artifact/OpenTS_THIRD_PARTY_LICENSES/freetype.txt + cp thirdparty/licenses/zlib.txt artifact/OpenTS_THIRD_PARTY_LICENSES/zlib.txt + cp thirdparty/imgui/LICENSE.txt artifact/OpenTS_THIRD_PARTY_LICENSES/dear-imgui.txt cat thirdparty/licenses/khronos-vulkan-notice.txt \ thirdparty/bgfx.cmake/bimg/3rdparty/astc-encoder/LICENSE.txt \ > artifact/OpenTS_THIRD_PARTY_LICENSES/khronos-vulkan.txt diff --git a/.gitmodules b/.gitmodules index 43da1b1f3..2446d1322 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,12 @@ [submodule "thirdparty/SDL"] path = thirdparty/SDL url = https://github.com/libsdl-org/SDL.git +[submodule "thirdparty/RmlUi"] + path = thirdparty/RmlUi + url = https://github.com/mikke89/RmlUi.git +[submodule "thirdparty/freetype"] + path = thirdparty/freetype + url = https://github.com/freetype/freetype.git +[submodule "thirdparty/imgui"] + path = thirdparty/imgui + url = https://github.com/ocornut/imgui.git diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 13cdcc4d1..4dbb68fb1 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -15,6 +15,10 @@ remains under its own license and copyright notices. | [Vulkan Headers](https://github.com/KhronosGroup/Vulkan-Headers) | Vulkan API headers used by bgfx | Apache-2.0 | | [miniaudio](https://github.com/mackron/miniaudio) | Audio device output, resampling, and WAV, FLAC, and MP3 decoding | MIT-0 or Unlicense | | [stb_vorbis](https://github.com/nothings/stb) | Ogg Vorbis decoding, bundled with miniaudio | MIT or Unlicense | +| [RmlUi](https://github.com/mikke89/RmlUi) | User interface documents, styling, and layout | MIT | +| [FreeType](https://freetype.org/) | Glyph rasterization for the RmlUi font engine | FTL | +| [zlib](https://zlib.net/) | Compressed font stream support, bundled with FreeType | zlib | +| [Dear ImGui](https://github.com/ocornut/imgui) | Developer tooling on the UI shell | MIT | The source checkout keeps the license texts under `thirdparty/`. Binary packages reproduce the license texts for the components used by OpenTS under diff --git a/docs/BUILDING.md b/docs/BUILDING.md index e1e16bccc..a94aa54ca 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -39,6 +39,23 @@ The audio layer uses [miniaudio](https://github.com/mackron/miniaudio), vendored through `thirdparty/miniaudio` at a tested tag and compiled as one translation unit from `thirdparty/miniaudio-impl.c`. +The UI shell uses [RmlUi](https://github.com/mikke89/RmlUi), vendored through +`thirdparty/RmlUi` at a tested tag and built static with the FreeType font +engine. Its samples carry their own window and renderer backends, which the +shell replaces, so none of them are built. + +RmlUi's font engine uses [FreeType](https://freetype.org/), vendored through +`thirdparty/freetype` at a tested tag with bzip2, PNG, HarfBuzz, and Brotli +disabled. FreeType's bundled zlib supplies compressed font stream support. + +Developer tooling uses [Dear ImGui](https://github.com/ocornut/imgui), vendored +through `thirdparty/imgui` at a tested tag. Only the core sources are compiled; +the bundled platform and renderer backends are not, because the shell feeds +ImGui through the engine's own message hook and draws it on bgfx. + +`bimg_decode`, which bgfx already carries, decodes the PNG and TGA images UI +documents reference and is built and linked with everything else. + For a fresh clone, use `git clone --recurse-submodules`. Configuration stops with instructions if a submodule is missing. Update a pinned tag in a separate change. diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 18423863b..b67beb0db 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -20,8 +20,9 @@ set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") add_subdirectory(bgfx.cmake) -# These libraries support the disabled texture tools and are not linked into OpenTS. -set_target_properties(bimg_decode bimg_encode PROPERTIES EXCLUDE_FROM_ALL TRUE) +# The encoder supports the disabled texture tools and is not linked into OpenTS. The decoder +# reads the PNG and TGA files the UI documents reference, so it is built with everything else. +set_target_properties(bimg_encode PROPERTIES EXCLUDE_FROM_ALL TRUE) if(OPENTS_EXPERIMENTAL_CLANG_CL AND CMAKE_SIZEOF_VOID_P EQUAL 4) # bx treats Clang with the MSVC CRT like a non-x86 compiler and erases @@ -136,3 +137,78 @@ if(NOT WIN32) add_subdirectory(SDL) endif() + +# +# --------------------------------------------------------- +# FreeType (glyph rasterization for the UI font engine) +# --------------------------------------------------------- +# +# Only RmlUi reaches this library, and only for the shipped sans-serif face. Its optional +# dependencies would each add a library the engine does not otherwise carry, so every one +# of them is refused here rather than left to whatever the build machine happens to have. +if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/freetype/CMakeLists.txt") + message(FATAL_ERROR + "thirdparty/freetype is empty. Fetch the submodules with:\n" + " git submodule update --init --recursive") +endif() + +set(FT_DISABLE_BZIP2 ON CACHE BOOL "" FORCE) +set(FT_DISABLE_PNG ON CACHE BOOL "" FORCE) +set(FT_DISABLE_HARFBUZZ ON CACHE BOOL "" FORCE) +set(FT_DISABLE_BROTLI ON CACHE BOOL "" FORCE) + +add_subdirectory(freetype) + +# FreeType names its export Freetype::Freetype but declares no target under that name in the +# build tree, which is the name RmlUi looks for. +if(NOT TARGET Freetype::Freetype) + add_library(Freetype::Freetype ALIAS freetype) +endif() + +# +# --------------------------------------------------------- +# RmlUi (documents, styling and layout for the UI shell) +# --------------------------------------------------------- +# +# Built as a static library with the FreeType font engine. The samples carry their own +# window and renderer backends, which is what the UI shell replaces, so none of them are +# built and no backend is selected. +if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/RmlUi/CMakeLists.txt") + message(FATAL_ERROR + "thirdparty/RmlUi is empty. Fetch the submodules with:\n" + " git submodule update --init --recursive") +endif() + +set(BUILD_SHARED_LIBS OFF) +set(RMLUI_FONT_ENGINE "freetype" CACHE STRING "" FORCE) +set(RMLUI_SAMPLES OFF CACHE BOOL "" FORCE) +set(RMLUI_LUA_BINDINGS OFF CACHE BOOL "" FORCE) +set(RMLUI_SVG_PLUGIN OFF CACHE BOOL "" FORCE) +set(RMLUI_LOTTIE_PLUGIN OFF CACHE BOOL "" FORCE) +set(RMLUI_HARFBUZZ_SAMPLE OFF CACHE BOOL "" FORCE) +set(RMLUI_TRACY_PROFILING OFF CACHE BOOL "" FORCE) +set(RMLUI_INSTALL_TARGETS_DIR "" CACHE STRING "" FORCE) +set(RMLUI_IS_ROOT_PROJECT FALSE) + +add_subdirectory(RmlUi) + +# +# --------------------------------------------------------- +# Dear ImGui (developer tooling on the same UI shell) +# --------------------------------------------------------- +# +# The core sources only. The bundled platform and renderer backends are left out; the UI +# shell feeds ImGui through the engine's own message hook and draws it on bgfx. +if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/imgui/imgui.cpp") + message(FATAL_ERROR + "thirdparty/imgui is empty. Fetch the submodules with:\n" + " git submodule update --init --recursive") +endif() + +add_library(imgui STATIC + "${CMAKE_CURRENT_SOURCE_DIR}/imgui/imgui.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/imgui/imgui_draw.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/imgui/imgui_tables.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/imgui/imgui_widgets.cpp" +) +target_include_directories(imgui PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/imgui") diff --git a/thirdparty/RmlUi b/thirdparty/RmlUi new file mode 160000 index 000000000..ba95ffe8b --- /dev/null +++ b/thirdparty/RmlUi @@ -0,0 +1 @@ +Subproject commit ba95ffe8bfb6370efb2cdcca927eaad4710c5413 diff --git a/thirdparty/freetype b/thirdparty/freetype new file mode 160000 index 000000000..42608f77f --- /dev/null +++ b/thirdparty/freetype @@ -0,0 +1 @@ +Subproject commit 42608f77f20749dd6ddc9e0536788eaad70ea4b5 diff --git a/thirdparty/imgui b/thirdparty/imgui new file mode 160000 index 000000000..f1cc2ae15 --- /dev/null +++ b/thirdparty/imgui @@ -0,0 +1 @@ +Subproject commit f1cc2ae15e53a861a874c3034aae6798fde194ab diff --git a/thirdparty/licenses/zlib.txt b/thirdparty/licenses/zlib.txt new file mode 100644 index 000000000..1b05130bc --- /dev/null +++ b/thirdparty/licenses/zlib.txt @@ -0,0 +1,24 @@ +zlib (thirdparty/freetype/src/gzip) + +version 1.3, August 18th, 2023 + +Copyright (C) 1995-2023 Jean-loup Gailly and Mark Adler + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +Jean-loup Gailly Mark Adler +jloup@gzip.org madler@alumni.caltech.edu From b462d1f05af4559d2f78888d32d7fc471b0b866e Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Tue, 8 Sep 2026 23:48:36 +0100 Subject: [PATCH 070/179] feat(ui): add the UI shell over RmlUi and Dear ImGui Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/CMakeLists.txt | 35 ++ code/backendviews.hh | 26 + code/bgfxbackend.cpp | 31 +- code/bgfxbackend.h | 8 +- code/gamedirs.cpp | 7 + code/mainloop.cpp | 2 + code/ui/uidev.cpp | 165 ++++++ code/ui/uifile.cpp | 163 ++++++ code/ui/uiinternal.h | 68 +++ code/ui/uirender.cpp | 463 +++++++++++++++++ code/ui/uirmlview.h | 58 +++ code/ui/uiscreen.h | 83 +++ code/ui/uishell.cpp | 665 +++++++++++++++++++++++++ code/ui/uishell.h | 50 ++ code/ui/uisystem.cpp | 188 +++++++ code/ui/uitexture.cpp | 132 +++++ code/video.cpp | 19 +- code/winstub.cpp | 13 + docs/UI_DESIGN.md | 17 +- platform/win32compat/include/windows.h | 3 + ui/LICENSE.md | 9 + ui/LatoLatin-Regular.ttf | Bin 0 -> 148540 bytes ui/uitest.rcss | 81 +++ ui/uitest.rml | 21 + 24 files changed, 2289 insertions(+), 18 deletions(-) create mode 100644 code/backendviews.hh create mode 100644 code/ui/uidev.cpp create mode 100644 code/ui/uifile.cpp create mode 100644 code/ui/uiinternal.h create mode 100644 code/ui/uirender.cpp create mode 100644 code/ui/uirmlview.h create mode 100644 code/ui/uiscreen.h create mode 100644 code/ui/uishell.cpp create mode 100644 code/ui/uishell.h create mode 100644 code/ui/uisystem.cpp create mode 100644 code/ui/uitexture.cpp create mode 100644 ui/LICENSE.md create mode 100644 ui/LatoLatin-Regular.ttf create mode 100644 ui/uitest.rcss create mode 100644 ui/uitest.rml diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 4feb36e0c..6eaf16aa4 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -176,6 +176,30 @@ set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/bgfxbackend.cpp" PROPER COMPILE_OPTIONS "$<$:/Zc:preprocessor>" ) +# The UI shell is scoped the same way: only code/ui sees RmlUi and ImGui, and uirender.cpp +# alone adds bgfx to that, so no other translation unit carries any of their headers or +# build settings. uitexture.cpp reads images through bimg, which bgfx already carries. +file(GLOB OPENTS_UI_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/ui/*.cpp") +set_source_files_properties(${OPENTS_UI_SOURCES} PROPERTIES + INCLUDE_DIRECTORIES + "${CMAKE_SOURCE_DIR}/thirdparty/RmlUi/Include;${CMAKE_SOURCE_DIR}/thirdparty/imgui" + COMPILE_OPTIONS "$<$:/Zc:preprocessor>" +) + +set_property(SOURCE + "${CMAKE_CURRENT_SOURCE_DIR}/ui/uirender.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ui/uitexture.cpp" + APPEND PROPERTY INCLUDE_DIRECTORIES + "${BGFX_ROOT}/include;${CMAKE_SOURCE_DIR}/thirdparty/bgfx.cmake/bx/include;${CMAKE_SOURCE_DIR}/thirdparty/bgfx.cmake/bimg/include;${BGFX_ROOT}/examples/common/imgui" +) + +set_property(SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/ui/uirender.cpp" APPEND PROPERTY + COMPILE_DEFINITIONS "BX_CONFIG_DEBUG=$,1,$>" +) +set_property(SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/ui/uitexture.cpp" APPEND PROPERTY + COMPILE_DEFINITIONS "BX_CONFIG_DEBUG=$,1,$>" +) + # bx rewrites __stdcall while its headers are being parsed by clang-cl. Force the # compatibility header into the renderer translation unit as well as bx/bgfx so the # MSVC standard-library headers that follow still see the Win32 calling convention. @@ -220,8 +244,11 @@ target_link_libraries(OpenTS PRIVATE bgfx bx bimg + bimg_decode miniaudio lzo + rmlui + imgui ) if(WIN32) @@ -317,6 +344,14 @@ add_custom_command(TARGET OpenTS POST_BUILD "${TS_RUN_DIR}" ) +# The shipped UI documents, styles and font travel with the executable. They are read +# through the game's file system, so this directory is what the search path points at. +add_custom_command(TARGET OpenTS POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_SOURCE_DIR}/ui" + "${TS_RUN_DIR}/ui" +) + # Copy the linker-generated .pdb alongside the exe. Only the MSVC linker writes one. if(MSVC) add_custom_command(TARGET OpenTS POST_BUILD diff --git a/code/backendviews.hh b/code/backendviews.hh new file mode 100644 index 000000000..8f99ebf5c --- /dev/null +++ b/code/backendviews.hh @@ -0,0 +1,26 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The bgfx views the frame and the overlays are drawn through. bgfx renders views in +// ascending order, so these numbers are the draw order: the magnify pass has to precede +// the present pass for the present to sample this frame's output rather than the last +// one's, and both have to precede the overlays for the overlays to land on top. +// +// bgfxbackend.cpp owns the first two and code/ui the last two. They share this header so +// that neither can renumber a view the other draws through. + +#pragma once + + +enum BackendViewType { + BACKEND_VIEW_PRESCALE = 0, + BACKEND_VIEW_PRESENT = 1, + BACKEND_VIEW_UI = 2, + BACKEND_VIEW_DEV = 3, +}; diff --git a/code/bgfxbackend.cpp b/code/bgfxbackend.cpp index 40ca82e2d..19450ce4c 100644 --- a/code/bgfxbackend.cpp +++ b/code/bgfxbackend.cpp @@ -12,6 +12,8 @@ #include "bgfxbackend.h" +#include "backendviews.hh" + #include "dbgprint.h" #include "except.h" @@ -37,12 +39,8 @@ static const bgfx::EmbeddedShader _EmbeddedShaders[] = { }; -// The view that magnifies the frame when the pixel art filter needs an intermediate -// target, and the one that draws onto the window. Views render in ascending order, so -// the magnify pass must carry the lower id for the present pass to sample its output -// from this frame rather than the last one. -static const bgfx::ViewId VIEW_PRESCALE = 0; -static const bgfx::ViewId VIEW_PRESENT = 1; +static const bgfx::ViewId VIEW_PRESCALE = BACKEND_VIEW_PRESCALE; +static const bgfx::ViewId VIEW_PRESENT = BACKEND_VIEW_PRESENT; static bool _Initialized = false; @@ -515,7 +513,9 @@ void Backend_On_Resize(int drawablewidth, int drawableheight) /// How wide the frame is drawn. /// How tall the frame is drawn. /// How the frame is filtered when it is drawn larger than it is. -void Backend_Present(void const * pixels, int pitch, int destx, int desty, int destwidth, int destheight, BackendScaleMode mode) +/// Has the frame changed since the last present? A present made only +/// to redraw an overlay leaves the frame texture as it is. +void Backend_Present(void const * pixels, int pitch, int destx, int desty, int destwidth, int destheight, BackendScaleMode mode, bool upload) { if (!_Initialized || pixels == NULL || !bgfx::isValid(_FrameTexture)) { return; @@ -526,7 +526,9 @@ void Backend_Present(void const * pixels, int pitch, int destx, int desty, int d return; } - if (_FrameIs565) { + if (!upload) { + // Nothing to do: the texture still holds the frame the last present uploaded. + } else if (_FrameIs565) { bgfx::updateTexture2D(_FrameTexture, 0, 0, 0, 0, (uint16_t)_FrameWidth, (uint16_t)_FrameHeight, bgfx::copy(pixels, (uint32_t)(_FrameHeight * pitch)), (uint16_t)pitch); } else if (_ConvertBuffer != NULL) { for (int y = 0; y < _FrameHeight; y++) { @@ -580,6 +582,19 @@ void Backend_Present(void const * pixels, int pitch, int destx, int desty, int d bool flipv = from_prescale && bgfx::getCaps()->originBottomLeft; Submit_Quad(VIEW_PRESENT, source, (float)destx, (float)desty, (float)destwidth, (float)destheight, samplerflags, flipv); +} + + +/// +/// Ends the frame the last present started, putting everything submitted to it on screen. +/// This is the only call to bgfx::frame() in the program; whatever draws between the +/// present and here shares the frame with the game's own image. +/// +void Backend_End_Frame(void) +{ + if (!_Initialized) { + return; + } bgfx::frame(); } diff --git a/code/bgfxbackend.h b/code/bgfxbackend.h index 8f3cb4184..cc37e0ea1 100644 --- a/code/bgfxbackend.h +++ b/code/bgfxbackend.h @@ -39,8 +39,10 @@ void Backend_Shutdown(void); bool Backend_Set_Frame_Size(int width, int height); void Backend_On_Resize(int drawablewidth, int drawableheight); -// Uploads the frame and presents it. The pixels are 16 bit 565 and stay owned by the -// caller; they are consumed before this returns. -void Backend_Present(void const * pixels, int pitch, int destx, int desty, int destwidth, int destheight, BackendScaleMode mode); +// Uploads the frame and submits it. The pixels are 16 bit 565 and stay owned by the +// caller; they are consumed before this returns. Nothing reaches the screen until +// Backend_End_Frame, so an overlay drawn in between shares the frame. +void Backend_Present(void const * pixels, int pitch, int destx, int desty, int destwidth, int destheight, BackendScaleMode mode, bool upload); +void Backend_End_Frame(void); char const * Backend_Renderer_Name(void); diff --git a/code/gamedirs.cpp b/code/gamedirs.cpp index 321507d6d..e42e0f87c 100644 --- a/code/gamedirs.cpp +++ b/code/gamedirs.cpp @@ -257,6 +257,13 @@ bool Apply_Game_Directories(void) DebugString("[GameDirs] Data directory is %s.\n", DataDirectory.c_str()); } + // Shipped UI documents, styles, images and fonts sit in ui/ beside the executable. + // Adding the directory to the search paths is what lets them resolve by bare name, so + // the same file loads from there or from a mix and a mod can override either. + std::string const uipath = Terminate_Path(Data_Directory() + "ui"); + CDFileClass::Add_Search_Drive(uipath.c_str()); + DebugString("[GameDirs] UI directory is %s.\n", uipath.c_str()); + return(true); } diff --git a/code/mainloop.cpp b/code/mainloop.cpp index 7ec6ef756..93cf13697 100644 --- a/code/mainloop.cpp +++ b/code/mainloop.cpp @@ -61,6 +61,7 @@ #include "theme.h" #include "timer.h" #include "tracker.h" +#include "ui/uishell.h" #include "bench.hh" #include "special.hh" @@ -301,6 +302,7 @@ bool Main_Loop(void) */ if (!Session.Play) { if (SpecialDialog == SDLG_NONE && GameInFocus) { + UI_Tick(); Map.Input(input, x, y); if (input) { Keyboard_Process(input); diff --git a/code/ui/uidev.cpp b/code/ui/uidev.cpp new file mode 100644 index 000000000..6124f4d0f --- /dev/null +++ b/code/ui/uidev.cpp @@ -0,0 +1,165 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The ImGui context and the developer overlays it draws. Tool visibility and frame rate +// never touch deterministic state, and the overlays are armed by a developer key rather +// than by anything a player can reach. +// +// docs/UI_DESIGN.md, "Dear ImGui", owns what belongs here. + +#include "always.h" + +#include "uiinternal.h" + +#include "dbgprint.h" + +#include + + +static ImGuiContext * _Context = nullptr; +static bool _Open = false; +static bool _FrameStarted = false; + + +bool UI_Dev_Init(void) +{ + if (_Context != nullptr) { + return(true); + } + + IMGUI_CHECKVERSION(); + _Context = ImGui::CreateContext(); + if (_Context == nullptr) { + return(false); + } + + ImGuiIO & io = ImGui::GetIO(); + + // The renderer creates and destroys textures on request through the draw data rather + // than from a font atlas it owns, which is the pinned version's backend contract. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; + io.BackendPlatformName = "OpenTS UI shell"; + io.BackendRendererName = "OpenTS bgfx overlay"; + + // Nothing is written beside the executable: the engine keeps its own settings. + io.IniFilename = nullptr; + io.LogFilename = nullptr; + + return(true); +} + + +void UI_Dev_Shutdown(void) +{ + if (_Context == nullptr) { + return; + } + + ImGui::DestroyContext(_Context); + _Context = nullptr; + _Open = false; + _FrameStarted = false; +} + + +bool UI_Dev_Is_Open(void) +{ + return(_Context != nullptr && _Open); +} + + +void UI_Dev_Toggle(void) +{ + if (_Context == nullptr) { + return; + } + + _Open = !_Open; + DebugString("[UI] Developer overlay %s.\n", _Open ? "shown" : "hidden"); +} + + +/// +/// Builds the overlay's frame. Called from the shell's tick, on wall-clock time. +/// +void UI_Dev_New_Frame(int width, int height, double deltaseconds) +{ + if (_Context == nullptr || !_Open || width <= 0 || height <= 0) { + return; + } + + ImGuiIO & io = ImGui::GetIO(); + io.DisplaySize = ImVec2((float)width, (float)height); + io.DeltaTime = deltaseconds > 0.0 ? (float)deltaseconds : (1.0f / 60.0f); + + ImGui::NewFrame(); + _FrameStarted = true; + + ImGui::SetNextWindowPos(ImVec2(16.0f, 16.0f), ImGuiCond_FirstUseEver); + if (ImGui::Begin("OpenTS")) { + ImGui::Text("Overlay %d x %d", width, height); + ImGui::Text("%.1f frames per second", io.Framerate); + } + ImGui::End(); + + ImGui::Render(); +} + + +void UI_Dev_Render(void) +{ + if (_Context == nullptr || !_FrameStarted) { + return; + } + + UI_Render_ImGui(ImGui::GetDrawData()); + _FrameStarted = false; +} + + +bool UI_Dev_Wants_Mouse(void) +{ + return(_Context != nullptr && _Open && ImGui::GetIO().WantCaptureMouse); +} + + +bool UI_Dev_Wants_Keyboard(void) +{ + return(_Context != nullptr && _Open && ImGui::GetIO().WantCaptureKeyboard); +} + + +void UI_Dev_Mouse_Position(float x, float y) +{ + if (_Context == nullptr || !_Open) { + return; + } + + ImGui::GetIO().AddMousePosEvent(x, y); +} + + +void UI_Dev_Mouse_Button(int button, bool down) +{ + if (_Context == nullptr || !_Open || button < 0 || button > 4) { + return; + } + + ImGui::GetIO().AddMouseButtonEvent(button, down); +} + + +void UI_Dev_Mouse_Wheel(float delta) +{ + if (_Context == nullptr || !_Open) { + return; + } + + ImGui::GetIO().AddMouseWheelEvent(0.0f, delta); +} diff --git a/code/ui/uifile.cpp b/code/ui/uifile.cpp new file mode 100644 index 000000000..a28182c46 --- /dev/null +++ b/code/ui/uifile.cpp @@ -0,0 +1,163 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// RmlUi's files, read through the game's own file system so that a document, a style, an +// image or a font loads from a loose ui/ directory or from a mix on equal terms. +// +// Every reference is reduced to its bare name before it is opened. RmlUi joins a relative +// reference with the path of the document that named it, and CDFileClass treats a name +// carrying a directory as a literal path that skips the search order, so a joined name +// would only ever be found on disk. The name alone is the lookup key: the user path, then +// the current directory, then the search paths, then the mix files. That is what lets a +// mod override a document by shipping it earlier in that order. +// +// docs/UI_DESIGN.md, "Assets and strings", owns this. + +#include "always.h" + +#include "uiinternal.h" + +#include "ccfile.h" +#include "dbgprint.h" + +#include + +#include +#include + + +/// +/// Strips every directory a reference carries, leaving the name the archives hold. +/// +static std::string Base_Name(std::string const & path) +{ + std::size_t const mark = path.find_last_of("\\/:"); + return(mark == std::string::npos ? path : path.substr(mark + 1)); +} + + +class UIFileInterface : public Rml::FileInterface +{ + public: + virtual Rml::FileHandle Open(const Rml::String & path) override + { + std::string const name = Base_Name(path); + if (name.empty()) { + return(0); + } + + CCFileClass * file = new CCFileClass(name.c_str()); + if (!file->Is_Available() || !file->Open(FileClass::READ)) { + delete file; + DebugString("[UI] File %s was not found.\n", name.c_str()); + return(0); + } + + return((Rml::FileHandle)file); + } + + virtual void Close(Rml::FileHandle handle) override + { + CCFileClass * file = (CCFileClass *)handle; + if (file != nullptr) { + file->Close(); + delete file; + } + } + + virtual size_t Read(void * buffer, size_t size, Rml::FileHandle handle) override + { + CCFileClass * file = (CCFileClass *)handle; + if (file == nullptr || buffer == nullptr || size == 0) { + return(0); + } + + // The engine counts bytes in an int, so a request larger than that is served in + // pieces rather than truncated silently. + size_t total = 0; + while (total < size) { + size_t const remaining = size - total; + int const chunk = remaining > (size_t)INT_MAX ? INT_MAX : (int)remaining; + + int const read = file->Read((char *)buffer + total, chunk); + if (read <= 0) { + break; + } + + total += (size_t)read; + if (read < chunk) { + break; + } + } + + return(total); + } + + virtual bool Seek(Rml::FileHandle handle, long offset, int origin) override + { + CCFileClass * file = (CCFileClass *)handle; + if (file == nullptr) { + return(false); + } + + int const size = file->Size(); + long target = offset; + + switch (origin) { + case SEEK_CUR: + target = (long)file->Seek(0, SEEK_CUR) + offset; + break; + + case SEEK_END: + target = (long)size + offset; + break; + + default: + break; + } + + // Seek() clamps, so a request past either end would otherwise report success at + // a position the caller never asked for. + if (target < 0 || target > (long)size) { + return(false); + } + + return(file->Seek((int)target, SEEK_SET) == target); + } + + virtual size_t Tell(Rml::FileHandle handle) override + { + CCFileClass * file = (CCFileClass *)handle; + if (file == nullptr) { + return(0); + } + + int const position = file->Seek(0, SEEK_CUR); + return(position < 0 ? 0 : (size_t)position); + } + + virtual size_t Length(Rml::FileHandle handle) override + { + CCFileClass * file = (CCFileClass *)handle; + if (file == nullptr) { + return(0); + } + + int const size = file->Size(); + return(size < 0 ? 0 : (size_t)size); + } +}; + +static UIFileInterface _FileInterface; + + +Rml::FileInterface * UI_File_Interface(void) +{ + return(&_FileInterface); +} diff --git a/code/ui/uiinternal.h b/code/ui/uiinternal.h new file mode 100644 index 000000000..843d3c4b4 --- /dev/null +++ b/code/ui/uiinternal.h @@ -0,0 +1,68 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// What the shell's own translation units hand each other. Nothing outside code/ui includes +// this. It names RmlUi types by forward declaration so that uirender.cpp stays the only +// file carrying bgfx and uitexture.cpp the only one carrying the image decoders. + +#pragma once + +#include +#include + +struct ImDrawData; + +namespace Rml { + class RenderInterface; + class SystemInterface; + class FileInterface; +} + + +// Decoded image pixels, RGBA8 with premultiplied alpha, top row first. +struct UIImageData +{ + std::vector Pixels; + int Width = 0; + int Height = 0; +}; + + +// uitexture.cpp +bool UI_Decode_Image(char const * source, UIImageData & image); + +// uirender.cpp +Rml::RenderInterface * UI_Render_Interface(void); +bool UI_Render_Init(void); +void UI_Render_Shutdown(void); + +// Points the overlay views at where the frame landed in the window. Coordinates handed to +// the toolkits afterwards are physical pixels from the frame's top left corner. +void UI_Render_Begin(int destx, int desty, int width, int height); +void UI_Render_End(void); +void UI_Render_ImGui(ImDrawData * data); + +// uisystem.cpp +Rml::SystemInterface * UI_System_Interface(void); + +// uifile.cpp +Rml::FileInterface * UI_File_Interface(void); + +// uidev.cpp +bool UI_Dev_Init(void); +void UI_Dev_Shutdown(void); +void UI_Dev_New_Frame(int width, int height, double deltaseconds); +void UI_Dev_Render(void); +bool UI_Dev_Wants_Mouse(void); +bool UI_Dev_Wants_Keyboard(void); +bool UI_Dev_Is_Open(void); +void UI_Dev_Toggle(void); +void UI_Dev_Mouse_Position(float x, float y); +void UI_Dev_Mouse_Button(int button, bool down); +void UI_Dev_Mouse_Wheel(float delta); diff --git a/code/ui/uirender.cpp b/code/ui/uirender.cpp new file mode 100644 index 000000000..c0355a877 --- /dev/null +++ b/code/ui/uirender.cpp @@ -0,0 +1,463 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The overlays' side of the renderer. This is the only file in the shell that includes +// bgfx, and every renderer handle the shell owns lives here, the way bgfxbackend.cpp holds +// the frame's handles. It carries RmlUi's render interface and the ImGui renderer, which +// share the program, the views and the blend state. +// +// docs/UI_DESIGN.md, "Rendering", owns the contract. + +#include "uiinternal.h" + +#include "backendviews.hh" +#include "dbgprint.h" + +#include + +#include + +#include +#include + +#include +#include + +#include +#include + + +static const bgfx::EmbeddedShader _EmbeddedShaders[] = { + BGFX_EMBEDDED_SHADER(vs_ocornut_imgui), + BGFX_EMBEDDED_SHADER(fs_ocornut_imgui), + BGFX_EMBEDDED_SHADER_END() +}; + + +static bool _Initialized = false; + +static bgfx::ProgramHandle _Program = BGFX_INVALID_HANDLE; +static bgfx::UniformHandle _TextureSampler = BGFX_INVALID_HANDLE; +static bgfx::TextureHandle _WhiteTexture = BGFX_INVALID_HANDLE; +static bgfx::VertexLayout _RmlLayout; +static bgfx::VertexLayout _ImGuiLayout; + +// Where the frame landed in the window, which is also the overlays' viewport. Scissor +// rectangles arrive relative to this origin and are made absolute before they are set. +static int _OriginX = 0; +static int _OriginY = 0; +static int _Width = 0; +static int _Height = 0; + +static const uint64_t _BlendState = + BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_MSAA + | BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_INV_SRC_ALPHA); + + +// One compiled geometry. RmlUi 6 compiles geometry once and re-submits it, so these are +// static buffers rather than transient ones, which must not outlive the frame they were +// filled in. +struct UIGeometry +{ + bgfx::VertexBufferHandle Vertices = BGFX_INVALID_HANDLE; + bgfx::IndexBufferHandle Indices = BGFX_INVALID_HANDLE; + uint32_t IndexCount = 0; +}; + + +/// +/// Builds an orthographic projection over a target measured in pixels, origin top left. +/// +static void Build_Ortho_Projection(float * result, int width, int height) +{ + const float depthnear = 0.0f; + const float depthfar = 1000.0f; + const bool homogeneous = bgfx::getCaps()->homogeneousDepth; + + std::memset(result, 0, sizeof(float) * 16); + + result[0] = 2.0f / (float)width; + result[5] = -2.0f / (float)height; + result[10] = homogeneous ? 2.0f / (depthfar - depthnear) : 1.0f / (depthfar - depthnear); + result[12] = -1.0f; + result[13] = 1.0f; + result[14] = homogeneous ? -(depthfar + depthnear) / (depthfar - depthnear) : -depthnear / (depthfar - depthnear); + result[15] = 1.0f; +} + + +/// +/// Turns a texture handle the toolkits carry back into the renderer's own. +/// Zero is reserved for "no texture", so the index is stored one higher than it is. +/// +static bgfx::TextureHandle Texture_From_Handle(uintptr_t handle) +{ + bgfx::TextureHandle texture = BGFX_INVALID_HANDLE; + if (handle != 0) { + texture.idx = (uint16_t)(handle - 1); + } + return(texture); +} + + +static uintptr_t Handle_From_Texture(bgfx::TextureHandle texture) +{ + return(bgfx::isValid(texture) ? (uintptr_t)texture.idx + 1 : 0); +} + + +class UIRenderInterface : public Rml::RenderInterface +{ + public: + virtual Rml::CompiledGeometryHandle CompileGeometry(Rml::Span vertices, Rml::Span indices) override; + virtual void RenderGeometry(Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation, Rml::TextureHandle texture) override; + virtual void ReleaseGeometry(Rml::CompiledGeometryHandle geometry) override; + + virtual Rml::TextureHandle LoadTexture(Rml::Vector2i & dimensions, const Rml::String & source) override; + virtual Rml::TextureHandle GenerateTexture(Rml::Span source, Rml::Vector2i dimensions) override; + virtual void ReleaseTexture(Rml::TextureHandle texture) override; + + virtual void EnableScissorRegion(bool enable) override; + virtual void SetScissorRegion(Rml::Rectanglei region) override; + + private: + bool ScissorEnabled = false; + Rml::Rectanglei Scissor = Rml::Rectanglei::FromPosition({0, 0}); +}; + +static UIRenderInterface _RenderInterface; + + +Rml::CompiledGeometryHandle UIRenderInterface::CompileGeometry(Rml::Span vertices, Rml::Span indices) +{ + if (!_Initialized || vertices.empty() || indices.empty()) { + return(0); + } + + UIGeometry * geometry = new UIGeometry; + + geometry->Vertices = bgfx::createVertexBuffer( + bgfx::copy(vertices.data(), (uint32_t)(vertices.size() * sizeof(Rml::Vertex))), _RmlLayout); + geometry->Indices = bgfx::createIndexBuffer( + bgfx::copy(indices.data(), (uint32_t)(indices.size() * sizeof(int))), BGFX_BUFFER_INDEX32); + geometry->IndexCount = (uint32_t)indices.size(); + + if (!bgfx::isValid(geometry->Vertices) || !bgfx::isValid(geometry->Indices)) { + ReleaseGeometry((Rml::CompiledGeometryHandle)geometry); + return(0); + } + + return((Rml::CompiledGeometryHandle)geometry); +} + + +void UIRenderInterface::RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) +{ + UIGeometry const * geometry = (UIGeometry const *)handle; + if (!_Initialized || geometry == nullptr || _Width <= 0 || _Height <= 0) { + return; + } + + float transform[16]; + std::memset(transform, 0, sizeof(transform)); + transform[0] = transform[5] = transform[10] = transform[15] = 1.0f; + transform[12] = translation.x; + transform[13] = translation.y; + + bgfx::setTransform(transform); + bgfx::setVertexBuffer(0, geometry->Vertices); + bgfx::setIndexBuffer(geometry->Indices, 0, geometry->IndexCount); + + bgfx::TextureHandle bound = Texture_From_Handle((uintptr_t)texture); + bgfx::setTexture(0, _TextureSampler, bgfx::isValid(bound) ? bound : _WhiteTexture, + BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + + if (ScissorEnabled) { + // RmlUi reports the region relative to the context, which sits at the frame's top + // left corner. bgfx wants it in the target's own coordinates. + int left = std::max(Scissor.Left() + _OriginX, _OriginX); + int top = std::max(Scissor.Top() + _OriginY, _OriginY); + int right = std::min(Scissor.Right() + _OriginX, _OriginX + _Width); + int bottom = std::min(Scissor.Bottom() + _OriginY, _OriginY + _Height); + + if (right <= left || bottom <= top) { + return; + } + + bgfx::setScissor((uint16_t)left, (uint16_t)top, (uint16_t)(right - left), (uint16_t)(bottom - top)); + } + + bgfx::setState(_BlendState); + bgfx::submit(BACKEND_VIEW_UI, _Program); +} + + +void UIRenderInterface::ReleaseGeometry(Rml::CompiledGeometryHandle handle) +{ + UIGeometry * geometry = (UIGeometry *)handle; + if (geometry == nullptr) { + return; + } + + if (bgfx::isValid(geometry->Vertices)) { + bgfx::destroy(geometry->Vertices); + } + if (bgfx::isValid(geometry->Indices)) { + bgfx::destroy(geometry->Indices); + } + + delete geometry; +} + + +Rml::TextureHandle UIRenderInterface::LoadTexture(Rml::Vector2i & dimensions, const Rml::String & source) +{ + UIImageData image; + if (!UI_Decode_Image(source.c_str(), image)) { + DebugString("[UI] Image %s could not be read.\n", source.c_str()); + return(0); + } + + dimensions.x = image.Width; + dimensions.y = image.Height; + + return(GenerateTexture(Rml::Span(image.Pixels.data(), image.Pixels.size()), + Rml::Vector2i(image.Width, image.Height))); +} + + +Rml::TextureHandle UIRenderInterface::GenerateTexture(Rml::Span source, Rml::Vector2i dimensions) +{ + if (!_Initialized || dimensions.x <= 0 || dimensions.y <= 0) { + return(0); + } + + bgfx::TextureHandle texture = bgfx::createTexture2D( + (uint16_t)dimensions.x, (uint16_t)dimensions.y, false, 1, bgfx::TextureFormat::RGBA8, + BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP, + bgfx::copy(source.data(), (uint32_t)source.size())); + + return((Rml::TextureHandle)Handle_From_Texture(texture)); +} + + +void UIRenderInterface::ReleaseTexture(Rml::TextureHandle handle) +{ + bgfx::TextureHandle texture = Texture_From_Handle((uintptr_t)handle); + if (bgfx::isValid(texture)) { + bgfx::destroy(texture); + } +} + + +void UIRenderInterface::EnableScissorRegion(bool enable) +{ + ScissorEnabled = enable; +} + + +void UIRenderInterface::SetScissorRegion(Rml::Rectanglei region) +{ + Scissor = region; +} + + +Rml::RenderInterface * UI_Render_Interface(void) +{ + return(&_RenderInterface); +} + + +/// +/// Creates the program, the sampler and the untextured stand-in the overlays draw with. +/// +/// bool; Is the overlay renderer ready to draw? +bool UI_Render_Init(void) +{ + if (_Initialized) { + return(true); + } + + // RmlUi's vertex is position, then a premultiplied RGBA byte colour, then the texture + // coordinate. bgfx builds the layout in the order the attributes are added, so this + // order is what makes the layout match the structure without a copy. + _RmlLayout.begin() + .add(bgfx::Attrib::Position, 2, bgfx::AttribType::Float) + .add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true) + .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float) + .end(); + + _ImGuiLayout.begin() + .add(bgfx::Attrib::Position, 2, bgfx::AttribType::Float) + .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float) + .add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true) + .end(); + + bgfx::RendererType::Enum type = bgfx::getRendererType(); + bgfx::ShaderHandle vertexshader = bgfx::createEmbeddedShader(_EmbeddedShaders, type, "vs_ocornut_imgui"); + bgfx::ShaderHandle fragmentshader = bgfx::createEmbeddedShader(_EmbeddedShaders, type, "fs_ocornut_imgui"); + + if (!bgfx::isValid(vertexshader) || !bgfx::isValid(fragmentshader)) { + return(false); + } + + _Program = bgfx::createProgram(vertexshader, fragmentshader, true); + _TextureSampler = bgfx::createUniform("s_uitex", bgfx::UniformType::Sampler); + + // The program always samples, so untextured geometry is drawn against an opaque white + // pixel and takes its colour from the vertices alone. + const uint32_t white = 0xFFFFFFFF; + _WhiteTexture = bgfx::createTexture2D(1, 1, false, 1, bgfx::TextureFormat::RGBA8, + BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP, bgfx::copy(&white, sizeof(white))); + + if (!bgfx::isValid(_Program) || !bgfx::isValid(_TextureSampler) || !bgfx::isValid(_WhiteTexture)) { + UI_Render_Shutdown(); + return(false); + } + + _Initialized = true; + return(true); +} + + +void UI_Render_Shutdown(void) +{ + if (bgfx::isValid(_WhiteTexture)) { + bgfx::destroy(_WhiteTexture); + _WhiteTexture = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(_TextureSampler)) { + bgfx::destroy(_TextureSampler); + _TextureSampler = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(_Program)) { + bgfx::destroy(_Program); + _Program = BGFX_INVALID_HANDLE; + } + + _Initialized = false; +} + + +/// +/// Points both overlay views at the rectangle the frame was drawn into. +/// +void UI_Render_Begin(int destx, int desty, int width, int height) +{ + _OriginX = destx; + _OriginY = desty; + _Width = width; + _Height = height; + + if (!_Initialized || width <= 0 || height <= 0) { + return; + } + + float projection[16]; + Build_Ortho_Projection(projection, width, height); + + for (bgfx::ViewId view : {(bgfx::ViewId)BACKEND_VIEW_UI, (bgfx::ViewId)BACKEND_VIEW_DEV}) { + bgfx::setViewFrameBuffer(view, BGFX_INVALID_HANDLE); + bgfx::setViewClear(view, BGFX_CLEAR_NONE); + bgfx::setViewRect(view, (uint16_t)destx, (uint16_t)desty, (uint16_t)width, (uint16_t)height); + bgfx::setViewTransform(view, nullptr, projection); + } +} + + +void UI_Render_End(void) +{ +} + + +/// +/// Draws one ImGui frame on the developer view. +/// The pinned ImGui asks the renderer to create, update and destroy its textures through +/// the draw data rather than owning a font atlas of its own. +/// +void UI_Render_ImGui(ImDrawData * data) +{ + if (!_Initialized || data == nullptr || data->CmdListsCount <= 0) { + return; + } + + for (ImTextureData * texture : *data->Textures) { + if (texture->Status == ImTextureStatus_WantCreate) { + bgfx::TextureHandle created = bgfx::createTexture2D( + (uint16_t)texture->Width, (uint16_t)texture->Height, false, 1, bgfx::TextureFormat::RGBA8, + BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP, + bgfx::copy(texture->GetPixels(), (uint32_t)(texture->Width * texture->Height * 4))); + + texture->SetTexID((ImTextureID)Handle_From_Texture(created)); + texture->SetStatus(ImTextureStatus_OK); + } else if (texture->Status == ImTextureStatus_WantUpdates) { + bgfx::TextureHandle existing = Texture_From_Handle((uintptr_t)texture->TexID); + if (bgfx::isValid(existing)) { + bgfx::updateTexture2D(existing, 0, 0, 0, 0, + (uint16_t)texture->Width, (uint16_t)texture->Height, + bgfx::copy(texture->GetPixels(), (uint32_t)(texture->Width * texture->Height * 4)), + (uint16_t)(texture->Width * 4)); + } + texture->SetStatus(ImTextureStatus_OK); + } else if (texture->Status == ImTextureStatus_WantDestroy) { + bgfx::TextureHandle existing = Texture_From_Handle((uintptr_t)texture->TexID); + if (bgfx::isValid(existing)) { + bgfx::destroy(existing); + } + texture->SetTexID(ImTextureID_Invalid); + texture->SetStatus(ImTextureStatus_Destroyed); + } + } + + for (int list = 0; list < data->CmdListsCount; list++) { + ImDrawList const * commands = data->CmdLists[list]; + + const uint32_t vertexcount = (uint32_t)commands->VtxBuffer.size(); + const uint32_t indexcount = (uint32_t)commands->IdxBuffer.size(); + + if (bgfx::getAvailTransientVertexBuffer(vertexcount, _ImGuiLayout) < vertexcount + || bgfx::getAvailTransientIndexBuffer(indexcount) < indexcount) { + break; + } + + bgfx::TransientVertexBuffer vertices; + bgfx::TransientIndexBuffer indices; + bgfx::allocTransientVertexBuffer(&vertices, vertexcount, _ImGuiLayout); + bgfx::allocTransientIndexBuffer(&indices, indexcount); + + std::memcpy(vertices.data, commands->VtxBuffer.begin(), vertexcount * sizeof(ImDrawVert)); + std::memcpy(indices.data, commands->IdxBuffer.begin(), indexcount * sizeof(ImDrawIdx)); + + for (ImDrawCmd const & command : commands->CmdBuffer) { + if (command.ElemCount == 0) { + continue; + } + + int left = std::max((int)command.ClipRect.x + _OriginX, _OriginX); + int top = std::max((int)command.ClipRect.y + _OriginY, _OriginY); + int right = std::min((int)command.ClipRect.z + _OriginX, _OriginX + _Width); + int bottom = std::min((int)command.ClipRect.w + _OriginY, _OriginY + _Height); + + if (right <= left || bottom <= top) { + continue; + } + + bgfx::setScissor((uint16_t)left, (uint16_t)top, (uint16_t)(right - left), (uint16_t)(bottom - top)); + + bgfx::TextureHandle texture = Texture_From_Handle((uintptr_t)command.GetTexID()); + bgfx::setTexture(0, _TextureSampler, bgfx::isValid(texture) ? texture : _WhiteTexture, + BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + + bgfx::setState(_BlendState); + bgfx::setVertexBuffer(0, &vertices, command.VtxOffset, vertexcount - command.VtxOffset); + bgfx::setIndexBuffer(&indices, command.IdxOffset, command.ElemCount); + bgfx::submit(BACKEND_VIEW_DEV, _Program); + } + } +} diff --git a/code/ui/uirmlview.h b/code/ui/uirmlview.h new file mode 100644 index 000000000..3537d9df0 --- /dev/null +++ b/code/ui/uirmlview.h @@ -0,0 +1,58 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The RmlUi half of a screen. A view owns its document and its data model and turns the +// toolkit's events into intents on the presenter it was built against. Only files under +// code/ui include this, because it names RmlUi types. +// +// docs/UI_DESIGN.md, "Screens", owns this contract. + +#pragma once + +#include "uiscreen.h" + +#include +#include + + +class UIRmlViewClass +{ + public: + UIRmlViewClass(UIPresenterClass & presenter, char const * document); + virtual ~UIRmlViewClass(void); + + // Loads the document, binds the data model and shows it. A failure leaves nothing + // shown and reports the resource that could not be prepared. + bool Prepare(bool modal); + + // Hides and releases the document in the order docs/UI_DESIGN.md sets out: the + // screen is marked closing first, then focus and capture are dropped, then the + // model is removed while its storage still lives. + void Close(void); + + bool Is_Visible(void) const; + + // Fills in the view-model's fields and events. Called once, before the document is + // loaded, since a document binds its model as it parses. + virtual void Bind(Rml::DataModelConstructor & model) = 0; + + // Marks whatever the last drained intents changed, so RmlUi redraws only that. + virtual void Sync(void) = 0; + + protected: + UIPresenterClass & Presenter; + Rml::String Document; + Rml::ElementDocument * Element = nullptr; + Rml::DataModelHandle Model; +}; + + +// Runs a screen to a result the way the legacy dialog drivers do. uishell.cpp owns it; it +// is declared here because it names both halves of a screen. +UIResult UI_Run_Modal(UIPresenterClass & presenter, UIRmlViewClass & view); diff --git a/code/ui/uiscreen.h b/code/ui/uiscreen.h new file mode 100644 index 000000000..bd8366af2 --- /dev/null +++ b/code/ui/uiscreen.h @@ -0,0 +1,83 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The toolkit-free half of a screen. A presenter holds the view-model, answers queries and +// executes intents; it never sees a window handle, a surface, an RmlUi element or an ImGui +// call, so the same presenter serves an RmlUi view, a legacy dialog and a test. +// +// docs/UI_DESIGN.md, "Screens", owns this contract. + +#pragma once + +#include +#include +#include + + +// What a view asks the presenter to do. An intent carries identities and copied data only: +// never a document node, a borrowed buffer, a window handle or an engine pointer, because +// it is executed at the owner's next safe point rather than where it was raised. +struct UIIntent +{ + std::string Action; + std::string Identity; + int Value = 0; +}; + + +// What a screen answers with. The outcome maps onto the return values the dialog drivers +// already use, and GameEnded carries what OwnerDraw::Dialog_Message_Handler returns. +struct UIResult +{ + enum OutcomeType { + OUTCOME_ACCEPTED, + OUTCOME_CANCELLED, + OUTCOME_SESSION_ENDED, + OUTCOME_FAILED_TO_OPEN, + }; + + OutcomeType Outcome = OUTCOME_CANCELLED; + int Value = 0; + bool GameEnded = false; +}; + + +class UIPresenterClass +{ + public: + virtual ~UIPresenterClass(void) {} + + // Records an intent for the owner to execute. Safe to call from a toolkit event + // handler, which must never act directly. + void Queue(UIIntent const & intent) { Intents.push_back(intent); } + + // Executes every queued intent in order at the owner's safe point. Intents raised + // while a screen is closing are discarded rather than replayed. + void Drain(void) + { + std::vector pending; + pending.swap(Intents); + + for (UIIntent const & intent : pending) { + if (IsClosing) break; + Execute(intent); + } + } + + void Discard(void) { Intents.clear(); } + + virtual void Execute(UIIntent const & intent) = 0; + virtual void Refresh(void) = 0; + + std::optional Result; + bool IsClosing = false; + + protected: + std::vector Intents; +}; diff --git a/code/ui/uishell.cpp b/code/ui/uishell.cpp new file mode 100644 index 000000000..f82cdfb0b --- /dev/null +++ b/code/ui/uishell.cpp @@ -0,0 +1,665 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The UI shell. It owns the RmlUi context, the ImGui context, the overlay pass, the input +// hook and the modal runner, and it is the only place outside code/ui that any of those +// libraries is reachable from. +// +// docs/UI_DESIGN.md owns the architecture. Nothing here knows what a screen means: it +// knows which presentation owns a region and an input scope, and no more. + +#include "always.h" + +#include "uishell.h" + +#include "uiinternal.h" +#include "uirmlview.h" + +#include "dbgprint.h" +#include "hostclock.h" +#include "conquer.h" +#include "mainloop.h" +#include "msgloop.h" +#include "session.h" +#include "vidscale.h" +#include "video.h" + +#include + +#include + +#include +#include + +#include + + +static bool _Initialized = false; +static Rml::Context * _Context = nullptr; +static bool _OverlayIsDirty = false; +static unsigned int _LastTickTime = 0; + +// Set while a document is being shown or hidden, so the message pump that the keyboard +// queue's own cleanup runs cannot re-enter the screen it is closing. +static bool _Changing = false; + +// The window holds the mouse capture while a gesture a toolkit consumed is in progress. +// The owner of a press owns its release, so a press that crossed into the game or out of +// it still completes where it started. +static int _CaptureButton = -1; + +#ifndef NDEBUG +static Rml::ElementDocument * _TestDocument = nullptr; +#endif + + +/// +/// Turns a Win32 virtual key into the identifier RmlUi names it by. +/// Only the keys a document can act on are mapped; an unmapped key is left to the game. +/// +static Rml::Input::KeyIdentifier Key_Identifier(WPARAM key) +{ + using namespace Rml::Input; + + if (key >= 'A' && key <= 'Z') { + return((KeyIdentifier)(KI_A + (int)(key - 'A'))); + } + if (key >= '0' && key <= '9') { + return((KeyIdentifier)(KI_0 + (int)(key - '0'))); + } + if (key >= VK_F1 && key <= VK_F12) { + return((KeyIdentifier)(KI_F1 + (int)(key - VK_F1))); + } + + switch (key) { + case VK_BACK: return(KI_BACK); + case VK_TAB: return(KI_TAB); + case VK_RETURN: return(KI_RETURN); + case VK_ESCAPE: return(KI_ESCAPE); + case VK_SPACE: return(KI_SPACE); + case VK_PRIOR: return(KI_PRIOR); + case VK_NEXT: return(KI_NEXT); + case VK_END: return(KI_END); + case VK_HOME: return(KI_HOME); + case VK_LEFT: return(KI_LEFT); + case VK_UP: return(KI_UP); + case VK_RIGHT: return(KI_RIGHT); + case VK_DOWN: return(KI_DOWN); + case VK_INSERT: return(KI_INSERT); + case VK_DELETE: return(KI_DELETE); + case VK_SHIFT: return(KI_LSHIFT); + case VK_CONTROL: return(KI_LCONTROL); + case VK_MENU: return(KI_LMENU); + default: return(KI_UNKNOWN); + } +} + + +static int Key_Modifiers(void) +{ + int modifiers = 0; + + if ((GetKeyState(VK_CONTROL) & 0x8000) != 0) modifiers |= Rml::Input::KM_CTRL; + if ((GetKeyState(VK_SHIFT) & 0x8000) != 0) modifiers |= Rml::Input::KM_SHIFT; + if ((GetKeyState(VK_MENU) & 0x8000) != 0) modifiers |= Rml::Input::KM_ALT; + + return(modifiers); +} + + +/// +/// Converts a position in the window's client area into the overlay's own space. +/// The overlay is laid out in physical pixels measured from the frame's top left corner, +/// so the letterbox bars fall outside it and never become an edge click. +/// +static void Client_Point_To_Overlay(POINT & point) +{ + VideoScaleInfo const & scale = Video_Get_Scale_Info(); + + point.x -= scale.DestX; + point.y -= scale.DestY; +} + + +/// +/// Reads the position a mouse message carries, in the overlay's space. +/// The wheel reports in screen coordinates and everything else in the client area, which +/// is the one difference the conversion has to make. +/// +static POINT Message_Point(UINT message, LPARAM lparam) +{ + POINT point; + point.x = GET_X_LPARAM(lparam); + point.y = GET_Y_LPARAM(lparam); + + if (message == WM_MOUSEWHEEL) { + ScreenToClient(MainWindow, &point); + } + + Client_Point_To_Overlay(point); + return(point); +} + + +static void Mark_Overlay_Dirty(void) +{ + _OverlayIsDirty = true; + Video_Mark_Dirty(); +} + + +/// +/// Points the context at where the frame lands and at the scale it is drawn. +/// One authored density-independent pixel is one game logical unit, so a document authored +/// at a legacy dialog's size keeps that size on screen while its text is rasterized at the +/// physical resolution. +/// +static void Apply_Scale_Info(void) +{ + if (_Context == nullptr) { + return; + } + + VideoScaleInfo const & scale = Video_Get_Scale_Info(); + if (scale.DestWidth <= 0 || scale.DestHeight <= 0) { + return; + } + + _Context->SetDimensions(Rml::Vector2i(scale.DestWidth, scale.DestHeight)); + _Context->SetDensityIndependentPixelRatio(std::min(scale.ScaleX, scale.ScaleY)); + Mark_Overlay_Dirty(); +} + + +/// +/// Starts the shell on the running renderer. +/// +/// bool; Is the shell ready? A false return leaves the game running without one. +bool UI_Init(void) +{ + if (_Initialized) { + return(true); + } + + if (!UI_Render_Init()) { + DebugString("[UI] The overlay renderer could not be started.\n"); + return(false); + } + + Rml::SetSystemInterface(UI_System_Interface()); + Rml::SetFileInterface(UI_File_Interface()); + Rml::SetRenderInterface(UI_Render_Interface()); + + if (!Rml::Initialise()) { + DebugString("[UI] RmlUi could not be started.\n"); + UI_Render_Shutdown(); + return(false); + } + + VideoScaleInfo const & scale = Video_Get_Scale_Info(); + _Context = Rml::CreateContext("game", + Rml::Vector2i(std::max(scale.DestWidth, 1), std::max(scale.DestHeight, 1))); + + if (_Context == nullptr) { + DebugString("[UI] The RmlUi context could not be created.\n"); + Rml::Shutdown(); + UI_Render_Shutdown(); + return(false); + } + + // The shipped font loads by bare name, so it is found in ui/ or in a mix on the same + // terms as everything else a document names. + if (!Rml::LoadFontFace("LatoLatin-Regular.ttf")) { + DebugString("[UI] The shipped font could not be loaded.\n"); + } + + UI_Dev_Init(); + + Apply_Scale_Info(); + _LastTickTime = Host_Milliseconds(); + _Initialized = true; + + DebugString("[UI] Shell started at %dx%d.\n", scale.DestWidth, scale.DestHeight); + return(true); +} + + +void UI_Shutdown(void) +{ + if (!_Initialized) { + return; + } + + _Changing = true; + +#ifndef NDEBUG + _TestDocument = nullptr; +#endif + + UI_Dev_Shutdown(); + + _Context = nullptr; + Rml::Shutdown(); + UI_Render_Shutdown(); + + _Initialized = false; + _OverlayIsDirty = false; + _CaptureButton = -1; + _Changing = false; +} + + +void UI_On_Resize(void) +{ + if (!_Initialized) { + return; + } + + Apply_Scale_Info(); +} + + +/// +/// Advances layout and animation for documents no modal loop is running. +/// Called from Main_Loop beside Map.Input, on wall-clock time, so nothing here reads or +/// advances a deterministic game timer. +/// +void UI_Tick(void) +{ + if (!_Initialized || _Context == nullptr || _Changing) { + return; + } + + unsigned int const now = Host_Milliseconds(); + double const elapsed = (double)(now - _LastTickTime) / 1000.0; + _LastTickTime = now; + + _Context->Update(); + + // RmlUi cannot say whether it needs redrawing, so anything on screen marks the overlay + // on every tick and the present pacing caps the rate. + if (_Context->GetNumDocuments() > 0 || UI_Dev_Is_Open()) { + Mark_Overlay_Dirty(); + } + + if (UI_Dev_Is_Open()) { + VideoScaleInfo const & scale = Video_Get_Scale_Info(); + UI_Dev_New_Frame(scale.DestWidth, scale.DestHeight, elapsed); + } +} + + +void UI_Render_Overlay(void) +{ + if (!_Initialized || _Context == nullptr) { + return; + } + + VideoScaleInfo const & scale = Video_Get_Scale_Info(); + UI_Render_Begin(scale.DestX, scale.DestY, scale.DestWidth, scale.DestHeight); + + _Context->Render(); + UI_Dev_Render(); + + UI_Render_End(); + _OverlayIsDirty = false; +} + + +bool UI_Overlay_Is_Dirty(void) +{ + return(_OverlayIsDirty); +} + + +bool UI_Document_Is_Visible(void) +{ + return(_Initialized && _Context != nullptr && _Context->GetNumDocuments() > 0); +} + + +bool UI_Use_Rml(void) +{ + // No screen has migrated yet. The transitional key docs/UI_DESIGN.md describes arrives + // with the first one, named by the change that introduces it. + return(false); +} + + +#ifndef NDEBUG +/// +/// Shows or hides the document that proves the shell renders, clips and takes input. +/// Debug builds only; it exists to be looked at, not to be shipped. +/// +static void Toggle_Test_Document(void) +{ + if (_Context == nullptr) { + return; + } + + _Changing = true; + + if (_TestDocument != nullptr) { + _TestDocument->Close(); + _TestDocument = nullptr; + DebugString("[UI] Test document closed.\n"); + } else { + _TestDocument = _Context->LoadDocument("uitest.rml"); + if (_TestDocument != nullptr) { + _TestDocument->Show(); + DebugString("[UI] Test document shown.\n"); + } else { + DebugString("[UI] Test document uitest.rml could not be loaded.\n"); + } + } + + _Changing = false; + + // Closing marks the overlay too, so the pixels a hidden document left behind go away. + Mark_Overlay_Dirty(); +} + + +/// +/// Answers the developer keys the shell owns. +/// +/// bool; Was the key one of them? +static bool Handle_Developer_Key(WPARAM key) +{ + if ((GetKeyState(VK_CONTROL) & 0x8000) == 0 || (GetKeyState(VK_SHIFT) & 0x8000) == 0) { + return(false); + } + + switch (key) { + case 'U': + Toggle_Test_Document(); + return(true); + + case 'I': + UI_Dev_Toggle(); + Mark_Overlay_Dirty(); + return(true); + + default: + return(false); + } +} +#endif + + +/// +/// Offers a window message to the toolkits before the game sees it. +/// The order follows docs/UI_DESIGN.md: ImGui's capture flags first, then a modal +/// document, then whatever an element under the cursor claims. A mouse move is always +/// delivered and never consumed, so the game keeps tracking the cursor underneath. +/// +/// bool; Was the message consumed? The window procedure returns without handling +/// it when so, which is what keeps it out of the keyboard queue. +bool UI_Handle_Window_Message(HWND window, UINT message, WPARAM wparam, LPARAM lparam) +{ + if (!_Initialized || _Context == nullptr || _Changing || window != MainWindow) { + return(false); + } + + int const modifiers = Key_Modifiers(); + + switch (message) { + case WM_MOUSEMOVE: { + POINT const point = Message_Point(message, lparam); + UI_Dev_Mouse_Position((float)point.x, (float)point.y); + _Context->ProcessMouseMove((int)point.x, (int)point.y, modifiers); + return(false); + } + + case WM_LBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDOWN: + case WM_RBUTTONDBLCLK: + case WM_MBUTTONDOWN: + case WM_MBUTTONDBLCLK: { + int const button = (message == WM_LBUTTONDOWN || message == WM_LBUTTONDBLCLK) ? 0 + : ((message == WM_RBUTTONDOWN || message == WM_RBUTTONDBLCLK) ? 1 : 2); + + POINT const point = Message_Point(message, lparam); + _Context->ProcessMouseMove((int)point.x, (int)point.y, modifiers); + UI_Dev_Mouse_Position((float)point.x, (float)point.y); + UI_Dev_Mouse_Button(button, true); + + if (UI_Dev_Wants_Mouse()) { + return(true); + } + + // A false return means the press reached an element, so the game must not see + // it. The press then owns its release wherever the cursor ends up. + bool const consumed = !_Context->ProcessMouseButtonDown(button, modifiers); + if (consumed) { + _CaptureButton = button; + SetCapture(MainWindow); + } + return(consumed); + } + + case WM_LBUTTONUP: + case WM_RBUTTONUP: + case WM_MBUTTONUP: { + int const button = (message == WM_LBUTTONUP) ? 0 : ((message == WM_RBUTTONUP) ? 1 : 2); + + POINT const point = Message_Point(message, lparam); + _Context->ProcessMouseMove((int)point.x, (int)point.y, modifiers); + UI_Dev_Mouse_Position((float)point.x, (float)point.y); + UI_Dev_Mouse_Button(button, false); + + bool const owned = (_CaptureButton == button); + bool const consumed = !_Context->ProcessMouseButtonUp(button, modifiers); + + if (owned) { + _CaptureButton = -1; + if (GetCapture() == MainWindow) { + ReleaseCapture(); + } + return(true); + } + + return(UI_Dev_Wants_Mouse() || consumed); + } + + case WM_MOUSEWHEEL: { + POINT const point = Message_Point(message, lparam); + float const notches = (float)GET_WHEEL_DELTA_WPARAM(wparam) / (float)WHEEL_DELTA; + + _Context->ProcessMouseMove((int)point.x, (int)point.y, modifiers); + UI_Dev_Mouse_Wheel(notches); + + if (UI_Dev_Wants_Mouse()) { + return(true); + } + + return(!_Context->ProcessMouseWheel(-notches, modifiers)); + } + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: { +#ifndef NDEBUG + if (Handle_Developer_Key(wparam)) { + return(true); + } +#endif + if (UI_Dev_Wants_Keyboard()) { + return(true); + } + + Rml::Input::KeyIdentifier const identifier = Key_Identifier(wparam); + if (identifier == Rml::Input::KI_UNKNOWN) { + return(false); + } + + return(!_Context->ProcessKeyDown(identifier, modifiers)); + } + + case WM_KEYUP: + case WM_SYSKEYUP: { + Rml::Input::KeyIdentifier const identifier = Key_Identifier(wparam); + if (identifier == Rml::Input::KI_UNKNOWN) { + return(false); + } + + bool const consumed = !_Context->ProcessKeyUp(identifier, modifiers); + return(UI_Dev_Wants_Keyboard() || consumed); + } + + case WM_CHAR: { + if (UI_Dev_Wants_Keyboard()) { + return(true); + } + + // Consuming the physical key never suppresses the text it generated, so this + // is decided on its own. + if (wparam < 32) { + return(false); + } + + return(!_Context->ProcessTextInput((Rml::Character)wparam)); + } + + default: + return(false); + } +} + + +//--------------------------------------------------------------------------------------- +// The RmlUi view base. It lives here rather than in a file of its own because the context +// it attaches to is the shell's. +//--------------------------------------------------------------------------------------- + +UIRmlViewClass::UIRmlViewClass(UIPresenterClass & presenter, char const * document) : + Presenter(presenter), + Document(document != nullptr ? document : "") +{ +} + + +UIRmlViewClass::~UIRmlViewClass(void) +{ + Close(); +} + + +/// +/// Loads the document, binds its data model and shows it. +/// Preparation completes before the view becomes interactive, so a failure here leaves +/// nothing shown and the caller opens the legacy view instead. +/// +/// Should the document take focus away from every other one? +/// bool; Is the view ready to be interacted with? +bool UIRmlViewClass::Prepare(bool modal) +{ + if (_Context == nullptr || Element != nullptr) { + return(false); + } + + Rml::DataModelConstructor constructor = _Context->CreateDataModel(Document); + if (!constructor) { + DebugString("[UI] The data model for %s could not be created.\n", Document.c_str()); + return(false); + } + + Bind(constructor); + Model = constructor.GetModelHandle(); + + Element = _Context->LoadDocument(Document); + if (Element == nullptr) { + _Context->RemoveDataModel(Document); + DebugString("[UI] The document %s could not be loaded.\n", Document.c_str()); + return(false); + } + + Element->Show(modal ? Rml::ModalFlag::Modal : Rml::ModalFlag::None); + Mark_Overlay_Dirty(); + return(true); +} + + +void UIRmlViewClass::Close(void) +{ + if (_Context == nullptr || Element == nullptr) { + return; + } + + Presenter.IsClosing = true; + Presenter.Discard(); + + Element->Close(); + Element = nullptr; + + _Context->RemoveDataModel(Document); + Model = Rml::DataModelHandle(); + + Mark_Overlay_Dirty(); +} + + +bool UIRmlViewClass::Is_Visible(void) const +{ + return(Element != nullptr && Element->IsVisible()); +} + + +/// +/// Runs a screen to a result, the way OwnerDraw::Dialog_Message_Handler runs a dialog. +/// The loop keeps the shape the legacy drivers have: it pumps messages, then steps the +/// game where a network session needs stepping and calls back where it does not, and only +/// then updates the toolkit and executes what the screen's events queued. An event handler +/// never acts directly, so a nested screen starts from the queue one level up. +/// +/// The screen to run. It carries the result it produces. +/// The view bound to it, updated after each pass. +/// The screen's result. GameEnded carries what the legacy driver returns when +/// the session ended underneath it. +UIResult UI_Run_Modal(UIPresenterClass & presenter, UIRmlViewClass & view) +{ + UIResult result; + result.Outcome = UIResult::OUTCOME_CANCELLED; + + if (!_Initialized || _Context == nullptr) { + result.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + return(result); + } + + static bool inmainloop = false; + + while (!presenter.Result.has_value()) { + Windows_Message_Handler(); + + if (Session.Type != GAME_NORMAL && Session.Type != GAME_SKIRMISH && !Session.NetOpen && !Session.Suspended) { + if (!inmainloop) { + inmainloop = true; + bool const ended = Main_Loop(); + inmainloop = false; + + if (ended) { + result.Outcome = UIResult::OUTCOME_SESSION_ENDED; + result.GameEnded = true; + return(result); + } + } + } else { + Call_Back(); + } + + _Context->Update(); + presenter.Drain(); + view.Sync(); + + Mark_Overlay_Dirty(); + Video_Present_If_Dirty(); + } + + return(presenter.Result.value()); +} diff --git a/code/ui/uishell.h b/code/ui/uishell.h new file mode 100644 index 000000000..706be3eb0 --- /dev/null +++ b/code/ui/uishell.h @@ -0,0 +1,50 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The UI shell's interface to the rest of the engine. No RmlUi, ImGui or bgfx type +// appears here, the way no bgfx type appears in bgfxbackend.h, so the engine reaches the +// shell without carrying any of those libraries' headers or build settings. +// +// docs/UI_DESIGN.md owns the architecture this belongs to. + +#pragma once + +#include + + +// Starts and stops the shell. The renderer must already be running, and the shell is torn +// down before it stops. +bool UI_Init(void); +void UI_Shutdown(void); + +// Called after the frame's size or the drawable area's size changed, so the context, the +// coordinate mapping and the clipping move together with the frame. +void UI_On_Resize(void); + +// Advances animation and layout for documents that are not being run by a modal loop. +void UI_Tick(void); + +// Draws the overlays between the frame and the flip. Only video.cpp calls this. +void UI_Render_Overlay(void); + +// Offers a window message to the toolkits before the game sees it. A true return means the +// message was consumed and the window procedure returns without handling it. +bool UI_Handle_Window_Message(HWND window, UINT message, WPARAM wparam, LPARAM lparam); + +// Does anything the shell draws need putting on screen again? True keeps a present +// happening when the game's own frame has not changed. +bool UI_Overlay_Is_Dirty(void); + +// Is an overlay document on screen? The coexistence rule in docs/UI_DESIGN.md forbids +// showing a legacy dialog while one is. +bool UI_Document_Is_Visible(void); + +// Should a migrated screen use its RmlUi view rather than its legacy one? No screen has +// migrated yet, so this answers false until one has. +bool UI_Use_Rml(void); diff --git a/code/ui/uisystem.cpp b/code/ui/uisystem.cpp new file mode 100644 index 000000000..808f60055 --- /dev/null +++ b/code/ui/uisystem.cpp @@ -0,0 +1,188 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// What RmlUi asks the host for: the clock its animations run on, where its messages go, +// the cursor it wants shown, the clipboard, and the strings its documents name. +// +// docs/UI_DESIGN.md, "Assets and strings", owns the string half. + +#include "always.h" + +#include "uiinternal.h" + +#include "dbgprint.h" +#include "hostclock.h" + +#include + +#include + +#include + + +static unsigned int _StartTime = 0; +static std::string _CursorName; + + +/// +/// Looks a document's string name up in the engine's string table. +/// The generated name table arrives with the UTF-8 transition, which docs/UI_DESIGN.md +/// makes a prerequisite of the first screen that shows text. Until then every name is +/// unknown and the document's own text is what appears. +/// +/// bool; Was the name resolved? +static bool Lookup_String(std::string const & name, std::string & text) +{ + (void)name; + (void)text; + return(false); +} + + +class UISystemInterface : public Rml::SystemInterface +{ + public: + virtual double GetElapsedTime() override + { + return((double)(Host_Milliseconds() - _StartTime) / 1000.0); + } + + virtual int TranslateString(Rml::String & translated, const Rml::String & input) override; + + virtual bool LogMessage(Rml::Log::Type type, const Rml::String & message) override + { + char const * label = "info"; + switch (type) { + case Rml::Log::LT_ERROR: label = "error"; break; + case Rml::Log::LT_ASSERT: label = "assert"; break; + case Rml::Log::LT_WARNING: label = "warning"; break; + default: break; + } + + DebugString("[UI] %s: %s\n", label, message.c_str()); + return(true); + } + + virtual void SetMouseCursor(const Rml::String & name) override + { + // The game's pointer is built from its own shapes rather than from a system + // cursor, so a document cannot ask for one yet. The request is recorded for + // the screen that first needs a text caret to act on. + _CursorName = name; + } + + virtual void SetClipboardText(const Rml::String & text) override; + virtual void GetClipboardText(Rml::String & text) override; +}; + +static UISystemInterface _SystemInterface; + + +/// +/// Replaces a [[NAME]] reference with the engine string it names. +/// +/// int; How many replacements were made. +int UISystemInterface::TranslateString(Rml::String & translated, const Rml::String & input) +{ + translated.clear(); + int replaced = 0; + + std::size_t position = 0; + while (position < input.size()) { + std::size_t open = input.find("[[", position); + if (open == Rml::String::npos) { + break; + } + + std::size_t close = input.find("]]", open + 2); + if (close == Rml::String::npos) { + break; + } + + std::string text; + std::string const name = input.substr(open + 2, close - open - 2); + + translated.append(input, position, open - position); + + if (Lookup_String(name, text)) { + translated.append(text); + replaced++; + } else { + translated.append(input, open, close + 2 - open); + } + + position = close + 2; + } + + translated.append(input, position, Rml::String::npos); + return(replaced); +} + + +void UISystemInterface::SetClipboardText(const Rml::String & text) +{ +#ifdef _WIN32 + if (!OpenClipboard(NULL)) { + return; + } + + EmptyClipboard(); + + HGLOBAL block = GlobalAlloc(GMEM_MOVEABLE, text.size() + 1); + if (block != NULL) { + char * buffer = (char *)GlobalLock(block); + if (buffer != NULL) { + memcpy(buffer, text.c_str(), text.size() + 1); + GlobalUnlock(block); + SetClipboardData(CF_TEXT, block); + } else { + GlobalFree(block); + } + } + + CloseClipboard(); +#else + // The compatibility layer carries no clipboard yet. An editable screen ships only + // after paste has been exercised, so this has to be supplied before one does. + (void)text; +#endif +} + + +void UISystemInterface::GetClipboardText(Rml::String & text) +{ + text.clear(); + +#ifdef _WIN32 + if (!OpenClipboard(NULL)) { + return; + } + + HANDLE block = GetClipboardData(CF_TEXT); + if (block != NULL) { + char const * buffer = (char const *)GlobalLock(block); + if (buffer != NULL) { + text = buffer; + GlobalUnlock(block); + } + } + + CloseClipboard(); +#endif +} + + +Rml::SystemInterface * UI_System_Interface(void) +{ + if (_StartTime == 0) { + _StartTime = Host_Milliseconds(); + } + + return(&_SystemInterface); +} diff --git a/code/ui/uitexture.cpp b/code/ui/uitexture.cpp new file mode 100644 index 000000000..f7f6aceae --- /dev/null +++ b/code/ui/uitexture.cpp @@ -0,0 +1,132 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// Turns an image a document names into the premultiplied RGBA8 pixels the render interface +// uploads. The bytes come through the game's file system, so an image resolves from a mix +// exactly as a document does. +// +// Images resolve by extension. PNG and TGA decode through bimg, which bgfx already carries. +// The engine's own PCX and SHP artwork, and the surfaces the engine draws at run time, are +// not read here yet; the first screen that shows game art adds them. +// +// docs/UI_DESIGN.md, "Assets and strings", owns the routing. + +#include "always.h" + +#include "uiinternal.h" + +#include "ccfile.h" +#include "dbgprint.h" + +#include +#include + +#include +#include +#include + + +static bx::DefaultAllocator _Allocator; + + +static std::string Extension_Of(char const * source) +{ + std::string const name = source != NULL ? source : ""; + std::size_t const dot = name.find_last_of('.'); + if (dot == std::string::npos) { + return(""); + } + + std::string extension = name.substr(dot + 1); + for (char & letter : extension) { + letter = (char)std::tolower((unsigned char)letter); + } + return(extension); +} + + +/// +/// Strips every directory a reference carries, matching how uifile.cpp resolves names. +/// +static std::string Base_Name(char const * source) +{ + std::string const path = source != NULL ? source : ""; + std::size_t const mark = path.find_last_of("\\/:"); + return(mark == std::string::npos ? path : path.substr(mark + 1)); +} + + +static bool Read_Whole_File(char const * name, std::vector & bytes) +{ + CCFileClass file(name); + + if (!file.Is_Available() || !file.Open(FileClass::READ)) { + return(false); + } + + int const size = file.Size(); + if (size <= 0) { + file.Close(); + return(false); + } + + bytes.resize((std::size_t)size); + int const read = file.Read(bytes.data(), size); + file.Close(); + + return(read == size); +} + + +/// +/// Reads an image a document referenced and hands back its pixels. +/// +/// The image source string as the document wrote it. +/// Receives premultiplied RGBA8 pixels, top row first. +/// bool; Was the image decoded? +bool UI_Decode_Image(char const * source, UIImageData & image) +{ + std::string const name = Base_Name(source); + std::string const extension = Extension_Of(name.c_str()); + + if (extension != "png" && extension != "tga") { + DebugString("[UI] Image %s has no reader; only PNG and TGA are read so far.\n", name.c_str()); + return(false); + } + + std::vector bytes; + if (!Read_Whole_File(name.c_str(), bytes)) { + return(false); + } + + bimg::ImageContainer * container = bimg::imageParse(&_Allocator, bytes.data(), + (uint32_t)bytes.size(), bimg::TextureFormat::RGBA8); + + if (container == NULL) { + return(false); + } + + image.Width = (int)container->m_width; + image.Height = (int)container->m_height; + image.Pixels.assign((unsigned char const *)container->m_data, + (unsigned char const *)container->m_data + (std::size_t)image.Width * image.Height * 4); + + bimg::imageFree(container); + + // The render interface uploads premultiplied alpha, which is what RmlUi's texture + // contract specifies, and a decoded file carries straight alpha. + for (std::size_t pixel = 0; pixel + 3 < image.Pixels.size(); pixel += 4) { + unsigned int const alpha = image.Pixels[pixel + 3]; + image.Pixels[pixel + 0] = (unsigned char)((image.Pixels[pixel + 0] * alpha + 127) / 255); + image.Pixels[pixel + 1] = (unsigned char)((image.Pixels[pixel + 1] * alpha + 127) / 255); + image.Pixels[pixel + 2] = (unsigned char)((image.Pixels[pixel + 2] * alpha + 127) / 255); + } + + return(image.Width > 0 && image.Height > 0); +} diff --git a/code/video.cpp b/code/video.cpp index bbce57854..ea95bbcc1 100644 --- a/code/video.cpp +++ b/code/video.cpp @@ -24,6 +24,7 @@ #include "hostclock.h" #include "misc.h" #include "surface.h" +#include "ui/uishell.h" #include "wincursor.h" #include @@ -47,7 +48,8 @@ static VideoScaleInfo _ScaleInfo; // Set whenever the visible surface is written to, and cleared once that frame has been // presented. A frame that is skipped for pacing stays marked, so the next present shows -// the newest content rather than a stale one. +// the newest content rather than a stale one. The shell keeps a second flag for the +// overlays, and a present happens when either is set. static bool _FrameIsDirty = false; static unsigned int _LastPresentTime = 0; static unsigned int _PresentInterval = 16; @@ -169,6 +171,10 @@ bool Video_Init(NativeWindow const & window, int drawablewidth, int drawableheig Update_Scale_Info(); Update_Present_Interval(refreshrate); + + // The overlays draw on the renderer this just started, so the shell follows it and is + // torn down before it. A shell that cannot start leaves the game running without one. + UI_Init(); return(true); } @@ -183,6 +189,7 @@ void Video_Shutdown(void) } Win_Cursor_Shutdown(); + UI_Shutdown(); Backend_Shutdown(); _Initialized = false; _FrameIsDirty = false; @@ -212,6 +219,7 @@ bool Video_Set_Mode(int width, int height) Update_Scale_Info(); Win_Cursor_Refresh(); + UI_On_Resize(); _FrameIsDirty = true; return(true); } @@ -231,6 +239,7 @@ void Video_On_Resize(int drawablewidth, int drawableheight) Backend_On_Resize(drawablewidth, drawableheight); Update_Scale_Info(); Win_Cursor_Refresh(); + UI_On_Resize(); Video_Mark_Dirty(); } @@ -274,8 +283,12 @@ void Video_Present(void) return; } + // The frame is uploaded only when the game drew something. A present made to redraw an + // overlay alone costs a few draw calls rather than the whole frame's pixels. _Presenting = true; - Backend_Present(pixels, surface->Stride(), _ScaleInfo.DestX, _ScaleInfo.DestY, _ScaleInfo.DestWidth, _ScaleInfo.DestHeight, Backend_Scale_Mode()); + Backend_Present(pixels, surface->Stride(), _ScaleInfo.DestX, _ScaleInfo.DestY, _ScaleInfo.DestWidth, _ScaleInfo.DestHeight, Backend_Scale_Mode(), _FrameIsDirty); + UI_Render_Overlay(); + Backend_End_Frame(); _Presenting = false; _FrameIsDirty = false; @@ -291,7 +304,7 @@ void Video_Present(void) /// void Video_Present_If_Dirty(void) { - if (!_FrameIsDirty) { + if (!_FrameIsDirty && !UI_Overlay_Is_Dirty()) { return; } diff --git a/code/winstub.cpp b/code/winstub.cpp index 6ecfa5c54..90f9a44c1 100644 --- a/code/winstub.cpp +++ b/code/winstub.cpp @@ -68,6 +68,7 @@ #include "resource.h" #include "session.h" #include "theme.h" +#include "ui/uishell.h" #include "video.h" #include "win.h" #include "wincursor.h" @@ -179,6 +180,7 @@ LRESULT CALLBACK /*_export*/ Windows_Procedure(HWND hwnd, UINT message, WPARAM w * The frame may be drawn scaled, so a click has to be matched against where the * player sees the controls rather than where Windows finds them. */ + LPARAM const window_lparam = lParam; { LPARAM translated_lparam; if (Route_Mouse_Message(hwnd, message, wParam, lParam, &translated_lparam)) { @@ -187,6 +189,17 @@ LRESULT CALLBACK /*_export*/ Windows_Procedure(HWND hwnd, UINT message, WPARAM w lParam = translated_lparam; } + /* + * The shell sees mouse, wheel, key and text messages after the routing, which keeps + * legacy child windows working under video scaling, and before the keyboard handler, + * which keeps whatever a toolkit consumed out of the KN_ queue. It reads the position + * Windows delivered rather than the routed one, because the overlays are laid out in + * the window's own pixels. + */ + if (UI_Handle_Window_Message(hwnd, message, wParam, window_lparam)) { + return(0); + } + int low_param = LOWORD(wParam); Map.Message_Handler(hwnd, message, wParam, lParam); diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index edebbfcb8..b6b755512 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -1,9 +1,18 @@ # UI system design -Status: proposal. Nothing here is implemented, built, or measured. Source -inspection and upstream documentation inform it. This page owns the proposed -UI architecture and migration; [Building OpenTS](BUILDING.md) owns build -support and [Project direction](DIRECTION.md) the wider architecture. +Status: in progress. Steps 1 and 2 of the migration plan have landed; nothing +from step 3 onward is implemented. Everything outside the migration plan +remains a proposal informed by source inspection and upstream documentation. +This page owns the UI architecture and migration; [Building +OpenTS](BUILDING.md) owns build support and [Project +direction](DIRECTION.md) the wider architecture. + +What step 2 left for later, inside its own files: `uitexture.cpp` reads PNG and +TGA only, so PCX, SHP and the `` element wait for the first screen +that shows game art; `uisystem.cpp` carries the `[[NAME]]` syntax but no name +table, which arrives with the UTF-8 transition; the cursor and clipboard +requests are recorded rather than acted on; and `UI_Run_Modal` is written but +unexercised, because no screen exists to run. ## Where the UI stands today diff --git a/platform/win32compat/include/windows.h b/platform/win32compat/include/windows.h index c708634ca..684d493a8 100644 --- a/platform/win32compat/include/windows.h +++ b/platform/win32compat/include/windows.h @@ -538,6 +538,9 @@ typedef struct tagHELPINFO { UINT cbSize; int iContextType, iCtrlId; HANDLE hIte #define MAKELPARAM(l, h) ((LPARAM)(DWORD)MAKELONG(l, h)) #define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp)) #define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp)) +#define WHEEL_DELTA 120 +#define GET_WHEEL_DELTA_WPARAM(wp) ((short)HIWORD(wp)) +#define GET_KEYSTATE_WPARAM(wp) ((int)LOWORD(wp)) #define IS_SURROGATE_PAIR(hi, lo) ((hi) >= 0xd800 && (hi) <= 0xdbff && (lo) >= 0xdc00 && (lo) <= 0xdfff) #ifndef TEXT #define TEXT(s) s diff --git a/ui/LICENSE.md b/ui/LICENSE.md new file mode 100644 index 000000000..1ead17558 --- /dev/null +++ b/ui/LICENSE.md @@ -0,0 +1,9 @@ +# Shipped UI files + +`LatoLatin-Regular.ttf` is part of the Lato family by Łukasz Dziedzic, released +under the SIL Open Font License 1.1. The copy here is the one RmlUi vendors in +`thirdparty/RmlUi/Samples/assets/`; that directory's `LICENSE.txt` holds the +license text. + +The documents and styles beside it are OpenTS files under the repository +license. diff --git a/ui/LatoLatin-Regular.ttf b/ui/LatoLatin-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..bcc57780d8f007bfc2106a89876f44cb56936e6b GIT binary patch literal 148540 zcmdSCcVHaF^#DAx{?Q}4~{&9)@Vwz@4>xqvIiy%)fy*nqKd z!Ny?DvH@dEu_2*^5Ly}}-~^J8W z;0}Q_<`2J5uE6g`Bu2}D_Y{gosb~c>xd#djcNu0Sz6-Gku`5{(v<(rC^d;0nSph8t zOHmX1g>e3$qc{J0UJ{p3FV4SNs{Mij)@5by=y zKw&G8h%HB~xMcV`D?`t+>1Z}z{)L_uVMH^V(6fA*5BHu%v)FIpx(J0+N$6QB8FkXt zD1>!D8ybocg_5JV0NO%3zHI@TC4wT^g4&sL(B?^az5vZ;{si?LRLGZyk%75{9PCTz z1YZCri5^9QEnZ259>Qs-VKqUiwZ{kAI5hQ$c7u{x`Y<__CP&9a@Z^K%Mvu>Z3kF zolyGlQ>YDaZlh9AH|0WW=~n1>1{}AeBK!?{8s9({NuBx&x&!!hLF7W~>HAOt)Ze8f zXfdTjtx)o)ZuAV4T&finLn)*i&>CoSJ1O+XsE1vJN};|P%1W?&cT#^scd~b(X3-8b zS9B-pA%*=tYNm5gi?5v?j-60;K}mv=3?&UpHk9K~Wr zTOPso5j#ih7;n3X{qhyi1F&7gpM(9{2FF*Rd?1_;{|b(uLV1qZF_5D!DBBpn4f7v~ zE&HBAY#NlMP&WE)oBzn$wVxw9v1fdF8^w*($u-}(xCHDRDMcu5q(0`Hw|jp3H}Tkt zHW7O`wh+7MFZ2$SMQkJ~?7b*oGz;~!YIK2l7Da(L(?X$QeKUOoKZheY7g2#k-pO=N zj+6ai0^x`MD0CA20{ssC28+=<=xuZbT}7{<8ImIfQX)0dAT0_(p(q>~z%MW%Gk79a6pP|e zJW4=z^a{F!UPW)A7tuE4KuIVWxsV5Cpcd4MW} zfHs1rZ$>{s7tv4A&w>1CY(o;9h%0a%?!-&+61)uGhL?kHmV(#eJMf)&1Kx}G;C=XR zd(-pxZt03Bl$>0x*gLR>sTLqCHeAG|*J`QV=rx^{-|9e%EDy><(@lKq9ci~Jl1J;mjI2YfA^YJ~n0PjaCGz%A^*|-Sr z#>MymF2VQ0JXDJB!)5q>q(?nC5@c3~x^X#v09WD%@f3UzzlXxmTwITj;s*REZp6ok z9l%ZaIG%=2;;Hxqo{k^G&G;0u!0gw8Pve>R2|NSchGNj|xC0w-7ruhK@m1V|U%|cj zRb)eV;y(NuUVz`k3-KcS7VgJyVXJw@Kv)EQ%M|jb1dhxunHtth|Wi z^_5;xV{5ZFHQXE9(%x&_IknkK#dJI;K@!x}W$6qznY^gQi;6781E6t5?S&36wtJ23 zy$&yBH+CDnFV}e)YwJKP4lF9^DruO}Y_gcbcQzZnb#=`qZ+=U-(VI?c=`AhBb3&tz zZf`7H@|_vIN#uSKY58(pvk{Qk*GIO?Cb(W?+-ckiu+Js2F+f&*b9-HQM?*`qr3LQhH#Wnqa6*)?XNQ-yd&NZw17I$A z)`;NLQfL7dTM9e8RA;XjcL97a8}IOn?M8xRP*K-K26aLcKr_F+g*0g|=8=@x2gE_B zsH8C7G{Wov`*`LD3Q%wYpil%9w;M}#S~|!;@SH;7gqvPtI3VfA(o4r!I*NrZf^PA+ zHyWOThet>o`;e5#jHPfOD3C5`HicVEE%7FYS7tv)Q6=8)j$(&bZU+L4Mz6G}g3tvO zOJR#wM$Q`GOa^BTuL8gX^DHp}2f6@lue_+;xU=2pl>^fp-e7xWQ}a2dySOFVE9ni&>q$rx&d`_W&@#`q`DiVm0g6I~Wlae19{CKerMuLv1 zYd%LL7noJJ6NZ;`8*j3}WBz)$aGyK^vQO@|03~IBe_1;W;<%x@#Q>f|NNoWY7kN?c z04N|Y4Qe|mCsoqa>{VC_jV0b75XwO4xV_NW4&6T;9E`!YDlFXDeoiGy@ZOdXZU#1K zfHrl4!>hHQ!{n#~K9i%~evT%`5c@fX97FBrSaJ-rpA(T|xc!`%93$-KB;*)rKNmob zadxBAi)T8#@qA^8!<)cYmO8u!JMzjB{$Jo21vnZ2wh?e7M-$*kj%L7-94&w&IYtAH zn^ z5SM|pO{z+kj&ut#W8d2XM9JYz8W|p}^CrfhW3jfR8O#tFgygZDx%qaA-I&JX>w7`jgeQHZ9R#~0-Qi8MJN4NPult9Zf)I8= za}Nv&(#05^#xkP3z{)8*ca~Yoz*sbcSqHNS8tukdtAYO0!OG}BLEwMBi8r7qp=YPl zVl-y&1XvlPw~bB#hL-`D(8}m-C;FaW-+Z1j(#G)fl$8!^DI_*f0@f7zgB~rVpnWi! z<5dat9w!LKo+@hZws`5Hj&3kRR8dDb)Z1G?cPBp60VsiCwv=|Hhg+bBQlJYCd@lfR zl1=~-Vk{Xj0Kj_|1PV<11WW*82Rt0ZBLTlK(-12++80PfCgA}%!CuztsHxd$%!av9I8+j@l>lFjr7*4e`BY^m;^?ge zwpQB*z|KIW1}d0TYV8A~%N!pM~ z7imK(-J}hv^w_a~UNw7qyX+?ZZTzCm$Nf-sd zRY~C`lrPlj?&5RNczZo~m)o1qkuxhfyChkUFq6N%3tTL?2u^g%d6@)D<1gajEna31 z%ofGx*zRI7Gu`y}EX0gvD3^An7$~hq&M2_OX3b^NQmq!VoU)j0>QuMy9-LXRNon#xxgMHPT5%-p84 zMk+1&GtxbESqat{ecx?nS3;~aGuD-@(y4TVHxl!ehB&=G)}YK|j$fbSX6@j-i-4|T zz!{==z){+XZ6Yy^0V5l2qUEsk#4g(Vhd^DRR`&<`%za@-$=0Dewu;Q5_fa{oQW~l( zti)FD9=bI2e_aAfmKYcfrRJ;jF~DcW#( zB_}#$Et#FMcmpS6dO3Y`{tWk>w;fDv-!(n0qp3)1II(rceG9S(|1p{k@+oIeLu?Y^ zlqd*oSj6I}`94l+=T{`~?mOuI^mS3IU+0+s!vnAQ8?|Ejm%5Fq(Q2jKqY?**&0*H4z%>C&U&hRX zw>gvNa9{LS{et9e{@QYVb)kEj6(#x0| zT+rd62jF>v<1CQ-B9MDxb(93jXscha3=|Q2o0A@udbdXj1}O)#MGL;U=Sl8wKbbe@ zMI3x;>OE`cIX$wt(2(4gRgJ4&-C0q+@2xw#AFTL9t*6cneOOb?Oa5^zzcMd0u_(S- z6Qq#G#)Q@Ge|O`yU*22eh{ajDUE$;{?#LV*u*vaYp{{ zf8Y3ufMev#ATjzmDFh~u2VTX)L?%E1uey2_&M@la9>+h2ngJS(j48o!{8-|j`^GZu zYWOV&@Fr6W@I!|GNu2=rgc4%q%(h(Y$;DuUh|Nh;x~LOb%T6uqnnQ^wN~=?4r4-IB zjk$`yT>0Xr(ul~62&GmP+q9{bK087On+WiX01s@32LhZ~7%&GbswXy%vO&l;fw0GTv6k<-LaV9ojxkAUlh(?5IhVBoE3JkDB47IR9 zia;tOR6Eqq^9OXE{tRj`bHsptxRmr~gYRECc8ooE15#F)SONyaeWh?;oR*r0p;e4l zBW9Hh9(@1#kYiWyh6wJ#W5=kt*;^xmrcrMV?S6HWDd-Ys0h%CFAoTGHWR+kJhEd;# z!5Ro-CfW(G!6FcXVA{l@1Z=ipotDrCW*p3^9{34NaEW$4PE(vlpBRy-=K@8K?+c5N z;V-57z(Ac0|5+LtMkI}i)Fy=vu6H=-&1sGspRxx7^@DpuQp3a3Lg*#>K%q}wUdeb7 z-LMgh&G!cC1FTI0(e2PjDF$DmsK>iyD!E)j=TI~O9;{~4LzA?Fa6*e^fj8a=OV-if z5}4c}9wxCHN?r)S;QM62or5RoA*OcCw4@rlTBMW7;;mDs556|Bw?z$G+tZZl8l6l& zXMP&vnxtdkAqDo}U+kw~57ZEK_Sx()Cc$sI$4rClr@Z|>%ncHQikZWsR(#pzE#>7~ zE-&+2@~^MXpMMq0jvmFbR~9UIh5P#G=J)Qdsk!^T&70r7ySn;rl1rR5ydV1i5c~ig zN(UJc#6H!U2)q>R5R9VVx&ynxLM34Jh|SXJfl*nQ3SRyY>9rucIs3qLf=6Wu-8`qC zM|J+?x^8V&WX((cYj}j0z0p{&F&eu+DFqZZuY04sK0bjUwe9^W^iMmvznwYC0p=-* z+YOBPqKJpP&P#o;jMglFx;UrFpd&yoOR-d%yY$j2j?Ez*dg5WER~6!mHT>4W;$ z+|w)a^H-dn+jn|pLBYz?>~TDJyoo!y_iv}#+D`p#?@_LaZX2p2v&(^+{qJwy{QiO3 z+5_aczb5eTmn*k`Vf8U|+sZF}-jr|dWxz-RAIaRy<75RjSNohOUgN1*jdxDBpMK)c zf8PGa=7Q2~uWkJE6>7jk*SJPigz2%d#%|5BSv0W32Rn`e2+4Ic9>j4cvy&v*C1j92*FJLrc zG);b|RHvtkn>I|hpE~sc-T6dXD7RZ_i`#z%-@E;%8*-CoY-)@Nr#_9VoSV0Pgua3DadP&!LGN(#D)%vl|4HmvE$%3pJS>F&?!(_0~p)kjpE!p*n*|q6$c2CplmRToOm-b9+3sPtym8-Ruv}YAGrrYA& z(>Bbm+PkQ*s$9U8dyvPqby8fv$9Kj56A@|M`)8>p^C?~+iPS6lQ^}j znC`~!CmF^E=6z;8$b=YZCeDc|&BUzdlCE%xOegc78-dJ!j`6&KwJoy`+7YMT?}Md* z?+2<+y@X4-OINsyxLkOOo4I7jK>cwEK6VTq#^}WGmn=*_M00s-0`nt33pmOAuLS*| zuC?s$a9ye0|H0#!sDF) z@poLoS?*pofMo+&ZvR=X;AohI#j#?UNUR9J_0VNH!J#fdA!emw9LEYcFeSm!8clTY zjpuo6BEoYmmb@_fGi4;;77>JVea+M({C_;(L0*s+&sH~x%P+9b> zP|eW$mYk@l91G=8MgY7tfVT|b$pM~5OdM+xioitVmuUunNxN>m9VDS0gMVg1*m_gc zjXj5}f+J+i?S}!A0GT><9-u?98mW)sDggUp(2{S=dsLs8@vr;MbX` zl;Zf<%owFQI>ku!*Vm=5-&cz=E()g-Srt=74a}~PcK<{kI8*@Wy9XN*}!K?b zRp9sQ>v~(8d-GR6)gKqxx~`8>t|G?G|~#Viu1`3WW~E^sPg*#PUI{N&q8_dT%fj_Dgb`A7R& z_I0OaFL|`5dxtAPVqJFr--YfsU%&KJ-IOgA&BYroE?e{FzS_!?HY;=hy3;iL6}^LD z`FPT}d7O_Oxk!jH z)wyN!kLA$?LDEn|Xj=E)=C*wut|)7ivLG(grYhZXb?uUucY(t2{lnZ$`Y!=Wzc-FV z`yB`{GDH{seycF*dU{93Nn!S_O^T~D1Pd`FU3`2~RowLb^Rs~eON``eM^a;}dSy#eLN!FD#M0)_2=^!_Ad0b#tACC)$NW)FYq(Rsr&82nG=CL^8 z(gOsm{Kv*(^^boH9H71^a=-k1*Uo9Sx+!$qjU6#-YuoeJy|}Lb#hul8=|f9?`&+=F zVfbs1-L0@LD-^r}k8>n0gAHI_iR22Fl!z0|e`1@}orkS9C+sDJWv@rC6NxEX*|G*F z9h_F#nOnKE%I2)=!K1>4hA z=AiP}aB+Net|BLCb%xSJSP67Al5qvOl#uAZV1dYNI7Syp{C|wy(bFzVQG3q%dsWmW zQ(IrZr|;Z`@`BagMKc~;lCKIKnn5QvEiW(XC{0xAq*Pz-oXWU+H*+z>7L@IHbJM(w z+p2Sxon8g?PVu0y4x|8s|NY!A{M)Mun-p zPpmDy6@DqM`}RDw53E%3t(r!;d7-SYKD1!BEL@&PsPAj^_SI&y=xR&6LjR z+h!(dT0)o*cXPqa4TDDn{v-~6C3>6v75D%kZ{wkZ7AhyPmrmfFzsqsPmG3`w>C@{I z>gt=*ZnmWHZShr$%FP5PYQw0>_2JjpdT4Q;SS%iTbb^^JmMR>ziwYV6P=E86gzn54ZW>bGJiKl~zz2oHTPF+7to?3_pR zAK90XapJg>oE1f!8!!U=D8JjU3pcel-<9> zX^IRhD;7w~UJ;}ZS`#2vuGNOD`Q)Upt1Tzb1a<~X+81mUsAKMy1Z|cCsW+>!1m=Iz z|C7+a1>J?e^MH4W<&7jiZMRfHbu5NPLt^6%|j?(qqrBU%Lf~BwkMNKgrW|V3aQ3 z^@IHN`$n&m@rJxrHpo=g*ip#klg(pgYV4Y4Oca6W#N@K38Nap>mPNJMj$4c1*d+`f zP{{1D`owZXE{VK+ue3}e9)iSA6bE7-fLEespxrYTCZTvBq)6bW$z!={m8e)4;}OelKnybUMA7*HJU*KR+e&xbgTc z)rCDe0l(Ki{Jgwb{xDJ6?E$KQlHL+CQKG5mx!g%nTzK{ct)Am#iu-U_=B%Q*f-dnm z@;WR|1(`}g1wVx41f?eEO{Ulruzh2kVgMW2bCc@&c(zU)YJ%-p?dS7KF!tgYz}<}M zc)h=iS?t$;u^g@w{U@=`3Hp9({l7_WZZ-&nYW7v4(Yjco*|9o3X!y-W;lHmCqws^g z_|bOahuH(93pPS*8pD$V%+itTnlab!Bi9AnA$l70r{Q}?|9>(jKVGktlN*>MuIWds z+pR2)V0HZVfynn5oAV>bnKwi~G=!6?++^Yl?B@A>fS>;%^E5hR-o##BVoBdWg|{-yH$KSet8oMdwg&`IcuGBeM)3ENxdXiIgjsVuF5hoE zZ$|FNkN8ap|Hy%!gaToN2~UZg^@5KGJJQC}0V!SBNidQ{7x|Ltko5KC&?og^1wYWK z30L5Jc~sa@ZdZ7eDpH1bKcY8Dx$}xhgX>lA74qF+#f+)8Cl@{;a0;l;`II~YWbn! zb&-*<7ZAk$$J48WVG@=T#QTL+4zGExxev{)-dD8hoO@y1#$8y&L2-qa!C*`&*=R6wF?U(xCT_rlnXyUCjOR_lOCu=3HJ z2yxsJSodduuqsD30{UGr=y|M1KF)JdljJz0qnLo)q{ujfMiLMtN_B25U9o{nI6q04 z;198kR@sW9>Gq)``MC;}LJ}}FA$jXMY5}bBCW{~7EQOjow!vqchW{yk0r0H-LH>~u)S{zOAj^RGaO^%2&XeElkw8V`iD}l^gN#OW6h>s2*)yh$# z`K2wa339pp^k<9+@*5U-W7(e_hBXR_QbIh5f&7y{=b_eW6tD+{w{yZ}Uow}(>)eoD zAP%MPz-NTMa5tn!ESbMxNo0BhUJ+k5t8YO{dYPqsVMY1U+W4pAmzguVcQ=&f8O>Sk zkmrT$?CNui7oS|@nbqJ(3pOPgE1NQU_cRqQO~hXs+?9@*ix$mvRJsjAAI3Ptf3@% zzpJfc)l_FB$qm7#IQq8`6D7M~eZe3QyOE_l1kcAWe9zO1Zd#gOfxV@V5jf&di1S7R zB;vJFkdGjHI$R(LE+JDi1}+u7QEF1&CY9c%G)eFH@<`x>ZbX3rnmsDj9!&sN3{IV- zON58NhCY7-xI^4*GTepeEtn0Qmw%*}#2p6u7C_u#;LxD~B2x79;wDeeo@qmOQT}N|r=qUuhcrkk(Bm_2xU|@-Qg13mL4E#oGNel~fm^B)+BP=Y@qNTqX z45GimxnT~AMq}Zx!4ak*3gy6lvr_<1O*{_uNQ9T)Bu%MF5S))!-n;|ti?JO*hev!H z(vk-h#IAb@(~%0A9r7 zY9n_i&g(aG!dSYn%IGHUMlf+Veyz1QNBQC;e7W)RI8TGgznMvR_!9I>a8gg!E3O#v z5hQ#slK}O}^FMqt@x4<#&j0?&j=gg0DeQ`17m4ot1MnsC_y4Ic`WvqA7R~|LdJo{? zYYT{~0lE}$Ld+0Iq<_yHRTF-6;OWqZ2IznGn1ad%Kc{|O+ZQe?r8F< zhrrhu0A;ifzKcL$0{-mZA%@RKX-F!IC@Lfkh0XguGXNWhY>?wIa8Key9fn{7GYIjgWgk^UxTZb|df4o_Wbh^up78wud8SQC{!Bb(G#uPn>JWxdF6^L+bafFzH#B{H&Z+JwO)7wXrxi)@CC-hOhEzA6Xf;%`|1)gA0p6d$ks^z z4kO3}Cg3si5x=DlZ!eu!xvVxeb;^{*Rm*E*v#43Qg#??bO>U1SC$i}7!cu}%?Y8t{ z6}(J+Dk-gdU+ewt)6(7Dcefttpw^bW$fIaBRv1EFEqP@P!P9D)5uqda0UtygfDeK2 z_Ix#rgD*EN#)Jj_tfUSyT|!*W&&h!wJ*MrBVY>F78ym$_@Zxp;0# z+1wKI4yvAUK>j(WCEuaaN?k(w`DIVq)g9f(Z-b3^kM(YP(H&Yob7^72?m1b(p`!_C zF7}NJt@}Dt1)kzQl6EHPqymy@TE3!xji2?a7N$Enf^=y zp-Ci$-M#!A=My)d1m`C&FW>us1#+3wH+cf`Pc3LBdCVmwkLfxOqDAr@&v)%5sm#^2 z?M0hN7IP0|F}ZxUikE3$pP={hvAJ%c&k4zH(kG<4$Ml&0XrI5sS({zw`AoO3JIHkx zZo0yESCYrM#PC80GmiEE=iVk)YzdV{cT8mh2*s!|>0u5d@itgcxVj+D;5Gv_^#9WT)-Rk0ZjIjJ?Np^l2# zU9&43No`xFbw83x*XVTN5XVi6vZY!hO|jY2b4piCPbqO1%j6N}h;V1NIo@N7G{)u5 zD9D^&pOgn7Nx%{E`UJ=6li~Oyw%q+F!sVhFSu`sPR&z)VCWGqNE#W$^M8nn{a=03i< z5b%e1B6SAv2k}z#*<@Jekd%xtH3|vacavK;K(H86;2}n}v%(d@=S!F(J}BH42=SHB z^wxqKSLs<&lC9?iUot(HJk_W-MFPN<4 z)S5Ca&fu4AW0pH4_7?~G5Y6w0SkCeDKPJWnBPaBK8TyX{x?E`!de@TmwtrDNvUIcL z?;VE)wsF7pEzNjLU}?6Oy70spT%K6My}&Oyg~bib-M z{|bK$nL3XtCeJ}E$P$P1HeFT3k>{(z7MmKxh#Qib!_>6;ZFa+`ikr(6~aw241 zN?zOgQF0EMV`05IVQ~2I8fQg(bWczLQRB#x7S@KaRy^uk(z>{y+@y$jKAgppIM0fW zT-*fiOklXiJ)FN6KMF%tdy0WM9@7@u5-&Vn50-b!bFq%4Te4sd}JXd5rKlZ-rd z>VF+)-qvmc`~Nm9B!x)E=r%X937xEoJWBd)*wQ91JU*}S&S?okl!Sx>Virzm@pSK< zMr|Ehy=+}&A@w%_9(f-U;K97;lLUx<@mmRaCw>$x1li?dUvc)o8JS}W7~k0d9zTd9 zZ%5MRn^2e#$^hE}bkY9;d{mR}L8t%4bsgcl?$$-H7)wxLCR_@|%Z6teD85p1r`V||hs?mh8?*^$PtRmp2tbl`=dP<+D*C&Py zHJHJN1$@f$O~n@KE`>&;80xnan@mN~KI8;ie-5-p`1YWax45DBH=(OxTP6QyApP?* zunB#b36T6B>_LA7cX4OV=rof5N|YbM`z6P@GkQ7RBM%Sa9)m6D68sOjjN2R;4v7sV z2cR+1!dl>4=to0%z{l};(y>m@Z*zCi-wypOAUtC8bKI;$`fwI2f`jD2SS*TE(cfIZ zrU}9K(3kY`p^fp*#CYls!dXo6-E=v?BryyP>=pzM!@m)oE|&!jeMH3%u~I1&JM;j ziP=R3Lm$wgfB%k+#&(lYes3=%{r|?E*6Lwv4txs^BfBV&GK;fGTeWW=feru7WLAE* zUKgt1UKeKv>bNghIR!l28#k| ztpM968_<^4a+eeln)QECT1?_>?vHX2R_OwHeVWDGO_8^5gP|jsA0So%>g3z=LtXV{ z{aFbk?D6rX^VvX#Z0CVTP!rJdE+1PKo*#)UQ}2!sB99Fk!*l+A!7gCh0ucw8xp-yG zBTG%{>L({ZStHen*0W+3{$DTF1(6roFlg^~*gp*a!-4c)JEngh97+K3?iVM&02HLF zrP5V0lT;ka{R=BZ%KKHS`&95|4Dc$ES&N6k_lBq!j2T(-@KG5x{#X|zRvAMBBSUjD z*jKdDKvPz0p-zs;Rwd|jF>{uBhTQ>C6nM_dLZ7j44}LK6#OEjjKa zeL1xl(f~H7C$f%sdtz;RRHQkwIjJ^-9C?^cA_?_d(dQ)3#=iwO3KUi{ez25WZwZku z3`s1uJIZq05qh&;x;WGcXXV*$(Vfc3XjMXOPPEBpQjuyMUnMwGsDn8~y~M5oUxLJq zeHaT)&m_C63ds(tkGYT~V53=VhRM@81%kl@PB)C_^I|1$Pr+9fz_{Tk!guXGyi;k-b+nf4 zd{`vexGg3lR$E_j=T3NzLa#IPsTTHQ;1@`Ou%Z>#HDvns@GgPyKE>-sPkC(Ilysvp zeM($xxyRV;PIP!Yjzl+Wi7!qG4~G|Z;);{P!;^~R?CI%tM>;I$DD)ot6;&f{_+j|( zMY)Qtw$jw7sMJ!Mwb&Ka;!Yy)liZ@CwnE4uI16pId~(dUje-9>vx_d~Z57EBj94q- zyDhxk3Vcl(sjb*4HJh;aYk9b4+J#_)iI}ipDVQ+clIb8^3Ai2uTed1&wop2|t$JCE{kKjj{$YQTztbHEZi>dRO`zEeNq z;!#HUDvhukFf<{{+&1ddy)R+;ZGTZ&?nJe0bQvnp9mlP~e;GbPY$U)QVQ-&*-JxO4wK$6cR+843b^{o1W6V`Vjq6WTo9v89CS#nQ6%IxO12< zdHmq()rkEIdy4F%gzp=`I}K^M@V3oJSdr3eO(a@jf_c{Idksg>CoxD8`b9 zU!J$_^18e{_?dTke~03lC8ArxMy)vZ=F_hF9i>+d|#Tqv-Z~lxO&hvGJYs>Yv z5Lb0tYgdvZerh!L*Vt$l4L!JYOJ@spbSSb0B6%Qd6~o^#`{=*(`xi9e9P^oSCJXWp zeAJa#eve5+2``1fOARTxSmm+s6M~7^7a6%9QxZjpF*ej%l+l^8;>_*kR#|yuRBY3hnU217_s@8O`|ird8WR&B z4_H-w8VA2p8Kl=pK@Ro~{|j#iIgs%=8UBq~3k;Bw_RXL|0Mbd}E=Cj}S_y?oP@5g$ ze&q~znAPxol2;wmrq4)CoiTlygUY`C%P=QVH)j~R3$3A_`r&`kHn2xFA|aD~`$`ZM z{^M6+Nk>suMrliKLTPfCA=;q7am3%=k0d`{^T9ir3Eysp zcc1lSvx3``OM66SvTh&!D3^Gn1Q-z*WegY5Vmh^;z-@3w1&1b=I=&K11H^JsP;_#Z z375DN3w1V^!O=0h)e14j?Erft{UPkRJY`0pm1j^W^#xqN1IAzlfBg)+sY$NOsV_iw z7Q^*Ez=de&gyC2`k>EuWsR6j~Ji7>cj5v(&1OXuNJ6i;g>{S2;P|4Y74qIQD2 z$G?5B`KaJ}E z1`Qu|l`>!Rxm18lt%hr6_!2zn?@0_h_Jp?7dbn=!wS{MJHhBhUZR4)Occ(=>E(XvN z@lmF#f$220-ayu>Vo02vmIvrF|NGxeeSrM19R5Dn#|1^;Eqc5qA}CJJ-3Semg#e7T z0OJ<`gZRl>8sgbx4GDQe8%^Fk!@m#!NMsWD|1bebMe4aV1O#TKY7wiJVwONAd;ol? zPXLw~zDrH;Jda~}{y-npxBT@B{PhduI`myMe3CAK{1&WJU_}ZGY_fX&Qs#-Qq*|oO z#wxAF%>LF0A_Z-q=8oZfb_k$Cyjp2_de<&?=)03}3v}=zcN!P*_r$_oFn5-VyWo!< z8vHZ&2lDax3H_-_vYR)NMODURDb-RLnJ#6on2pzMW(bL5_%}|Z)O_j~-yisQci=BL z6?N=>9?k_yMt?x<^WVEb8wamZ`!@3T)>ATeVD#R4Hs$);{(A-7r}Wg}8RR(@mcR7W z!T&q4a;1RVyXYGIEd4ssiHTm>yII}as;am3WM%bitFGGCokcZPZzpHltE=G*?gGAx z;hX9Q;hXo9Jik~Umu84cj|~ZlO^-69#p$W~kux`WUBL~{w{johk3!3PNZXSh!&D~vl?U%{>c9eJ6>L?$3yby^qB z-G%$Zb?o;4{yaicDmIjA2pX{G>l1v|_Y8?Zz*agwY-z%0xx2!3r{O`|&wWy=i4eKM zHKqKR7DGQF@Jv1c5H^SLeu_W|EbGY^2_iw5z?}zkM%?E#A>&O>g((sLuBKV8lI!9; zCX**l2i4{pjWohtzos!RSQT7aSXc^&xW+a0?g%Nny=Pkb+^)`)n1a-pfPm=Kf|!)f zuDR*cyH%PxcvG?@wF_?M)z7cXPwDLJPAys9LSJU=%J zgA2IA$Cbc;@lQ-7tU~EVk%xoQrR7(Q<`Fb`V);gSgs4~ zs_fQdDoEM`*F%&Ra@_*ggW!6Ql%j&*O;?O==MFI{_NuVQM-6_BO${Iafawx?r`@nk zg$7eehMw9H5}TG58^W!KwU->N6;5i8me^zIR<3>UTfLqRz(=y9l5m(9f3DYaA>uHc z6qP-u4+nI}%Pz1%=nAOhJi#W}yq%>>7jDk=BpOmn zYnx&VJd|0kSJ)ueo>b+INUrVe>#a?$X-dgS>Dbpcl5fvy%5&AaBT^dr7WOryOq-dK zm(;dxTGu0))URA+8EH{rm5I^mPOHVDF-*aKCu#W5`1B}iMq+}~Q`VMKf2TF3vt%?G zZ;1$X+1D18d;GO;Ni zJCI2=ojomDIO!=MCsfbW(xU!4yU}K9Duapx!ZiZiJL!X%e3OO* zwv?bnd^}Ex!-CmxLwF<`3=y!8A2eDl#s{&P%;(Uaf={);eEv27pwZj78jcidhJ4#reJYYa)~pgKEn`faa-|2R;QTKs21YV zLh5Szky!;@#g>K=?sK{Bo*DDqa}LZby<=XJq2}(IDeGr@TuqC!brM@xy3=6HsCHQL z3yZDX%;No1)VgKA?*N+S4#Sr$*jk9E%mVHR34S31o`*#SzhFZQP7eFHR0c4ZFnz&V z7`}%od|NFSzUDN#uVIu4L;)E18f)bpt**j?sJyHcQq6 zih3QiJ0P!jb4mGhP0F-_;Huefb$ai<={w)qRnmLmKX*UQ4WDf;bV&nLh2;&4eu3>T zE`0N;yXR!Y)-1dC+4&2e-C9*yT~``6r8{%#le?O=8W`Um*uND9Yu$KMirRq-Zegh} z3?`w2mzmMEFWJTw13NZ+gaap>j0ZM%z;*vF4u;87NV79eEblzJEU$R=W1S1{&v3Gk zFNjL1&MfP%i%+QP%S|m!OBl>cZQt_frJkPWAKf}THFfsZN1yNMx%B9k_S7DG{jyz; zHa8#Jv#i0{QU&5!O{R&U22VrhQil;YK=x_eHqDk@4Z5J|xZIACd1y{aK0SRcXl zJbw(H2e`+c@9BP?z!LgsIlgasqr=g-eBbewt$%;2$Ju!MzLW4RV?X_Y$O!q0X}tE4 zwLbsNHHb~{?~1@%c_b;se|!xhsUw7vT`g)s0(eG%|T@N&4AFb+J|t$M1lk|Z>FEenfk20DkeQz#l3@1-LZPs=8zzRh8qaQPMMrLOs~E1 z@&syH1RB}4o2eJL!s{_Tg)IR67L$G35XB^R&k_Sa9+K_?GWAd=;|@tLKDXp*#(%zW z$J5MT7fPO$MyaA?7Xud0Q!0W^%fu4d=|F{&J=n*E;=l9_dGW#u?p#C!uC3tqQ*{y< z_ilJ-LPJ(Gq|V3 z$4_w^{6}VP(e2YyQl{TtRCwETmuvcM1#mYiinOZmL`8WjNS~Y0HHKl@p--^+H+`cH zY&F;~_~Zs}w%i_@2c|wky+_YabRw}HcUf}l6B8SAqN8&f6BFxmEmY|P_BojuJqd?e z?l+iCkq2koZ=ai)(QAL8<^Cu*e}FpTtS8Ouorw+bfvMaEPV<1!@B#bWjEvrdgRKwn z4TT4L>|}`nbAW#MU(6hsho`{ExP(2Cqv|IT^R9vkOm76%3w2_Wh${Nxa}WZ8J@D34 zG80?$`!%!oJiDN1X-9)4SFDsPV{+Ro@3~^6Ea9OXWJWQ5A69kqB3`=3-H@GN43!2* zC6#6G>|K6o$F$(RTjBmTdegd}?W+n)Dzz5}$gEE5>o0p$k(yX-utAZojC}iTtue-^ z6-i2ame%fne^al*5UhzKHl=e|#YBkaz?`E+HLy+95!K?&Lm^?0{I(di4Q93g{r93Srn7T(4 zn_U@~ROXHiw&+tE*0i-AURf!AN*(zJX-8#4kldQ&lu&AI>a_J8Q+6#VVeV2Jej;ng zDl~+ejS`TnUBg^q56@hcx+ z@C7Dk_{5CxDH>q`MMR1OFySbNk35r@r_z#SjNIy8bZk-0>RBZr>?M0kQ zJ~hpq84wO|I_E%`NrJUfT5Wo(RfXTkZ%P*K1?n(Adbg@`WuwoK%VtebjIM5sveR>9|_$>DNQf}yVI+osw` z6FUVqwOzt*R7Qq#mrQXPT#Hl#A9riz9*&8^kHH08S`it6dkto;6`!<5at{l5XM>%n z0$UY1iaM269>*{vDuxQ~+yX&9x%v@bqxVp{b zX(Pv(9_p>j+&?ZfH($W&%a^g{>E`CAx&OO-{FCijS=&E3e*9x{{20dRI5>sZ*@uX~ z3*QM&GbvL&;31Gj8-{NNUmrq{86K8}>C|jGK6Cf>?c3>h?T(LvSVk(Y#!IgM9~o=# zwwN%$A7nFzU%?ZD1v~itF?Ik5Uuc725CW%Sum>*sqe$~$aj+8qEMrz-bZp5ix2LTv zDNq_0GgDr_=5X7L!^;bk8s=wnTVornr_JFDa@4U({PWED3l`_)&CZQ7X4E8F^lWhF z{k>`6D9)u=joA7d+?>2}Wq z+-0^HMoaKXZQtHlJijho75&tvuEUG7GP-xv8D-M&h??@05j?Md zR{s;rvTAo-UYNJMXPSvj6Dj2#MU^Y3Ic>GK*Sc@Z>0d@YmY4xwCRiI%xcu#0^*MDUPC32U4>vt4ncF)W5T92&0Nx%H;`tTU z#F}1D(ZZ^@gsOR*Br-M3?sWGZ?3#6ab!qYH$LHiOp5N!;Bx)EqLgO4TLN55Xt4S;u zbjhXktC9!4#_Qtx_OlBDl)*CE_VI$LQ>WsK_7*WsOPKT*hgS2?Lso_=g9Ri+R!8)R z%sIY~l3Nz|^;X{r-s z5z>!R=DA&-qJfq42F9Q3c)B<|UQAcfZ(EX}|la485A zvrDZ7S%jE1IE=oS_48_bWEGvFavH1CqI7Iqq><-K4?4?4GkeLuHvo}Ieg=ccw26u4 z=HAWC!}r~dAE4T}27LDa;q6VptE$p`@4YiHBq1W;ISL7eBqt{WLk2QIAPiy9LPkL$ zVGLwA$$NyT`qlILtoDC`hG!o)7jlj?rz$$;;#GKqkXaWneRuN zi|%|ncISUXOY@yOp4R*@;|>AJ;bm;aQ;R=%zXTn)UJng;xUNd7(vE!I{1OG`dx{@; z|Iw!u@rw09T+%fx8NH1DpU^eo0k7uy<}2;-PgOkC{zr4t{PvmV@0M3A{}=Brm!JRs z`{yq||1bYSv*CX4nWf%ed0)hO3dj9uxbfUtixiyA-*|ne_g|5F_Xz699uPU8#{&-qxD(=VW@-D89*`Et z4n{h#LmJ6p56Zj8_?`EDpC{DLdDb}b7(dv=H%~bnGtc+ITcv~gnCJTR)W&3-1MmVYT5k{VR46f{ZHie8|1~cs0ZNlNy!d0a1l-ikC^lR&G&!l z$&3a)l;CVn@6*%k(xdH5%X{|q`<4HFUHtp}+V7$J%a{+=1Mvm-FrNn06?X0=(_2to zH(Ys%_v!D)c%N?1-_%}UzP9PB=e>US0+-!iFrRLJ!SnY&HKux>@!5#KN57=Mzq@2V z_3rZhpUnYwY$l1bnLq8@>-#@@cj?s^*`4iv9}4LAsee$<)l@ikk74r)7<^Oy2HGgW0!&Vi0`-Hx|kZ;SQ?awl+PyO!eg6)`X%%A$2#ujt9 z_jiatJW<3tAl%LSyVrjh?wy5M$bjc@Uzhd!E)V&>gY!qZV%xD101qB~)tw}9M?}Yh zkiI(8-^V_{chK0i_3na(iizImzaRNXP0P9+f5==IyJp*mMenx%(*;*yGrlOZym{9% zGX{D6Wx986_QAE|AE|gRx5sz$kJUW>)SB@)&?B<=g6aR}TV{_-#Y5*myQ^^_Z7>S` zb0Fr>0X*fxq%U*a6v3Wsr0`fjt?+mRF=K4EmKeba+8i`GvXoj1`LGCI_Ez`Q-*mPY5U zsv0+T!VeR=_4apNKDxPJc+A9wN!dr=D31^98(6e;PDE(>?3D0FDi@|CuD72u02NSn znVj&@LkYD?WZ!(a7X1 zk0jIF_~4L#mUzwz%4xmVA)C)ZAzw03rE=(#trlh%J&l(#L`%Y1Q7%!HKa9{#=j%aWHJ zT0UsTDCbD`Yfp^M8Sn5p*)KXM@X-w)m4DR!-XEV&EqG)8ijTI;dU9s?n7)^;_IapJ zKri@um-ap0zwa_nJN7)2QJjmZHWAZv>VrK3@*8|hV@!7Wq_9W(`+0Zo6Y|jTCq{&K ztAwoLCmvbvkiNs77~$yV^KH(id4mG(|4BfPE?)hH%^mZXaXFjj59-l#5(H+y;d3#p z#{N`{!@lTJ&oL{``R1KG8)!OqQ*&_Q^TiKeZT)jyP?R~v9_0I)e-B?@d=c#^woP}b z|M#h0uVX@FyRUu8fH9%{l7oFGc=i~NZ$Ice0DUw2+Z${6=;D0`eDHp||N9^KeCuoc z`@t?x@c(a8k4Ld@=HENUm1x&E*JMN49a9)6Rl{=hV# z??3h)>Yb=|$y(K3;}{`SNA&A^1ws$er!q<+lIa4A1om#Q4MH7|~}QVq`4-1AneL1A8~rIsT(# zv0(%r1LCDLRK{?0U7pd%ZHp9WZoPBcA2FM->aZ>~KDhI$!};^)gKe)Qx%XkAV(z#H zC-fbRg^C0$R4l+kMU&S*tWWA4Ty^Kl!_gdy-?-OO#Wqxc=lno^9mf|>o5P?xmj4Gl zWDXeYjUgaD8>D%bjKKCO!Tf)B>?_IF|RQbOJ)ojIIU^*&u*Y^(&vV}#Yx_9Md zA1a+VuIxn7yf0Rp|J3Ut*=Qkj;$A4OMNGI`tKM+h8(k5n&ItD#Lr$u9>tPZ6!7F3@ zvd5z0W)uz|UN|Ez>ak_xM=cyZAZTRv`0?2zg9eOV_|kJ5CzLf56f~5N-}qedijm8A zEm~N+eB_E!=Y;IENAt%#{P39kM{z2$(`R?Ym?z`ovL?jDOvs9he{xL3461+Gh99}o>-x|qR*V|Ea%ODo%#~v^R?dp835pxzaExM)HOk=_6Bp$5tY1HS z#b18gJ*ZEWZz3U#030R}OYv0^~o7?8TxN{=Ve$Xp;(99>Yh7QeoV&UgWeE26%=A}(}?&b!+ z)i*Zy4WEn+ej^+KeFNHy@7(bAIXCzXoWANOlU$RMV&evE+dpLV(Ac!;X#>Z3Hu(AU zyn9a7l|I|;awng48x{YZe(U(Z?$_SAaQ*a*6|-VvXTkM7kph>$;rgTFg8utlfAo)q zhGkiQKu7Q?+FWnrpG-~H;m2sBy?%`*`NsJwb>BXkkks4e%Dzc~N%K2k-rSF+xjX(d>KmUB@ z%%|pLga#z9eDQG6HcTn|{bAM*L)#BU3^cvJ>|+}|r+St-aPT()fBOC}^HLkX{nPKi znKf(moN=K6DJwS|Ub?OQYWqL@VHWytoDYbz3Y)lgaO=D_?uB7b?m698j=~{H=YtAw zo%wn5FrPb4D7`D`>W)P=kY^vS@xa-qnZ`M+RdapoTKnojbEZeG8^>~rgS zyt{QHpI1A{|8P^jxs!b7P5EoL%kj?8jeK73B%gj${-*hW?#u6r_K^NvE$UxmZoExS zJw5Ub=3DoazuZaQ>*n=4%x!2*^$kehSMhFNC|6UF|I21D{`I1@eE+X~7uKVPPmIdR zwI$S(@k^Bl_1#Hc(@9RfJ=bqA{-4{fza@Y9b~)ZryK(*IPIA_hC!g1Dm#dyU^4B}b zX-AKIhw;B;M}7ZQcEmP&=)b{y_hx;JRX!g1>z(Ax$0Ofi9`@vec;I(rZ^&y+jC?!l zX_WHE8ldtoqTD)+er=EJcE87NR=KW+mw3H)o4m#&xBd?DEak)Ny^IYn;veKg`C|FE zNB(Mu{O6F*k=;D<%^o?{@eQwdyk8f}St#;(t>b#Uvg0*g<@Ly4@3_7edT!~;@_OVu zYT(ZJyC(RzP5pU4gQs95c*gEduAlKSv!3xmitomH@3_|&Tk+AfS@-;AE4~bf_wJuX zS?zo)=id1yvyHs0YK>0e{~^oHFar=A}9%f1#Z4=HroXFh0EP`djjsZ^%)P{N|+RdM~54#8bbR$M}>>pK#^> zvT2{~Q#=Kq5vU{%IU{1Yty?nqUf3=gG zKH!mW?j)xVc;v6$E|(8@P3 zwv)W3lbq%9T))927 zJQ4Yr>(<6oUi>AfB?K2XnxZawn6wP=A2zBf(Grx9F>?{q}NfBchn58r9e zAOFUk+4FV--d+}#-BXsASeBb*Jb6>Ty+f{Vk*Vy-D*N+j$$0&p^7WL%_dIgGA3TUp zX>NTr>^3{_F-@nhm)&KR|HS9ahTLhF8($0iTi-YHKHteQH_Jo2c*^-wr*hJ_Jn~JQ z$i8vQ42nYF#ikXiZ1N8o?n@PK9F|}mmF;{*G0V8@}i{YkMgyucv?5X#d}F+O~#Lw;wHH{`VcfopWae{{M3x_k~V1 zE?eq+a`n+{0I+YO1pmPH9y#mEBj4UhUVBr%yF*@#HJd`;V~}&T)Z18Kwlg2=x2S~@ z#m;;Th`o*HttHAIBQd=1#<9eFUB8}koDJO!IH8X>$Ci8|;kYv}%twxyuij}d&tXg3 z?<$}5Ir8hLd9&yK8$I`@US3{UFTwq(muc&ee+hXXoVn2A+Rdna2LC{w zjsM7FY?wZ>YmGI-V{e>+z~?+S&Zl&_~8weFGAo&$_{Mlc&nKDqWh7rAlL5uftd z?(U@j`q9Vc)jQ4nw+}=1>2sGab?C;jd&;u0Q(5Rs9{HwDa{7`-zNM3#w)M!jc9JtD zJo0TFa@2*_71?kZY~*FEe*piWj+Wy;zLZZcgAd~@Urxs$uWVnVGjBr;UXO9Ng&2lh z=hiA8j1`$rEdIg29{HO%JMl4CTzlZT(upqo$4<1pt1thr9dUdAoptk{Kl2txJ@6B4 zH&1oCRbJZJQ{IMEhVb0nR)*33(cx zH~!6UDg+<3l2j`Le_J{A3LHOh^C*6VTnotMtkd=UN>W(?KZ z#g835cvlbkBS(7v-Cfmw;{e6G4;QV!y?RksEXyC&etXJjyH!SCU$x(!GH$u8jDG3| zJZ0R-GIr#{@47uupg4V^v_b(h4FmH+V`FE^_0VJ zJ@PlPE*8l?`L+))-#Vk_E^FT7h{}w+EOpN_D?4}2D2M0%><>KpZtA2j`vZ@BOD8#f z&?Dd4NzS>LNB%~~c$xYCQoiPqZ|k@ot3h47B(Lz;$*h)L@072n9Cq@^-}KBOJF``ZJ&_TP3) z)$k_=J=*z{Dw92^?>~Fq>-sO8QT5M`;2-|?v<*4Kr>#G+IQs<8sM;{?hyTqpsvJC{ zYWXPlhd86^FCP~Ee>tP-IQH7{>{6_({Rhve`Xqgr=Zva1c}7(io>7Hw2WPDx+;ICD zRYL|v-F`+@_8hNWo-?XYCl6!dndmu}$7;rTJYGg_7Og)=|9j5(-0WhGH*3uAy!w01 z@+$H=>~-GT&pX_EviBVC8t(@0-+BMpC&(w#XOYhapO0`Pt{b`@@7mriqT9-D2fAJB_WSN*y1(z2;bI zJ>Kc@UEr|5J%OL!*X_QE_dR~!@%zr+_i@jVo{#o?qv!ALpLqY{_g}yN7x(|J*PLE^ zdi}Qdq}~Vmg!Gx(XM3OT9+>yQFKk}6G@Hw|$@Y!yxAt)Rc>7v=gZ-NQ=MVOMFyq0> z2OA&i^3ckMjy&}1zWw`7?7OXB&wfSyw)eZ%@8>~%gIqyB5AGH03jRg^RsFB^|F?%n zJ)HM&OMa4#I34l#&MwYg&i>A!&N0qe&O+xi&KI0po%@^@oWG6?j2s#HWaRS5%TfKKmPXY^ z{Wf}W^mEaNqQ8v(ee|DW7RUT%V9vl_3 z5c~VM__$ed`{FLdd&N(We>(o<_yh5c@n0wOO&FXoJ>i{%j}yKd+H+{y(7d5f4?Ue| zOMEWz7fF4S#wX27s!lqd^l!-zCa+9BlKf#xmlRjZZ&QO(BT{2ihow$RotIjadSTeA zVdcX%4Ewj?Ny9e{Zyo+!+IZ}~nV05DTa{Ls_A>srE$yAOhP2aZAEkYn_RF;2r~P?E zml3^2^dAvDV$q1DBc2)Y+=xvhc8)kSqH)Ce5v?P>8S$GDe;Cm|(r=_~Wa!AjBZrL~ zKXUrWc_TNBygc&rk$*q(e~tXNbf5H|=|Sm{=|j^;rB6zKGJSD+QTp2S7t*(+*QOs% ze?R>~`nB|*r~h_T|54GSl17agHF?yWQ8}ZQk18EiGivLoJ)@3{IyvgnsOzJCG3s}t z{&lq1=)lo^M?W`u)99U}4~=ddeSUQ7n8-0h$BY^?Y0Q&jj*mGz=HoG6kNNeO@5X#T zw%gb~V~>nIIrh@n>tla0_IG3dHKTt2_&3Jy8(%;E!wG&9Y!gB!44yD- z!uSc(C(N60ZNkqd{C2{hCK?k1CO$av$%%_67EN3`@r8+7Ce}_o{7A1y`acr=NZ}(- zKT`e3#z(e4a{kdik4}D+OPgq5t#-qVK5*7BdZd4c`1Q6;xT0A-QIcpPm(&8yo zE#l`^71q&srH`)kF&5w}kv_WKN7wu4dLLcyWAJ)kBMxQvHR8nt>r14Eiiu*9^(iFD z`ZYyNl{`&M7bl7{#aUv$u5^h7;!??1h%2$K<7|W?SEaMX2#EMzL5)iCMs3;U}b9d_nT{VvYEs zxSxMz9uNdLZXB?# zf_2ts;9>Eo^)=EB;(76c^$8@GP{!_v4&;pJAVbA4F<E!%aZ8BP9*CVxefzoN-s(d2J3n*2>hlfTJm@;4bx z{wAZzU(w`mGMfBNMw7qEX!181P5vgM$=_r&`J0R;f0NPVZ!((vO-7Ty$!PL78BPA! zM?x~1{7puazsYFwHyKU-CZoyUWHkAkj3$4R(d2J3n*7l!l8mMR**QRV4v?J#Waj|c zIl!Qu17zm_**QRV4v?J#Waj|cIl!Qu17zm_**QRV4v?J#Waj|cIY4#}kevf$=K$F` zKz0t0odaa&0NFV}b`Fr817zm_**QRV4v?J#Waj|cIY4#}kevf$=K$F`Kz0t0odaa& z0NFV}b`Fr817zm_**QRV4v?J#Waj|cIY4#}kevf$=K$F`Kz0t0odaa&0NFV}cJ6`R z-LgJUGZ&4h|NFh;i0INaDo=eD|{l>V-@cldRW~PS&p}Vyf~_ z6Vt_s;!JUtINREVznYKTL_Lgb>kybD=8Ab@zH)Pk1!AG(OLgC6N*7sg<8O<__5i$|@;kZurhBfJ86qUC`l(ejX?Vwe~%I>ZRkDQ3{lW-)DRE*DpbE5#CVmH3qSw78lU zfbWrO#I@o&u~aM*%f$+@QmhiI#TRIKbG=w2z9_zoKBxzdSagDK(6;8A;x_Rual3NZ zA?_4+iM8TxagVrH+^4^K8!c)N^Btx4%Wen6gW@5PElCe~cn^3ut$JEKBc2t{iRZ;; z@q+j<>(Tr~Y!$ELUV(_*Z;U|rKlzb(73_(2=4W8Mn1E9Egg=vsVzT5Z;%wA)PqZPt z-X#{0Xa~q=#5Lktah+HymWkzJg;*(8iPho@%pY|@)`%~P+x6=Xai_RTtQB{Qd&Iq9 zZ^Z1^;9zlxm?X|dOWPY`k}ttx5?+TZ;dSH+aiv%yt`eUTpB7(W4$zRS5nmMdE4KsU zLGcjS2YW*gf!)MFv7Z_1;F2$kt~rJB}R)eVv?9FriiKHB=IqEvN%PYDozupi!;R4 zEDQXNTqCX(*NLTKnOH7Xh?QcMSS`MU6{|kxX6y~=gFcA4?GSg0yTn>?x41{#E7s9s zh*0vBN_|>9Bc2t{iRZ;;kt4J|=0#c+Gb6G^yewX!oe`VlRqz4yO&7qfnD0CQFCqiQ zeqxXqEOH+A07j1)phJwXK1SLpMp{#mjxM#hKzPF<)1@!~$`ta$BKaS6T;P! z<%O@pZtztyT8t5s#AGo=Ocf`IkBO7TDdJRdnmApYA!f2JFrp^sit|__=6rF1xKLaq zW{Zo(C1Q@4%epi3#C*{u7O*xkJ}2GcYFZxAL9P+kitEHuu}mx%E5u5%N~{*Qi#x=f z;x4gP+%4`A_lk9_Q$z%LO13>Mo)OQA=fv}3vv@(g$ht#BkS*e6@d`Zw5kX!BZHfq+ zBEqJKuqh&J1|z~|Fd}RQBf@4dB5aBXngbm~E4{;?U!iE-@WJK5$5jKpsnPx=TFy1B^5jOOdypj=NLkmnYB5W9y@mEHK zO%Y*33(Pbl!iE-@WJK5$5jI7HO%Y*JMA#G&HbsQZU_{sqMuZKm^j?q=VZ%zu3Xl2%93pW-=md7{4J36cIK>gk2F~S47wq5q3p{ zT@hhdMA#J(c14685s`-{j}DO$VOK=pS){cQ=_qRl7^5`%2)iP}u86QJBJ7F?yCTA_ zh_EXn?1~7xBEqhSuqz_$iU_+R!mfz0D5SNiU_+R!mfz0D;uPmw+>9@XDAi03h<=!dnn zR*Bt{+QvCp<UL6%5vD zRWRBL{>oLsV69dKqpjetTonvPTS0QQDp;#k!CI{f)@oI-R;z;1R`6G@3I=OUBN%PP z=SXu^Fc@vc7a-TzgV9!82e~R3j3|agt5w19dE6a7PjXc-SgTdRTCED!YE`gStAe#! z6|B{&V69dKYqcs^t5w19dHyX|1%tI(6%3zenyZ4rTCEC(&oj-rd@y{Tq>R;z-w zS{1C-s$i{F1#7h`SgTdRTCED!YE>|Nn}5qy!TxCNuYlgxcF;%kwX%@zEyiJeyFY4y zOu)RaKWc$Y6qC>&^+zr6*A$U6`u?ypB`&c*T&n92Sfe4Yvy#EX;!!Id=?1aMdI#wb zVCVj5OF?)Sw1Rh$p<2h-<}lVyRdrmWvf)rC23ai(EbE zuhoP8T0Q7*a`m9U)^Pfx)|meeai_RTtQB{Qd&Irq!?5t@ppV$wszaJj#2!Y!N%G0U z!-&N9LC$R-HU`214EobrI#LyQoeVg~Py z7{q-MgXCPXfcHfVl5TM+b-+7nWDyvm_9R5@Nr>8$5Va>EYEMGco`k492~m3zqV^<2 z?MaB*lMuBSA!;!~)MA9F#RyT05uz3&L@h>$T8t327$IsgLeye}sKp3TixHw0BSbC6 z0O;^3cEvbg$pO&hTQCN`Jpe7q88BW;zmWpL!xmY1qidAB@ zxLw>K?i6>4wc>7ZkGNMnB@IuDXT-DOIq|&MEYhPwQL|rwTxSkNi%N2xITS4_$#v#X zw5TLkgRy%QE4QI&ImuOGIr@lDgHJ%P*9X=MMXQBh(Q1)bB>70Z3Wn*9VY*|O?ii*! zhUt!Bx?`B`7^XXh>5gH#W0>w3hW5D?`S6Zmx?`B`7^XXh>5gG&_i!(?dmm%(I#)u% z;m<2UAJG?Mws4Gl&w|~=K(V*A3X*Gq>6meZqtr~#v`WEQO3$|Zk)DtB^Kh)^hl4p{u9zq0D>s)| zAQno#RQFw`bdiNo3)U*hCN0e#4D}Sl5%1(tYaQ4i94ZLw^x&e2TSe?3khsXg5$(XgA1EF-!~>9b$y&6eC$X7-N&sVvLw1CW|Rzs+hrA z#z>pjKPFBVr-)O+sC}&WP6St4sbGoq2)N3M0H3mwz^8T1YSs%{ zfGBW{6$7r7WF2b+V+Q_O#+t&I!3kCd;@(G`4Y}D3pZcJ z7%$v>1?Pf=<8%S$vl-90!_jl4fo~}PH^pt@TjF-B4}RUD{C8TxNbgd**6NS+Zl(8# zd&PbFtGBIf`1Kv7_p1~KtVNIYHW6fxDRMLJDn{5lZ7l*~j#I1s-~&qf3}6u%C{?|h_l#9T2? z%vWwMu|O=8e5qB0>z65AgfE>r5Wghj*MazD{wuAwz!GtlSZd`%QYMyLYmu(7D!@u> z8CYdK16IRs4#lqn@%vMx55aRBieHD~*P-}zD1II2^(yh}acc{BLg_}Oo3NM0f%x44 zo)Q_q4#lqn@e555zvbWnF;ol_!$t1WaiHHPog(AWf#)gd;BwZB;>)4}d3YLW5fz}OGt3B(ub4)H}Y zz8s1#hvLhj_;M(|9EvXo;tTbQr+ef!@hy?@=6N9{Te(>Vyp-q;4{zxK0~e$SBfR#D)A}tX>m1k zhR={|#I@o&u~aM*%f$+@Qsgtx2+ZTi7nIw2u||AR+^%1Dh&#nyVy(Da+#~K4_v^0? zhzG?(pi_2s%Fa&N*(p0aG3wy2w6jxocFN99j5;Wxot>C3e+JUdPTARs7KwjFJ3BE` zBWY(RX2c}z>_j_5(#}rV*(p0aWoM`C?3A6Iva=JhiR%%YB<<`(Y?8FI6S0Y$WoM`C z>_lu*LOVMVnts*4f$Ykh!}@8z(~{)nSfS35_Lo-ib)tFMWT-QYl@hPRx}bl3z;rX6laRF#MxE> zew}Y^2D34ii$u>t=8Ab@zH)Pk1tQn(B2m}8`!c1ARF-0Kxh$|k`L9Ip8Ht{STqTyG zMT$hvLUO-TBzhK-yFMb(vs?$Ov4FI0UPYoeISrmrx>4yS zwEvOlP0oX-#B0j=Q_Q0y(VO7D=uJ>B=uOB_F-!~>9b$y&6uCwhiP0b#Eyjp^jvt8? zS29IR71{elVl+tkW8!3Sia1rACQcV;h{e{+xPCcHiijXriX~{PBM}jlJcSWLBqD<8 z)vOCd1i1$NX(S?o>2+2ySgLfHSWe$SMDW*2u}Z8KU!V^lBAy0olzvfs$(RJm%ZU9* z>~9hS;#=Z&<+(%LDee+$#ogi_aj&>ffAuzIj*;l6nA?6? z?*RIdNJQ3$;2}xs=s)PE$WyAv)8ZNNtawg5FE)!8#EbMX^iyPucv-wce?vb-UKKy4 z@1dV+23wWB4y~dP(T6}E(bswk>E75c6ot0*6>zXPM2r*T#RTg${5n)j6qBqMAxYM+ zDI#|yMj=i~u02L!1W0o2F$yCfL&ALvQHafNK&~D{Au2b4b;{wecoaL7 zq7aqjF^mJF5RYV&wa17;#32Vn9IS_kBSXb7F9tHo!;HR4)vomeWCiREI2SSePC)#3}ZHX?$o5nmKv!n;OMh={Mi&4`64L;-36 zQ9y1J-x9YgpB>^(ahF&t?iTlmd&PbFtGBU29)&1iZu@Cl^e^N=v}{p`0;ahV5~X#C zC`18mds;jro)yoD=f!66g7`6O5WY>eiq~B9d1XpAEGw@a1V3jMkgnn%Mk z$yH*ht}GMFu~$DDJ;)KT3QyLe;ejn6_XS0x7q|wVBH@R~AJ5KEQnch`v=}2MiOFJ$ zm?};Z9}_2wQ^cv_G;z8(LtIV$(I%5?#I@o&u~aM*%f$+@QmhiI#g}kCOf-CKFt}Zk z9pX-Lmsl(A7WasI#X4FE9zvc{sZWb%#Ixc#@x0h9UJx(Rl4vK%7V)xpg|>H9 zbHrRRPs~?tF0nu?lzge~yG-dKtiZ&eEhU$u^@~AU%KTR4?Ku@bc}Hb(G-K0HVZr~9<_3iZV-=&$I*Jkz}qQrRJsXcgBW;z z8F)&(2Cs<0Y?`%p9sOF2@fjj31`z?Rv@#Z>l`(u@lC^1aWh}`Ih?2*|$>J1osyI!YF3u32pg)*TinGNz zVlh_yV$dVNPtYTfE3sE32D668!Btq{jKLUbF8H*5UCp{fJd$g$W*CDp64UFfDPXD6 zWnwwz!Z8>lC4-fcRN;A33}y|yo-_Iw^a#wMM(Gztj*()}Bb)>`<6QC>^a$`P^a!X0 z^axD9C2q(1UJT|J%x$N*ORTkGA=$0;9%~rVdzId&zj~YXg&u+V?5C%pM;HYj#J=tr z^azyH(Z>+)mq<1F=d^ritm|L~*7#OUxHtBHzCl zs3)xh5noKN#ID7G=n=?OVkt&$0})?jImY}05nmsJRj}Sb_&MYlE#q(D2_&Dl4umI= zd<$bBJb~m}7z6dZbs#)}X+9AcsOPN%;R#IhEsTMvN0QH52kLq2K+F@F=JVEpm?x5a z-Z~Jq13ghYB%ezTMD37g#Ixc#@x0h9UJx&`6sR4tMZ7FtQMo=6uY!Y&{kVRRangDh zynv^BgYbm?W3UCaFbMmpK1ENm0OR(-x^l3t9IRgl>sPFiTMrsT@cfI6!1J#m*oQ+# zVw64v`*7BSF<5IFf*e}GcaifD>}(-VTJM3>e+ce=4!nf^cL?&o27b=#@q`OG;|Uiz zSDeTE&H3U2aiO?K%oZ1mOT-*8mztP)V!nv)d_Yn_-SE7Obc3-NpPvQ8@%$ndIJaaq_##o8qKxi9_5%1UFbGm*%EF6pSwt+`bqF9ug>3XG4V!RrQ zvR?u(V73yAvR?#Su$mN$Jv?86ZQ|#cxx`}i16siv&JvL0!8nM`8 zL(&?t*mXwI8nM`8L*`O{ScA+LU19;t0&9?NaVg7%r?F%a7zfKlgAOs$dI{+$)FWC` ztdPfH9Q6%&LOhB7A`Ydy1YUwC$3cJmE%Ybnin;t-XhP znv9{Q5ASI95;ssctS6H%iyOsP#ZBU7@ikq)ReW8%LY+;nun#q_il5-hBv|_^@H~7q z2{!)(yo5eCSuI1dT83mrP_iN@SuI1dT83n`49RL4lGQRKt7S-5%aE*=AsKm|LvCyt zlGQRKt7S-5%aE*=Az3X$GIn5oYa}Zck`)WdiiKpwLb75ZS+S6;SV&eZB*PQdb1k$#DJoHlN|d4!rKm(HDp86`l%f))s6;6$QHn~Gq7tR3 zL@6p!ib|BC5~Zj_DJoHlN|d4!rKm(HDp86`l%f))s6;6$QHn~Gq7tR3L@6p!ib|BC z5~Zj_DJoHlN|cJx__szXM&s{*K4NdIx~HQ5Jr5p0dzFfj}ijgGQgcd0k z<3Ri^#(`w07$$~`4lzP>iW$rw9+(Vn#F#P_Yeb3QCU{gT#zL{^ZKze6v`Uj!X=sz5 zfrMJ6Nvkwzl_ssy@U-X>ZX&9Z7)G7_FEAp3C zY0@f9TBS*=G-)-{IE&fkEO9nGAr-Oc5F_zs`fSwxCtwU#oMxjQ@hj?)oGa$?SJViZ zFS^76U0*1=!8ype8|V<(@63^p&4G`tLploc);YLKEXcll4*cp}kbU zzIzTdL2l55oGUU$=E$Sw$fM@Sqvpt?=E$Sw$fM@Sqvpt?=D?$HU+7Qfi!QN%n!uw- zH<&5?@pK6H%2bSGO8-phpDFz@Ue=Y=KU4Z=O8-phpDFz_rGKXM&y@a|(mzuXktzK% zrGKXM&y@a|(mzxBXG;G}>7Ob6Go^o~^v^^@>@zYE5ztxsXG(wUl7QxT)+*BeS<*jC z`e#Xh%&YKMS+akY^v{z1S<*jC`e#Z1Ea{&m{j;Qhmh{h({#nvLOZsO?|19aBCH=Fc zf0p#mlKxrJKTG;&N&hVApC$dXr2it!qrbvgh26wJv7Z%UPUKXKWCQo8?vmbLpi(t1ej769m;=Y(0lF?#}m?S2PDPpQPNqkJ4EKU)piqpjD;tVm9WkfBIbH!ZA z^Td47B^I#kuqWvjSFoFTU;YrqGw(2n(UV0X3S`sT9&_7%Cn5}xu#ysm2 zNLY{As>f`!sJx!_n5}xu#f{AW47usTlJW&ddyZm zW~&~vvBMmiV2)3+9EnF&pzZ zrdf~Kn8%T<$85~wNY-OE=5ZwJF&pzZlJ%I4c^t`lT#UZ#Ly&#P67+3rz&T>3m?h3N zRzW^boG&gA7mAB?&0=wh=oXiY<;ZmjPUDIOE76}W!6{d9;Iqb4;Bz?RatX$kIpBU{ zE%+|Zn8MeL@yim7Et#%Y`h94*1Y^s0K##WPjkl1#VC(`f;f&@b7-5!yt;Tw=P5j(A zZ7jhE6Xn7PlU#!n_?DO(s5$ym@?~+O_^P-`+$_GPzuGFkF1~4G;I9s%1WU||ESJeu z<|QUqW|x?k#Vaf$#-ij^@e|~hqt-h|t#^)kpB%N`IcmLg)Our`1Fd(CTJIdS-Z^T$ zbJTk0sP)cK>z$+4J4dZ|j#}>=wca^uy>rxh=cx70QR|(f);mY7caB=`9JSs#YQ1yR zdgrM1&Qa@~qt-h|t#^)E?_BAhEB$k&f3Eb;mHxTXKUezaO8;ExpDX=yrGKvU&z1hU z(mz-F=Su%v>7Oh8bESW-^v{+4xzayZ`sYgjTf1dQulm27OV4^Q3>C^v{$2dD1^m`sYdiJn5e&{qv-Mp7hVhIP)ALCfw={Iz*l>l8;i5k$AqD zk5X&|qw)TGKFSgf4#YS)A9MfDz~^-3e#}7gHTTcQ-2V%tIrq=U+@I-_*kPEDxd5-Z zfb(ARQTArA1<$_oHTTcg+&>?7fCQscXa#FPD_Db^E6(G+U=4DCxKLaqW{Zo(C1Q@q zXY~272AMCq!~&KD)*#*DQtFJkKUoC2WFMF8Lz$FW~WPt*d{Xxvyx!zSE z4=<31<4HKyy9!YD^&r=~3Q*Ekkn3FqngbS~r0*fk^{xVx^c=`pZ2?Mp4di-P0m_J+ zQAU#MT?O*_0{MJ_e7-q+; zq1L;wHx{c&g(w%vac`l@RfuvGA-WgVE&I4-AGhq|mVMl^k6ZR}%RX+|$1VG~ zWgoZf=X#+A^SoGa$?dfc7N7hPfjbHLq6H@FO=sV_lq>?K%+l>xFho?I-04%0!0 z7>TvVWvC0TTE$>r=rYvB1uzaH!(|u=lL=@Cuqq}dihMJA8AihVm1ERp7zvXcPcFkq zm|UtW40lqyEiPqAQ9ERjxSDszxR+cbt`*mbe7j^B z#=T^@$g}m9VcbhriPhqEafi55+$GkEyTv`?Ua$x?(jRn)d>5|>@j^ygHDHvr8;rqz z=^}W@Y4AMO42xi;3n15ui{R(D9;HS;C^b1(oG1Bwae=r{TqI_Ti^U~kj+o0k!pF#b z(Ipm82iTBwgT>OkSeh3j(!YU(nqzMiG%uFs#nQZ3nios+VrgD1&5NaZu{1B1=Ec&y z7_o_b5St`5FGg&V)Vvt6NmBD-#3o72ixHcX*xbH@zM1NbNzb(<LUEC`V7>$WVR`Zuk!zbJuspd*usr#ia^5PwE*``wE_fFN_reZzk+rZ& zmRuzrR!N6d(qWZ!SS1}+Ne8_5fVLF7vqiSUrD}&uk;9WnvmGvlhkOLG9WF(lxE^_u zY==wXX-7b|!=-A6OW|ouvmGwQy-2phcv}e9m+4;ESBQI+>0a17pkEi@89uR*Ri=BD>0V{JS2^1JYX(l!#BPLgwE1LjYbnS!A3OKO zNL>?+HNA4^@G3YEBaL#bZ+s3Oz|PWgwAiP>!{SjqxhqGDO*V-fm6bzV{uSGwaiiXg_1%ml$2yEU5*k#AC!o6i%WSIw9;e|Sb;Kr1$tvdRDm** zy{&3698aULM_yzZG0#VhRG^GoLB6e7fzo{n)?q|lfzpvjF$SwZ>BuJ3cm>+1pMiXb zx&o!cy-+&b1*IcH#V|2kbchk6Q_P?~C>``c>BzYvS6V7iI+8196&N8vE0m7p`#a&c?`8y zi4`i6>xz}o7I%cUB-^e^cpdHwuOqn&y%JtWvJ{mnMWsqniS;7f5&b5)MqDed6HCQ1 zv0SVWIR~snze!e$+r=H?PH~r5EAAHeh4Xv>N&2O5{(@6?1tdYLLtqU19O&j<&uKyN1Azr|*Z{x}@ zj00G|{RHeL28#W}ATd}B5ySDu;sIELj6m5BV7x&_VjuefSY`_tgLhsIV2{dA!3xCG z0qjvZ0ltf8=m(%bc7S0v!F0XStvGe+05k-#Dho}pDoaL-F=CRKET)L5;w14gak4l? zoGMNer;9VhOlpogBIk49O|pPxN6nIM z@ggmN{VimRcv-wcn_%}5c@;bePkRG&p#8`GBdgIk2#tHuAQ0ylLiNRus$QrLxjn|>Zzed_AMq>9D z_VB_p>u}#pupG0JI@I_VAZxr%HD0G0uTzcJsmANzlSA<jiPOayB5S-(HD0G0ufxa|r9h373&e%uA~9QBEG`jq z#9WpXHBRP>F0p`RM~#zi@ggmN8Yf%C%OY#MPBmVq8h;mU;KvvXW@1hFU1JYM?C+vI zj5glI*laLHhu(N6@d(;g(ic18j-a3AsBsWH`3QE9l0(EeJQ+EHUWrV=THg`$N@SwQ zH=B;2SK_aHpZ5rMhqi(FO1s1YajC8^()GpS0YvB#w9n*W@hILoJ%aX`Jcilj5ws^H z-*7yFx#U6clz0vO_z|?#&>w9z87hW};i5x~5S?NM%ZOd^C>{1dk}E{+h&zHkkR*4+ z9l;(*@@a83^+yj#t`XOY>%>yAOe_~G#7ePBtQKFOWv~a5tPx)nU%@E-2u8?Ga0~WT z9Ko{{-fM@rQ`{xiio3-<;$Cq-?TMX@=J^8?U@`3l{1MkTP>g5CV@_~B! zK)rmRUOrGSAE=iP)MI3F7Eed$1NHKOdig-Te4rk8`3!RUK)rmRUOrGSAE?K=0p;|8 zdaN5Xfb@ZS?E59@1NHKOdig*-`sg8$&g5CV@_~B! zK)p#Hs5j{Y_1NJ+(g*5I`anI_i%LNHK)rmRUOrHdeXx|!2kPYm_40vw`9M8JLC{}5 zP%j^-$5S&(=mYh5YDUrr>alu2(g*6XdO*?#>M;r;=>zrhfqMBsy-6Ra$6P4^qz}}~ z2kPYm_40vwJYVDA(g*7Ce2t_J)SL8ydOTkv=>zrhfqMBsJ$AQ#W*kLp{0-Pm3>5o` zL1M5NB8FpU#Zk1zWF$`3I*Jx}7Z_vZg5}uVaTG0ZD_Dg!%%f;~hk*4WTiv5-b&sOe z#U0V=lF?#}m?ZKo$fJ1rNv4XE#K*+R;uLYJI8B@`&JZ)HAzEE>u9!0neRD+N}Z4ok`lQL3V49 z-5O-K2HCAac59H`8e})Lov;|HOOuaIIW9m+O0u$YmnWJ!5Rg|F<4_W7>O0FW3a9esUb`&tWheW1Hj1p#qG=n~8Ww2je}wje+&>%W)Ds`hu-H4G-tI?=ZH>%W)Ds`hu-KbJGqQ-w}G^*5%Ds>}joD!D0QKfEFsT)zV z-$24rH>%W)CQIF@Qa7s9P3S>h!Olt_(HC{vgtEL04#GM>6M7JGh!}@CS`&H@G6Aa- z*pDeDib?R3CiEcuHAS3_T5H1FC6~b*F;~nJ^L33&ED#GNU#i@eDP4pSQ4@L@k|!ZI z89WKO3H{(Ea35wdO-57uXrl@H+aX6kg0i3=Aw$J5FnQRFVOO2A0RfbfiZYX=mThY7OW=`eYhUF;d7Xz*LGSX`-`OT<-TDb^*zM*Bzn~=F;o|vyZU1EV)DEU&|eVNjHhS&@*CHWRr zGv1=Q3YLhg#8Ry2Hp5rRa*?CuX7t)$fLsf0Mz2lo!K}a8;MWbB(GxMBw`oIoHU1Vo z5#;EJ$WSp%3>O_Dcbhbu+-=f~D8OA11!S}sBQge?F>WMN#8hzv_L(=EBk^`bGa`b& zJ|<2Ur-)Oxwsjh~w}er`rz z1AjnYLtYg>7C#YN!HbxiUjw^B!;6S7GEnR%28qF9h&TuuUPOG6L&P{l&_%=-nSiGl z7ZG1%qL_qIUqpQI*A$VvQZFLD$aHa{I8&S@X5!7wi-@lXaG}Wc%!`OGGFQwK^OdJd zED#GNU#hz=Q=04H7ZG3N3gypl++0L_kvtFfB1UnoV3}Bsc)=4JU0H=FxQNK2w)>PD zM=Tc+O}HM>0gE9z$Y?P}OcImD6fspCfwuP|q9YERB*|mqWO0f(Rh%YH7iWmYv^JuH zTp_ZRxQPCOWGitI{RR27xSG~LbdYPrwc4+uuc&dij?iMa!GgSU|UT1^YuQj%Y*X@R$p z{8~*5yoFp%9pEkG8gZ?-PAnD6#B!12=N7C%kyT=~_yS9ZxdT}vz9_y5|7pRfgV*m6 zcZ$2jT5-3yN8BrN)X}0*M~g-sEm(K@7CQ+zH*D41uoW%X<4E_$YB}DUMBTNbF31G< zKr8BkOcc46*owN~uiSmqs@+Ge+I@swfs!oMm3uJ8ZpHc`-}I{!4~s{|2Jx7#JRvsW zY=KtI@>(^^Yt<~T)#NO%)#NO%6|03LXL+sYTgeRS49i0kSf1qGp;oN^k=#4fiq$`o zdxu)Jcc>Mse@t`lP%A7?vUO_3D&Ig5@0x<#N7M>CliWwtiuFB``-oai?jvf&`X0%B zL~XKXo9x*pd$!4*ZL(*Z?Aa!Jw#lAtvS*vtX4_=XHrW$9Sd|-hbG6ByZCI6UMVj_( zlRevH&o->b^8HELvkmL9B<hv%A4a>na#y9Wc8g z2gs2FIae%@yijz5_rdGF1-n`wf!#zu>vN<7mF}r@Z|e(4Y*q`{Px2r!SPT)vtr_^$ zVHJWA)@9HsMp~~R9c8@@##lulo<@Oq8U^BM6o}KLL7XNHq9q5>l7nc;LF`=wahf!U z)1*P1CJnkooFK81AWqc=ajG_mQ?)^ystw{)ZSeoF_a@+Rm1n-^Ibdv-gme-@=n#lvHWQGg zTI2=NNeDItXol+fDp~c1vwzi~$orNqUUeIm{+dvXX(sa}9-AKX$ zcVZF-C zI(5$XX;y8aX4Mw7HU>);RvW%wbdR2$H6DecRKGF z9t018nvqg_G$W-@ccp~7D<#xjDWUF433XRWsJl`^-IWsRu9WZvj?xnsYEOeR!VfqX z6kifPAtpbd3b|kSWN}jX6!=VWkJ`_IdWK4EJ!2+3QG7vos(4a3CEc#kIIjq=FV+fg z02dWU)xNoSM0iVav#>`;*-8f|g-;ic3BOZ(Uie+w-=qC}@z1npx_Df8MjBqDDrEQ* z8%MdU@f9JElbcQAyygCW!%4598|2(_Y$ zQ0puUwa&6o>nsbkTCq^8kPEfWvRjVk(MQJR;dc{!Swqj@=+m!o+( znwO(_IhxyiFE8sT)4Uwb%h9|X&CAie9L>woyd2HT(YzeZ%h9|X&CAie9L>woyd2FJ z>&*2dcd^b~hMMO`sCj;bn&(HT^?!w$=SQe{euTPXDb%cnLd|L@yc^W4hH7h8L!svR z5o(?vq2~D!wzFSz5~{6l&_aEK7U~q^>h99*x!;eri z{0KF}k5Jzqh5G&|)QT#?BcRqV3$@O&P*3X#wa&6oPc{m*&azM|stC2BidzBC z74TdE&lT`o0nZiiTmjD&@LU1U74TdE&lT`op?it1YBb}y0-h`2xdNUm;JHF~D(q9^ zxdNUm;JE^xE8w{To-5$F0-h`2xdNUm;JE^xE8w{To-5$F0-h`2xdNUm;JE^xE8w{T zo-5$F0-h^$#{7|60nZiiTmjD&@LU1U74TdE&lNf|{#5;q=L&ePfaeN$u7KwXc&>ow z3V5!B=Sq04gy%|lu7u}Gc&>!!N_eh>XU%CL#wy{t5}qsJxe}f$;kgo?E8)2kp7m@# zM;Xt04jr_3u7u}Gc&>!!N_eh>=Sq04gy%|lu7u}Gc&>!!N_eh>=Sq04gy%|lu7u}G zc&>!!N_eh>=Sq04gy%|lu7u}Gc&>!!N_eh>=Sq04gy%|lu7u}Gc&>!!N_eh>=Sp}E z!E*?nL+~7e=MX%H;5h`(A$ShKa|oV8@En5Y5Il$AIRwukcn-mH2%bam93q}W@EjtZ zL+~7e=MX%H;5h`(A$ShKa|oV8@En5Y5Il$AIRwukcn-mH2%bam9D?T%Jcr;p1kWLO z4#9H>oq49{VB4#RU8p2P4QhUYLmhv7L4&tZ5D!*dv(!|)u2 z=P*2n;W-S?VR#P1a~Ph(@EnHcFg%ChISkKXcn-sJ7@ouM9ERsGJcr>q49{VB4#RU8 zp2P4QhUYLmhv7L4&tZ5D!*dv(!|)u2=LkGU;5h=%5qOTka|E6v@En2X2s}sNIReiS zc#gnx1fC=C9D(NuJV)R;0?!e6);lq(7!i1mz;gtiBk&x7=LkGU;5h=%5qOTka|E6v z@En2X2s}sNIReiSc#gnx1fC=C9D(NuJV)R;0?!e6j=*ySo+I!af#(Q3N8mXE&k=Zz zz;gtiBk&x7=LkGU;5iD2;ko}=&_h362;ko}=&_h36uyDtNAf=PG#C3{N_jRl#!=JXgVU6+BnLa}_*S!E+TnSHW`? zJXgVU6+BnLa}_*S!E+TnSHW`?JXgVU6+BnLa}_*S!E+TnSHW`?JXgVU6+BnLa}_*S z!E+TnSHW`?JXgVU6+BnLa}_*S!E+TnSHW`?JXgVU6+BnLa}_*S!E+TnSHW`?JXgW< z5?SC@-M!O(K@jQ}1fhOG5Nf7+p;nw1YQ=e>R-AY5w0rnM-NP5^9=_1-Cf(`U-K0BR zyPI^U-NP5^9=`C4HiGWG8*0UQp;nw1YQ=fs0Z=Q>tF0C1-4$rE0!>z+$qF=CfhH@^ zWCfb6kp3&wf6BSTU4d3B&}s!*tw5_4Xte^ZR-n}iv|52yE6{2MTCG5<6=<~rtybtB zi}XROmAVIb$X%&>fQFZYSA!n~uK_hN*8DW2Mw@~w43pL-h@Xv}GVx^4hQY9~RlkK=^;>v- zafR>(P-`5keG{lvztz^N-@;ppcM7$}u`mqksU)@aRFY6nB?;~R@Lh^G>!Ve_g6kDc&yp3s9?myLTy8C0DU3-W02bp8)ksf!bR2Tc}mPg9~RlkK= z^;@V_zlB=$Tc}mPg9~RlkKEs5Oq&ZUnXJx7u3uTc}mPg<9iSs5Op- zTJ>9)23yT{ZU)q<-)d{sZ=qKG7HZXRVE}54W3{({TS2Y*tvy=xTc}mPg9~RlkK=^;@V_zlB=$Tc}mPg9~RlkK=^;@V_zlB=$Tlf@s7;cY% zM?tOntxu1GC%}`SR{hp~t@uvoE65%h!`0WBO_vDL`=V-9M-3n z5iv3%Mn=TQ2(7p!=41K|;O7U7s9PPs@lH84)8RVq`>&jEIpDF)|`XM#RX77#R`M^~nJpYZ(zEBVuGk zjEsnp5iv3%Mn=TQh!`0W(;e?+I?6I4Mn=TQh!`0WBO_vDM9fzGRv*iV7#R^GBVxJ> zsy$>xjEsnp5iv3%W~+YdQ_F}L84)8RVq`>&jEIpDF)|`XM#RX77#R^GBVuGkjEsnp z5iv3%=2}L?$cUKU1+%tgM2w7xkr6R6B1T5U$cUKkmdkf!L`-+ft!)_*BO_vDM2w7x z>ALPY?YE4G>3m^m84)8RVq`>&jEIpDF)|`XM#RX77#R^GBVuGkjEsnp5iv3%Mn=TQ zh!`0WBO_vDL`>%hc~<8LL(7Pm&Jl)|5iy-33@syKI!72Q%ba`(vS2{T4pMvCo2f z?^}EH%!yD>o4BiVr&r_aPOsrY@H+5%P-|yvzh=DZyCwhNk&yG+&M8tI>Qlny*Il)o8w25hZQWd^MU^qj@!&SEG3~ znrp>TSz3+e)o5Og=GACkjpo&8UXA9}XkLxx)o5Og=GACkjpo&8UXA9}XkLxx)o5Og z=GACkjpo&8UXA9}XkLxx)o5OW<~3+ugXT48UW4W}XkLTnHE3Rg<~3+ugXT48UW4W} zXkLTnHE3Rg<~3+ugXT48UW4W}XkLTnHE3Rg<~3+ugXT48UW4W}XkLTnwP;?8=Cx>E zi{`axUW?|nXkLrvwP;?8=Cx>Ei{`axUW?|nXkLrvwP;?8=Cx>Ei{`axUW?|nXkLrv zwP;?8=Cx>Ei{`axUW?|nXubx`*P!_tG+%?}S}9b~vIfo9p!pg!UxVgr(0mP=uR-%Q zXubx`*P!_tG+%?}YtVcRny*3gHE6yD&DWs$8Z=*n=4;S=4Vte(^EGI`2F=%?c^#V9 zp?Mve*P*#q*_GyXXkLfrb!c9P=5=UZhvs!?UWev&XkLfrb!c9P=5=UZhvs!?UWev& zXkLfrb!c9P=5=UZhvs!?UWev&XkLfrYteiyny*FkwP>zYSf%+|G+&G6Yteiyny*Fk zwP?N;&DWy&S~Opa=4;V>Et;=I^R;Nc7R}e9`C2qzi{@+5d@Y)w^pJ zNrrVgpBmbe4C{10HMA!g*6Dm|XiqY%)A`iUo@7|3^QmD!H~`v{4C{10HMA!g*6Dm| zXiqY%)A`hJ3>*g!qu~+oD0mD!4xRu{f?6Nkt%u2an5>7%dYG(-$$FTqhsk=FtcS^Z zn5>7%dYG(-$$FTqhsk=FtcS^Zn5>7%dYG(-$$FTqhsk=FtcS^Zn5>7%dYG(-$$FTq zhsk=FtcS^Zn5>7%dYG(-$$FTq*Okdpw_aB!hQ?$)OxD9>JxtcaWIasQ>&nFX7?bt7 z8Zk5`>vc6^XiV1YYQ)f(tk>1ZYiI4jhe^+K&#FT5Mnn)PaH&3d8MtQTs%JfYUC7j6W#g1p-HYx2GN zUhtB8udXxosjf2(wPw9gE65AAg1mbl67NIeeMr0yiT5G#K7Cs`t3D?2J|y0U#QTtV z9}@3#OyYe=)T}KwCKB&M;(bWG4~h37aXk{(BXKm zN8)-Uu1Df}B(6u|dL*t#;(8>mN8$z~Zb0G&ByK?B1|)7k;szvcK;i}@Zb0G&ByK?B z1|)7k;szvcK;i}@Zb0G&B;N1XotgV}meG7?I?EVpwQk`8onP+PH&esQL9M>3_SLjM z3SI-2f#t=I>eIzw1sE#6M|;9xq#g?PwCjtS zu}fClr|XNC2^))_6}A+w7pB2hFa!FGmId2D&Fre4?a0m3E)=iUo(Dm#>#cSt<98K5 zuXZ=M5!_zXN<7MF!=9qf*-md!*BoM4*QsJy=R&=S@G0Q|-K)9ZIjH-M_d8m7Q23<2 z>D;fgu;DYshlS6A-!C?){R2G@dB5s|wf~s*b9&P0etpmVg77e?b-mU8v7X$!Uo}KJ z=zJ|7>3nUd)yITdeN3p;$AnsaOsLhzgj#(}sCm(aH-n!5Zvi#`x%O)g2chQi5H1I` z606#)Xs-sV!5Xj@Tm#mDYeB6WuA@54@4Ekdx3H_YT-aT_N4T+gqi|DkiSQx%Y&O5B z%H1s7TD(QLjXhd7T)0HvS%PFSrlXy5ZVC$oN_}TXG9$I0}#exb0oWqO=UkL&Dtx!RWLah*L4Ez{#uH%D zC)4AqZ<-yCOpmL+X;wZmJx->_$@Dmx9w*b|de6vgu}qKaENr7$rpI*_HndESlj&Ni zh_+>VoJ@~9mg#XaJ+8|6d9^Lm_$@Dmx9w*b|WO|%TkCW+fGCfYF z$I0|KnI0$82WeWPNv7n^f;LwC)49(dR+Ir_btNw^lj(8Y^|C&e>2cll zGPF#O>#mofWqRDTOpoiArcVnk)8k}%oJ^0C>2WeWPNv7n^ti5P2WeWuIm{))-pZrTBgUz^f;LwC)49LvyJv!rpL+jIGG+N)8k}% zoJ^0C>2cRGJ?>hj$90W#h0roRu4^PcK})8`UCZ=1nI3m7)8npXdfc^4kGq!XaWXxw z-^2cRG-6PXIGTkH7Ju=-R(>*fXBhx)H-6PXI zGTkH7Ju=-R(>*fXBhx)H-6PXIGF|I;DaSoB-6PXIGTkH7Ju=-R(>*fXBhx)H-6PXI zGTkH7Ju=-R(>*fXBhx)HU8|1C3m%#7k?9_p?vd%9ejB}7pV~RyBhx)H-6PXIGF_`p z$S)q5?vd#pneLJ49+~cu=^mNxk?9_p?vd#pneLJ49+~cu=^mNxk?9_p?vd#pneLJ4 z9+~cu=^mNxk?9_p?vd#pneLJ49+~cu=^mNxk?9_p?vd#pneLJ49+~cu=^mNxk?9_p z?vd#pneLJ49+~cu=^mNxk?9_p?vd#pneLJ49+~cu=^mNxk?9_p?vd#pneLJ49+~cu z=^mNxk?9_p?vd#pneLJ49+~cu=^mNxk?9_p?vd#pneLJ49+~cu=^mNxk?9_p?vd#p zneLJ49+~cu=^mNxk?9_p?vd#pneLJ49+~cu=^mNxk?9_p?vd#pneLJ49+~cu=^mNx zk?9_p?vd#pneLJ49+~cu=^mNxk?9_p?vd#pneLJ49+~cu=^mN>fbx3EeLz|KqOe@w zq#jVV8diWI-5GvB*=iU8qhJ-NH}AAxvkVC}JCkrdNBOj~U>n#D7Wni*@PNMWJfK|o znb6+6ct9~G9TYQ?u9z{@Tl2y$up8V6ZUP?yH`~aH8N;pMHt=C^JGcYv0eeBMuCD&} ze8>Ze8N)9!+9Tjja2NPEpALdU;4nA>j)G(0IQRrd9RLr4hlGvr+z8K&@Z1Q`jqu#4 zs`!Tb7|)IH+^DK(ZR5F7Rq+*}@!SZ{jqu#4nr3^9=SI~uL*uzob<89hlbZKYJU7B~ zBRn_4b0a)A!gC`$H^OtHa#KesHw}&FM&+iV@!SZ{jqu#4+_XK$bE9(8(0FcCZW&yC7WL*uzo`DJK4H!8mjjps(?m!a|8sQfZCo*R`-hQ@QF zvgt=|6FfJ;a}zu_!E+NlH^FlgJU78}6FfJ;vu0iv&rR^$1kX+I+yu{BpHgz0;JFE& zo8Y+#o}1vg37(taxe1<|;JFE&o8Y+#o}1vg37(taxe1<|;JFE&o8Y+#o}1vg37(ta zxe1<|;JFE&o8Y+#o}1vg37(taxe1<|;JFE&o8Y+#o}1vg37(taxe1<|;JFE&o8Y+# zo)hq#fae4}C*U~&&k1-=z;gng6Y!jX=L9?_;5h-$33yJxvsM5V&k1-=z;gng6Y!jX z=L9?_;5h-$33yJxa{`_d@SK3>1Ux6;IRVcJcuv4`0-h7_oPg&9JSX5e0nZ6|PQY^l zo)hq#fae4}C*U~&&k1-=z;gng6Y!jX=L9?_;5h-$33yJxa{`_d@Z1c~&G6g|&&}}M z49~XG+#9Z~G-qh_*;bk}w7Ss@&$iN>wXHrk!*eq{H^Xx?JU7F0GdwrLb2B_Q!*eq{ zH^Xx?JU7F0GdwrLb2B_Q!*eq{H^Xx?Jlh&@k`B+!@Z1c~&G6g|&$b4f^|89q4A0H* z+zijn@Z1c~&G6g|&&}}M4A0H*+zijn@Z1c~&G6g|&&}}M4A0H*+zihx@Z18=E%4j| z&n@t5E5W_t+DdSS#>4XIrDq+E$-!jW$E8&$dRJq4C@T z&$dRJwXHte8f}Jl9&CZ<7I&q;Vr!gCUylklvyd^K7Uo|EvLgy$qYC*e5>&q;Vr!gCUylkl8` z=OjEQ;W-J&q;Vr!gCUylkl8`=OjEQ;W-JTCgg69-Gr{FmS&nb9L!E*|pQ}CRE=M+4r z;5h})DR@r7a|)hQ@SK9@6g;QkS?l5{22${xg69-Gr{FmS&nb9L!E*|pQ}CRE=M+4r z;5h})DR@r7a|)hQ@SK9@6g;QkIR(!tcuv8y*4I;Pr{Fma&uMs0!*d#*)9{>z=QKR0 z;W-V@X?RYz=QKR0;W-V@X?RYffRjtURx?dDeb-9~r{e5N?8_Osyki+`&255eb(V{WVN4`>A49}sW4KVY~J zybin`yaBusya`-k+$xsDs$$8o6YK)J!HwW1@F8$BxCPt_ZUY|%w}U&t9yNy+STQ}Sb?gJlZoI!9190o_gQE&_#2lsQ- zgr1vf)wgvU?Fr*nacFo@Zx**I4y|p!Ft+krSgXFR8@ETmqu?>{ICug)37!I9G7suI zxZxS_tYl?~?F_MVmqU{WO6OG zGpbJC5?X9$h;6N#gmjDT46&Udwll zEU}#>wzI@`me|e`+gV~eOKfL}?JTjKCAPD~c9z)A65Cl~J4 zEU}#>wzI@`me|e`+gV~eOKfL}?JTjKCAMuHTX9QlXNm1Bv7IHhv&43m*v=B$SzEU}#>wzI@`me|e`+gV~eOKfL}?JTjKCAPD~c9z)A65Cl~J4MVmnK0XNm1Bv7IHhv&43m*v=B$SzEU}#>wzI@`me|e`+gV~e zOKfL}?JTjKCAPD~c9z)A65Cl~J4_wkGu%A5x#k}?( zC_W%OsNb5~9L<6*d{WOgv?*&0pDAixb7hU;_rV`1XWNuJw&#y&KUdVsvd*9BnTIy# zF!-b5_tgHe-u!CQ8;B=`ujq-aHf5RgQI>TH?P;GjWtpKp?bD_#Gqk6D+W2L#O<87b zttlqF8Tq#6SD5$p%b@L94OW9SU@f=?tOM7A_gXx-_krud z4d8=%&Y?}&tvFG38+Pe^oHk|mI^jk=wbiEV_Jj}d>1OktvfFU0o|I@)c3b=5qMn;q zc3XP~*i*b;?cU;2VIO%-0>A96(w?vAiQ+b8_e$Yziw$MB zyrAs1_CD}&J+suN>^6x*;4nB+tkj-S+GEAr)E=k3pQ9$sU&?M9=Lw56W%p-<2aDep z9%9dv7Guh8!^0K{%5L-QQScae96SM@1W$o4SOn_#LBnZq27J+CQoj!x&Vnyle5wu{ z6rQ1dR$Aq#13BtIjyjN|4&OfBCM?OfBCM{8Rh z$WaG!)PWpzAV(d@>HKIGSslpf{Ag%(AgA-Aq1Az$&X2a=>OfBCM{8Rh$WaG!)PWpz zAV(d@>HIjXPpuB*bbh=~Xmub*9mr7!Y^870%IZK)XH2a+OdZHk2XfSb9CaW^9mr7! za@2twbs(pw0=}xFtPbR;138^ZwYD^MAV(d@>8XJ4X^+)`9CaW^9mr7!a@2twbs$F_ z$WaG!)PWpzAV(d@Q3rC=fgE)przZsDK|LX0Xmub*9mr7!a?}A^b6no14&g->;rl2XfSboX+xhsBLv1r?dQWq1AyL zbs(p+yzQ|%kfRRdr~^6bK#n?)qYmV#138`L?^aK%13BtIPG@;*TOG(z2XZ>g-=?OhVDje$135h>V0)|%OfA{1By@TK#n?))Ahi;Za^Ifr~?6YAfOHe z)PaCH5Ksqf&G(G@+x0*|9SEoc0d*jt4g}PJfI1LR2LkFqKphCE0|9j)pbiAofq*&? zPzM6)KtLS`r~?6YAfOHe)PaCH5KspK>Oepp2&e-Abs(S)1k{0mIuK9?0_s3O9SEoc z0d*jt4g}PJfI1LR2LkFqKphCE0|9j)pbiAo0b9}jTjIp(KtLS`r~?6YAfOIt4MSZC z1k{0mIuK9?0_s3O9SEoc0d*jt4g}PJfI1LR2LkFqKphCE0|9j)pbiAofq*&?PzM6) zKtLS`r~?6YAfOHe)PaCH5KspK>Oepp2&e-Abs(S)1k{0mIuK9?0_s3O9SEoc0d*jt z4g}PJfI1LR2LkFqKphCE0|9j)pbiAofq*&?PzM6)KtLS`r~?6YAfOHe)PaCH5KspK z>Oepp2&e-Abs(S)1k{0mIuK9?0_s3O9SEoc0d*jt4g}PJfI1LR2LkFqKphCE0|9j) zpbiAofq*&?PzM6)KtLS`r~?6YAfOHe)PaCH5KspK>Oepp2&e-Abs(S)1k{0mIuK9? z0_s3O9SEoc0d*jt4g}PJfI1LR2LkFqKphCE0|9lQT^af#w_O=(7}5Q`c4erc%}Lm< z41Gmta}u^ILk+d=hVYZ1*4;9@^~SS7xH)^j~DWIA&(dGcp;A$@^~SS7xH)^ zj~DWIA&(dGcp;A$@^~SS7xH)^j~DWIA&(dGcp;A$@^~SS7xH)^j~DWIA&(dGcp;A$ z@^~SS7xK!Hvu<8FVrX8-D@P2?3wgYdSB_ZQypYEWdAyKUj@TaaLLM*V@j?MF6!1a; zFBI@X0WTErLIE!n@InDE6!1a;FBI@X0WTErLIE!n@InDE6!1a;FBI@X0WTErLIE!n z@InDE6!1a;FBI@X0WTErLIE!n@InDE6!1a;FBI@X0WTErLIE!n@InDE6!1a;FBI@X z0WTErLIE!n@InDE6!1a;FBI@X0WTErLIE!n@InDE6!1a;FBI@X0WTErLIE!n@InDE zJg7OA&$7-95eLCsWMV~JEbkV1aK3bVUvbyNgO`mT1bknDsKHc=`rq4$DY^2Xd`fQ}nM*3`| z&nEh8qR%G!Y@*L5`fQ@lL-cuwJ`d67A^JQ-pNHtPS#KG6UaU+~ko!o-lEy&%1+%1~X;=9^!a<^zk3qzB;MQ=Mw7IL>BcPnzYB6llt zw<32daSdXEP*eYU%e^4E6Fh+>!ko52<^38ug_X!>tQ|Ly3%U3}Ue(|Ipi*{hq4lLTCIbLkPS+qlQycn8AJ2c0Op;@#8i*{hq4lL4LKv}c{ zi*{g94;J-cQ4bdNU{Mbi^McY3g>2a9^Js0WLBu&4)%da$U6 zXz9VC9xUp?q8=>j!J-~4>cOHOEb76c9xUp?q8=>j!J-~4>cOHOEb7IgUM%XxqFyZO z#iCv;>cyg7Eb7IgUM%XxqFyZO#iCv;>cyg7Eb7IgUM%XxqFyZO#iCv;>cyg7Eb7Ig zUM%XxqFyZO#iCv;>cyg7Eb7IgUM%XxqCPC@!=gSc>cgTwEb7CeJ}m0PqCPC@!=gSc z>cgTwEb7CeJ}m0PqCPC@!=gSc>cgTwEb7CeJ}m0PqCPC@!=gSc>cgTwEb7CeJ}m0P zqCPC@!=gSc>c^sfEb7Oiek|(8qJAvu$D)2L>c^sfEb7Oiek|(8qJAvu$D)2L(p#;{ z9lh}eS|0UdQ9l;-V^KdA^R~ z0W2E8q5&)#z@h;x8o;6fEE>R~0W2E8q5&)#z@jh8qUG)vWs%{c;`fC&>-_sgS+UXm zqV&;z>0{`Dt@f!#HuS+P*hc>x7ziI>?L$U7N%CnN7<MaVInf<5!(8sQp#vY3DP-?}A$AN^Pyq zq_>rx*Sm_3YP2Q7Q_gbX6z!kredMp{SX;m03HAA!Mz~4%U2s~Od`(iK?$`PB8yewO z-M=`c{ol|C*0xW-sS)lFKCNd_zNupkwfdG&Yeu<`>0aM!!i&Mn!K=ZKg4cj$;Pra9 z{V{QCXz!0dCT{K6TXYBPF@0~E7KXuy{Qj7}HyKue_O|I``rc%HmVrK)1>3-OdVXK4 zt30NwEJM4xdrbGi&I;{&_hZVtpSq9f{*~nF{*~b;z+1p2pjHjjeytiN)Cz{eRp4r{ z8ms|p!8KqVxE8$MY*d^WdSEO2GoV(^(tfR+C2TX>6(@!PxY=yc_cFt+;5P7K&}P7U zOyA24d%#|>59|jAz{kNsa0na*N5D~V3>+7Ji*=p84enMJT`-?H12Z3+;Q>Zq>Axh4$TNw`$st-QCI>jUzS;J-;4fx(SI-c??wN;=)V{J_oDw^^xupA znm<7L>)m3|_}`2Ed(nR{`tL>mz39Ie{r95(Ui9CK{(I4XFZ%CA|GntH7yb95|6cUp zhyMG}e;@kqL;ro~zYqQQq5nSg--rJD(0?EL??eB6=)VvB_o4qj_}_>A`_O+M`tL*k zedxar{r92&KJ?#*{`=5>ANub@|9$Ac5B>L{|3378T)$Rm)nr+5iSP!{R+)KRRv0cS zwh3?66MBzpj;v>ex9I)o$DLa>Z}8(zpYC=(?tFosS^-w=r`i7)1uODnMl3#D-xLm;jr> z7BC5>z%MKSS^{1V2OYGXy_F z@G}HIL+~>MKSS`NXJN#Tesu>eeum&@2!4j(XBd8l;b$0rhT&%zeum*^7=DJ~XBd8l z;b$0rhT&%zeum*^7=DJ~XBd8l;b$0rhT&%zeum*^7=DJ~XPEdIhM!^h8HS%>_!)+u zVfYz_pJDhJhM!^h8HS%>_!)*DJ$hL)HjKj}3{EWlTIQ)#m&p7;y!_PSUjKj}3{EWlTIQ)#m&p7;y!_PSUjKj}3{EQPn zXU;b$Cv_UpNhSKa-3uH);%dEf{2l+k`MRwujxT%=zI_KTly z3vVe73UAffe80ZOy(ZMU%)*Gyr2F+f&aeu!ne+GSdz^i0->~-Uw=F~aHnd;AZ5g)H z^MLL;?$?tK&j_CapV9jr`}O35;dA=Nv0vZptiR3HxL?mONLxL_V7L&x4!j<`0lX2s z30z?oiEUXVwhcY7&1?}rh5^`ac8MRu0;qMI)z&)BLapO0)H=?>he4Y`bicl%8TNo$ z$60Ny<1ExV&O)u@EY#Y~!a;Bd90o@~t=+6owRW>mYd7m&fca$tzf9nl2}N>Rd(1Br z_+>)T`-a-)mkInbfnO%@%LIOzz%LW{WdgrU=y%CKSAX-%1b&&oFBAA>0>4b)mkB+c z@?G^Yzf9nl2|bZArndQI0>4b)mkInbfnO%@%LIOz(6c39cPCu)%Y>dSF*LtS=-CoO z^NZ%CGmG%c1b&%N+zx7w`DFsXOek(oscn9lz%LW}O;h^gmkInbfnO%@%Y=T@v`@`1 z6ZmC9ziC?A{4#-GCh&`{%GqOnnb2>VhUS+E{ibPXewn~86ZmBkzf9tnN&GU2UncR( zBz~F1FO&FX62DC1mr49GiC-r1%Orl8#4nThWfH$k;+IMMGKpU%@yjHBnZz%X_+=8m zOyZYG{4$AOCh^N8ewkEVdd;0wU3y74rSB}0cxn<)P2#CZJT-}@Ch^oHo|?o{lXz+p zPfg;fNjx=)rzY{#B%YeYQHLysYyIFiKiy<)Fhso#8Z=aY7$RP;;BhIHHoJt z@zf-qnk0iJ@zf-qn#5C+cxn<)P2#C1NwTkW&^3JD( z&x?~M#Lw%(DeEIkb(AbM^uPz$-v~B=39uP#0h3?~OoOc^MU~mm2eV+C$rW#gf$#v@ z9zfdzXnO!{51{Pnjb{-gJ^yb%@3ma zK{P*z<_FRIAetXU^Mh!v=bEMYK{P*z=7-Sy5SkxC^FwHU2+a?n`5`nv1m}m){1BWU zLi0mtehAGEq4^;+KZNFo(EJdZA42m(XnqLI525)XG(UvqdN)@L>kUQFqU8{pA42m( zXnqLIpOog+?vv8oaFOomJt;jOb)S?b+9OR2?QZ0gI@b2*z(DvE$3DfePjT#19Qzc< zKE<(5aqLqZ`xM7MCAp8fhjr{tLR;DSu#Wwv&{lRntP$=L+RDy{#m{M>t?YbQW2&dd zG_;kS56edNmyL$Dvh!itXlN@tAC@dbTiN*tvW_6@2(peK>j<)rAnORSjv(s@vW_6@ z2(t9fu0}qBtRu)ef~+IRI)bbt$U2IwqsTgntfR;}imao^I*P2L$U2IwqsTgnEWOby zS$YE!G`sX(6liuGMb=Sd9YfYJWF14+F=QP>)-hxqL)I~5Xo~HGBkMS_jw9tK-LLloj}$JWSv0P31po>)(K>tK-LLloj}$JWSv0P31po>)(K>t zMAk`UokZ42WSvCTNo1Ww)=6ZYMAk`UokZ42WSvCTNo1WwmfpsYzw|6NXmNN7S*MV7 z3R$O+bqZOhBlCu|j*g9wtW((ag03!Jb6?Qa z#S6lD;0KEXYG1GCUS5z6hKuy%#tXW-_>RziPkBLC7iWbBR8e2hRl!e%Pl3c4Z`Ms=-4xbMVcitg>DMWaHS4CZZVKzB zbj|Y@+GEyDVcitgO<~;>)=gpE6xK~)-IT6#UT~*$onvU$P3bzv(5#!%b&mAGx+$!i z!n!GW>RZ}x)=gpE6xK~)-4xbMW8E~?O=I0O)=gvGG}cXH-89xsW8E~?P2;<1teeKV zX{?*Zx@oMN#=2>&o5s3nteeKVX{?*Zx@oMN#=2>&o5s3nteeKVX{?*Zx@oMN#=2>& zo5s3nteeKV8LXSZx*4pS!MYi&o58vntee5Q8LXSZx*4pS!MYi&o58vntee5Q8LXSZ zx*4pS!MYi&o58vntee5Q8LXSZx*4pS!MYi&o58vntee5Q8LXSZx*4pS!MYi&dr{W? zh5Mqcds;XT{GjgqzbNZ8tGlc-T%??OQPxceE$?2GbuS8+=t}HG+4w`@lX_F^McHWh zjJ`L$C>sr*<5OFU{6*PlM@=bsqIg4co9gExRTf;WLHOe@(aePpAd2U>i- zh>b5|lHL3kP)Ph;b0Y&K!m_JdKT~vGFuEp2o)0*mxQnPh;b0Y&?yP zr?K%gHlD`D)AHj_HKy5k8XHez<7sR>jg6<}$EV%X@}r^Icv^lmG#gLLkJ1VoHJ`Dp zJB^K}@#AT1JdKT~vGFuEp2o&mY@Ef$S!|re##wBf#l~4|oW;gjY@Ef$S!|re##wBf z#l~4|oW;gjY@Ef$S!|re##wBf#l~4|oW;gjY@Ef$S!|re##wBf#l~4|oW;gjY@Ef$ zS!|re##wBf#m1Lp%S!hp* z4edALv(n#Czi~VE-?e(X!KLLmC-whQ+jTC{ze?>3oCVHHbL|VAO6Qfi_B`kP?uB#h zi}Z}g6?5(R+JE<4dx4$zv)%-u4Yrn_& z{rN3(?e{t-=Wm;9U+#Ql!KWQbo_C4V{lbFvb8Xl8z{US|u6==X`NjV{*S^qcy0|#k zp64vQq;al&k(0Tkcdk8O`~P;Xy})U@^s>43#ZKj=ZFB8Qoc2ri&9yJR_^&U!WUhUg zQ~AMvoNK?w319KCx%PYAFI=H@snGLsC;Fk+?#vf9clfQDuChffHR9 zGG%qC&Q!<7RI==@e0x_}bs|WWEnJo8$}e18w#siwwRh^H?)GG=qpT~FDqDNcsiwKVirCn?SV=R}tVGE%C_E0>j5 zF1fwzZIEWtd8G|m6GSnf|S%K zTeub(3r(cV^*b~2-#uNT$WL{aiLjPLM=IT&lXlGs#h#Sy%qx5fd8yLnC+vfke0w_Q zx5(eEWrYqu-{E)Z;M>aluCmTdzB`vJ>rADBvIo2UF2$Y5Z|^K<)b_50Wt%bzvu@K@ z@|*KX#khg%@(E zuC7!^I^Pj=mK^0LI?6JMjv(K@x#X5ys@0fTTy}?uli*HEur5E)D5hp-E|qH2&N;8= z^7*#1Akij%QycxHc;Fxff;AFdq)*wVMCUmlzU6NV1R0RpDAKybY-`3iPUbaId%mkI zozLa+o1~Il(%oSMizo$5iH*)w;FovgEzK=Dx)Oe_llYgyB@;VKHWR3cvUDnyGmk1o z6xKmk=2r3Tiz+Pa>d5P}&aRH`mags&#Y0ElY>}~DiDp0Nca^BIIL~)?wx>GflY81N zlp|KoqPNc3Ox&BTtsSXEmmKm|j4vXuOjstBE0`L;JI8J&V)Gjn2a(Fk%3I1>bDIkp znQ434{q|JZrj(Udg^ra)zbm1*HO;z2;HI)pxnE(@Sr)Qm#e?5ImraWN*6u`WO6*tZ zqdQfhb2{|hpO(pxO{rYY{vYcwuUjR!r}mDrLMGpyQrOLfCLB|%%9IIZiH-6`!s0Nk zqs-P81z2n9cNJxI&j%Q%+rmy+Vyx-~LG@nD5t?8_M|Ftl3Q4<5Qg5Eq zv!r!NdrfnOy_$RyhC9UnVti5M+^*4#`&DY4v-SK@D(!!Bq!Q!jHF&qxZ`bHtq*Q72N@p3kMEaHeD?ZQ7`6xZ-L1X8f*MC!!ciVKX zN13?ol#GP7E^(Srw51dW3ANj_FYl!JD+sj*5CZt-@`YK!HxK55ba%V=pz1&w9- zYfM=Lx9T{{QsdI%{(MGsOHa!w%ac+as8s8mmMP~U^|f5Fk^cLRkLdVP^|%+?P3ujH zm1h0lGTTOZr(P!ecd@(Z9XVfy&LuXi8mv_YowH-1Mk`ra@~%ZsW-kBkku{|#vZ`fK zWi_Ns+*vdkkCurg?o3~c;PyFYJJqvPeXNd^STPS;uhJ0~+wB@dQ`_hm`>b8em#H$C zzpNful`BcUP5bR%WoW0DyLFT?V+>hzmpKn=pYArnVhTRB~O}Xx{5AaW&QvD3lpkiZ)#H-gwyy=vZTu4N8@12IXx6`t zt#`(Fw~n+N=|G0@YonP>c7{2h3wCUm{;a1}tPXj>Y_!}eWt92aVl~76H8qJ?F}}`c zi;dCBzbzZh);l!fc^Q@qr5G|htYx~KW3vZF5*L!!UhjC%?WpRGqvUS>P(RDtK&tF&BsOE9||3+=5pT%1#a?^9N&iU1@M9x*` z|GQVpmPr=vcE+-A6~B4qZ<<&HoY(gJRp4(w!t%p>R`P_^g>zSpzrFwem4rJr&j0_t z=l|{RJ@53}f8RGI-D$s|bl3e~7CY0*5m`}eNywd-BVi&aR!=6uhIIR8OklB%2~&i|o%?U!hs z*-M?}`r3NC^GW9qoE7e6&WN*;ub(mZJ??v*zjN+(FL&;7-{=0G`+oNW?gyP??iKEb z+$-G=yH~lt?|#I++Wn||jeD*8F}KY9xVzB3&b{8f!M)MF$z9|ecW-t-;ojnWPH&^$ z>XtjJ+{Mmo&T6;9sdg)!|Je;G`PaB%eR&&op3w#EKj>6guP>qh)p^8?IQP0yx5{1O zE_Ii=%iY`D+uct(_ql%{UN$)QJ8{Qz2Av`2PWMyJm)%c0KXX6h{5$s!_p{FH?w!t8 z+!gLh_b%t>Zp`^N?%mG6b?wMq&tn){1t#g_49_LfeXPgI| zCg;75-i_CLtY1_aOE{N07wI>W>zrNAPUlNbzjN4Kqu)seoXr3|UJ6AgwJLArObbjglv-6*w|K$A2(UbD-2CbtTcRj5# z*XTC639WC`;wH6%NLuRvWc0S}i_Wz39j93{cWWkR=f7zN*P`a&3-p`MtO}}rsdn`H zru(4V;dVN|cDvkeccb3OeaU&*-Q=8cAJQBwTQr-=HqFVgU2{S7xV?^6=x|axQ%36mz>~y*#?x>#1 zAJ@~%6MDw_Ck1lm*X)9!cN@4DY}pK+gc9&*30i}B6QR_9@7 zoBIRzhwdM{&$)l%{;B)C`)AHC+&|Y-U`O=a)G_zCdqU6SoYFHSQ|`1oqhGU6yR-V0 z@@4mov%}f$^fEd77p-OY)P1^1NxF@Kfrw&&X~T-n|_Z=LP0pX;2M`#j0#^OG$d z`9#-()Z7q_l~v2;36@`&ZcMAOBxDdah#FKGEHPMWV11V?7c8wb2wkM38hvX<4D67l zcF0mYWT_pp)DBr{hb*&0mf85rZ2V<*#Ih(OY;fVGsK@W z#8?PNmcFM=w|i6V_FIEQ663vowrIy&hPH>bIBCCRaUN8a7W3!`FAvT=r@AP?mX{>OYcf zrLpdL>!1tn`P^lnJKw)FMr6tSRf(4Fu96v{$YU(Q4 z-R-T3j_x3r=7_-$2JpH@rp0g2bGV z1VS!rIp6uxmbZG%Pn8ta&y^Or^HU{GQ*U*c*BDu*Fo`Tzm_(L`E^B?;Fs*O(nsZm= z0zW%nzs_0EC^{L@FNx2dKW@%1OXug%dG4Qg&Tr?R^V`o;7m6-fke?eT&p7YRzk{WY z(joduP0E)Bs#-d~lf9R9zDr~{R7$gO=(DA+OBCx-ORcD-R@71}8c`BNBlbZ==^u?K zaif;@(TLI}YPlbc*b!C$qES1-szB7LKs2g!p3}A)Z7=G^v)%8|c~i-=n@UPPcU*1q6v?F*fGZK;lS=ObVlcq4eL zj<+#>6KyQJvb@as=%NRruimlluDj~4s#_JmBKc@)p})d$7hQpbeeP%6UtMtP1sxYW ze!=tl`y2grTzKV$D=z%Xg(DY!=faT%*DqMIVBLZ(`rEHP`n&ie7k}h}j*IWUc*~`k zONTB!roXe7jlAzh{jGZ6U;ke1@AbX^?)QJ;{ZGCB)%XAU0}J)H;sezm{HrVG>))?l z@$2{N??bnJXq*0qKD7Ts&t7@emEXGZXIK7<{-wVU-}>R(KK%GqAGs=iRbOe(hlZ~D zp8ihj?`Qh^_Z+$Hs=vSLKWei-`}Dv5{s&6?uU>R@waI<|udg1sdg$t7`uodkny&iU zHDg!(?22Ds_4gWW?*CU0T>H?q-@5h(*PgxhuReCc$FBI;r#@EuvGm6V%668`e0=`G z_bgn(_RzwI7H(fS!1k3JW^Q=(rhn4kf6>+Ug~h{q0_?DJQSk?Q25ic?viKkL9FRT7 zYtJeEiJtoTg`W9&)=3t>ulW@JS+gnrljcwSk!DYP&FL?mamMrpep&Ibp6NQQC%O*n zd9K5Hn(MHhO*0z4ruhtC(`<&XX)ePvn#u5t<}o~@*;V)J+Vuiyd}Z;!NXs5Q zkM=jt=Zp4!_}BGB(pU97(wll3=^ykg(pNRF?>}f(-+#~(N&UJ~e)myLitlqrKBH^g zXSLY~ClvXobgew4|G#^bOC|5ePGj+}G`I0zyI*!bp>=MT+Pp(krg3>PFcyt?S{RIG5?uD>csb8s`?x zL35SHdXIX4M7=L9{>u41ZCC238)Ts!eV}Mh+WoDbv-_FGx97}$>XxgIja|~QNf!Ms zJwG5`&gzJ7JMrRwlQvK32z%1@4>jNNKWVn*f0B#|S$d5u{kb%}P8xn#8h%(BUL*}K zmxecKM&4(YiA#$ANt*nl`)IMK9vA8R-jd>9x?e5++WltnzdHZMSs?8$)^@qJtF?Vp z+cnzCwB20%rDnQ*Q}bNEsadYybSh<8sQ9|x1AAR>f&E)o%{ zHB;!{JGW`OUE3$M{eiYmY5TOc&uF_t+h?`iscnU}mD=vo7SndOwtKXFPTL=9TcvHa zwrXuP+G@3}(N?Ezt+sXA>a{g!yI0$N+SV%zHb~F=wZ*l0#lLeN(AKD}Nn1i&v-Y=W zOKMALOKWS@meJQcK~+eU4hv^}J4v$ieT zwrbm^?O|=(i+}Cx(AJ}x+NmTOz3ZMC*)Z8h3zwXM-sr){ma*5WUf z;lEUd|56$LOU+OHre>#pQ*%?lshO$Y)V$Phy0SxaQopGgso&JQZ?Egkx7YRF+v|Gk z?RCBL_PXA9d)?iktw&q0wmxnB+6J_JUV45(+aGEBqAJ8A+IDK&rR___zjME=?JL?W z@*dUyzozZ$+P0yz6Vm;Fwu9OZX?s%JQ`(-^_8o2C)%HDY&uDvA+xNBoK-&+s{js*^{?GRAJie;x zZuI{-VG0DoARu894T?&uXc5I3MMXhCKu~7q0dYjC3<@e}TL)~_YN-_m(5eiMAUGf( zXb?=95@T`wE3pdi;ys#JfYo?kJ+Hx9tRp)?E4PuxStLQ+}4dTwGpL)*6z{XdpQH>C49bpA&yV-_!A zj4TV3@FAVjkhhKRNPG?CufzD8il{lgrty%lVWUG(F6JGGni!=dm{n zusFH7>MXsD2OnTG_<$N`yOJNdoFBQIAGw^Ah1p2LTqI#8k}wa+41Qz=KQd!4i}|^f zw@U5vDvMbfo}y=`!ZWi)(PB1~$M>SW?v^~mD%Vmvf3tTFr4vSker~q-omI9k+B+@s zTFC~Ua|5sWoZ0$!o2!4fs|=&Q!kDj3uCmnA(~Df`v*G!EwylzFt7O|M*|tiyqmu2Y zWIHO^j!L$plI^IJa;4g;WIHO^j!L$plI^HuJ1W_ZO17ht?WklgD%p!lwxW`asHFG1 z>HY51ZOPYk{Tt8dhU;DK8SU3Rq5YcYv%?sdFv8_euD{e(6}kS;)zKI1LM6LUN%wd2 z<{NnP4e78FNm!4lBM!tt=!Blus~cj!^Kd?fVi+$!93yZU9lIP?;7W|Kn&4F!i!jzD zjC2X(T&|Oze6tCaY(gcQP{}4#vI&)JLS_0c{0u+GFK{>R!M(T-GjKnCiC^IXJcx(z zYdnlc@Egp;Q<#l8ctf7w#9LU3x3LV%u>$Y25o@p(>y&My8vb{0X?lU@dJzki=(40l zss3H5{v%3Nwxho0wZA4=Uz3YXWN`jv}6T3vlqv+m(AJAcI;$(<@+1_@FohME!U{}b1*Wh9Q&@p1tiTiz!d4N&RuRHh5yDmx!d4N& zRuRHh5yDmx!d4N&RuRHh5yDmx!d4N&RuRHh5yI9ek=G|@{8OpC$@e!WeZG~>Nj?V& zy~Vbsl-}n5+i?e`xsK`bey8iGmfWA4G2s{ZJ_&v}8O2hJVkt(k6r-XWs6&Bd_dVpwl6thRWC2wz*#b#ADthE$>EX8(G+$^;>S=VmKE6!R*hu?PQ3fHvFHEnaICeCt#vy?l!nfx|& zba>3r3V`yo%aIqc6c0Ii2!H)IM?WTCBjszMe7z!HW0hR}@08qvumX)#mP=(tEV;bo zVcPhx63ndT6xMVLE4qdC+``&zVa>L%I#XDi>2_-}-D%&j5>uvB7rVZua+oiN`^({J zam|&m+EQ3qDXgdz)=&zor-ilC!a8ZzGSlBvEg{4CpiJv_yIjTAvojyyy1!C-^{dX_ z#@QPzbw|Uy1=g6LZ8-K|JAB9xaTWeLsu|3L}El)@XO@{`sCC+q2}`J+qzb zBj;Kx8l3N3VT@52dA-UWh0e9sxu%hb=~_xpE4hX8xmc97j2x~Ik*<{TD)HO~z9(C~ z?66n4JyzIbn?1s)*ERO|${yQNS*~jz*RijANLl`NaT~fj={^?6;dq>Y6LAtwMi1@w zQ*bJJ;xwG@`!j5N*`A5B&>MYlHu|C;`r{l7z(5SbxfqNgI1fYRVHk#Eg!@V(F$$w` z0Wyz^g@<^;?fme_g!M-pH^n`?sqPEjrvJe#)9&Am(StX_y|#plOca-Ma zF0ftXuH<6(PM5i7x5B-v)81Xp*f%5LQ9~PZJupwv_V@; z^WAjYJ8^d^toCWGPlS@4VS7J*iC;lUn|1aY^Q&It{@FD~ShqJaxqW&S-i2|b+A;@f z%N%Iz+?8r@I=y__cCLGo^RY@PZ=lK5^>)07pfYtVj>GXd0Vm=loQxhg1*f7XPQ&Rq z1HEu2&O&eW!P)4Ge&~;LFaQHF2|vdZ+Gcu!lp0ITu7AKhGDonqZisyx>5 zWUih2xVh%tu1A)dI?M0nS``8O{xvrG!lHwd?c&U`rQVz38Uo7QIq&!v16Q%qU zDc6;9T`AW!&r>INa+^y1bY(l7wvTWReq_84A5v@!Yp<|+ z^kw!sRIb{|m9dwsSg413_6YT0WTI5sD4{k=sEzYnqcqx7l|~z-(MD-xD~&cvqm6oK zB+reV;c4kic9!#{Gg>+aJKOV0qK%Shqa@m>gHF;rCe}f=6tbm|Ern1A4V8!%Oss>3 zRdtYEm1?$Bv#aVLTdLVgtD&pAtg5uSNcj>ehdK!5FiFZ?rJOD0Y$<2E+JjwfGpV2P zgX>_3vtH$_p$=|v*1pcVrw&e#{*kVFw7u@I*A4cXZm+BCb+L2~i*;~}y++uplfBNd zS3i4&^$*9`tFv6Sm#g~rK9P)>wVOo<{k0oegHqPuQ@=FUiU z&0dR>Pt}|L7ypmu+ry_iY{2WWXQ1Yh)9jJ2ZYR=$JU%Rs56e^gd3;!&9OUs~dFmpM z56kDn^7*iQJ}jRP%O|^e$}XP|%j3iH_^>=aERPS%KCvOwg{6u;`QO!?O^Aq{7Jl?0NCnB2KtEmSAn|iFh zsV5+s@>Wgl(KIy)lQG3rPW9xF6(IP!BU01sJ>3%+b3DHGw7urCE%SUc-*$oRBB?F* ztI}dE&8_T-hn1dBS>^eNwW*)+cRyn}-z4)BN#8{NE}y^4A#XQW% z0=$41@e*FfD|i(Pu?VkWF_z$UnCFBKJ(LeUln*_W4?ThpJ%SHCf)5=ZBec)i>&@(S zn2TjLj}&HOSwaSvv)2XWuz+=*CWf9aE(&|i)rUME|7`EI+7TZh997reA^v*LId?i| zn9m@L0sn+Ho1KsRzRpX{=cVS_qsSg%-2Hk|zmIz)Pe%{=re8MrCCm*I{KrnmZy_n& zMdHWeI2?}?a3W5^$>@Poa4LG@G@Onz& zJZGQhF&FbN9}DmTUc^gy8L!|~EW{$bhQ(Nd*YS=%^l)ct72d^r?${JywR|_Bxp_1< zugYfURe7;I(tdxHT|SM~8%f$PC++8x_SZ=JnXF!zC*WMq?1hJigEbp!I}F1yg3gY_ zD2&DhxX||(;}VR;^|+l3O=HQX^LBHzP@iVO=ISGx$5PF=U0}ONoV%FSS?263oN=WT zRyk%Z3-$pv@Zz=QrWq#cA9r7bzIJv6ovW^(vnvQuQn=^V*%gF-tuWzsV1O4ZpyEAkclZ}e-N8zt`lXZ!!vH|yB zHBW1F0Tye=uW-hdSd~1eU;RPa`=Dc=(EIpQ^fV#8az5p0xnAsEU`CG%?Eirryd>Iu z1&iG?SR(#>11tREjw3%(kIrTz7j4iM?a%=o-4!?x2gzM09IP+l5dBk~^-E0AD?L@o z-0qs@CWp#zXBOvBdG0KaUFET>ex^10nbzoMTBDz7O}ZP7#xdxQJ~$hF(GUG`4hCQ# zh9x)XnZ7~K^bO{pSa0@;_36>L02ksST#QR_DK0Zx+vT_dS7MAl9{qTFuSd}ovom28 z-o<-nt0};0`ETq>!}<`5Tw#eTtZ;>|NZkICF(Fzv|4dI`gaX z=!YD=u*T7^IQk(+Kji3#{>jm=IQkVwzY=R;of=rD2G*$o@jcq11NPKFu^K3r!dUqW zYm5w21Fx%rjo(uP>(szHHLy+%tWyK))WAA5uucuEQv>VNz&bUsP7SP61MAelIyJCP z4TO0>YSuup8Yor+#cH5f4HT<^Vl_~#28z`{u`7L0IX$SH!ipeaJrFBVV=UfrhdbQm zUWIp2vj)PQ`S;m?BS`hn>DHm?6L{Sd^@4={zt9%?RjM`3ZW-G?74}|l@3yQ(w!J&k zto!sEZPp)C>Zoi-?dPbUunpmUd?&V{g)_8phGWF9Eonenjr~fzb)bIP*=l^Pp6>hf zbl>M`iZQgJLY-8olM45U?o}@p>ZL-xRH&B64% z9zVrRxY^Sa6EG2zFd4VtR!qTE+=kn62d24>>9`YjCC`fio)-f=F9vv?OddfNbI4-d zD9sO>L;Df@1~c(n$3KeS;W5m@<9Gs3;wk&gX65GCKFxZB*|>fW{gdK}Qt?Eoc%oE1 zQ7WD&6;G6kCrZT=rQ(TF@kFV3qEtLlDxN46Pn3!$O2reU;)zo6M5#W-HF~bs=(%2_ z=X#Bv>ot0=*XX%kqvv{!evA_R7$y2KO45b;px23wYSF|Nv@M%cmE%Y<8VAqz==2sC!+^W!Kvtp({MV@Krft$ zv(OuTa5nm)ANu1Q48TAP!nqiXAvg~=U>v!<5##Yw+=QDk0TVF^lQBhHFqK^2&I``d zSG)jg-L3h+o$ThGEogyO$U|$iL0cTG%nre!=nT(4k>Bm)cRTsrPIkAG-R)#|JK5b% zcDIw=?PPa5+1<__hgAu~nuK9R!X51K4)%Bld%S}^-oYO4V2^jO$2-{L9qe(KQ~n9| zILs#>W|JSw9*<>@$CA|@WOWBw-9dgz$WICRDIq^44>7O2|(M`6(ek zCFG}s{FIQN67o|*eoDws2^(L)#uu>h1#El)8~++F`x-C%TC}x~vIJj+o*V7BOSGA5 zqZuB;uYDJ0c*-(%A!SU#Y!T!$Mo8S5nk!EHg*b6Z>es0^ydOy|_5MvN-SA>@P>L;X zUF+bwa~k|8XQ=nx4UaWrR6VViY~-R1+M*pg;9#w#&iJu*%3(MhM`*tsiJ$1H?uw(( zO>gzlzB>lpS+HYq9FE5cI1wk|Wc0u(I2Ao{8cxR<=!G+J7J8!(&PHGKLw}ru0T_ru zI2VI41m|IxKAF)*j$D9?v`j9=Ww;zy$lsM1gRA_1wY*7NcJX@y@^+DOu9Fb?oFh76Y1VWx;K&TO{9Ah>E6ULH)5F^vCNHF z<}EDq7M6Jn%e;kU-oi3(VVSqE%vSRCEaC)A#3W3{Ew~j^Fcr7qcHDt!@;M!MBGWIF zBRU9un|Um9xFep=ejVV+gPpAMPF8s*tGts{wx$n$hsQ7rkK+kEiBLAPF$Yh3#_k#X z9?#-A`FtL8F%R>x059N0gmsBuMp&8nRV>6JyoSYCg4gke@_iF;VJY55m>YXJR^T0X zz*b@v-o<-bf(2NOb;*CG%tfXqYoiYTT#r;|BNr{zZX4USXon6sz<2pLSU*i?{Fqc7 zhQo1$UWX&`6R~Gk9EEOrA&&OlG3c)CbS#d;@i+k|!g}nqq>z>r(vm`2QbEh(TS1+=7qmMozqOK8awT2equUZ*7mv}6S>SwTw*X~}9@vYM8xrX{Os z$!c1%nwG4lC97%4YFe_ImaL>DD{09}TC!Hm6XNjo;-2;5o?>y&W?HkFwOd1T3TaLu z%_*cgg*2y-<`mMLLYfoiZ`~~B*(~PSEaurP=GiRf*(~PSEaurvv)0nAwKQuj&00&d z*3zuCG;1x*S}RgMigtBPpXAw&lX>DkzB?Oz(GUG`4hCQ#JpY*X{A2oj48<7H^;H-P zz2^ECA4k{ty@Ctme9H-v~CHlTSDuW(7GkG zZV9a`q;-X~u8`J+wH!k)c551!N8=8nafi^j0vcC9;|ge;mBAeMD1L{>Fbj|42|Nk$ zB#qlh;|ge;x#979Jd5YdzWzMsVjkvW0ban1cnL4#6}*atScKOQW^P}C*YSpWc@uA8 zDc*)Vlr(N7jay0M*3!7OG;S@8TTA2C)426CZoL@EypdRob)G2tnD&*?zB1ZZNc%R@ zzKyhRqn5=NG_afoZlr-5Y2Zd0xRC~y)4*~Xm`ek5Y2a!axS9s)6QpfN;wSEtcEwTX zMv9O2-7&DjBMmH~fkia1hz1tXz#PHUNE&zq z4QxjPn`(u8K?^t1!i}_WBQ4xW3pdijjkIthE!;>8%V}XbEi9*n<+L!D7Ut5zTw1uA z7Otj+t7+kCTDY1PuBL^nY2j*GxSSR)r-jRD;c{BIoE9#ph0AH-a$2~Y7A~iS%W2_q zT3AF2i)djHEez|bW>;xpcuGC^y@P1sdRn-i7Otm-*8Fqaqxc;j!z?_GC-5YcZB=W; zx)NqSeFneBvv`hAdLDBz5A(4AFW^PIgqQIOUd2Kz!fRNJC3qcgsGB$O7M9{|EW>id z`Mq7w-eV0m)56WP(EPA|TZ6S&rrk`)KQh)fB_p z4bN%gEqAx4lXl(%o>>t}5EsSeFB{ikag>|=kSJdmD z>YCjwyKVOM4O0zU@BLOfHShF=9>+=2536skRyv=`Ls-{jk~~b3he@vLQ&;t=tNPSc zeJW33MUt={$usF=$nrL_yp1gXF;!RJOFi$R88=?_%ec#x&iSL`*Hp4!= zOmo{7XsNZZuWc*jp*7k#rY+i`z5n+^2cP%1JwQ7?Kepuux&q_M)LSPUtncg){YZyu zZFKhJV z^#YQ8q30034O9Jpn{p32bBB>vwMj)Y{JZfW?eytn`qj3v zP21R}ZEVvvwrLyNw2f`r#x`x^;R<-T0v@h_hbu_`$ZJ#U6-Pnj z!4nqngatfdLHbzV9f#v_0#3w9I2k=~3Qk2&9`H2V({ToR;Y^%`-st08XQMCrp+C;S z01U(+`<#ox7@}X~Je-fAv5#aREgDFR2Bt@{C!;VL7vMtZ1Jy_Ju0E1?^^v?w(`M7O z*)(l7O$#e?T}#uhrD@luuhyq=4X(v?V)^TF1IFP-jK@!L6K-ZXCSW2aVKQ#Pt(bzT zxDB`C4%8e6bGLCJ_uyXKhZ(pZzr?TbfIhFbj|4 z2|S6X>@yp4@U#fR=ri7WHE+F|w=U+bi+Sr}-ny8#F6OO^dFx`{x|p{v=Btf!z zn71zGt&4fuhU;qYU5YEM548eK0 z0pr|dyAk8@Q{04`F#!`X36qf-x$#wMx_b*xvxd*;Nq^2W>(67Z`{{bs+#madrzMgxPvF$;eO;twEG*e>o;Q8Z^W+Oh+V%CyM7~f z{e}d6Bd+^KT=$Jy+^rUOtHs@FakpCBtrmBy#ocOgw_4n-7I&+~-D+{STHLJ`cdNzS zYH_z(+^rVF=!vh?#8+zKtN7GPIzDX_o(Rbk+vkabu6DOK&)wee)K{Lny?O5T=DACo z=PqrYr;T#lnQbIasD9dLut+34Z4_F2&$%<3=gw@NJF|J7HagIK=5C^g5uOeiiOkbQ z6V-n@`pfp7QoYXI=^Na~9jBdpqvx!~r+SzZaJ6**5y^*1v} zH#39mpT6DxcY9vr9<#CBt9^W*xnX9c)`?u!%h6YIv|Ene@jSEFolZ=Lsd+NY2C zJS%m0`f)jYLcB3MHOpF0z0z+v|5E3E+qp%E?k6{MKlxZ!@PSumh#7}$fk*E2N zaF0B#rYl@aEBUQ?AN$^_``Dx8Ej)K}qr5#NZ;#2_Mbhr?{8y(wp%3eM`CYvHE*^as z550?r-o-=j;+c1eC;Ico{dwardE+m6-toW6+&U z9*g5}JWjxgI0+}C2TsAM=!w&CI?g~ZoQbp08+~v#`l28D;~WgYKn%jU7>pq}5C4^s zR7NWCx~05sDX*IuRduy%s~%Z(gY%E`{QHd8mBh)I}?Tb$!oOwlf#>UjX| zr_?kad%7n=!c)j|J^wY&D4_Ya3v3tbNm}LU=CwTY2iRcMy6Vwb&3TL#XoWnqMjNz+ zh>FME#pCYcad+{!yLi%FJn1f;bQe#$iwE4r1McDhckzI`c)(pe;4U6;7Z12gtk|EI z>(9&e=jFcS<-X+QzU1Y;}Fo>W?t@Q zo~xASD&@IKd9G5PtCZ&|<+)0Eu2P<>l;&rO8(0uyMF6@kK%WD z472b!p1_l+Ir8f{qavTjT+G9KEWitR5ij9oynqQybYu3 zv@1)sD@*n6Y~`u8@>E-Ss;xZLa_!3H+LgAHv2K$`nV zv#0a)5bOP)8@F3?&VjwhJi7B+^O+@4zES-fV)S^eJ|%k=+`Ov)8grPBxN zxk;tcIo4yd9Z7HYet(wEO?Sb7@4UyQw`BEVu6uIb33V6M-BNc~y_V?eePX>{^#<2lntf3AZ?l)x?^*x)`jgUI z>i@j{XALfFa92*ght zQ#oz@zEM#%_b7IbFm5V5;rPd@bNt0Qrhm^UtuWT3y{NyvsK33azrCoxeO1)oUew>d z>S_P>qJAR=wNLNyOmya1%pPLc6UDG$v_k_Ce|r&sdlA1e#^UDRdp2v07<-*3&|6ET zgH$?5rGr$$#MxoG?EozkKA&N4|XI%SXO^&yj!|ZF zwTb3AV!rJH+eNmEJ#W8EFY^jlwbEV4Fe}Vj{evH1gOPl-<)sK@I#Yb zmB#8e%*GSus|YjlG*-8bm01I2)u64$>F%pNEoXD}N6&Lr^KBQ{E_M}Gc$M1&<@P|i?IO4Na+@!=UFEjE+}4-d zmU7!tZp8waflO}0Qv@yLwx!(Gm)rVsTi+9l)8u)&+RdceTB?nt+FGiOq}oWT`$;ul zss~85y;Pe>wTV>s4`+^NXyObFoT06JH*tnMXK3OKdCt($8GhspU7X=Z&akgDw04Hp zG2h_~?flkCiM3Kb&He>Gy}`^TS!ajD-w>fyV4z0TT&u@PZ? z&G7tlW^4ps;{HwbScZAZaRI`ZhQ&&InW%MzD_aRqMYyV5S9JiZv5zv?`^dVEtXp+t zGe_2QWF1G=ab&h5!}z_{>DlBp9R0(`P^_=A(wpUbi+qPM6eZ67??zZu{~Fe64C62U z;MhImFZT3EWTj`&8{?kiD6%#_g;8YtMcdnvOmrj@9mzyTGSQJt1l!w@Omrj@VN6{| zb=#5c4fDv{P2w9m-;vI82L0?w;;(hKCM3S2dhe*-JG%bk*tCP#w0)FA7*DjfUAtaN z-yJvDKuXo)1{)hUm`%UJxWQ+o7DmtwliEd6J5*}(r1q`t(u;JcRE~`@UeksRWEJ*h z751&_RoXZ9!ene%xW;N57Ot_{4(;n2yGuEY@Ea!OYovUcl*34%0a6}a(+)|sEB!V) z&QV7>;vz@f>4-3P=x0?WbD6!4WcRv|vY{A;;l^!^phqK->G5h(mA9O#^3PHJIaTGK zqx^GNoiK(yjLZ#r3r{wWkL5oQbXEr~)j?f3=p9o%&KWLn z^r4O(`s!HnLjsDyx)<>qKs>cXkEah=h?kDBpQr&#>Zr&oq;!Rprb+2mDgEnFkwI?$*=WVFj{Ik%75`NqMRo5* zusLDOS6KfuGv@2xk8b(lV_AX~D;BYL)2CK*bW4cnuQR8yh(6vc4%>!G^n1r5J8oSn ztM0?06;O9xy?MO*LC*5u_)5x@K`Qx6Qlh^vm3-p=Qrpk`|CPUAgrt(~$={PNlAV70 zqwk86Ew($|?fe%n$8K}fU+q)5XBYqd*|#4iJKV?f($63Meii=y&oAk`=+{lj?r@G| zS4iEqJb6B_me1e_v)8E41@ZyoHULno!a<0r#ub9IjWn@J7t|EClT#sD* zCHZ}Fx=)WKcPF!x!O6R<3;t>H04D+Meor z?^pHN)l|sOr^%n=IjgU6YuG!~ZStpB&l}apCZEfc`!;#}wybKNHCbe&Q7ZX>r>^lz zmc`P0MA^Jt{k0w$uRW6EE`9iaW&C?;@_y1kc_uk6IWBoC{E{4lp5{JEk%BjpW0GFU z4MrqYy)q?N{hxiFjOBj4Pvs#+IsT?fzP<=j>D#5>k*}}F*LzB0iz|L3`8dkgmgEmL zrC&*evWzklbNLUg)v7D-TS0aD$?C-Yb+!Lne}~)SBb<~R?QcdGf+l_`wO#aUTP)iW zdy{qb{s(gXm8%c`edzeNVoJ)YN>-z$#_x*1P46G^YX3^&-nXAI3sJY#PmrKY2`6i% zR2j4^UfW;jMxi{^(4NfMs_P?}W)7!RG9x)W>1Z44+PQMw8Av4ql5>)V$#|b~l5t6! zq<7LcX`7swOg5G-m5ff@r%0yzM3ckI6@{z$RLY_JGFIWUpbP%{(!N_fiIkF`53y1m zcoxU!Z?7-xSN%$iZL@Dd@*7V&?J4_f!`_Z7OsAwQdvmp#Fg(og7>{73R&ln{AM?lY1jlz~~y63=D(|1FySknfBOU@K`? z^~*V)^u4)YV%cY8`)m7?=j5qmp7B#&K~Ae|+8!y&$bDh;ne6dca=*X7OU^S}&z@`S z6ean7c?okBrjqW;zB9RcC^;axCb=|9+NJWiA!Z6XZ!doxw7y!$KC4c?J=C{#c&xhKX|cT4fHQNlbZge0KW~&p=xBr=?HRdOh7r77fz9(!G7(Cp}P9IVf$WoAi+MP*L5mv_1msJzOd>z09f( z&C+8$z1Tc`wLZHRt~IQN?XGYvg*Nf}o5rhc8n3gZs|qV%gez$puc2eShDPxkYQ<{^ ztBV}sm~ajI#%sum*Ra14#fQg8v5RXyLVVrAc;cg6`O(Jrwb0veo^m|jJG6O++h;^o z)OexK7kP(%j7yZ`r54j^5LbR^VHupOd=AUoHFn4KYG(@zfi*A#!F4gNz25g>$*63z z987jqx0rjNerk&MKISr*s(f$rZfn%a?P~N6@AgKoJn#DEdN)hW^Ug`l_imP2;GL6t zA@!nm*h}6Aq+a&UPrc%OKqK)%iXSHIT<;FV8igmWX*z6|ls+-ey zkdkTXJDvHi^e;sJcZ*BY*35WNeTQ{38m1pk&m>}HFIACGh|#`aBiYL6PrvrxXVsAaOI~Gls*kEl!T$2U)!Y2N^8YhzVyR16`BM3B z6)duAD9hN!y^r4OzkK`c_Jp5&kfRhCuJKAHBy-{!MP2gpAFMxLk$F{F-Qcszk~N+u zNSQO-x$O68(*4`LDq|$XTlsCTYYCn{^K!*shqA2tFXTGp#_L~QfBdZ|{^dLDm-)Oj zeD>d`N#D$;_}?lXZq1&2$M1H^XDQ$HS>hQ(vCC8V6K`8aGCos&+5f)W9Q%bj-Kfkv ziatN)(MrhA7*h12?dGJuTK|kUe7mOnR7cZ$@+Pm3qQdz?eevJPuX()PW}J*KKIoU? zn2(*YNX~X7Pl)%b%ceX=%qKefljL#7{5|=(TzwSfE%+9%5cg(2#s8{~-1Ch+X2f53 zZJ(pxj7QZhzp6Gs&7s=8oR7Trr=Np-5r}~UBcKBTBhw=-@&*PUXgI2~g?nvGVH5nuN z&<6NGosrb|+sE-Q`5}E>=Q*!r(Z_h#<~_$cTkxE@(R1cR&)Lv5jCY=}Ky@AS0p08j z6TBOF+RKwr=K7iJo8U`xQ^A)uj=r=>^rcOsFKrfm={~U(>cvv17fYd0EQMU9FiAW! znb)cvR)FFsgV!<(7_U_?dadl}uj)sC)gbz-oanC_hBceSGjn;ZdeLj;Mz7T*daY*B zYw5vMruCJn5d^NXrP8SD`a?Wd%lD0JLkw3-&dqxjW1ANIT5F#}gi}itu%FK%&Z!k6 zw(J<~cmiE5H?0w|HH^_t{TS_paX+1<6C$?8G1_SpYjNLLi+QmY_lCiNvjx{w2qNU>lm5TiFK2X(Ma0LZ0$r2AsT5Jqmf!-&7*u1qRd*N%u{_1kx2_t zW^bQEWLYalmf0~%X%M57rZpnV`Y}qW8>5uQF-mC@qm+GPl#-71-8RhbfYOiqG|fMt=9jI&9&ES?{V>np8hkMfm)jdJB<#w$Fij-dXA@ zQ2naqL*U=PYRgMjx-#S_t7X<;=i$HqdtPkg`ec%b=f&h?7O!)1qn?=0MK*{3H=X}6 zrSwvAnfR)!Ht=L|#j~C?{6VihJuRVq$x=qIWjiMR_vZJVBy+skEUI3{mItqUslRLa z+flY#v}SjT6gKbO&r*Fv*k*iy5iV7)Olvi?4gIz>w9jpq@wtU;{YT-yJug1-b1lgt z=?(I^YiQToJIs6Y!(XBAa~>TFt$>$A|F4Mpm(Z#Z>u0g5p}mlfI+%|8WJMjUWzUyM zOlT?8iYeGK=ZK8Ui|$FH>!B(*Nd%4SD`ooXB{RQ+h18b_;SEDDKk5iL>6 z*zRgoqutdyTB4@W66HlpR4-bhFx&Z2&K+7@!JnMR$^~DM<{d6_-q5lNOBQE5Ls*bC zc!oOBGlZpAuXBx|HML)~Q0=1!Xcz5NlW3=!)o4xa8|_pZTKE*(8LU;9PjoJ88p^17 zv{w7al4>4Hs!=SdhOwk_Vo5cOC6yCPszEHN2C<|vUS+>%x%P|xq+PUJwW8%}9Bo$P zXszl;JJmSarWVmQwXD&CY8q`*UbId1qHPMk=uS^M1YeYn{-;BD_p+h{Hi$klC(2=N^pQDH8grwM%!xiSJ4$B#D4BI?^a#|8KC*6<%tlo{ zGRQ_)PxEkFwD$N=3Mf{ef!;9xEr-d&5}oxzS_g#M;k| z9xErxK!fP9a-t;UMvs*fJyv#O`ri7d=*^jK@-XVLh%BX+&0Bk1IE>!PPq0 zeO4o>`aL~pK<^I9C-Y5K)z%!WS6y-01*zuoZ`=4cwCwyIj|(2D`oBMSq{FJN{b}-OIlWe_HYIhJIfWlK$M7>s za8YV$>RsdCHl_ZQDzQFkg|$B`ouIB&Tbt3K_UVr4L(^T9+VP$@?PayoLE_7hS{K(I zwnIG^6SgCz61Jm6n_)XzUq;woAnpv?3*{kf$B{f+GIwU&-XppT|KICbo3Onv#$;hs zd9Zff)Ay&&j@w_F>oWZRD}Fvp>c{)rU4)w@2dDcx(ma`2Qa{Jv(dp*?ULfYpvN~y= zzn6%7v)uh_>+g-EKTG;2`+HB?{G8GbcSr9F@q=_v_19BK{{GSvIa$^gZRPI+G$V^< zcwR95>vS7`pVNQUg{3^%?3~{%;d`yR`Wt0ztO!rA$SSZaJkM*564&3|?2%zRLYy47 zBSp($JIaibw(_(vZtpd-WcdF+vr2~T46{sz?fqt&4BKCdaKrXjqHtwl&dN~xH~BkK tL>|gwg1_U;V;Qb + + OpenTS UI shell test + + + +
+
OpenTS UI shell
+
This document is drawn by RmlUi on bgfx.
+
A click on the panel is consumed.
+
A click beside it reaches the game.
+
+
+
+
+
+
+
Click the panel
+
+ + From 8e86ad9dec95a8c82b2f43aa370f83175e59a46b Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 00:15:44 +0100 Subject: [PATCH 071/179] fix(ui): translate overlay geometry and keep its submission order The overlays share bgfx's embedded imgui program, whose vertex stage multiplies by u_viewProj alone, so the per-draw model transform was dropped and every element drew at the frame's corner. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/conquer.cpp | 8 ++++++++ code/ui/uirender.cpp | 47 ++++++++++++++++++++++++++++---------------- code/ui/uishell.cpp | 11 ++++++++++- ui/uitest.rcss | 5 +++++ 4 files changed, 53 insertions(+), 18 deletions(-) diff --git a/code/conquer.cpp b/code/conquer.cpp index 2d1fbacc1..2c3f6fc7d 100644 --- a/code/conquer.cpp +++ b/code/conquer.cpp @@ -113,6 +113,7 @@ #include "savemgr.h" #include "scenario.h" #include "session.h" +#include "ui/uishell.h" #include "sidebar.h" #include "sounddlg.h" #include "stats.h" @@ -538,6 +539,13 @@ void Main_Game(int argc, char * argv[]) *=============================================================================================*/ void Call_Back(void) { + /* + * Overlay maintenance. This and Main_Loop are the shell's service points, so a screen + * outside a game -- a menu, a dialog driver, a loading wait -- keeps its documents + * laid out and animating without a loop of its own. + */ + UI_Tick(); + /* ** Music and speech maintenance */ diff --git a/code/ui/uirender.cpp b/code/ui/uirender.cpp index c0355a877..e99a6ca96 100644 --- a/code/ui/uirender.cpp +++ b/code/ui/uirender.cpp @@ -31,6 +31,7 @@ #include #include +#include static const bgfx::EmbeddedShader _EmbeddedShaders[] = { @@ -60,12 +61,17 @@ static const uint64_t _BlendState = | BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_INV_SRC_ALPHA); -// One compiled geometry. RmlUi 6 compiles geometry once and re-submits it, so these are -// static buffers rather than transient ones, which must not outlive the frame they were -// filled in. +// One compiled geometry. RmlUi 6 compiles geometry once and re-submits it, so the indices +// are a static buffer that outlives the frame. +// +// The vertices cannot be, yet. RmlUi re-submits the same geometry at a different +// translation, and the program the overlays share is bgfx's embedded imgui shader, whose +// vertex stage multiplies by u_viewProj alone and so ignores the per-draw model transform +// that bgfx::setTransform sets. Until the shell carries a program with a model transform, +// the translation is applied to a copy of the vertices on the way to a transient buffer. struct UIGeometry { - bgfx::VertexBufferHandle Vertices = BGFX_INVALID_HANDLE; + std::vector Vertices; bgfx::IndexBufferHandle Indices = BGFX_INVALID_HANDLE; uint32_t IndexCount = 0; }; @@ -142,13 +148,12 @@ Rml::CompiledGeometryHandle UIRenderInterface::CompileGeometry(Rml::SpanVertices = bgfx::createVertexBuffer( - bgfx::copy(vertices.data(), (uint32_t)(vertices.size() * sizeof(Rml::Vertex))), _RmlLayout); + geometry->Vertices.assign(vertices.begin(), vertices.end()); geometry->Indices = bgfx::createIndexBuffer( bgfx::copy(indices.data(), (uint32_t)(indices.size() * sizeof(int))), BGFX_BUFFER_INDEX32); geometry->IndexCount = (uint32_t)indices.size(); - if (!bgfx::isValid(geometry->Vertices) || !bgfx::isValid(geometry->Indices)) { + if (!bgfx::isValid(geometry->Indices)) { ReleaseGeometry((Rml::CompiledGeometryHandle)geometry); return(0); } @@ -164,14 +169,21 @@ void UIRenderInterface::RenderGeometry(Rml::CompiledGeometryHandle handle, Rml:: return; } - float transform[16]; - std::memset(transform, 0, sizeof(transform)); - transform[0] = transform[5] = transform[10] = transform[15] = 1.0f; - transform[12] = translation.x; - transform[13] = translation.y; + uint32_t const count = (uint32_t)geometry->Vertices.size(); + if (bgfx::getAvailTransientVertexBuffer(count, _RmlLayout) < count) { + return; + } + + bgfx::TransientVertexBuffer buffer; + bgfx::allocTransientVertexBuffer(&buffer, count, _RmlLayout); - bgfx::setTransform(transform); - bgfx::setVertexBuffer(0, geometry->Vertices); + Rml::Vertex * target = (Rml::Vertex *)buffer.data; + for (uint32_t index = 0; index < count; index++) { + target[index] = geometry->Vertices[index]; + target[index].position += translation; + } + + bgfx::setVertexBuffer(0, &buffer); bgfx::setIndexBuffer(geometry->Indices, 0, geometry->IndexCount); bgfx::TextureHandle bound = Texture_From_Handle((uintptr_t)texture); @@ -205,9 +217,6 @@ void UIRenderInterface::ReleaseGeometry(Rml::CompiledGeometryHandle handle) return; } - if (bgfx::isValid(geometry->Vertices)) { - bgfx::destroy(geometry->Vertices); - } if (bgfx::isValid(geometry->Indices)) { bgfx::destroy(geometry->Indices); } @@ -365,6 +374,10 @@ void UI_Render_Begin(int destx, int desty, int width, int height) for (bgfx::ViewId view : {(bgfx::ViewId)BACKEND_VIEW_UI, (bgfx::ViewId)BACKEND_VIEW_DEV}) { bgfx::setViewFrameBuffer(view, BGFX_INVALID_HANDLE); bgfx::setViewClear(view, BGFX_CLEAR_NONE); + + // Both toolkits submit back to front and expect that order kept, which bgfx's + // default sorting does not promise. + bgfx::setViewMode(view, bgfx::ViewMode::Sequential); bgfx::setViewRect(view, (uint16_t)destx, (uint16_t)desty, (uint16_t)width, (uint16_t)height); bgfx::setViewTransform(view, nullptr, projection); } diff --git a/code/ui/uishell.cpp b/code/ui/uishell.cpp index f82cdfb0b..e1da0a5ac 100644 --- a/code/ui/uishell.cpp +++ b/code/ui/uishell.cpp @@ -272,10 +272,17 @@ void UI_On_Resize(void) ///
void UI_Tick(void) { - if (!_Initialized || _Context == nullptr || _Changing) { + // The service points nest: a dialog driver's Call_Back runs inside a loop that already + // ticked. A nested request is dropped rather than updating the context twice, which is + // what keeps a pump reached from inside an update out of it. + static bool ticking = false; + + if (!_Initialized || _Context == nullptr || _Changing || ticking) { return; } + ticking = true; + unsigned int const now = Host_Milliseconds(); double const elapsed = (double)(now - _LastTickTime) / 1000.0; _LastTickTime = now; @@ -292,6 +299,8 @@ void UI_Tick(void) VideoScaleInfo const & scale = Video_Get_Scale_Info(); UI_Dev_New_Frame(scale.DestWidth, scale.DestHeight, elapsed); } + + ticking = false; } diff --git a/ui/uitest.rcss b/ui/uitest.rcss index 12755f3a6..b7c366f0e 100644 --- a/ui/uitest.rcss +++ b/ui/uitest.rcss @@ -17,6 +17,7 @@ body #panel { + display: block; pointer-events: auto; position: absolute; @@ -31,6 +32,7 @@ body #title { + display: block; font-size: 20dp; color: #8bff9c; margin-bottom: 8dp; @@ -38,11 +40,13 @@ body .row { + display: block; margin-bottom: 4dp; } #swatches { + display: block; margin-top: 10dp; margin-bottom: 10dp; } @@ -63,6 +67,7 @@ body #hit { + display: block; padding: 6dp; background-color: #1d3a24; border: 1dp #57d06a; From e8b4e04c047b5df4da0a2e75ad81f9eb889a33b4 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 00:16:30 +0100 Subject: [PATCH 072/179] docs: record what step 2 of the UI migration left for later Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/UI_DESIGN.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index b6b755512..b803ca54b 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -7,7 +7,12 @@ This page owns the UI architecture and migration; [Building OpenTS](BUILDING.md) owns build support and [Project direction](DIRECTION.md) the wider architecture. -What step 2 left for later, inside its own files: `uitexture.cpp` reads PNG and +What step 2 left for later, inside its own files: the renderer keeps compiled +geometry's indices in a static buffer but streams its vertices through a +transient one, because the program the overlays share is bgfx's embedded imgui +shader, whose vertex stage multiplies by `u_viewProj` alone and so ignores the +per-draw model transform; a program with a model transform restores the static +vertex buffer the renderer table describes. `uitexture.cpp` reads PNG and TGA only, so PCX, SHP and the `` element wait for the first screen that shows game art; `uisystem.cpp` carries the `[[NAME]]` syntax but no name table, which arrives with the UTF-8 transition; the cursor and clipboard From a129f2c73c736fde6d22c3b38f4ed0ec76293fdc Mon Sep 17 00:00:00 2001 From: Michael Snow Date: Wed, 9 Sep 2026 00:50:48 +0100 Subject: [PATCH 073/179] fix(ui): keep the overlay from marking the game's frame dirty Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/ui/uishell.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/code/ui/uishell.cpp b/code/ui/uishell.cpp index e1da0a5ac..6d73c7611 100644 --- a/code/ui/uishell.cpp +++ b/code/ui/uishell.cpp @@ -147,10 +147,15 @@ static POINT Message_Point(UINT message, LPARAM lparam) } +/// +/// Records that something the shell draws has to be put on screen again. +/// Only the overlay's flag is set. The game's frame is left alone so that a present made +/// for a document costs the overlay's draw calls and not the frame's pixels; both resize +/// paths mark the frame themselves, because a new target needs the frame uploaded again. +/// static void Mark_Overlay_Dirty(void) { _OverlayIsDirty = true; - Video_Mark_Dirty(); } From 8bfd92fd31201435f96b6f82ae9e440ddf69f779 Mon Sep 17 00:00:00 2001 From: Michael Snow Date: Wed, 9 Sep 2026 00:51:05 +0100 Subject: [PATCH 074/179] feat(ui): add the LegacyDialogs key that selects a screen's view Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/options.cpp | 5 +++++ code/options.h | 7 +++++++ code/ui/uishell.cpp | 11 ++++++++--- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/code/options.cpp b/code/options.cpp index 74ce00f6a..615ad4398 100644 --- a/code/options.cpp +++ b/code/options.cpp @@ -123,6 +123,7 @@ OptionsClass::OptionsClass(void) : SidebarSorting(true), ActionLines(true), ToolTips(true), + LegacyDialogs(false), TextBackgroundColor(12), AutoSaveInterval(10800), ScreenWidth(-1), @@ -404,6 +405,9 @@ void OptionsClass::Load_Settings(void) AutoSaveInterval = ConfigINI.Get_Int("Options", "AutoSaveInterval", AutoSaveInterval); DebugString("AutoSaveInterval = %d\n", AutoSaveInterval); + LegacyDialogs = ConfigINI.Get_Bool("Options", "LegacyDialogs", LegacyDialogs); + DebugString("LegacyDialogs are %s\n", LegacyDialogs == true ? "ON" : "OFF"); + ScreenWidth = ConfigINI.Get_Int("Video", "ScreenWidth", ScreenWidth); ScreenHeight = ConfigINI.Get_Int("Video", "ScreenHeight", ScreenHeight); DebugString("Resolution = %d X %d\n", ScreenWidth, ScreenHeight); @@ -475,6 +479,7 @@ void OptionsClass::Save_Settings (void) ConfigINI.Put_Bool("Options", "ToolTips", ToolTips); ConfigINI.Put_Int("Options", "TextBackgroundColor", TextBackgroundColor); ConfigINI.Put_Int("Options", "AutoSaveInterval", AutoSaveInterval); + ConfigINI.Put_Bool("Options", "LegacyDialogs", LegacyDialogs); ConfigINI.Put_Int("Video", "ScreenWidth", ScreenWidth); ConfigINI.Put_Int("Video", "ScreenHeight", ScreenHeight); ConfigINI.Put_Bool("Video", "StretchMovies", StretchMovies); diff --git a/code/options.h b/code/options.h index 5910b1a37..459899b8a 100644 --- a/code/options.h +++ b/code/options.h @@ -127,6 +127,13 @@ class OptionsClass { */ bool ToolTips; + /* + * Should a screen that has been migrated to the new user interface open the Win32 + * dialog it replaced instead? This is transitional: it exists while both views of a + * screen do, and goes when the last legacy dialog does. + */ + bool LegacyDialogs; + /* * The palette index drawn behind each glyph of the in-game message list, or zero for * none. Twelve, black, is the value the CnCNet client's chat background option writes. diff --git a/code/ui/uishell.cpp b/code/ui/uishell.cpp index 6d73c7611..bca5b5e98 100644 --- a/code/ui/uishell.cpp +++ b/code/ui/uishell.cpp @@ -24,6 +24,7 @@ #include "dbgprint.h" #include "hostclock.h" #include "conquer.h" +#include "goptions.h" #include "mainloop.h" #include "msgloop.h" #include "session.h" @@ -338,11 +339,15 @@ bool UI_Document_Is_Visible(void) } +/// +/// Should a migrated screen use its RmlUi view rather than its legacy one? +/// The answer is latched at screen entry, never mid-gesture, and LegacyDialogs in SUN.INI +/// returns every migrated screen to the view it replaced for as long as one exists. The key +/// and this function both go when OwnerDraw does. +/// bool UI_Use_Rml(void) { - // No screen has migrated yet. The transitional key docs/UI_DESIGN.md describes arrives - // with the first one, named by the change that introduces it. - return(false); + return(_Initialized && _Context != nullptr && !Options.LegacyDialogs); } From c9d0a51478a4d238f16fb300ba5fec7004aa6028 Mon Sep 17 00:00:00 2001 From: Michael Snow Date: Wed, 9 Sep 2026 00:51:25 +0100 Subject: [PATCH 075/179] feat(ui): give a modal document an exclusive input scope Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/ui/uirmlview.h | 9 ++++ code/ui/uishell.cpp | 111 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 104 insertions(+), 16 deletions(-) diff --git a/code/ui/uirmlview.h b/code/ui/uirmlview.h index 3537d9df0..7fa6fd332 100644 --- a/code/ui/uirmlview.h +++ b/code/ui/uirmlview.h @@ -48,8 +48,17 @@ class UIRmlViewClass protected: UIPresenterClass & Presenter; Rml::String Document; + + // The document's name without its extension. A document names its model with this, + // and no two live screens may share one. + Rml::String ModelName; + Rml::ElementDocument * Element = nullptr; Rml::DataModelHandle Model; + + // Was the document shown exclusively? The shell's input scope is opened and closed + // with it, so this records what to undo rather than being asked again at close. + bool IsModal = false; }; diff --git a/code/ui/uishell.cpp b/code/ui/uishell.cpp index bca5b5e98..dd05cc26e 100644 --- a/code/ui/uishell.cpp +++ b/code/ui/uishell.cpp @@ -21,10 +21,12 @@ #include "uiinternal.h" #include "uirmlview.h" +#include "_keyboar.h" #include "dbgprint.h" #include "hostclock.h" #include "conquer.h" #include "goptions.h" +#include "keyboard.h" #include "mainloop.h" #include "msgloop.h" #include "session.h" @@ -50,6 +52,12 @@ static unsigned int _LastTickTime = 0; // queue's own cleanup runs cannot re-enter the screen it is closing. static bool _Changing = false; +// How many exclusive modal documents are shown. A modal takes every mouse and key message +// the way IgnoreInput does around a legacy dialog, and screens nest, so this counts rather +// than flags. +static int _ModalDepth = 0; + + // The window holds the mouse capture while a gesture a toolkit consumed is in progress. // The owner of a press owns its release, so a press that crossed into the game or out of // it still completes where it started. @@ -412,11 +420,63 @@ static bool Handle_Developer_Key(WPARAM key) #endif +/// +/// Opens an exclusive input scope for a modal document. +/// The keyboard queue is cleared so a key pressed before the screen opened cannot be read +/// by whatever runs underneath it, and the screen is marked changing first so that the +/// message pump inside Keyboard->Clear() cannot re-enter it. +/// +static void Enter_Modal_Scope(void) +{ + bool const changing = _Changing; + _Changing = true; + + _ModalDepth++; + + if (Keyboard != nullptr) { + Keyboard->Clear(); + } + + _Changing = changing; +} + + +/// +/// Closes the input scope a modal document opened, dropping any capture it still holds. +/// +static void Leave_Modal_Scope(void) +{ + if (_ModalDepth <= 0) { + return; + } + + bool const changing = _Changing; + _Changing = true; + + _ModalDepth--; + + if (_CaptureButton != -1) { + _CaptureButton = -1; + if (GetCapture() == MainWindow) { + ReleaseCapture(); + } + } + + if (Keyboard != nullptr) { + Keyboard->Clear(); + } + + _Changing = changing; +} + + /// /// Offers a window message to the toolkits before the game sees it. /// The order follows docs/UI_DESIGN.md: ImGui's capture flags first, then a modal -/// document, then whatever an element under the cursor claims. A mouse move is always -/// delivered and never consumed, so the game keeps tracking the cursor underneath. +/// document, then whatever an element under the cursor claims. A modal document takes +/// every mouse and key message, which is what IgnoreInput does around a legacy dialog. +/// With none shown a mouse move is always delivered and never consumed, so the game keeps +/// tracking the cursor underneath. /// /// bool; Was the message consumed? The window procedure returns without handling /// it when so, which is what keeps it out of the keyboard queue. @@ -433,7 +493,7 @@ bool UI_Handle_Window_Message(HWND window, UINT message, WPARAM wparam, LPARAM l POINT const point = Message_Point(message, lparam); UI_Dev_Mouse_Position((float)point.x, (float)point.y); _Context->ProcessMouseMove((int)point.x, (int)point.y, modifiers); - return(false); + return(_ModalDepth > 0); } case WM_LBUTTONDOWN: @@ -456,7 +516,7 @@ bool UI_Handle_Window_Message(HWND window, UINT message, WPARAM wparam, LPARAM l // A false return means the press reached an element, so the game must not see // it. The press then owns its release wherever the cursor ends up. - bool const consumed = !_Context->ProcessMouseButtonDown(button, modifiers); + bool const consumed = !_Context->ProcessMouseButtonDown(button, modifiers) || _ModalDepth > 0; if (consumed) { _CaptureButton = button; SetCapture(MainWindow); @@ -485,7 +545,7 @@ bool UI_Handle_Window_Message(HWND window, UINT message, WPARAM wparam, LPARAM l return(true); } - return(UI_Dev_Wants_Mouse() || consumed); + return(_ModalDepth > 0 || UI_Dev_Wants_Mouse() || consumed); } case WM_MOUSEWHEEL: { @@ -499,7 +559,7 @@ bool UI_Handle_Window_Message(HWND window, UINT message, WPARAM wparam, LPARAM l return(true); } - return(!_Context->ProcessMouseWheel(-notches, modifiers)); + return(_ModalDepth > 0 || !_Context->ProcessMouseWheel(-notches, modifiers)); } case WM_KEYDOWN: @@ -515,21 +575,21 @@ bool UI_Handle_Window_Message(HWND window, UINT message, WPARAM wparam, LPARAM l Rml::Input::KeyIdentifier const identifier = Key_Identifier(wparam); if (identifier == Rml::Input::KI_UNKNOWN) { - return(false); + return(_ModalDepth > 0); } - return(!_Context->ProcessKeyDown(identifier, modifiers)); + return(_ModalDepth > 0 || !_Context->ProcessKeyDown(identifier, modifiers)); } case WM_KEYUP: case WM_SYSKEYUP: { Rml::Input::KeyIdentifier const identifier = Key_Identifier(wparam); if (identifier == Rml::Input::KI_UNKNOWN) { - return(false); + return(_ModalDepth > 0); } bool const consumed = !_Context->ProcessKeyUp(identifier, modifiers); - return(UI_Dev_Wants_Keyboard() || consumed); + return(_ModalDepth > 0 || UI_Dev_Wants_Keyboard() || consumed); } case WM_CHAR: { @@ -540,10 +600,10 @@ bool UI_Handle_Window_Message(HWND window, UINT message, WPARAM wparam, LPARAM l // Consuming the physical key never suppresses the text it generated, so this // is decided on its own. if (wparam < 32) { - return(false); + return(_ModalDepth > 0); } - return(!_Context->ProcessTextInput((Rml::Character)wparam)); + return(_ModalDepth > 0 || !_Context->ProcessTextInput((Rml::Character)wparam)); } default: @@ -559,8 +619,13 @@ bool UI_Handle_Window_Message(HWND window, UINT message, WPARAM wparam, LPARAM l UIRmlViewClass::UIRmlViewClass(UIPresenterClass & presenter, char const * document) : Presenter(presenter), - Document(document != nullptr ? document : "") + Document(document != nullptr ? document : ""), + ModelName(Document) { + Rml::String::size_type const dot = ModelName.rfind('.'); + if (dot != Rml::String::npos) { + ModelName.erase(dot); + } } @@ -583,7 +648,7 @@ bool UIRmlViewClass::Prepare(bool modal) return(false); } - Rml::DataModelConstructor constructor = _Context->CreateDataModel(Document); + Rml::DataModelConstructor constructor = _Context->CreateDataModel(ModelName); if (!constructor) { DebugString("[UI] The data model for %s could not be created.\n", Document.c_str()); return(false); @@ -594,12 +659,18 @@ bool UIRmlViewClass::Prepare(bool modal) Element = _Context->LoadDocument(Document); if (Element == nullptr) { - _Context->RemoveDataModel(Document); + _Context->RemoveDataModel(ModelName); DebugString("[UI] The document %s could not be loaded.\n", Document.c_str()); return(false); } Element->Show(modal ? Rml::ModalFlag::Modal : Rml::ModalFlag::None); + + if (modal) { + IsModal = true; + Enter_Modal_Scope(); + } + Mark_Overlay_Dirty(); return(true); } @@ -611,15 +682,23 @@ void UIRmlViewClass::Close(void) return; } + // The order docs/UI_DESIGN.md sets out: mark the screen closing and discard its + // intents, then drop focus and capture, then release the document while the storage its + // data model reads still lives, and only then clear the keyboard queue. Presenter.IsClosing = true; Presenter.Discard(); Element->Close(); Element = nullptr; - _Context->RemoveDataModel(Document); + _Context->RemoveDataModel(ModelName); Model = Rml::DataModelHandle(); + if (IsModal) { + IsModal = false; + Leave_Modal_Scope(); + } + Mark_Overlay_Dirty(); } From 6f2ae56db08931469f9110e7302bb8d2aa2141fc Mon Sep 17 00:00:00 2001 From: Michael Snow Date: Wed, 9 Sep 2026 00:51:36 +0100 Subject: [PATCH 076/179] feat(ui): show the version information through RmlUi Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/init.cpp | 13 +++ code/ui/uiversion.cpp | 215 ++++++++++++++++++++++++++++++++++++++++++ code/ui/uiversion.h | 23 +++++ ui/version.rcss | 105 +++++++++++++++++++++ ui/version.rml | 14 +++ 5 files changed, 370 insertions(+) create mode 100644 code/ui/uiversion.cpp create mode 100644 code/ui/uiversion.h create mode 100644 ui/version.rcss create mode 100644 ui/version.rml diff --git a/code/init.cpp b/code/init.cpp index 19e772b7e..4b3e7444a 100644 --- a/code/init.cpp +++ b/code/init.cpp @@ -115,6 +115,8 @@ #include "gamedirs.h" #include "gamedlg.h" #include "getcpu.h" +#include "ui/uishell.h" +#include "ui/uiversion.h" #include "globals.h" #include "hostclock.h" #include "houstype.h" @@ -3079,6 +3081,17 @@ void Version_Dialog(void) HWND dialog; int res = 0; + /* + ** The migrated screen, unless the player has asked for the dialog it replaced. A view + ** that could not be prepared reports so rather than showing nothing, and the legacy + ** dialog below is what it falls back to for as long as that dialog exists. + */ + if (UI_Use_Rml()) { + if (UI_Version_Screen().Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { + return; + } + } + dialog = OwnerDraw::Begin_Dialog(IDD_VERSION, Version_Dialog_Proc); if (dialog != NULL) { diff --git a/code/ui/uiversion.cpp b/code/ui/uiversion.cpp new file mode 100644 index 000000000..618be01b4 --- /dev/null +++ b/code/ui/uiversion.cpp @@ -0,0 +1,215 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The version information screen: the first screen to migrate, and the pattern the rest +// follow. The presenter gathers the same facts the dialog procedure gathered and holds +// them as plain strings; the view renders them and turns a click or a key into an intent. +// Neither half knows about the other's world. +// +// docs/UI_DESIGN.md, "Screens", owns the contract this keeps to. + +#include "always.h" + +#include "uiversion.h" + +#include "uirmlview.h" + +#include "addon.h" +#include "data.h" +#include "getcpu.h" +#include "globals.h" +#include "language/language.h" +#include "opents_build.h" +#include "version.h" + +#include +#include +#include + +#include + + +// What the view asks for. The strings are the intents' whole vocabulary, so a document can +// name an action without naming a control. +static char const * const ACTION_ACCEPT = "accept"; +static char const * const ACTION_CANCEL = "cancel"; + + +/// +/// The toolkit-free half of the version screen. +/// +class VersionPresenterClass : public UIPresenterClass +{ + public: + // The view-model. Plain values, copied out of the engine by Refresh, and living + // longer than the data model the view binds to them. + std::vector Lines; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; +}; + + +/// +/// Collects what a player is asked for when they report a problem. +/// These are the same facts, from the same sources and in the same order, that the dialog +/// procedure put into its list box. +/// +void VersionPresenterClass::Refresh(void) +{ + char buffer[256]; + + Lines.clear(); + + if (Addon_Installed(ADDON_FIRESTORM) == true) { + std::string title = Fetch_String(TXT_SHORT_TITLE); + title += ": "; + title += Get_Addon_Title(ADDON_FIRESTORM); + Lines.push_back(title); + } else { + Lines.push_back(Fetch_String(TXT_SHORT_TITLE)); + } + + std::snprintf(buffer, sizeof(buffer), "Version %s", Version_Name()); + Lines.push_back(buffer); + + std::snprintf(buffer, sizeof(buffer), "Internal Version %s", VerNum.Version_Name()); + Lines.push_back(buffer); + +#ifdef _DEBUG + std::snprintf(buffer, sizeof(buffer), "Debug Build: %s - %s", OPENTS_BUILD_DESCRIPTION, OPENTS_COMMIT_DATE); +#else + std::snprintf(buffer, sizeof(buffer), "Release Build: %s - %s", OPENTS_BUILD_DESCRIPTION, OPENTS_COMMIT_DATE); +#endif + Lines.push_back(buffer); + + int cpu_type = 5; + char vendor[32]; + vendor[0] = '\0'; + Get_CPU_Type(cpu_type, vendor, sizeof(vendor) - 1); + std::snprintf(buffer, sizeof(buffer), "CPU vendor: %s", vendor); + Lines.push_back(buffer); + + Get_Language_Version(buffer); + Lines.push_back(buffer); +} + + +/// +/// Answers an intent the view raised. The screen reads nothing and changes nothing, so +/// the only transition it has is the one that ends it. +/// +void VersionPresenterClass::Execute(UIIntent const & intent) +{ + UIResult result; + + if (intent.Action == ACTION_ACCEPT) { + result.Outcome = UIResult::OUTCOME_ACCEPTED; + } else if (intent.Action == ACTION_CANCEL) { + result.Outcome = UIResult::OUTCOME_CANCELLED; + } else { + return; + } + + Result = result; +} + + +/// +/// The RmlUi half. It owns the document, the data model bound to the presenter's +/// view-model, and the mapping from what the player did to what the screen was asked for. +/// +class VersionViewClass : public UIRmlViewClass +{ + public: + VersionViewClass(VersionPresenterClass & presenter); + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + private: + void Dismiss(char const * action); + + VersionPresenterClass & Screen; +}; + + +VersionViewClass::VersionViewClass(VersionPresenterClass & presenter) : + UIRmlViewClass(presenter, "version.rml"), + Screen(presenter) +{ +} + + +void VersionViewClass::Dismiss(char const * action) +{ + UIIntent intent; + intent.Action = action; + Screen.Queue(intent); +} + + +void VersionViewClass::Bind(Rml::DataModelConstructor & model) +{ + model.RegisterArray>(); + model.Bind("lines", &Screen.Lines); + + // An event handler never acts: it queues, and the runner executes the queue after + // Context::Update has returned. + model.BindEventCallback("dismiss", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + Dismiss(arguments.empty() ? ACTION_ACCEPT : arguments[0].Get().c_str()); + }); + + // Return accepts and Escape cancels, which is what the dialog's IDOK and IDCANCEL did. + // The document listens rather than the shell, because which keys dismiss a screen is + // the screen's business. + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + + if (key == Rml::Input::KI_RETURN || key == Rml::Input::KI_NUMPADENTER) { + Dismiss(ACTION_ACCEPT); + } else if (key == Rml::Input::KI_ESCAPE) { + Dismiss(ACTION_CANCEL); + } + }); +} + + +void VersionViewClass::Sync(void) +{ + // Nothing an intent can execute changes the view-model, so there is nothing to dirty. + // The screen's only transition ends it. +} + + +/// +/// Shows the version information and waits for the player to dismiss it. +/// +/// The screen's result. OUTCOME_FAILED_TO_OPEN means nothing was shown. +UIResult UI_Version_Screen(void) +{ + // The presenter is declared first so that it is destroyed last: the data model the view + // binds reads the presenter's view-model, and must not outlive it. + VersionPresenterClass presenter; + VersionViewClass view(presenter); + + presenter.Refresh(); + + if (!view.Prepare(true)) { + UIResult result; + result.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + return(result); + } + + UIResult const result = UI_Run_Modal(presenter, view); + view.Close(); + return(result); +} diff --git a/code/ui/uiversion.h b/code/ui/uiversion.h new file mode 100644 index 000000000..af4adb6d1 --- /dev/null +++ b/code/ui/uiversion.h @@ -0,0 +1,23 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The version information screen, reached from the main menu. Only the result contract +// crosses this header, so the caller carries no toolkit of any kind. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + + +// Shows the version information and does not return until the player dismisses it. A +// OUTCOME_FAILED_TO_OPEN result means the documents could not be prepared and nothing was +// shown, which is the caller's cue to open the legacy dialog instead. +UIResult UI_Version_Screen(void); diff --git a/ui/version.rcss b/ui/version.rcss new file mode 100644 index 000000000..f998d34c6 --- /dev/null +++ b/ui/version.rcss @@ -0,0 +1,105 @@ +/* The version information screen. Its geometry is the IDD_VERSION template's, converted + from dialog units at the 8 point MS Sans Serif the template names: 1.5 pixels across and + 1.625 down. One authored dp is one game logical unit, so the screen keeps the size the + dialog had while its text is rasterized at the window's own resolution. + + The panel is drawn rather than blitted because the dialog's own background is a PCX, and + PCX decoding arrives with the first screen that shows game art. Everything here stays + inside the styling profile docs/UI_DESIGN.md declares: text, ordinary layout, borders and + basic decorators, with no filter, layer, shader or transform. */ + +body +{ + font-family: LatoLatin; + font-size: 12dp; + color: #d6d8cc; + + width: 100%; + height: 100%; +} + +/* 272 x 106 dialog units. The panel is centred on the frame and re-centres itself when the + frame is resized, because the offsets are a proportion of the context. */ +#dialog +{ + display: block; + position: absolute; + left: 50%; + top: 50%; + margin-left: -204dp; + margin-top: -86dp; + + /* The bevel sits outside the declared size, so the content box is the template's + 408 by 172 pixels less the two device-independent pixels of border on each side. */ + width: 404dp; + height: 168dp; + + background-color: #23261fF2; + border-width: 2dp; + border-top-color: #6e7360; + border-left-color: #6e7360; + border-right-color: #10120e; + border-bottom-color: #10120e; +} + +/* The list box: 22, 12, 228 x 55 dialog units. It clips rather than scrolls, as the + template's list box did, and the clip is what exercises the renderer's scissor. */ +#info +{ + display: block; + position: absolute; + left: 31dp; + top: 18dp; + width: 342dp; + height: 89dp; + overflow: hidden; +} + +.line +{ + display: block; + height: 14dp; + line-height: 14dp; + white-space: nowrap; +} + +/* The OK button: 111, 80, 50 x 14 dialog units, with the bevel inverted while it is held. */ +#ok +{ + display: block; + position: absolute; + left: 165dp; + top: 128dp; + width: 71dp; + height: 19dp; + line-height: 19dp; + text-align: center; + + color: #e4e6da; + background-color: #33372c; + border-width: 2dp; + border-top-color: #767c68; + border-left-color: #767c68; + border-right-color: #14160f; + border-bottom-color: #14160f; +} + +#ok:focus +{ + color: #ffffff; + background-color: #3d4234; +} + +#ok:hover +{ + background-color: #474d3d; +} + +#ok:active +{ + background-color: #2a2e24; + border-top-color: #14160f; + border-left-color: #14160f; + border-right-color: #767c68; + border-bottom-color: #767c68; +} diff --git a/ui/version.rml b/ui/version.rml new file mode 100644 index 000000000..9068fd8c7 --- /dev/null +++ b/ui/version.rml @@ -0,0 +1,14 @@ + + + Version information + + + +
+
+
{{ line }}
+
+
OK
+
+ +
From 0cb7e43c82dbbcb2348aac0eb60daf2537e2c5ec Mon Sep 17 00:00:00 2001 From: Michael Snow Date: Wed, 9 Sep 2026 00:51:36 +0100 Subject: [PATCH 077/179] docs: record step 3 of the UI migration Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/UI_DESIGN.md | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index b803ca54b..469f2303f 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -1,7 +1,7 @@ # UI system design -Status: in progress. Steps 1 and 2 of the migration plan have landed; nothing -from step 3 onward is implemented. Everything outside the migration plan +Status: in progress. Steps 1 to 3 of the migration plan have landed; nothing +from step 4 onward is implemented. Everything outside the migration plan remains a proposal informed by source inspection and upstream documentation. This page owns the UI architecture and migration; [Building OpenTS](BUILDING.md) owns build support and [Project @@ -15,9 +15,19 @@ per-draw model transform; a program with a model transform restores the static vertex buffer the renderer table describes. `uitexture.cpp` reads PNG and TGA only, so PCX, SHP and the `` element wait for the first screen that shows game art; `uisystem.cpp` carries the `[[NAME]]` syntax but no name -table, which arrives with the UTF-8 transition; the cursor and clipboard -requests are recorded rather than acted on; and `UI_Run_Modal` is written but -unexercised, because no screen exists to run. +table, which arrives with the UTF-8 transition; and the cursor and clipboard +requests are recorded rather than acted on. + +Step 3 exercised the rest. `UI_Run_Modal` now runs a screen, and the input hook +gained the modal scope its rules always described: while an exclusive document +is shown it takes every mouse and key message, as `IgnoreInput` does around a +legacy dialog, and the keyboard queue is cleared as the scope opens and closes. +The version screen needed no name table, because it composes its own text and +takes its one string-table entry through `Fetch_String`, which already yields +UTF-8 on a build whose active code page is 65001; a document that writes +`[[TXT_OK]]` still waits for the UTF-8 change. The screen draws its panel rather +than blitting `dbak6440.pcx`, which waits for PCX decoding with the rest of the +game art. ## Where the UI stands today @@ -565,8 +575,9 @@ strings, is inserted as text, never as markup. ## Configuration -One transitional key in `SUN.INI`, named by the change that introduces it, -returns every migrated screen to its legacy view while that view exists. +One transitional key in `SUN.INI`, `LegacyDialogs` under `[Options]`, returns +every migrated screen to its legacy view while that view exists. Step 3 named +it and `UI_Use_Rml` reads it. Defaults are decided per screen family in code, so a family switches to RmlUi by default when its evidence is in without a key per family. The key is deleted with OwnerDraw. There is no build option: RmlUi and ImGui are always @@ -708,7 +719,9 @@ text beyond an ASCII test document. close; repeated open and close leaks nothing. 3. **Version dialog** (S, leaf). The integration pilot: fonts, clipping, mapping, dismissal by mouse and keyboard, focus return, UI-only redraw, - resize, preparation failure. The main menu keeps hiding around it. + resize, preparation failure. The main menu keeps hiding around it. Landed: + `code/ui/uiversion.cpp` with `ui/version.rml` and `ui/version.rcss`, the + geometry converted from the `IDD_VERSION` template's dialog units. 4. **Modal runner and message boxes** (M, leaf). `WWMessageBox::Process` and `OwnerDraw::Custom_Message_Box` behind the kill switch, preserving button order, default button, Escape, the no-button case, return mappings, and @@ -787,7 +800,6 @@ geometry memory are recorded on an agreed baseline before defaults change. ## Open decisions - The shipped font. -- The kill-switch key name, fixed by the change that introduces it. - The in-game text route for the sidebar view: TrueType conversions of the game fonts or a bitmap font engine for every document. - The document and binding versioning rules for mods, fixed with the first From c06042cea2b88c731700a1a15cb1404a9a2005e0 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 01:03:25 +0100 Subject: [PATCH 078/179] feat(ui): resolve a document's string names from language.rc Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- cmake/StringTable.cmake | 43 ++++++++++++++++++++++++++++++------ code/language/CMakeLists.txt | 23 +++++++++++++++++++ code/ui/uisystem.cpp | 27 +++++++++++++++++----- 3 files changed, 80 insertions(+), 13 deletions(-) diff --git a/cmake/StringTable.cmake b/cmake/StringTable.cmake index ebfcf8f20..30eafda6e 100644 --- a/cmake/StringTable.cmake +++ b/cmake/StringTable.cmake @@ -9,11 +9,19 @@ # Run with: # cmake -DRC_FILE= -DHEADER_FILE= -DOUTPUT= -P StringTable.cmake # +# NAME_TABLE names a second output, the table that turns a string's symbolic name back into +# its identifier. UI documents reference a string by name, and this keeps the resource script +# the only place a name and a number are paired. +# # Encoding: the resource script carries "#pragma code_page(65001)", so its bytes are already # UTF-8 and are copied through unchanged. The data file is UTF-8 for the same reason. -if(NOT DEFINED RC_FILE OR NOT DEFINED HEADER_FILE OR NOT DEFINED OUTPUT) - message(FATAL_ERROR "StringTable.cmake needs RC_FILE, HEADER_FILE and OUTPUT") +if(NOT DEFINED RC_FILE OR NOT DEFINED HEADER_FILE) + message(FATAL_ERROR "StringTable.cmake needs RC_FILE and HEADER_FILE") +endif() + +if(NOT DEFINED OUTPUT AND NOT DEFINED NAME_TABLE) + message(FATAL_ERROR "StringTable.cmake needs OUTPUT, NAME_TABLE, or both") endif() # A semicolon separates list elements everywhere in this language, and at least one string @@ -53,6 +61,7 @@ set(IN_BODY FALSE) set(PENDING_NAME "") set(RECORD_COUNT 0) set(RECORDS "") +set(NAMES "") set(MISSING "") foreach(line IN LISTS RC_LINES) @@ -127,6 +136,7 @@ foreach(line IN LISTS RC_LINES) # Length-prefixed, so a string that contains a newline needs no escaping of its own. string(APPEND RECORDS "${ID_${name}} ${length}\n${raw}\n") + string(APPEND NAMES "OPENTS_STRING_NAME(\"${name}\", ${ID_${name}})\n") math(EXPR RECORD_COUNT "${RECORD_COUNT} + 1") endforeach() @@ -144,11 +154,30 @@ if(RECORD_COUNT EQUAL 0) message(FATAL_ERROR "StringTable.cmake found no strings in ${RC_FILE}") endif() -get_filename_component(OUTPUT_DIR "${OUTPUT}" DIRECTORY) -if(OUTPUT_DIR) - file(MAKE_DIRECTORY "${OUTPUT_DIR}") +if(DEFINED OUTPUT) + get_filename_component(OUTPUT_DIR "${OUTPUT}" DIRECTORY) + if(OUTPUT_DIR) + file(MAKE_DIRECTORY "${OUTPUT_DIR}") + endif() + + file(WRITE "${OUTPUT}" "OPENTS-STRINGS 1\n${RECORD_COUNT}\n${RECORDS}") + + message(STATUS "String table: ${RECORD_COUNT} strings from ${DEFINE_COUNT} identifiers -> ${OUTPUT}") endif() -file(WRITE "${OUTPUT}" "OPENTS-STRINGS 1\n${RECORD_COUNT}\n${RECORDS}") +# The name table is a list of invocations rather than a declaration, so the including file +# decides what a pair becomes and the header carries no storage of its own. +if(DEFINED NAME_TABLE) + get_filename_component(NAME_TABLE_DIR "${NAME_TABLE}" DIRECTORY) + if(NAME_TABLE_DIR) + file(MAKE_DIRECTORY "${NAME_TABLE_DIR}") + endif() + + file(WRITE "${NAME_TABLE}" + "// Generated from language.rc by cmake/StringTable.cmake. Do not edit.\n" + "// Each line pairs a string resource's symbolic name with its identifier.\n" + "\n" + "${NAMES}") -message(STATUS "String table: ${RECORD_COUNT} strings from ${DEFINE_COUNT} identifiers -> ${OUTPUT}") + message(STATUS "String names: ${RECORD_COUNT} names -> ${NAME_TABLE}") +endif() diff --git a/code/language/CMakeLists.txt b/code/language/CMakeLists.txt index d5bbe0950..4bfe0e908 100644 --- a/code/language/CMakeLists.txt +++ b/code/language/CMakeLists.txt @@ -27,6 +27,29 @@ add_custom_command(TARGET Language POST_BUILD "${TS_RUN_DIR}/$" ) +# A UI document names a string by its symbolic name, so the same script that reads the resource +# script writes the table that turns a name back into its identifier. Every platform needs it, +# because every platform builds the UI shell. +set(OPENTS_STRING_NAMES "${OPENTS_GENERATED_DIR}/stringnames.hh") + +add_custom_command( + OUTPUT "${OPENTS_STRING_NAMES}" + COMMAND ${CMAKE_COMMAND} + "-DRC_FILE=${CMAKE_CURRENT_SOURCE_DIR}/language.rc" + "-DHEADER_FILE=${CMAKE_CURRENT_SOURCE_DIR}/language.h" + "-DNAME_TABLE=${OPENTS_STRING_NAMES}" + -P "${CMAKE_SOURCE_DIR}/cmake/StringTable.cmake" + DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/language.rc" + "${CMAKE_CURRENT_SOURCE_DIR}/language.h" + "${CMAKE_SOURCE_DIR}/cmake/StringTable.cmake" + COMMENT "Generating the string name table from language.rc" + VERBATIM +) + +add_custom_target(OpenTSStringNames ALL DEPENDS "${OPENTS_STRING_NAMES}") +add_dependencies(OpenTS OpenTSStringNames) + # Only the resource compiler turns the string table in language.rc into a module resource, so a # host without one reads the same strings out of a flat data file generated from the same # script. The Windows build neither generates nor ships it and keeps using LoadString. diff --git a/code/ui/uisystem.cpp b/code/ui/uisystem.cpp index 808f60055..02c003480 100644 --- a/code/ui/uisystem.cpp +++ b/code/ui/uisystem.cpp @@ -16,6 +16,7 @@ #include "uiinternal.h" +#include "data.h" #include "dbgprint.h" #include "hostclock.h" @@ -24,6 +25,7 @@ #include #include +#include static unsigned int _StartTime = 0; @@ -32,16 +34,29 @@ static std::string _CursorName; /// /// Looks a document's string name up in the engine's string table. -/// The generated name table arrives with the UTF-8 transition, which docs/UI_DESIGN.md -/// makes a prerequisite of the first screen that shows text. Until then every name is -/// unknown and the document's own text is what appears. +/// The name table is generated from language.rc by the script that also builds the portable +/// string table, so the resource script stays the only place a name and a number are paired. +/// An unknown name leaves the reference in the text, which is what makes a missing string +/// visible rather than silent. /// /// bool; Was the name resolved? static bool Lookup_String(std::string const & name, std::string & text) { - (void)name; - (void)text; - return(false); + static std::unordered_map const _names = { +#define OPENTS_STRING_NAME(symbol, id) { symbol, id }, +#include "stringnames.hh" +#undef OPENTS_STRING_NAME + }; + + auto const found = _names.find(name); + if (found == _names.end()) { + return(false); + } + + // Fetch_String already yields UTF-8, so the shell copies the bytes out of its cache and + // hands them to RmlUi unchanged. + text = Fetch_String(found->second); + return(true); } From 4aabb8bf09340d1f96129abd7a0a0361863d6ac8 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 01:07:59 +0100 Subject: [PATCH 079/179] docs(manual): classify the UI shell's modifier reads and document LegacyDialogs The command extractor demanded a classification for every GetKeyState site the shell added, which stopped manage.py update and every page behind it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- manual/content/keys/legacydialogs.md | 13 ++++++++++ manual/data/command-adapters.yaml | 37 ++++++++++++++++++++++++++++ manual/data/ini-keys.yaml | 22 +++++++++++++++-- 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 manual/content/keys/legacydialogs.md diff --git a/manual/content/keys/legacydialogs.md b/manual/content/keys/legacydialogs.md new file mode 100644 index 000000000..5ee1c4f9f --- /dev/null +++ b/manual/content/keys/legacydialogs.md @@ -0,0 +1,13 @@ +--- +key: LegacyDialogs +summary: Returns the rebuilt screens to the dialogs they replaced. +when_omitted: + kind: value + value: "no" +--- + +The game's dialogs are being rebuilt one screen at a time. A screen that has been rebuilt keeps the dialog it replaced alongside it, and `LegacyDialogs=yes` is what selects the old one. Set it when a rebuilt screen misbehaves, so that the screen can still be reached while the fault is reported. + +The choice is read once per screen, as the screen opens, so a running screen is never swapped for the other one. Screens that have not been rebuilt are unaffected either way, and a rebuilt screen whose files cannot be loaded falls back to its dialog on its own without the key being set. + +The key exists only while both halves do. It is written back to `sun.ini` with the rest of `[Options]`, and it goes when the last dialog does. diff --git a/manual/data/command-adapters.yaml b/manual/data/command-adapters.yaml index 3496023ef..016dfe413 100644 --- a/manual/data/command-adapters.yaml +++ b/manual/data/command-adapters.yaml @@ -422,6 +422,43 @@ fixed_exclusions: reason: Encoded sidebar gadget IDs and input flags, not physical bindings. - site: { file: code/tab.cpp, function: TabClass::AI, expression: KN_LMOUSE } reason: Tactical-tab hit-testing handled as pointer input. + - sites: + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_F1 } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_F12 } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_BACK } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_TAB } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_RETURN } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_ESCAPE } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_SPACE } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_PRIOR } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_NEXT } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_END } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_HOME } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_LEFT } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_UP } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_RIGHT } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_DOWN } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_INSERT } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_DELETE } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_SHIFT } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_CONTROL } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_MENU } + reason: >- + Translates a Windows virtual key into the identifier the UI toolkit names it by. The + shell hands the key to whichever document has the input scope; the key's meaning is the + document's, so no site here is a game command. + - sites: + - { file: code/ui/uishell.cpp, function: Key_Modifiers, expression: VK_CONTROL } + - { file: code/ui/uishell.cpp, function: Key_Modifiers, expression: VK_SHIFT } + - { file: code/ui/uishell.cpp, function: Key_Modifiers, expression: VK_MENU } + reason: Reads the modifier keys held while a UI message is delivered. It reports state and dispatches nothing. + - sites: + - { file: code/ui/uishell.cpp, function: Handle_Developer_Key, expression: VK_CONTROL, guard: "!NDEBUG" } + - { file: code/ui/uishell.cpp, function: Handle_Developer_Key, expression: VK_SHIFT, guard: "!NDEBUG" } + - { file: code/ui/uishell.cpp, function: Handle_Developer_Key, expression: VK_CONTROL+VK_SHIFT, guard: "!NDEBUG" } + reason: >- + Reads the modifiers that qualify the shell's Debug-only developer keys. The keys those + modifiers qualify are the controls; this test is not one of them. - sites: - { file: code/wdtsel.cpp, function: Selection::Pick_Territory, expression: KN_NONE } - { file: code/wdtsel.cpp, function: Selection::Pick_Territory, expression: KN_LMOUSE } diff --git a/manual/data/ini-keys.yaml b/manual/data/ini-keys.yaml index 2f4d3e79a..1435e9718 100644 --- a/manual/data/ini-keys.yaml +++ b/manual/data/ini-keys.yaml @@ -12774,6 +12774,24 @@ LastTilesInSet: source: code/isotype.cpp guard: null level: IsometricTileTypeClass +LegacyDialogs: + key: LegacyDialogs + scopes: + - applies_to: + - client settings + file: sun.ini + section: + kind: literal + name: Options + value_type: boolean + status: generated + _provenance: + default_candidate: 'no' + declared_in: OptionsClass + member: LegacyDialogs + source: code/options.cpp + guard: null + level: OptionsClass LegalTarget: key: LegalTarget scopes: @@ -13123,10 +13141,10 @@ Locomotor: section: kind: identifier source: object-type - value_type: Locomotor CLSID + value_type: classid status: generated _provenance: - default_candidate: the Teleport locomotor + default_candidate: null declared_in: TechnoTypeClass member: Locomotor source: code/techtype.cpp From b919274b578f9180fee40e956a59a0abb7f68641 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 01:12:54 +0100 Subject: [PATCH 080/179] feat(ui): show the message and wait boxes through RmlUi Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/msgbox.cpp | 16 ++ code/ownrdraw.cpp | 36 ++++ code/ui/uiinternal.h | 3 + code/ui/uimessagebox.cpp | 403 +++++++++++++++++++++++++++++++++++++++ code/ui/uimessagebox.h | 41 ++++ code/ui/uiscreen.h | 4 + code/ui/uishell.cpp | 18 +- ui/messagebox.rcss | 99 ++++++++++ ui/messagebox.rml | 14 ++ ui/waitbox.rcss | 85 +++++++++ ui/waitbox.rml | 12 ++ 11 files changed, 730 insertions(+), 1 deletion(-) create mode 100644 code/ui/uimessagebox.cpp create mode 100644 code/ui/uimessagebox.h create mode 100644 ui/messagebox.rcss create mode 100644 ui/messagebox.rml create mode 100644 ui/waitbox.rcss create mode 100644 ui/waitbox.rml diff --git a/code/msgbox.cpp b/code/msgbox.cpp index 9877bd534..b7355d326 100644 --- a/code/msgbox.cpp +++ b/code/msgbox.cpp @@ -39,6 +39,8 @@ #include "globals.h" #include "init.h" #include "ownrdraw.h" +#include "ui/uimessagebox.h" +#include "ui/uishell.h" #include "winfix.h" INT_PTR CALLBACK Message_Box_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); @@ -80,6 +82,20 @@ int WWMessageBox::_Process(const char * msg, int defresponse, const char * b1txt int retval = -1; int numbuttons = 0; + if (UI_Use_Rml()) { + UIResult const result = UI_Message_Box_Screen(msg, defresponse, b1txt, b2txt, b3txt); + + // A session that ended under the box is what the dialog driver reported by leaving + // the result unset, and the caller reads that as the -1 it started from. + if (result.Outcome == UIResult::OUTCOME_SESSION_ENDED) { + return(-1); + } + + if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { + return(result.Value); + } + } + _default_response = defresponse; HWND dialog = OwnerDraw::Begin_Dialog(IDD_MSGBOX_3, Message_Box_Proc); diff --git a/code/ownrdraw.cpp b/code/ownrdraw.cpp index 7d7592e10..01c4ee7f2 100644 --- a/code/ownrdraw.cpp +++ b/code/ownrdraw.cpp @@ -41,6 +41,8 @@ #include "session.h" #include "srfcache.h" #include "theme.h" +#include "ui/uimessagebox.h" +#include "ui/uishell.h" #include "utf8.h" #include "voc.h" #include "vox.h" @@ -119,6 +121,20 @@ LRESULT CALLBACK GroupBoxCtrlProc(HWND window, UINT message, WPARAM wparam, LPAR LRESULT CALLBACK HotkeyCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); INT_PTR CALLBACK Custom_Message_Box_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); + +/// +/// The handle that stands for the rebuilt wait box. +/// That box is a document rather than a window, but its callers hold a handle and pass it +/// back to Display_Dialog, Set_Custom_Message_Box_Text and End_Dialog, so it is given one +/// no window can have. The handle and the three tests that answer to it go with OwnerDraw. +/// +static HWND Wait_Box_Handle(void) +{ + static HWND__ _token; + return(&_token); +} + + BOOL CALLBACK ODRemoveFromDict(HWND window, LPARAM); int WINAPI ODUpdateWindowRect(HWND window, RECT *rect); bool ODGetFontMetrics(char const *font_name, FontMetrics *metrics); @@ -6777,6 +6793,12 @@ HWND OwnerDraw::Begin_Dialog(int id, DLGPROC proc) void OwnerDraw::End_Dialog(HWND window) { Keyboard->Clear(); + + if (window == Wait_Box_Handle()) { + UI_Wait_Box_Close(); + return; + } + DestroyWindow(window); for (int index = 0; index < g_DialogCount; index++) { @@ -6817,6 +6839,11 @@ void OwnerDraw::End_Dialog(HWND window) /// void OwnerDraw::Display_Dialog(HWND window) { + if (window == Wait_Box_Handle()) { + Keyboard->Clear(); + return; + } + ShowWindow(window, SW_SHOWNORMAL); SetForegroundWindow(window); Keyboard->Clear(); @@ -6966,6 +6993,10 @@ int OwnerDraw::Move_Dialog(HWND window, int x, int y) /// created. HWND OwnerDraw::Custom_Message_Box(const char *btn1txt, const char *btn2txt, bool * cancelled) { + if (UI_Use_Rml() && UI_Wait_Box_Open(btn1txt, btn2txt, cancelled)) { + return(Wait_Box_Handle()); + } + HWND dlg = OwnerDraw::Begin_Dialog(IDD_MSGBOX_1, Custom_Message_Box_Proc); SetWindowLongPtr(dlg, DWLP_USER, (LONG_PTR)cancelled); @@ -7014,6 +7045,11 @@ INT_PTR CALLBACK Custom_Message_Box_Proc(HWND window, UINT message, WPARAM wpara /// void OwnerDraw::Set_Custom_Message_Box_Text(HWND window, LPCSTR text) { + if (window == Wait_Box_Handle()) { + UI_Wait_Box_Set_Text(text); + return; + } + SetDlgItemText(window, IDC_MSGBOX_TEXT, text); UpdateWindow(window); } diff --git a/code/ui/uiinternal.h b/code/ui/uiinternal.h index 843d3c4b4..60b286654 100644 --- a/code/ui/uiinternal.h +++ b/code/ui/uiinternal.h @@ -48,6 +48,9 @@ void UI_Render_Begin(int destx, int desty, int width, int height); void UI_Render_End(void); void UI_Render_ImGui(ImDrawData * data); +// uimessagebox.cpp +void UI_Message_Box_Service(void); + // uisystem.cpp Rml::SystemInterface * UI_System_Interface(void); diff --git a/code/ui/uimessagebox.cpp b/code/ui/uimessagebox.cpp new file mode 100644 index 000000000..27e470753 --- /dev/null +++ b/code/ui/uimessagebox.cpp @@ -0,0 +1,403 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The message box and the wait box. The message box is the first screen that runs the game +// underneath itself: in a network session the modal runner keeps stepping Main_Loop while +// the box owns the input, which is what WWMessageBox::Process has always done through +// OwnerDraw::Dialog_Message_Handler. +// +// What is preserved from the dialog, and why each of these is here rather than obvious: +// the buttons are laid out first, third, second across the box, which is the order the +// IDD_MSGBOX_3 template places them in; a lone button moves to the middle slot; Enter +// answers with the caller's default response rather than with a button, because the +// template names no default push button and Windows then sends IDOK; Escape answers with +// button two, because Windows sends IDCANCEL whether or not that button exists; and a box +// with no button at all answers with zero without waiting. +// +// docs/UI_DESIGN.md, "Screens" and "Scheduling", own the contracts this keeps to. + +#include "always.h" + +#include "uimessagebox.h" + +#include "uiinternal.h" +#include "uirmlview.h" + +#include "_keyboar.h" +#include "globals.h" +#include "init.h" +#include "keyboard.h" + +#include +#include +#include + +#include +#include + + +// The vocabulary a document has. A press names the button it came from; the other two are +// the keyboard's answers, which are not buttons and do not carry an index of their own. +static char const * const ACTION_PRESS = "press"; +static char const * const ACTION_DEFAULT = "default"; +static char const * const ACTION_CANCEL = "cancel"; + +// The button index Windows produces for the Escape key, which reaches the dialog as +// IDCANCEL and so answers with the second button whether or not one is shown. +static int const ESCAPE_RESPONSE = 1; + + +/// +/// The toolkit-free half of the message box. +/// +class MessageBoxPresenterClass : public UIPresenterClass +{ + public: + // The view-model. Plain values, and the only thing a document reads. + std::string Message; + std::string Buttons[3]; + bool Shown[3] = { false, false, false }; + + // Does the first button sit in the middle slot? It does when it is the only one, as + // the dialog moved it there. + bool FirstIsCentred = false; + + int DefaultResponse = 0; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override {} + virtual void Service(void) override; +}; + + +/// +/// Answers an intent the view raised. Every one of them ends the screen, so the mapping +/// from what the player did to what the caller is told is the whole of the behavior. +/// +void MessageBoxPresenterClass::Execute(UIIntent const & intent) +{ + UIResult result; + + if (intent.Action == ACTION_PRESS) { + if (intent.Value < 0 || intent.Value > 2 || !Shown[intent.Value]) { + return; + } + result.Outcome = UIResult::OUTCOME_ACCEPTED; + result.Value = intent.Value; + } else if (intent.Action == ACTION_DEFAULT) { + result.Outcome = UIResult::OUTCOME_ACCEPTED; + result.Value = DefaultResponse; + } else if (intent.Action == ACTION_CANCEL) { + result.Outcome = UIResult::OUTCOME_CANCELLED; + result.Value = ESCAPE_RESPONSE; + } else { + return; + } + + Result = result; +} + + +/// +/// The maintenance the dialog driver ran on every pass of its own loop. +/// +void MessageBoxPresenterClass::Service(void) +{ + if (!GameActive) { + Title_Screen_Restore(); + } +} + + +/// +/// The RmlUi half of the message box. +/// +class MessageBoxViewClass : public UIRmlViewClass +{ + public: + MessageBoxViewClass(MessageBoxPresenterClass & presenter); + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + private: + void Answer(char const * action, int value); + + MessageBoxPresenterClass & Screen; +}; + + +MessageBoxViewClass::MessageBoxViewClass(MessageBoxPresenterClass & presenter) : + UIRmlViewClass(presenter, "messagebox.rml"), + Screen(presenter) +{ +} + + +void MessageBoxViewClass::Answer(char const * action, int value) +{ + UIIntent intent; + intent.Action = action; + intent.Value = value; + Screen.Queue(intent); +} + + +void MessageBoxViewClass::Bind(Rml::DataModelConstructor & model) +{ + model.Bind("message", &Screen.Message); + model.Bind("button1", &Screen.Buttons[0]); + model.Bind("button2", &Screen.Buttons[1]); + model.Bind("button3", &Screen.Buttons[2]); + model.Bind("shown1", &Screen.Shown[0]); + model.Bind("shown2", &Screen.Shown[1]); + model.Bind("shown3", &Screen.Shown[2]); + model.Bind("centred", &Screen.FirstIsCentred); + + // An event handler never acts: it queues, and the runner executes the queue after + // Context::Update has returned. + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + Answer(ACTION_PRESS, arguments.empty() ? 0 : (int)arguments[0].Get()); + }); + + // Enter and Escape are the box's own hotkeys, and neither answers with a button: Enter + // yields the caller's default response and Escape the second button's index. + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + + if (key == Rml::Input::KI_RETURN || key == Rml::Input::KI_NUMPADENTER) { + Answer(ACTION_DEFAULT, 0); + } else if (key == Rml::Input::KI_ESCAPE) { + Answer(ACTION_CANCEL, 0); + } + }); +} + + +void MessageBoxViewClass::Sync(void) +{ + // Nothing an intent can execute changes the view-model. Every one of them ends the + // screen instead. +} + + +/// +/// Shows a message and waits for the player to answer it. +/// +UIResult UI_Message_Box_Screen(char const * message, int defresponse, + char const * b1txt, char const * b2txt, char const * b3txt) +{ + // The presenter is declared first so that it is destroyed last: the data model the view + // binds reads the presenter's view-model, and must not outlive it. + MessageBoxPresenterClass presenter; + MessageBoxViewClass view(presenter); + + char const * const captions[3] = { b1txt, b2txt, b3txt }; + + // The dialog counted its buttons this way: each caption that is present raises the count + // to its own slot, so a box that skips a slot still counts by the highest one filled. + int count = 0; + for (int index = 0; index < 3; index++) { + if (captions[index] != nullptr && captions[index][0] != '\0') { + presenter.Buttons[index] = captions[index]; + presenter.Shown[index] = true; + count = index + 1; + } + } + + // A box with nothing to press is answered for the player, without a pass of the loop and + // so without a frame in which it could be seen. + if (count == 0) { + UIResult result; + result.Outcome = UIResult::OUTCOME_ACCEPTED; + result.Value = 0; + return(result); + } + + presenter.FirstIsCentred = (count == 1); + presenter.DefaultResponse = defresponse; + + if (message != nullptr) { + presenter.Message = message; + } + + if (!view.Prepare(true)) { + UIResult result; + result.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + return(result); + } + + UIResult const result = UI_Run_Modal(presenter, view); + view.Close(); + return(result); +} + + +//--------------------------------------------------------------------------------------- +// The wait box. It stands over a long operation rather than waiting for an answer, so it +// is not modal and the caller's own loop keeps running underneath it. +//--------------------------------------------------------------------------------------- + +class WaitBoxPresenterClass : public UIPresenterClass +{ + public: + std::string Message; + std::string CancelCaption; + bool CanCancel = false; + + // The caller's flag, raised when the player cancels. It is the caller's storage, the + // way the dialog kept it in DWLP_USER, and it outlives the box. + bool * Cancelled = nullptr; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override {} +}; + + +void WaitBoxPresenterClass::Execute(UIIntent const & intent) +{ + if (intent.Action != ACTION_CANCEL || !CanCancel) { + return; + } + + // The escape key is what the operation underneath is watching for; the flag only tells + // the caller which of the two ways out was taken. + if (Keyboard != nullptr) { + Keyboard->Put(KN_ESC); + } + + if (Cancelled != nullptr) { + *Cancelled = true; + } +} + + +class WaitBoxViewClass : public UIRmlViewClass +{ + public: + WaitBoxViewClass(WaitBoxPresenterClass & presenter); + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + private: + WaitBoxPresenterClass & Screen; +}; + + +WaitBoxViewClass::WaitBoxViewClass(WaitBoxPresenterClass & presenter) : + UIRmlViewClass(presenter, "waitbox.rml"), + Screen(presenter) +{ +} + + +void WaitBoxViewClass::Bind(Rml::DataModelConstructor & model) +{ + model.Bind("message", &Screen.Message); + model.Bind("cancelcaption", &Screen.CancelCaption); + model.Bind("cancancel", &Screen.CanCancel); + + model.BindEventCallback("cancel", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const &) { + UIIntent intent; + intent.Action = ACTION_CANCEL; + Screen.Queue(intent); + }); +} + + +void WaitBoxViewClass::Sync(void) +{ + if (Model) { + Model.DirtyVariable("message"); + } +} + + +// The one wait box. Every caller opens one, holds it for the length of an operation and +// closes it, and no caller opens a second while one is up. +static std::unique_ptr _WaitPresenter; +static std::unique_ptr _WaitView; + + +bool UI_Wait_Box_Open(char const * message, char const * cancelcaption, bool * cancelled) +{ + if (_WaitView != nullptr) { + return(false); + } + + // The presenter is created first so that it is destroyed last, for the same reason the + // modal screens declare it first. + _WaitPresenter = std::make_unique(); + _WaitView = std::make_unique(*_WaitPresenter); + + if (message != nullptr) { + _WaitPresenter->Message = message; + } + + if (cancelcaption != nullptr && cancelcaption[0] != '\0') { + _WaitPresenter->CancelCaption = cancelcaption; + _WaitPresenter->CanCancel = true; + } + + _WaitPresenter->Cancelled = cancelled; + + if (!_WaitView->Prepare(false)) { + _WaitView.reset(); + _WaitPresenter.reset(); + return(false); + } + + return(true); +} + + +void UI_Wait_Box_Set_Text(char const * message) +{ + if (_WaitView == nullptr) { + return; + } + + _WaitPresenter->Message = (message != nullptr) ? message : ""; + _WaitView->Sync(); +} + + +void UI_Wait_Box_Close(void) +{ + if (_WaitView == nullptr) { + return; + } + + _WaitView->Close(); + _WaitView.reset(); + _WaitPresenter.reset(); +} + + +bool UI_Wait_Box_Is_Open(void) +{ + return(_WaitView != nullptr); +} + + +/// +/// Executes what the wait box's own events queued. +/// The box has no loop of its own, so the shell's tick is its safe point: the queue is +/// drained after Context::Update has returned and never from inside an event handler. +/// +void UI_Message_Box_Service(void) +{ + if (_WaitPresenter != nullptr) { + _WaitPresenter->Drain(); + } +} diff --git a/code/ui/uimessagebox.h b/code/ui/uimessagebox.h new file mode 100644 index 000000000..c24734fad --- /dev/null +++ b/code/ui/uimessagebox.h @@ -0,0 +1,41 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The message box and the wait box, the two screens every other screen can open. Only the +// result contract crosses this header, so a caller carries no toolkit and no window handle. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + + +// Shows a message with up to three buttons and does not return until one is answered. The +// result's Value is the index of the button the player picked, counted the way +// WWMessageBox::Process counts them; GameEnded says the session ended underneath the box. +// OUTCOME_FAILED_TO_OPEN means nothing was shown, which is the caller's cue to open the +// legacy dialog instead. +UIResult UI_Message_Box_Screen(char const * message, int defresponse, + char const * b1txt, char const * b2txt, char const * b3txt); + + +// Opens the box that stands over a long operation. It is not modal: the caller keeps +// running its own loop underneath and closes the box when the operation finishes. +// +// A caption for the cancel button shows it; cancelling raises the flag and feeds an escape +// key to the game keyboard, as the dialog's cancel button does. The flag is read while the +// box is open, so it must outlive it. +bool UI_Wait_Box_Open(char const * message, char const * cancelcaption, bool * cancelled); + +// Replaces the text of the box that is already showing. +void UI_Wait_Box_Set_Text(char const * message); + +void UI_Wait_Box_Close(void); +bool UI_Wait_Box_Is_Open(void); diff --git a/code/ui/uiscreen.h b/code/ui/uiscreen.h index bd8366af2..0a8bd5837 100644 --- a/code/ui/uiscreen.h +++ b/code/ui/uiscreen.h @@ -75,6 +75,10 @@ class UIPresenterClass virtual void Execute(UIIntent const & intent) = 0; virtual void Refresh(void) = 0; + // The maintenance a screen's own driver ran on every pass of its loop, kept where it + // was rather than moved into the runner. + virtual void Service(void) {} + std::optional Result; bool IsClosing = false; diff --git a/code/ui/uishell.cpp b/code/ui/uishell.cpp index dd05cc26e..3381c6a16 100644 --- a/code/ui/uishell.cpp +++ b/code/ui/uishell.cpp @@ -57,6 +57,11 @@ static bool _Changing = false; // than flags. static int _ModalDepth = 0; +// How many modal runners are on the stack. A runner owns the context between its own +// passes, so the tick that Main_Loop and Call_Back make from inside one is dropped rather +// than updating the context a second time in the same pass. +static int _RunningModal = 0; + // The window holds the mouse capture while a gesture a toolkit consumed is in progress. // The owner of a press owns its release, so a press that crossed into the game or out of @@ -291,7 +296,7 @@ void UI_Tick(void) // what keeps a pump reached from inside an update out of it. static bool ticking = false; - if (!_Initialized || _Context == nullptr || _Changing || ticking) { + if (!_Initialized || _Context == nullptr || _Changing || ticking || _RunningModal > 0) { return; } @@ -302,6 +307,7 @@ void UI_Tick(void) _LastTickTime = now; _Context->Update(); + UI_Message_Box_Service(); // RmlUi cannot say whether it needs redrawing, so anything on screen marks the overlay // on every tick and the present pacing caps the rate. @@ -730,8 +736,13 @@ UIResult UI_Run_Modal(UIPresenterClass & presenter, UIRmlViewClass & view) return(result); } + // Main_Loop can reach a screen of its own, and the guard is the one + // OwnerDraw::Dialog_Message_Handler keeps for the same reason: the inner driver services + // the game with a callback rather than stepping it twice. static bool inmainloop = false; + _RunningModal++; + while (!presenter.Result.has_value()) { Windows_Message_Handler(); @@ -742,6 +753,7 @@ UIResult UI_Run_Modal(UIPresenterClass & presenter, UIRmlViewClass & view) inmainloop = false; if (ended) { + _RunningModal--; result.Outcome = UIResult::OUTCOME_SESSION_ENDED; result.GameEnded = true; return(result); @@ -751,13 +763,17 @@ UIResult UI_Run_Modal(UIPresenterClass & presenter, UIRmlViewClass & view) Call_Back(); } + presenter.Service(); + _Context->Update(); presenter.Drain(); view.Sync(); + UI_Message_Box_Service(); Mark_Overlay_Dirty(); Video_Present_If_Dirty(); } + _RunningModal--; return(presenter.Result.value()); } diff --git a/ui/messagebox.rcss b/ui/messagebox.rcss new file mode 100644 index 000000000..29ce1fe0c --- /dev/null +++ b/ui/messagebox.rcss @@ -0,0 +1,99 @@ +/* The message box. Its geometry is the IDD_MSGBOX_3 template's, converted from dialog units + at the 8 point MS Sans Serif the template names: 1.5 pixels across and 1.625 down, with a + child's offset taken from the panel's content box and a bordered element's declared size + taken inside its own border. + + The panel is drawn rather than blitted because the dialog's own background is a PCX, and + PCX decoding arrives with the first screen that shows game art. Everything here stays + inside the styling profile docs/UI_DESIGN.md declares: text, ordinary layout, borders and + basic decorators, with no filter, layer, shader or transform. */ + +body +{ + font-family: LatoLatin; + font-size: 12dp; + color: #d6d8cc; + + width: 100%; + height: 100%; +} + +/* 260 x 84 dialog units. The panel is centred on the frame and re-centres itself when the + frame is resized, because the offsets are a proportion of the context. */ +#dialog +{ + display: block; + position: absolute; + left: 50%; + top: 50%; + margin-left: -195dp; + margin-top: -68.25dp; + + width: 386dp; + height: 132.5dp; + + background-color: #23261fF2; + border-width: 2dp; + border-top-color: #6e7360; + border-left-color: #6e7360; + border-right-color: #10120e; + border-bottom-color: #10120e; +} + +/* The message: 22, 12, 216 x 38 dialog units, centred both ways as the template's CTEXT with + SS_CENTERIMAGE was. A message that carries its own line breaks keeps them. */ +#text +{ + display: block; + position: absolute; + left: 31dp; + top: 17.5dp; + width: 324dp; + height: 61.75dp; + + text-align: center; + white-space: pre-line; + overflow: hidden; +} + +/* The three buttons, 60 x 14 dialog units each, on one row 58 units down. They are laid out + first, third, second across the box, which is where the template puts them. */ +.button +{ + display: block; + position: absolute; + top: 92.25dp; + width: 86dp; + height: 18.75dp; + line-height: 18.75dp; + text-align: center; + + color: #e4e6da; + background-color: #33372c; + border-width: 2dp; + border-top-color: #767c68; + border-left-color: #767c68; + border-right-color: #14160f; + border-bottom-color: #14160f; +} + +.first { left: 31dp; } +.third { left: 148dp; } +.second { left: 265dp; } + +/* A lone button takes the middle slot, as the dialog moved it there. */ +.first.centred { left: 148dp; } + +.button:hover +{ + background-color: #474d3d; +} + +.button:active +{ + background-color: #2a2e24; + border-top-color: #14160f; + border-left-color: #14160f; + border-right-color: #767c68; + border-bottom-color: #767c68; +} diff --git a/ui/messagebox.rml b/ui/messagebox.rml new file mode 100644 index 000000000..5162f8bd1 --- /dev/null +++ b/ui/messagebox.rml @@ -0,0 +1,14 @@ + + + Message + + + +
+
{{ message }}
+
{{ button1 }}
+
{{ button3 }}
+
{{ button2 }}
+
+ +
diff --git a/ui/waitbox.rcss b/ui/waitbox.rcss new file mode 100644 index 000000000..02e635fca --- /dev/null +++ b/ui/waitbox.rcss @@ -0,0 +1,85 @@ +/* The wait box that stands over a long operation. Its geometry is the IDD_MSGBOX_1 + template's, converted from dialog units the way ui/messagebox.rcss describes. The cancel + button is hidden unless the caller supplies a caption, which is the template's own + NOT WS_VISIBLE. */ + +body +{ + font-family: LatoLatin; + font-size: 12dp; + color: #d6d8cc; + + width: 100%; + height: 100%; +} + +/* 218 x 64 dialog units. */ +#dialog +{ + display: block; + position: absolute; + left: 50%; + top: 50%; + margin-left: -163.5dp; + margin-top: -52dp; + + width: 323dp; + height: 100dp; + + background-color: #23261fF2; + border-width: 2dp; + border-top-color: #6e7360; + border-left-color: #6e7360; + border-right-color: #10120e; + border-bottom-color: #10120e; +} + +/* 22, 12, 174 x 23 dialog units. */ +#text +{ + display: block; + position: absolute; + left: 31dp; + top: 17.5dp; + width: 261dp; + height: 37.375dp; + + text-align: center; + white-space: pre-line; + overflow: hidden; +} + +/* 83, 38, 50 x 14 dialog units. */ +#cancel +{ + display: block; + position: absolute; + left: 122.5dp; + top: 59.75dp; + width: 71dp; + height: 18.75dp; + line-height: 18.75dp; + text-align: center; + + color: #e4e6da; + background-color: #33372c; + border-width: 2dp; + border-top-color: #767c68; + border-left-color: #767c68; + border-right-color: #14160f; + border-bottom-color: #14160f; +} + +#cancel:hover +{ + background-color: #474d3d; +} + +#cancel:active +{ + background-color: #2a2e24; + border-top-color: #14160f; + border-left-color: #14160f; + border-right-color: #767c68; + border-bottom-color: #767c68; +} diff --git a/ui/waitbox.rml b/ui/waitbox.rml new file mode 100644 index 000000000..48e54a5a6 --- /dev/null +++ b/ui/waitbox.rml @@ -0,0 +1,12 @@ + + + Please wait + + + +
+
{{ message }}
+
{{ cancelcaption }}
+
+ +
From 95aa3d3898f8291bb9d5fe02bbea8f97dd5f6278 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 01:39:07 +0100 Subject: [PATCH 081/179] docs: record step 4 of the UI migration Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/UI_DESIGN.md | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index 469f2303f..c0b58d05f 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -1,7 +1,7 @@ # UI system design -Status: in progress. Steps 1 to 3 of the migration plan have landed; nothing -from step 4 onward is implemented. Everything outside the migration plan +Status: in progress. Steps 1 to 4 of the migration plan have landed; nothing +from step 5 onward is implemented. Everything outside the migration plan remains a proposal informed by source inspection and upstream documentation. This page owns the UI architecture and migration; [Building OpenTS](BUILDING.md) owns build support and [Project @@ -14,9 +14,8 @@ shader, whose vertex stage multiplies by `u_viewProj` alone and so ignores the per-draw model transform; a program with a model transform restores the static vertex buffer the renderer table describes. `uitexture.cpp` reads PNG and TGA only, so PCX, SHP and the `` element wait for the first screen -that shows game art; `uisystem.cpp` carries the `[[NAME]]` syntax but no name -table, which arrives with the UTF-8 transition; and the cursor and clipboard -requests are recorded rather than acted on. +that shows game art, and the cursor and clipboard requests are recorded rather +than acted on. Step 3 exercised the rest. `UI_Run_Modal` now runs a screen, and the input hook gained the modal scope its rules always described: while an exclusive document @@ -25,10 +24,20 @@ legacy dialog, and the keyboard queue is cleared as the scope opens and closes. The version screen needed no name table, because it composes its own text and takes its one string-table entry through `Fetch_String`, which already yields UTF-8 on a build whose active code page is 65001; a document that writes -`[[TXT_OK]]` still waits for the UTF-8 change. The screen draws its panel rather -than blitting `dbak6440.pcx`, which waits for PCX decoding with the rest of the +`[[TXT_OK]]` waited for the name table step 4 brought. The screen draws its +panel rather than blitting `dbak6440.pcx`, which waits for PCX decoding with the rest of the game art. +Step 4 put the runner under load. `UI_Run_Modal` runs the game as well as a +screen: in a network session it steps `Main_Loop` between passes and reports a +session that ended underneath the box, which is what `WWMessageBox::Process` +has always had through `OwnerDraw::Dialog_Message_Handler`. A presenter gained +`Service`, the maintenance a dialog driver ran on every pass of its own loop, +and the shell drops the tick that `Main_Loop` and `Call_Back` make from inside +a runner, so a pass updates the context once. The `[[NAME]]` table arrived with +it, generated from `language.rc` by the script that already builds the portable +string table. + ## Where the UI stands today OpenTS has four UI systems plus a few bespoke screens. They share the software @@ -568,9 +577,11 @@ byte, which bounds in-game text to the range the transition supports. Documents reference strings by name: `[[TXT_OK]]`. RmlUi passes every text node through `SystemInterface::TranslateString`, where the shell maps the -name to its identifier. The names are `#define`s in `language.h`, so a CMake -script generates the name table into the build's generated directory; no -hand-maintained list. Dynamic text, including player and map names and error +name to its identifier. `cmake/StringTable.cmake` writes that table into the +build's generated directory from the same `language.rc` it reads for the +portable string table, so the resource script stays the only place a name and a +number are paired and no list is hand-maintained. A name the table does not +carry is left in the text rather than replaced with nothing. Dynamic text, including player and map names and error strings, is inserted as text, never as markup. ## Configuration @@ -583,7 +594,7 @@ by default when its evidence is in without a key per family. The key is deleted with OwnerDraw. There is no build option: RmlUi and ImGui are always compiled and linked, so one configuration matrix carries the evidence. `Options` reads and writes the key where it handles `[Video]` today, and the -key gets a manual page. A sidebar view key follows the sidebar view. +key has its manual page. A sidebar view key follows the sidebar view. ## Dear ImGui @@ -726,7 +737,12 @@ text beyond an ASCII test document. `OwnerDraw::Custom_Message_Box` behind the kill switch, preserving button order, default button, Escape, the no-button case, return mappings, and session-end interruption. Evidence includes the multiplayer cases where - `Main_Loop` runs under the box. + `Main_Loop` runs under the box. Landed: `code/ui/uimessagebox.cpp` with + `ui/messagebox.rml` and `ui/waitbox.rml`, the geometry converted from the + `IDD_MSGBOX_3` and `IDD_MSGBOX_1` templates. The wait box is not modal and + answers to a handle no window can have, because its callers hold one and + hand it back to `Display_Dialog`, `Set_Custom_Message_Box_Text` and + `End_Dialog`; the handle goes with OwnerDraw. 5. **Sound** (M, two changes). The behavior pilot: volumes, eligible themes, selection, availability, shuffle and repeat, immediate previews, play and stop, both templates, frontend and in-game service paths. From 1fab6430171e21e86cdff01f88fc8388f15ed32a Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 02:08:22 +0100 Subject: [PATCH 082/179] fix(net): hear a local peer whose UDP port differs from ours The receive filter discarded a broadcast the socket sent, and matched the source address alone, so two instances on one machine never heard each other. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/netsocket.h | 7 ++++++- code/netsocket_null.cpp | 1 + code/netsocket_posix.cpp | 12 ++++++++++++ code/netsocket_win32.cpp | 12 ++++++++++++ code/wspudp.cpp | 17 ++++++++++++----- tests/socketudp/socketudp.cpp | 27 ++++++++++++++++++++++++--- 6 files changed, 67 insertions(+), 9 deletions(-) diff --git a/code/netsocket.h b/code/netsocket.h index 397752e5d..d2c938e42 100644 --- a/code/netsocket.h +++ b/code/netsocket.h @@ -81,6 +81,11 @@ class SocketClass virtual void Close(void) = 0; virtual bool Is_Open(void) const = 0; + // The port the socket ended up bound to, in host order, which for a + // socket opened on port zero is the one the platform chose. Zero while + // the socket is closed. + virtual unsigned short Bound_Port(void) const = 0; + virtual bool Set_Broadcast(bool enable) = 0; virtual bool Set_Buffer_Sizes(int receive, int send) = 0; @@ -137,7 +142,7 @@ class NullSocketClass : public SocketClass void Set_Interfaces(std::vector interfaces) { Interfaces = std::move(interfaces); } - unsigned short Bound_Port(void) const { return(Port); } + unsigned short Bound_Port(void) const override { return(Port); } private: std::vector Inbound; diff --git a/code/netsocket_null.cpp b/code/netsocket_null.cpp index 9d55eb037..8c35f5412 100644 --- a/code/netsocket_null.cpp +++ b/code/netsocket_null.cpp @@ -25,6 +25,7 @@ bool NullSocketClass::Open(unsigned short port) void NullSocketClass::Close(void) { Opened = false; + Port = 0; Inbound.clear(); NextInbound = 0; } diff --git a/code/netsocket_posix.cpp b/code/netsocket_posix.cpp index 735be65e7..37d23bf26 100644 --- a/code/netsocket_posix.cpp +++ b/code/netsocket_posix.cpp @@ -60,6 +60,7 @@ class PosixSocketClass : public SocketClass bool Open(unsigned short port) override; void Close(void) override; bool Is_Open(void) const override { return(Socket >= 0); } + unsigned short Bound_Port(void) const override { return(BoundPort); } bool Set_Broadcast(bool enable) override; bool Set_Buffer_Sizes(int receive, int send) override; @@ -72,6 +73,7 @@ class PosixSocketClass : public SocketClass private: int Socket = -1; + unsigned short BoundPort = 0; }; @@ -110,6 +112,15 @@ bool PosixSocketClass::Open(unsigned short port) return(false); } + // A port of zero was bound to whatever the platform had spare, so ask for the + // one it chose; the receive pass recognizes our own broadcast by it. + BoundPort = port; + sockaddr_in bound = {}; + socklen_t bound_len = sizeof(bound); + if (getsockname(Socket, reinterpret_cast(&bound), &bound_len) == 0) { + BoundPort = ntohs(bound.sin_port); + } + // The transport polls, so the socket must never wait on a call. int nonblocking = 1; if (ioctl(Socket, FIONBIO, &nonblocking) < 0) { @@ -136,6 +147,7 @@ void PosixSocketClass::Close(void) close(Socket); Socket = -1; } + BoundPort = 0; } diff --git a/code/netsocket_win32.cpp b/code/netsocket_win32.cpp index 460b43ba6..0822de9dc 100644 --- a/code/netsocket_win32.cpp +++ b/code/netsocket_win32.cpp @@ -80,6 +80,7 @@ class WinsockSocketClass : public SocketClass bool Open(unsigned short port) override; void Close(void) override; bool Is_Open(void) const override { return(Socket != INVALID_SOCKET); } + unsigned short Bound_Port(void) const override { return(BoundPort); } bool Set_Broadcast(bool enable) override; bool Set_Buffer_Sizes(int receive, int send) override; @@ -92,6 +93,7 @@ class WinsockSocketClass : public SocketClass private: SOCKET Socket = INVALID_SOCKET; + unsigned short BoundPort = 0; bool Started = false; }; @@ -140,6 +142,15 @@ bool WinsockSocketClass::Open(unsigned short port) return(false); } + // A port of zero was bound to whatever Winsock had spare, so ask for the one it + // chose; the receive pass recognizes our own broadcast by it. + BoundPort = port; + sockaddr_in bound = {}; + int bound_len = sizeof(bound); + if (getsockname(Socket, reinterpret_cast(&bound), &bound_len) == 0) { + BoundPort = ntohs(bound.sin_port); + } + // The transport polls, so the socket must never wait on a call. u_long nonblocking = 1; if (ioctlsocket(Socket, FIONBIO, &nonblocking) == SOCKET_ERROR) { @@ -166,6 +177,7 @@ void WinsockSocketClass::Close(void) closesocket(Socket); Socket = INVALID_SOCKET; } + BoundPort = 0; } diff --git a/code/wspudp.cpp b/code/wspudp.cpp index dff61927c..35189df74 100644 --- a/code/wspudp.cpp +++ b/code/wspudp.cpp @@ -479,12 +479,19 @@ void UDPInterfaceClass::Receive_Pending(void) /* ** Make sure this packet didn't come from us. If it did then throw it away. */ + // A broadcast is delivered back to the socket that sent it, which is the echo + // this discards. The source port has to match as well as the address, because + // another instance of the game on this machine answers from the same addresses + // and is a peer, not an echo. bool ours = false; - uint32_t const from_ip = source.Get_IP(); - for ( int i=0 ; iBound_Port() : 0; + if (bound != 0 && source.Get_Port() == Socket_Network_Port(bound)) { + uint32_t const from_ip = source.Get_IP(); + for ( int i=0 ; i const wire = harness.Send("echo", self); harness.Socket->Deliver(self, wire.data(), static_cast(wire.size())); harness.Transport.Service(); - Check(harness.Drain().empty(), "a packet from one of our own addresses is thrown away"); + Check(harness.Drain().empty(), "a packet from our own address and port is thrown away"); +} + + +/// Another instance of the game on this machine sends from one of our own +/// addresses and a port of its own. It is a peer, so it must be heard. +void Test_Local_Peer_On_Another_Port_Is_Heard(void) +{ + uint32_t const mine = 0x0100007f; + + Harness harness({{mine, 0}}); + IPXAddressClass const peer = Peer(mine, 50001); + + std::vector const wire = harness.Send("neighbour", peer); + + harness.Socket->Deliver(peer, wire.data(), static_cast(wire.size())); + harness.Transport.Service(); + + std::vector const got = harness.Drain(); + Check(got.size() == 1 && got[0] == "neighbour", "a local address on another port is heard"); } @@ -323,6 +343,7 @@ int main(void) Test_Drain_Is_Bounded(); Test_Reset_Does_Not_End_The_Pass(); Test_Own_Address_Is_Discarded(); + Test_Local_Peer_On_Another_Port_Is_Heard(); Test_Malformed_Is_Rejected(); Test_Tunnel_Framing(); Test_Broadcast_Addresses(); From 2efced73089d9aeec3513f9c7bf63ad28890ad7f Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 02:08:27 +0100 Subject: [PATCH 083/179] refactor(ui): put the sound screen's behaviour behind a presenter The dialog procedure now reads and writes the presenter and queues its intents for the driver to execute, as docs/UI_DESIGN.md requires. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/sounddlg.cpp | 354 ++++++++++++++++++++++---------------------- code/ui/uisound.cpp | 173 ++++++++++++++++++++++ code/ui/uisound.h | 75 ++++++++++ 3 files changed, 428 insertions(+), 174 deletions(-) create mode 100644 code/ui/uisound.cpp create mode 100644 code/ui/uisound.h diff --git a/code/sounddlg.cpp b/code/sounddlg.cpp index 6a8c817c8..578b7956a 100644 --- a/code/sounddlg.cpp +++ b/code/sounddlg.cpp @@ -45,10 +45,34 @@ #include "language/language.h" #include "ownrdraw.h" #include "theme.h" +#include "ui/uisound.h" #include "winfix.h" bool DialogInitialized = false; +// The screen the dialog procedure reads and writes. A dialog procedure is reached by +// Windows rather than by its driver, so this is how it finds the presenter its driver made. +static UISoundPresenterClass * _Screen = nullptr; + + +/// +/// Puts the view-model back into the controls that an executed intent can have changed. +/// Only the check boxes need it: shuffle and repeat exclude one another, so checking one +/// clears the other, and nothing else changes a control from underneath the player. +/// +static void Sound_Sync_Controls(HWND window, UISoundPresenterClass const & screen) +{ + HWND button = GetDlgItem(window, IDC_SOUND_SHUFFLE); + if (button) { + Button_SetCheck(button, screen.Shuffle ? BST_CHECKED : BST_UNCHECKED); + } + + button = GetDlgItem(window, IDC_SOUND_REPEAT); + if (button) { + Button_SetCheck(button, screen.Repeat ? BST_CHECKED : BST_UNCHECKED); + } +} + /// /// Handles the sound and music options dialog. @@ -59,12 +83,16 @@ bool DialogInitialized = false; /// This routine will not return until the player closes the dialog. void SoundControlsClass::Dialog(void) { - int rc = -1; DebugString("SoundControls: GameSpeed = %d, ScrollRate = %d, Detail = %d\n", Options.GameSpeed, Options.ScrollRate, Options.DetailLevel); DialogInitialized = false; + UISoundPresenterClass screen; + screen.Refresh(); + + _Screen = &screen; + HWND dialog; - if (!GameActive) { + if (screen.Is_Lite()) { dialog = OwnerDraw::Begin_Dialog(IDD_SOUND_OPTIONS_DIALOG_LITE, Sound_Option_Dialog_Func); } else { dialog = OwnerDraw::Begin_Dialog(IDD_SOUND_OPTIONS_DIALOG, Sound_Option_Dialog_Func); @@ -72,22 +100,29 @@ void SoundControlsClass::Dialog(void) if (dialog) { - SetWindowLongPtr(dialog, DWLP_USER, (LONG_PTR)&rc); - OwnerDraw::Display_Dialog(dialog); - while (rc == -1) { + while (!screen.Result.has_value()) { if (OwnerDraw::Dialog_Message_Handler() == true) { - rc = 2; - } - if (!GameActive) { - Title_Screen_Restore(); + UIResult ended; + ended.Outcome = UIResult::OUTCOME_SESSION_ENDED; + ended.GameEnded = true; + screen.Result = ended; } + + // A control handler queues rather than acts, so the queue is executed here, + // after the pump has returned and before the pass's maintenance. + screen.Drain(); + Sound_Sync_Controls(dialog, screen); + + screen.Service(); } OwnerDraw::End_Dialog(dialog); } + _Screen = nullptr; + DebugString("SoundControls: GameSpeed = %d, ScrollRate = %d, Detail = %d\n", Options.GameSpeed, Options.ScrollRate, Options.DetailLevel); } @@ -110,193 +145,164 @@ INT_PTR CALLBACK SoundControlsClass::Sound_Option_Dialog_Func(HWND window, UINT { INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - if (rc == 0) { - switch (message) { - case WM_INITDIALOG: { - DialogInitialized = false; - bool enabled = AudioEngine.Is_Available(); - - /* - ** Music volume slider. - */ - HWND track = GetDlgItem(window, IDC_MUSIC_VOLUME); - if (track) { - SendMessage(track, OD_TRACKSILENT, 0, 0); - Slider_SetRange(track, 0, VOLUME_LEVELS); - Slider_SetPos(track, (int)(Options.ScoreVolume * (double)VOLUME_LEVELS + 0.5)); - EnableWindow(track, enabled); - } - - /* - ** Sound volume slider. - */ - track = GetDlgItem(window, IDC_SOUND_VOLUME); - if (track) { - SendMessage(track, OD_TRACKSILENT, 0, 0); - Slider_SetRange(track, 0, VOLUME_LEVELS); - Slider_SetPos(track, (int)(Options.SoundVolume * (double)VOLUME_LEVELS + 0.5)); - EnableWindow(track, enabled); - } - - track = GetDlgItem(window, IDC_VOICE_VOLUME); - if (track) { - SendMessage(track, OD_TRACKSILENT, 0, 0); - Slider_SetRange(track, 0, VOLUME_LEVELS); - Slider_SetPos(track, (int)(Options.VoiceVolume * (double)VOLUME_LEVELS + 0.5)); - EnableWindow(track, enabled); - } - - if (GameActive) { - - /* - ** Shuffle control. - */ - HWND button = GetDlgItem(window, IDC_SOUND_SHUFFLE); - if (button) { - Button_SetCheck(button, Options.IsScoreShuffle ? BST_CHECKED : BST_UNCHECKED); - EnableWindow(button, enabled); - } - - /* - ** Repeat control. - */ - button = GetDlgItem(window, IDC_SOUND_REPEAT); - if (button) { - Button_SetCheck(button, Options.IsScoreRepeat ? BST_CHECKED : BST_UNCHECKED); - EnableWindow(button, enabled); - } + if (rc != 0) { + return(rc); + } - /* - ** Add all the themes to the list box. The list box entries are constructed - ** and then stored into allocated EMS memory blocks. - */ - HWND list = GetDlgItem(window, IDC_SOUND_TRACKLIST); - if (list) { - int active_theme = 0; - int visible_num = 1; - - ListBox_ResetContent(list); - - for (ThemeType index = THEME_FIRST; index < Theme.Max_Themes(); index++) { - if (Theme.Is_Allowed(index)) { - char buffer[100]; - int length = Theme.Track_Length(index); - char const * fullname = Theme.Full_Name(index); - - sprintf(buffer, "%02d - %s [%d:%02d]", visible_num, fullname, length / 60, length % 60); - visible_num++; - - int row = ListBox_AddString(list, buffer); - if (row != LB_ERR) { - ListBox_SetItemData(list, row, index); - if (Theme.What_Is_Playing() == index) { - active_theme = row; - } - } - } - } + // The driver owns the screen for the whole life of the dialog, so a message that + // arrives without one has nothing to act on. + if (_Screen == nullptr) { + return(FALSE); + } - ListBox_SetCurSel(list, active_theme); - ListBox_SetTopIndex(list, active_theme); - EnableWindow(list, enabled); - } - } + UISoundPresenterClass & screen = *_Screen; + + switch (message) { + case WM_INITDIALOG: { + DialogInitialized = false; + + /* + ** Music volume slider. + */ + HWND track = GetDlgItem(window, IDC_MUSIC_VOLUME); + if (track) { + SendMessage(track, OD_TRACKSILENT, 0, 0); + Slider_SetRange(track, 0, UISoundPresenterClass::VOLUME_LEVELS); + Slider_SetPos(track, screen.MusicVolume); + EnableWindow(track, screen.Available); + } - DialogInitialized = true; + /* + ** Sound volume slider. + */ + track = GetDlgItem(window, IDC_SOUND_VOLUME); + if (track) { + SendMessage(track, OD_TRACKSILENT, 0, 0); + Slider_SetRange(track, 0, UISoundPresenterClass::VOLUME_LEVELS); + Slider_SetPos(track, screen.SoundVolume); + EnableWindow(track, screen.Available); } - break; + track = GetDlgItem(window, IDC_VOICE_VOLUME); + if (track) { + SendMessage(track, OD_TRACKSILENT, 0, 0); + Slider_SetRange(track, 0, UISoundPresenterClass::VOLUME_LEVELS); + Slider_SetPos(track, screen.VoiceVolume); + EnableWindow(track, screen.Available); + } - case WM_COMMAND: - switch (LOWORD(wparam)) { + if (screen.HasMusic) { /* - ** Toggle the shuffle button. + ** Shuffle control. */ - case IDC_SOUND_SHUFFLE: - Options.Set_Shuffle(Button_GetCheck((HWND)lparam) == BST_CHECKED); - if (Button_GetCheck((HWND)lparam) == BST_CHECKED) { - SendDlgItemMessage(window, IDC_SOUND_REPEAT, BM_SETCHECK, BST_UNCHECKED, 0); - Options.Set_Repeat(false); - } - break; + HWND button = GetDlgItem(window, IDC_SOUND_SHUFFLE); + if (button) { + Button_SetCheck(button, screen.Shuffle ? BST_CHECKED : BST_UNCHECKED); + EnableWindow(button, screen.Available); + } /* - ** Toggle the repeat button. + ** Repeat control. */ - case IDC_SOUND_REPEAT: - Options.Set_Repeat(Button_GetCheck((HWND)lparam) == BST_CHECKED); - if (Button_GetCheck((HWND)lparam) == BST_CHECKED) { - SendDlgItemMessage(window, IDC_SOUND_SHUFFLE, BM_SETCHECK, BST_UNCHECKED, 0); - Options.Set_Shuffle(false); - } - break; + button = GetDlgItem(window, IDC_SOUND_REPEAT); + if (button) { + Button_SetCheck(button, screen.Repeat ? BST_CHECKED : BST_UNCHECKED); + EnableWindow(button, screen.Available); + } /* - ** Stop all themes from playing. + ** Add the eligible themes to the list box, in the order the screen + ** built them, and show the one that is playing. */ - case IDC_SOUND_STOP: - if (HIWORD(wparam) == 0) { - Theme.Queue_Song(THEME_QUIET); - } - break; - - case IDOK: - if (HIWORD(wparam) == 0) { - HWND button = GetDlgItem(window, IDC_MUSIC_VOLUME); - if (button) { - Options.Set_Score_Volume(Slider_GetPos(button) / (double)VOLUME_LEVELS, false); - } - button = GetDlgItem(window, IDC_SOUND_VOLUME); - if (button) { - Options.Set_Sound_Volume(Slider_GetPos(button) / (double)VOLUME_LEVELS, false); + HWND list = GetDlgItem(window, IDC_SOUND_TRACKLIST); + if (list) { + ListBox_ResetContent(list); + + for (int index = 0; index < (int)screen.Tracks.size(); index++) { + int const row = ListBox_AddString(list, screen.Tracks[index].Label.c_str()); + if (row != LB_ERR) { + ListBox_SetItemData(list, row, index); } - button = GetDlgItem(window, IDC_VOICE_VOLUME); - if (button) { - Options.Set_Voice_Volume(Slider_GetPos(button) / (double)VOLUME_LEVELS, false); - } - int * res = (int *)GetWindowLongPtr(window, DWLP_USER); - *res = IDOK; } - break; - /* - ** Start the currently selected theme to play. - */ - case IDC_SOUND_PLAY: - if (HIWORD(wparam) == 0) { - HWND list = GetDlgItem(window, IDC_SOUND_TRACKLIST); - if (list) { - int row = ListBox_GetCurSel(list); - if (row != LB_ERR) { - ThemeType theme = (ThemeType)ListBox_GetItemData(list, row); - Theme.Stop(); - Theme.Queue_Song(theme); - } + ListBox_SetCurSel(list, screen.Selected); + ListBox_SetTopIndex(list, screen.Selected); + EnableWindow(list, screen.Available); + } + } + + DialogInitialized = true; + } + + break; + + case WM_COMMAND: + switch (LOWORD(wparam)) { + + /* + ** Toggle the shuffle button. + */ + case IDC_SOUND_SHUFFLE: + screen.Queue(UIIntent{UI_SOUND_SHUFFLE, "", Button_GetCheck((HWND)lparam) == BST_CHECKED}); + break; + + /* + ** Toggle the repeat button. + */ + case IDC_SOUND_REPEAT: + screen.Queue(UIIntent{UI_SOUND_REPEAT, "", Button_GetCheck((HWND)lparam) == BST_CHECKED}); + break; + + /* + ** Stop all themes from playing. + */ + case IDC_SOUND_STOP: + if (HIWORD(wparam) == 0) { + screen.Queue(UIIntent{UI_SOUND_STOP, "", 0}); + } + break; + + case IDOK: + if (HIWORD(wparam) == 0) { + screen.Queue(UIIntent{UI_SOUND_ACCEPT, "", 0}); + } + break; + + /* + ** Start the currently selected theme to play. + */ + case IDC_SOUND_PLAY: + if (HIWORD(wparam) == 0) { + HWND list = GetDlgItem(window, IDC_SOUND_TRACKLIST); + if (list) { + int const row = ListBox_GetCurSel(list); + if (row != LB_ERR) { + screen.Queue(UIIntent{UI_SOUND_SELECT, "", (int)ListBox_GetItemData(list, row)}); + screen.Queue(UIIntent{UI_SOUND_PLAY, "", 0}); } } - break; - } - break; - - /* - * Control volume. - */ - case WM_HSCROLL: - if (DialogInitialized) { - HWND track = (HWND)lparam; - if (track == GetDlgItem(window, IDC_MUSIC_VOLUME)) { - Options.Set_Score_Volume(Slider_GetPos(track) / (double)VOLUME_LEVELS, true); - } else if (track == GetDlgItem(window, IDC_SOUND_VOLUME)) { - Options.Set_Sound_Volume(Slider_GetPos(track) / (double)VOLUME_LEVELS, true); - } else if (track == GetDlgItem(window, IDC_VOICE_VOLUME)) { - Options.Set_Voice_Volume(Slider_GetPos(track) / (double)VOLUME_LEVELS, true); } + break; + } + break; + + /* + * Control volume. + */ + case WM_HSCROLL: + if (DialogInitialized) { + HWND track = (HWND)lparam; + if (track == GetDlgItem(window, IDC_MUSIC_VOLUME)) { + screen.Queue(UIIntent{UI_SOUND_MUSIC, "", Slider_GetPos(track)}); + } else if (track == GetDlgItem(window, IDC_SOUND_VOLUME)) { + screen.Queue(UIIntent{UI_SOUND_SOUND, "", Slider_GetPos(track)}); + } else if (track == GetDlgItem(window, IDC_VOICE_VOLUME)) { + screen.Queue(UIIntent{UI_SOUND_VOICE, "", Slider_GetPos(track)}); } - break; - } - return(FALSE); + } + break; } - return(rc); + return(FALSE); } diff --git a/code/ui/uisound.cpp b/code/ui/uisound.cpp new file mode 100644 index 000000000..1464a2f5f --- /dev/null +++ b/code/ui/uisound.cpp @@ -0,0 +1,173 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The sound and music screen's behavior, taken out of Sound_Option_Dialog_Func so that the +// dialog procedure and an RmlUi document can drive the same one. +// +// What the dialog did that is not obvious from the controls, and is kept here: +// the "Music Volume" slider sets Options.ScoreVolume; a volume dragged previews itself and +// the same volume is applied again without a preview when the screen is accepted; shuffle +// and repeat exclude one another, the newly checked one clearing the other; the track list +// holds the themes Theme.Is_Allowed admits, numbered from one in that order, and is built +// once when the screen opens, so a song starting later does not move the selection; and the +// screen has no cancel, because the template names no cancel button and the dialog +// procedure ignored the IDCANCEL that Escape produces. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#include "always.h" + +#include "uisound.h" + +#include "audio/audioengine.h" +#include "globals.h" +#include "goptions.h" +#include "incdec.h" +#include "init.h" +#include "theme.h" + +#include +#include + + +/// Turns a volume into the slider step that stands for it. +static int Sound_Volume_To_Step(float volume) +{ + return((int)(volume * (double)UISoundPresenterClass::VOLUME_LEVELS + 0.5)); +} + + +/// Turns a slider step back into the volume it stands for. +static float Sound_Step_To_Volume(int step) +{ + int const clamped = std::clamp(step, 0, UISoundPresenterClass::VOLUME_LEVELS); + return((float)(clamped / (double)UISoundPresenterClass::VOLUME_LEVELS)); +} + + +/// +/// Copies the engine's audio state into the view-model. +/// +void UISoundPresenterClass::Refresh(void) +{ + Available = AudioEngine.Is_Available(); + + // The dialog picked its template by this, not by which menu opened it. + HasMusic = (GameActive != false); + + MusicVolume = Sound_Volume_To_Step(Options.ScoreVolume); + SoundVolume = Sound_Volume_To_Step(Options.SoundVolume); + VoiceVolume = Sound_Volume_To_Step(Options.VoiceVolume); + + Shuffle = Options.IsScoreShuffle; + Repeat = Options.IsScoreRepeat; + + Tracks.clear(); + Selected = 0; + + if (!HasMusic) { + return; + } + + int visible = 1; + for (ThemeType index = THEME_FIRST; index < Theme.Max_Themes(); index++) { + if (!Theme.Is_Allowed(index)) continue; + + char buffer[100]; + int const length = Theme.Track_Length(index); + char const * const fullname = Theme.Full_Name(index); + + std::snprintf(buffer, sizeof(buffer), "%02d - %s [%d:%02d]", visible, + (fullname != nullptr) ? fullname : "", length / 60, length % 60); + visible++; + + if (Theme.What_Is_Playing() == index) { + Selected = (int)Tracks.size(); + } + + TrackType track; + track.Label = buffer; + track.Theme = index; + Tracks.push_back(track); + } +} + + +/// +/// Answers an intent a view raised. +/// +void UISoundPresenterClass::Execute(UIIntent const & intent) +{ + if (intent.Action == UI_SOUND_MUSIC) { + MusicVolume = std::clamp(intent.Value, 0, VOLUME_LEVELS); + Options.Set_Score_Volume(Sound_Step_To_Volume(MusicVolume), true); + + } else if (intent.Action == UI_SOUND_SOUND) { + SoundVolume = std::clamp(intent.Value, 0, VOLUME_LEVELS); + Options.Set_Sound_Volume(Sound_Step_To_Volume(SoundVolume), true); + + } else if (intent.Action == UI_SOUND_VOICE) { + VoiceVolume = std::clamp(intent.Value, 0, VOLUME_LEVELS); + Options.Set_Voice_Volume(Sound_Step_To_Volume(VoiceVolume), true); + + } else if (intent.Action == UI_SOUND_SHUFFLE) { + Shuffle = (intent.Value != 0); + Options.Set_Shuffle(Shuffle); + if (Shuffle) { + Repeat = false; + Options.Set_Repeat(false); + } + + } else if (intent.Action == UI_SOUND_REPEAT) { + Repeat = (intent.Value != 0); + Options.Set_Repeat(Repeat); + if (Repeat) { + Shuffle = false; + Options.Set_Shuffle(false); + } + + } else if (intent.Action == UI_SOUND_SELECT) { + if (intent.Value >= 0 && intent.Value < (int)Tracks.size()) { + Selected = intent.Value; + } + + } else if (intent.Action == UI_SOUND_PLAY) { + if (Selected >= 0 && Selected < (int)Tracks.size()) { + // Stopping first is what the dialog did, so the queued song starts rather than + // waiting behind the one already playing. + Theme.Stop(); + Theme.Queue_Song((ThemeType)Tracks[Selected].Theme); + } + + } else if (intent.Action == UI_SOUND_STOP) { + Theme.Queue_Song(THEME_QUIET); + + } else if (intent.Action == UI_SOUND_ACCEPT) { + // The volumes are applied again without a preview, which is what the dialog's OK + // handler did with the positions it read back off the sliders. + Options.Set_Score_Volume(Sound_Step_To_Volume(MusicVolume), false); + Options.Set_Sound_Volume(Sound_Step_To_Volume(SoundVolume), false); + Options.Set_Voice_Volume(Sound_Step_To_Volume(VoiceVolume), false); + + UIResult result; + result.Outcome = UIResult::OUTCOME_ACCEPTED; + Result = result; + } +} + + +/// +/// The maintenance the dialog driver ran on every pass of its own loop. +/// +void UISoundPresenterClass::Service(void) +{ + if (!GameActive) { + Title_Screen_Restore(); + } +} diff --git a/code/ui/uisound.h b/code/ui/uisound.h new file mode 100644 index 000000000..384c6cb78 --- /dev/null +++ b/code/ui/uisound.h @@ -0,0 +1,75 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The sound and music screen's behavior, with no toolkit in it. Both views drive this one +// presenter: the OwnerDraw dialog procedure in sounddlg.cpp and the RmlUi document. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + +#include +#include + + +// What a view raises. Value carries the slider step, the check state or the list row. +inline constexpr char const * UI_SOUND_MUSIC = "music"; // Value: slider step +inline constexpr char const * UI_SOUND_SOUND = "sound"; // Value: slider step +inline constexpr char const * UI_SOUND_VOICE = "voice"; // Value: slider step +inline constexpr char const * UI_SOUND_SHUFFLE = "shuffle"; // Value: nonzero to shuffle +inline constexpr char const * UI_SOUND_REPEAT = "repeat"; // Value: nonzero to repeat +inline constexpr char const * UI_SOUND_SELECT = "select"; // Value: track list row +inline constexpr char const * UI_SOUND_PLAY = "play"; +inline constexpr char const * UI_SOUND_STOP = "stop"; +inline constexpr char const * UI_SOUND_ACCEPT = "accept"; + + +class UISoundPresenterClass : public UIPresenterClass +{ + public: + // The steps a volume is expressed in, which is the range the dialog's track bars + // were given. A view shows steps; only this class knows what they mean. + static int const VOLUME_LEVELS = 10; + + struct TrackType + { + std::string Label; + int Theme = 0; + }; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + virtual void Service(void) override; + + /* + ** The view-model. Plain values, and the only thing a view reads. + */ + int MusicVolume = 0; + int SoundVolume = 0; + int VoiceVolume = 0; + bool Shuffle = false; + bool Repeat = false; + + // Can anything be heard? With no audio device every control is shown but disabled, + // as the dialog disabled them. + bool Available = false; + + // Does this screen carry the music controls? Only the template the game shows during + // play does; the one it shows with no game running carries the three volumes alone. + bool HasMusic = false; + + std::vector Tracks; + int Selected = 0; + + // Which of the two dialog templates the state above corresponds to, for a view that + // has to choose a document. + bool Is_Lite(void) const { return(!HasMusic); } +}; From 574db0b10a16d16292865029b5584dfa348745f0 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 02:31:04 +0100 Subject: [PATCH 084/179] feat(ui): show the sound controls through RmlUi One document per dialog template, both selected in SoundControlsClass::Dialog by UI_Use_Rml, with the legacy dialog kept behind it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/sounddlg.cpp | 13 +++ code/ui/uisound.cpp | 160 +++++++++++++++++++++++++++ code/ui/uisound.h | 6 ++ ui/sound.rcss | 257 ++++++++++++++++++++++++++++++++++++++++++++ ui/sound.rml | 29 +++++ ui/soundlite.rcss | 155 ++++++++++++++++++++++++++ ui/soundlite.rml | 20 ++++ 7 files changed, 640 insertions(+) create mode 100644 ui/sound.rcss create mode 100644 ui/sound.rml create mode 100644 ui/soundlite.rcss create mode 100644 ui/soundlite.rml diff --git a/code/sounddlg.cpp b/code/sounddlg.cpp index 578b7956a..ac081609e 100644 --- a/code/sounddlg.cpp +++ b/code/sounddlg.cpp @@ -45,6 +45,7 @@ #include "language/language.h" #include "ownrdraw.h" #include "theme.h" +#include "ui/uishell.h" #include "ui/uisound.h" #include "winfix.h" @@ -89,6 +90,18 @@ void SoundControlsClass::Dialog(void) UISoundPresenterClass screen; screen.Refresh(); + // The selection is latched here, at screen entry, and the legacy dialog opens only when + // the document could not be prepared. + if (UI_Use_Rml()) { + UIResult const result = UI_Sound_Screen(screen); + if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { + DebugString("SoundControls: GameSpeed = %d, ScrollRate = %d, Detail = %d\n", Options.GameSpeed, Options.ScrollRate, Options.DetailLevel); + return; + } + screen.IsClosing = false; + screen.Result.reset(); + } + _Screen = &screen; HWND dialog; diff --git a/code/ui/uisound.cpp b/code/ui/uisound.cpp index 1464a2f5f..bc33608bf 100644 --- a/code/ui/uisound.cpp +++ b/code/ui/uisound.cpp @@ -25,6 +25,8 @@ #include "uisound.h" +#include "uirmlview.h" + #include "audio/audioengine.h" #include "globals.h" #include "goptions.h" @@ -32,6 +34,10 @@ #include "init.h" #include "theme.h" +#include +#include +#include + #include #include @@ -171,3 +177,157 @@ void UISoundPresenterClass::Service(void) Title_Screen_Restore(); } } + + +//--------------------------------------------------------------------------------------- +// The RmlUi view. One document per dialog template, because the two templates differ by +// which controls exist rather than by how one is arranged. +//--------------------------------------------------------------------------------------- + +/// +/// The RmlUi half of the sound screen. +/// +class SoundViewClass : public UIRmlViewClass +{ + public: + SoundViewClass(UISoundPresenterClass & presenter, char const * document); + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + // A slider takes its position from the model as the document loads, and that raises + // a change event of its own. Nothing is queued until this is set, which is what + // DialogInitialized did for the dialog's own WM_HSCROLL. + void Settle(void) { Settled = true; } + + private: + void Volume(char const * which, int step); + + UISoundPresenterClass & Screen; + bool Settled = false; +}; + + +SoundViewClass::SoundViewClass(UISoundPresenterClass & presenter, char const * document) : + UIRmlViewClass(presenter, document), + Screen(presenter) +{ +} + + +void SoundViewClass::Volume(char const * which, int step) +{ + if (!Settled) return; + + // A position the screen already holds raises no intent, so setting a slider from the + // model cannot preview a volume the player did not move. + if (which == UI_SOUND_MUSIC && step == Screen.MusicVolume) return; + if (which == UI_SOUND_SOUND && step == Screen.SoundVolume) return; + if (which == UI_SOUND_VOICE && step == Screen.VoiceVolume) return; + + Screen.Queue(UIIntent{which, "", step}); +} + + +void SoundViewClass::Bind(Rml::DataModelConstructor & model) +{ + if (auto track = model.RegisterStruct()) { + track.RegisterMember("label", &UISoundPresenterClass::TrackType::Label); + } + model.RegisterArray>(); + + model.Bind("music", &Screen.MusicVolume); + model.Bind("sound", &Screen.SoundVolume); + model.Bind("voice", &Screen.VoiceVolume); + model.Bind("shuffle", &Screen.Shuffle); + model.Bind("repeat", &Screen.Repeat); + model.Bind("available", &Screen.Available); + model.Bind("tracks", &Screen.Tracks); + model.Bind("selected", &Screen.Selected); + + // An event handler never acts: it queues, and the runner executes the queue after + // Context::Update has returned. + model.BindEventCallback("volume", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + + Rml::String const which = arguments[0].Get(); + int const step = (int)(event.GetParameter("value", 0.0f) + 0.5f); + + if (which == UI_SOUND_MUSIC) Volume(UI_SOUND_MUSIC, step); + else if (which == UI_SOUND_SOUND) Volume(UI_SOUND_SOUND, step); + else if (which == UI_SOUND_VOICE) Volume(UI_SOUND_VOICE, step); + }); + + model.BindEventCallback("toggle", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + + Rml::String const which = arguments[0].Get(); + if (which == UI_SOUND_SHUFFLE) { + Screen.Queue(UIIntent{UI_SOUND_SHUFFLE, "", Screen.Shuffle ? 0 : 1}); + } else if (which == UI_SOUND_REPEAT) { + Screen.Queue(UIIntent{UI_SOUND_REPEAT, "", Screen.Repeat ? 0 : 1}); + } + }); + + model.BindEventCallback("pick", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{UI_SOUND_SELECT, "", (int)arguments[0].Get()}); + }); + + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + + Rml::String const what = arguments[0].Get(); + if (what == UI_SOUND_PLAY) Screen.Queue(UIIntent{UI_SOUND_PLAY, "", 0}); + else if (what == UI_SOUND_STOP) Screen.Queue(UIIntent{UI_SOUND_STOP, "", 0}); + else if (what == UI_SOUND_ACCEPT) Screen.Queue(UIIntent{UI_SOUND_ACCEPT, "", 0}); + }); + + // Enter accepts, because the template names no default push button and Windows then + // sends the dialog IDOK. Escape does nothing, because the dialog procedure ignored the + // IDCANCEL it produces, so this screen has no cancel either. + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key == Rml::Input::KI_RETURN || key == Rml::Input::KI_NUMPADENTER) { + Screen.Queue(UIIntent{UI_SOUND_ACCEPT, "", 0}); + } + }); +} + + +void SoundViewClass::Sync(void) +{ + if (!Model) return; + + // Only what an executed intent can change is dirtied. The volumes are not, because a + // slider already carries the position its own change event reported. + Model.DirtyVariable("shuffle"); + Model.DirtyVariable("repeat"); + Model.DirtyVariable("selected"); +} + + +/// +/// Shows the sound controls and waits for the player to accept them. +/// +UIResult UI_Sound_Screen(UISoundPresenterClass & presenter) +{ + SoundViewClass view(presenter, presenter.Is_Lite() ? "soundlite.rml" : "sound.rml"); + + if (!view.Prepare(true)) { + UIResult result; + result.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + return(result); + } + + view.Settle(); + + UIResult const result = UI_Run_Modal(presenter, view); + view.Close(); + return(result); +} diff --git a/code/ui/uisound.h b/code/ui/uisound.h index 384c6cb78..24bf6d614 100644 --- a/code/ui/uisound.h +++ b/code/ui/uisound.h @@ -73,3 +73,9 @@ class UISoundPresenterClass : public UIPresenterClass // has to choose a document. bool Is_Lite(void) const { return(!HasMusic); } }; + + +// Shows the screen for the state the presenter was refreshed into and does not return until +// the player accepts it. A OUTCOME_FAILED_TO_OPEN result means the document could not be +// prepared and nothing was shown, which is the caller's cue to open the legacy dialog. +UIResult UI_Sound_Screen(UISoundPresenterClass & presenter); diff --git a/ui/sound.rcss b/ui/sound.rcss new file mode 100644 index 000000000..62af3dae3 --- /dev/null +++ b/ui/sound.rcss @@ -0,0 +1,257 @@ +/* The sound controls the game shows during play. The geometry is the + IDD_SOUND_OPTIONS_DIALOG template's, converted from dialog units at the 8 point MS Sans + Serif the template names: 1.5 pixels across and 1.625 down, with a child's offset taken + from the panel's content box and a bordered element's declared size taken inside its own + border. + + The panel is drawn rather than blitted because the dialog's own background is a PCX, and + PCX decoding arrives with the first screen that shows game art. Everything here stays + inside the styling profile docs/UI_DESIGN.md declares: text, ordinary layout, borders and + basic decorators, with no filter, layer, shader or transform. */ + +body +{ + font-family: LatoLatin; + font-size: 12dp; + color: #d6d8cc; + + width: 100%; + height: 100%; +} + +/* 294 x 215 dialog units. The panel is centred on the frame and re-centres itself when the + frame is resized, because the offsets are a proportion of the context. */ +#dialog +{ + display: block; + position: absolute; + left: 50%; + top: 50%; + margin-left: -220.5dp; + margin-top: -174.7dp; + + width: 437dp; + height: 345.4dp; + + background-color: #23261fF2; + border-width: 2dp; + border-top-color: #6e7360; + border-left-color: #6e7360; + border-right-color: #10120e; + border-bottom-color: #10120e; +} + +/* The three right-aligned captions, 70 x 15 dialog units at x 22. */ +.label +{ + display: block; + position: absolute; + left: 33dp; + width: 105dp; + height: 24.4dp; + line-height: 24.4dp; + text-align: right; +} + +#musiclabel { top: 19.5dp; } +#soundlabel { top: 55.3dp; } +#voicelabel { top: 91dp; } + +/* The three track bars, 175 x 15 dialog units at x 97. TBS_NOTICKS, so the bar is a plain + groove with a thumb. */ +.slider +{ + display: block; + position: absolute; + left: 145.5dp; + width: 262.5dp; + height: 24.4dp; +} + +#music { top: 19.5dp; } +#sound { top: 55.3dp; } +#voice { top: 91dp; } + +slider +{ + width: 100%; + height: 100%; +} + +sliderbar +{ + width: 14dp; + height: 100%; + + background-color: #5c6152; + border-width: 2dp; + border-top-color: #949a84; + border-left-color: #949a84; + border-right-color: #14160f; + border-bottom-color: #14160f; +} + +sliderbar:hover { background-color: #6f7563; } +sliderbar:active { background-color: #4a4e41; } + +slidertrack +{ + width: 100%; + height: 6dp; + margin-top: 9.2dp; + + background-color: #14160f; + border-width: 1dp; + border-top-color: #10120e; + border-left-color: #10120e; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +sliderarrowdec, sliderarrowinc +{ + width: 0dp; + height: 0dp; +} + +/* The track list, 175 x 99 dialog units at 97, 82. */ +#tracklist +{ + display: block; + position: absolute; + left: 145.5dp; + top: 133.3dp; + width: 262.5dp; + height: 160.9dp; + + background-color: #14160f; + overflow-y: auto; + overflow-x: hidden; +} + +/* A row spans the list less the scrollbar. The width is stated rather than left at 100%, + because a scrolling container gives its children no width to be a proportion of. */ +.track +{ + display: block; + box-sizing: border-box; + width: 250.5dp; + padding: 1dp 4dp; + white-space: nowrap; + color: #b9bcae; +} + +#tracklist scrollbarvertical +{ + width: 12dp; + background-color: #23261f; +} + +#tracklist scrollbarvertical slidertrack +{ + width: 12dp; + margin-top: 0dp; + background-color: #14160f; + border-width: 0dp; +} + +#tracklist scrollbarvertical sliderbar +{ + width: 12dp; + min-height: 20dp; + background-color: #5c6152; + border-width: 2dp; + border-top-color: #949a84; + border-left-color: #949a84; + border-right-color: #14160f; + border-bottom-color: #14160f; +} + +.track:hover { color: #e4e6da; } + +.track.picked +{ + background-color: #3f4536; + color: #e4e6da; +} + +/* Play and Stop, 70 x 14 dialog units at x 22, and OK, 62 x 14 at 210, 189. */ +.button +{ + display: block; + position: absolute; + height: 22.8dp; + line-height: 22.8dp; + text-align: center; + + color: #e4e6da; + background-color: #33372c; + border-width: 2dp; + border-top-color: #767c68; + border-left-color: #767c68; + border-right-color: #14160f; + border-bottom-color: #14160f; +} + +#play { left: 33dp; top: 139.8dp; width: 101dp; } +#stop { left: 33dp; top: 183.6dp; width: 101dp; } +#ok { left: 315dp; top: 307.1dp; width: 89dp; } + +.button:hover { background-color: #474d3d; } + +.button:active +{ + background-color: #2a2e24; + border-top-color: #14160f; + border-left-color: #14160f; + border-right-color: #767c68; + border-bottom-color: #767c68; +} + +/* Shuffle and Repeat, 70 x 14 dialog units at x 22. BS_FLAT check boxes, so a ticked one + reads as pressed in rather than as a tick beside a caption. */ +.check +{ + display: block; + position: absolute; + left: 33dp; + width: 101dp; + height: 22.8dp; + line-height: 22.8dp; + text-align: center; + + color: #b9bcae; + background-color: #2b2f25; + border-width: 2dp; + border-top-color: #5c6152; + border-left-color: #5c6152; + border-right-color: #14160f; + border-bottom-color: #14160f; +} + +#shuffle { top: 227.5dp; } +#repeat { top: 271.4dp; } + +.check:hover { background-color: #3b4032; } + +.check.ticked +{ + color: #e4e6da; + background-color: #4a5140; + border-top-color: #14160f; + border-left-color: #14160f; + border-right-color: #767c68; + border-bottom-color: #767c68; +} + +/* With no audio device every control the dialog disabled is dimmed and inert. OK stays + live, because the dialog left it enabled. */ +.unavailable .slider, +.unavailable #tracklist, +.unavailable .check, +.unavailable #play, +.unavailable #stop +{ + color: #6b6e63; + pointer-events: none; +} diff --git a/ui/sound.rml b/ui/sound.rml new file mode 100644 index 000000000..e9566124a --- /dev/null +++ b/ui/sound.rml @@ -0,0 +1,29 @@ + + + Sound controls + + + +
+
Music Volume:
+ + +
Sound Volume:
+ + +
Voice Volume:
+ + +
+
{{ track.label }}
+
+ +
Play
+
Stop
+
Shuffle
+
Repeat
+ +
OK
+
+ +
diff --git a/ui/soundlite.rcss b/ui/soundlite.rcss new file mode 100644 index 000000000..b4b35221d --- /dev/null +++ b/ui/soundlite.rcss @@ -0,0 +1,155 @@ +/* The sound controls the game shows with no game running. The geometry is the + IDD_SOUND_OPTIONS_DIALOG_LITE template's, converted from dialog units at the 8 point MS + Sans Serif the template names: 1.5 pixels across and 1.625 down, with a child's offset + taken from the panel's content box and a bordered element's declared size taken inside + its own border. It carries the three volumes alone, as that template does. + + The panel is drawn rather than blitted because the dialog's own background is a PCX, and + PCX decoding arrives with the first screen that shows game art. Everything here stays + inside the styling profile docs/UI_DESIGN.md declares: text, ordinary layout, borders and + basic decorators, with no filter, layer, shader or transform. */ + +body +{ + font-family: LatoLatin; + font-size: 12dp; + color: #d6d8cc; + + width: 100%; + height: 100%; +} + +/* 294 x 112 dialog units. The panel is centred on the frame and re-centres itself when the + frame is resized, because the offsets are a proportion of the context. */ +#dialog +{ + display: block; + position: absolute; + left: 50%; + top: 50%; + margin-left: -220.5dp; + margin-top: -91dp; + + width: 437dp; + height: 178dp; + + background-color: #23261fF2; + border-width: 2dp; + border-top-color: #6e7360; + border-left-color: #6e7360; + border-right-color: #10120e; + border-bottom-color: #10120e; +} + +/* The three right-aligned captions, 70 x 15 dialog units at x 22. */ +.label +{ + display: block; + position: absolute; + left: 33dp; + width: 105dp; + height: 24.4dp; + line-height: 24.4dp; + text-align: right; +} + +#musiclabel { top: 27.6dp; } +#soundlabel { top: 65dp; width: 106.5dp; } +#voicelabel { top: 100.8dp; width: 106.5dp; } + +/* The three track bars, 173 x 15 dialog units at x 99. TBS_NOTICKS, so the bar is a plain + groove with a thumb. */ +.slider +{ + display: block; + position: absolute; + left: 148.5dp; + width: 259.5dp; + height: 24.4dp; +} + +#music { top: 27.6dp; } +#sound { top: 65dp; } +#voice { top: 100.8dp; } + +slider +{ + width: 100%; + height: 100%; +} + +sliderbar +{ + width: 14dp; + height: 100%; + + background-color: #5c6152; + border-width: 2dp; + border-top-color: #949a84; + border-left-color: #949a84; + border-right-color: #14160f; + border-bottom-color: #14160f; +} + +sliderbar:hover { background-color: #6f7563; } +sliderbar:active { background-color: #4a4e41; } + +slidertrack +{ + width: 100%; + height: 6dp; + margin-top: 9.2dp; + + background-color: #14160f; + border-width: 1dp; + border-top-color: #10120e; + border-left-color: #10120e; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +sliderarrowdec, sliderarrowinc +{ + width: 0dp; + height: 0dp; +} + +/* OK, 62 x 14 dialog units at 115, 86, centred across the panel as the template places it. */ +.button +{ + display: block; + position: absolute; + left: 172.5dp; + top: 139.8dp; + width: 89dp; + height: 22.8dp; + line-height: 22.8dp; + text-align: center; + + color: #e4e6da; + background-color: #33372c; + border-width: 2dp; + border-top-color: #767c68; + border-left-color: #767c68; + border-right-color: #14160f; + border-bottom-color: #14160f; +} + +.button:hover { background-color: #474d3d; } + +.button:active +{ + background-color: #2a2e24; + border-top-color: #14160f; + border-left-color: #14160f; + border-right-color: #767c68; + border-bottom-color: #767c68; +} + +/* With no audio device the sliders are dimmed and inert. OK stays live, because the dialog + left it enabled. */ +.unavailable .slider +{ + color: #6b6e63; + pointer-events: none; +} diff --git a/ui/soundlite.rml b/ui/soundlite.rml new file mode 100644 index 000000000..7a5e5f9e9 --- /dev/null +++ b/ui/soundlite.rml @@ -0,0 +1,20 @@ + + + Sound controls + + + +
+
Music Volume:
+ + +
Sound Volume:
+ + +
Voice Volume:
+ + +
OK
+
+ +
From 5820ea4995d90018c5ff70c006f17277b22dc08b Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 02:33:01 +0100 Subject: [PATCH 085/179] fix(ui): define the sound screen's volume step count std::clamp takes it by reference, so a Debug build odr-uses it and needs the definition a constexpr member carries. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/ui/uisound.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/ui/uisound.h b/code/ui/uisound.h index 24bf6d614..02d6591c1 100644 --- a/code/ui/uisound.h +++ b/code/ui/uisound.h @@ -37,7 +37,7 @@ class UISoundPresenterClass : public UIPresenterClass public: // The steps a volume is expressed in, which is the range the dialog's track bars // were given. A view shows steps; only this class knows what they mean. - static int const VOLUME_LEVELS = 10; + static constexpr int VOLUME_LEVELS = 10; struct TrackType { From a83ff2440b4340e4d99064b1ee3fb5e0dff1ffab Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 02:33:01 +0100 Subject: [PATCH 086/179] docs: record step 5 of the UI migration Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/UI_DESIGN.md | 25 ++++++++++++++++++++++--- manual/changes/local-peer-udp-port.md | 12 ++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 manual/changes/local-peer-udp-port.md diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index c0b58d05f..1512eda68 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -1,7 +1,7 @@ # UI system design -Status: in progress. Steps 1 to 4 of the migration plan have landed; nothing -from step 5 onward is implemented. Everything outside the migration plan +Status: in progress. Steps 1 to 5 of the migration plan have landed; nothing +from step 6 onward is implemented. Everything outside the migration plan remains a proposal informed by source inspection and upstream documentation. This page owns the UI architecture and migration; [Building OpenTS](BUILDING.md) owns build support and [Project @@ -38,6 +38,20 @@ a runner, so a pass updates the context once. The `[[NAME]]` table arrived with it, generated from `language.rc` by the script that already builds the portable string table. +Step 5 split a screen in two. `UISoundPresenterClass` holds the sound screen's +whole behavior and both views drive it: the dialog procedure now reads the +view-model and queues intents that its driver executes after the pump, and the +RmlUi documents queue the same intents from their own events. Two facts the +extraction fixed in place: the screen picks its template from `GameActive` +rather than from the menu that opened it, and it has no cancel, because the +templates name no cancel button and the dialog procedure ignored the `IDCANCEL` +that Escape produces. A form control's value is bound one way, with the view +dropping a change that matches the value it already holds, so setting a slider +from the model cannot preview a volume the player did not move; that is what +`DialogInitialized` did for `WM_HSCROLL`. Two live documents may not share a +data-model name, which `Context::CreateDataModel` refuses; a second screen of +the same kind therefore fails preparation rather than opening. + ## Where the UI stands today OpenTS has four UI systems plus a few bespoke screens. They share the software @@ -745,7 +759,12 @@ text beyond an ASCII test document. `End_Dialog`; the handle goes with OwnerDraw. 5. **Sound** (M, two changes). The behavior pilot: volumes, eligible themes, selection, availability, shuffle and repeat, immediate previews, play and - stop, both templates, frontend and in-game service paths. + stop, both templates, frontend and in-game service paths. Landed: + `code/ui/uisound.{h,cpp}` with `ui/sound.rml` and `ui/soundlite.rml`, the + geometry converted from the `IDD_SOUND_OPTIONS_DIALOG` and + `IDD_SOUND_OPTIONS_DIALOG_LITE` templates. A list row states its width + rather than taking it from the list, because a scrolling container gives its + children no width to be a proportion of. 6. **Progress and wait** (S, leaf). `IDD_PROGRESS_WAIT`, the saving and loading boxes in `savemgr.cpp`, the `` element, milestone effects moved out of drawing. diff --git a/manual/changes/local-peer-udp-port.md b/manual/changes/local-peer-udp-port.md new file mode 100644 index 000000000..ef6043e3d --- /dev/null +++ b/manual/changes/local-peer-udp-port.md @@ -0,0 +1,12 @@ +--- +title: Hear a peer on the same machine +category: fix +release: 0.2.0 +targets: +- type: system + id: network-packet-validation + effect: changed +credit: [OpenTS contributors] +--- + +A datagram is discarded as this machine's own broadcast coming back only when its source port is the one this game is listening on, as well as its source address being one of this machine's. Before, the address alone was enough, so two copies of the game running on one machine discarded everything the other sent and never found each other. From 331beee4176c2cbe8077ca631410c23c7343a4e8 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 03:00:11 +0100 Subject: [PATCH 087/179] feat(ui): show engine-drawn pixels through a element Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/ui/uiinternal.h | 4 + code/ui/uishell.cpp | 3 +- code/ui/uisurface.cpp | 362 ++++++++++++++++++++++++++++++++++++++++++ code/ui/uisurface.h | 75 +++++++++ 4 files changed, 443 insertions(+), 1 deletion(-) create mode 100644 code/ui/uisurface.cpp create mode 100644 code/ui/uisurface.h diff --git a/code/ui/uiinternal.h b/code/ui/uiinternal.h index 60b286654..b48522a49 100644 --- a/code/ui/uiinternal.h +++ b/code/ui/uiinternal.h @@ -51,6 +51,10 @@ void UI_Render_ImGui(ImDrawData * data); // uimessagebox.cpp void UI_Message_Box_Service(void); +// uisurface.cpp +void UI_Surface_Element_Init(void); +void UI_Surface_Element_Shutdown(void); + // uisystem.cpp Rml::SystemInterface * UI_System_Interface(void); diff --git a/code/ui/uishell.cpp b/code/ui/uishell.cpp index 3381c6a16..cfcddeb11 100644 --- a/code/ui/uishell.cpp +++ b/code/ui/uishell.cpp @@ -238,6 +238,7 @@ bool UI_Init(void) DebugString("[UI] The shipped font could not be loaded.\n"); } + UI_Surface_Element_Init(); UI_Dev_Init(); Apply_Scale_Info(); @@ -262,6 +263,7 @@ void UI_Shutdown(void) #endif UI_Dev_Shutdown(); + UI_Surface_Element_Shutdown(); _Context = nullptr; Rml::Shutdown(); @@ -346,7 +348,6 @@ bool UI_Overlay_Is_Dirty(void) return(_OverlayIsDirty); } - bool UI_Document_Is_Visible(void) { return(_Initialized && _Context != nullptr && _Context->GetNumDocuments() > 0); diff --git a/code/ui/uisurface.cpp b/code/ui/uisurface.cpp new file mode 100644 index 000000000..aed580ccb --- /dev/null +++ b/code/ui/uisurface.cpp @@ -0,0 +1,362 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The element and the providers behind it. This is where engine-drawn pixels +// enter a document: the map preview, the desync host icons and the progress bar are all +// surfaces the game draws for itself, and none of them can be a file a document names. +// +// A document writes . The name is looked up among the live providers +// at render time rather than at parse time, so a document may be shown before the screen +// that owns its pixels has registered them, and an element whose provider went away simply +// draws nothing rather than failing to lay out. +// +// The element uploads only when the provider's generation moves, so a document holding a +// surface costs one quad per present while nothing changes. +// +// docs/UI_DESIGN.md, "Assets and strings" and "Rendering", own the contracts here. + +#include "always.h" + +#include "uisurface.h" + +#include "uiinternal.h" + +#include "bsurface.h" +#include "dsurface.h" +#include "rgb.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +UISurfaceProviderClass::~UISurfaceProviderClass(void) +{ +} + + +//--------------------------------------------------------------------------------------- +// The registry. A provider is reached by name and nothing holds a pointer to one past its +// registration, so a screen that closes takes its pixels with it. +//--------------------------------------------------------------------------------------- + +static std::map _Providers; + + +void UI_Register_Surface(char const * name, UISurfaceProviderClass * provider) +{ + if (name == nullptr || name[0] == '\0' || provider == nullptr) { + return; + } + + _Providers[name] = provider; +} + + +void UI_Unregister_Surface(char const * name) +{ + if (name == nullptr) { + return; + } + + _Providers.erase(name); +} + + +static UISurfaceProviderClass * Find_Provider(std::string const & name) +{ + std::map::const_iterator found = _Providers.find(name); + return(found == _Providers.end() ? nullptr : found->second); +} + + +//--------------------------------------------------------------------------------------- +// The surface-backed provider. +//--------------------------------------------------------------------------------------- + +UISurfaceBufferClass::UISurfaceBufferClass(int width, int height) : + Width(width > 0 ? width : 1), + Height(height > 0 ? height : 1) +{ + Buffer = new BSurface(Width, Height, 2); + Buffer->Fill(Transparent); +} + + +UISurfaceBufferClass::~UISurfaceBufferClass(void) +{ + delete Buffer; + Buffer = nullptr; +} + + +void UISurfaceBufferClass::Clear(void) +{ + Buffer->Fill(Transparent); + Mark_Dirty(); +} + + +/// +/// Converts the engine surface into the premultiplied RGBA8 pixels a texture wants. +/// +/// Receives Get_Width() by Get_Height() pixels, top row first. +/// bool; Were the pixels written? +bool UISurfaceBufferClass::Read_Pixels(unsigned char * pixels) const +{ + if (pixels == nullptr || Buffer == nullptr) { + return(false); + } + + unsigned short const * const source = (unsigned short const *)Buffer->Lock(); + if (source == nullptr) { + return(false); + } + + int const stride = Buffer->Stride() / (int)sizeof(unsigned short); + unsigned short const transparent = (unsigned short)Transparent; + + for (int y = 0; y < Height; y++) { + unsigned short const * row = source + (std::size_t)y * stride; + unsigned char * out = pixels + (std::size_t)y * Width * 4; + + for (int x = 0; x < Width; x++) { + unsigned short const pixel = row[x]; + + if (pixel == transparent) { + out[0] = 0; + out[1] = 0; + out[2] = 0; + out[3] = 0; + } else { + // Opaque, so the premultiplied color the render interface expects is the + // color itself. + RGBClass const color = DSurface::Deconstruct_Hicolor_Pixel(pixel); + out[0] = (unsigned char)color.Get_Red(); + out[1] = (unsigned char)color.Get_Green(); + out[2] = (unsigned char)color.Get_Blue(); + out[3] = 255; + } + + out += 4; + } + } + + Buffer->Unlock(); + return(true); +} + + +//--------------------------------------------------------------------------------------- +// The element. +//--------------------------------------------------------------------------------------- + +namespace { + +class UISurfaceElement : public Rml::Element +{ + public: + UISurfaceElement(Rml::String const & tag) : Rml::Element(tag) {} + + virtual bool GetIntrinsicDimensions(Rml::Vector2f & dimensions, float & ratio) override; + + protected: + virtual void OnUpdate(void) override; + virtual void OnDpRatioChange(void) override { DirtyLayout(); } + virtual void OnRender(void) override; + virtual void OnResize(void) override { GeometryIsStale = true; } + virtual void OnAttributeChange(Rml::ElementAttributes const & changed) override; + + private: + void Generate_Geometry(void); + bool Refresh_Texture(UISurfaceProviderClass & provider); + + std::string Source; + Rml::Geometry Quad; + Rml::CallbackTexture Pixels; + + // The provider generation the texture was made from. Zero means there is no texture. + unsigned int Uploaded = 0; + + // The provider extents the last layout was made from. A provider that registers + // after the document was laid out, or one that changes size, is what these catch. + int LaidOutWidth = -1; + int LaidOutHeight = -1; + + bool GeometryIsStale = true; +}; + + +/// +/// Notices a provider arriving, leaving or changing size, none of which the layout would +/// otherwise hear about, and asks for the element to be laid out again. +/// +void UISurfaceElement::OnUpdate(void) +{ + Rml::Element::OnUpdate(); + + UISurfaceProviderClass const * const provider = Find_Provider(GetAttribute("src", "")); + int const width = (provider != nullptr) ? provider->Get_Width() : 0; + int const height = (provider != nullptr) ? provider->Get_Height() : 0; + + if (width != LaidOutWidth || height != LaidOutHeight) { + LaidOutWidth = width; + LaidOutHeight = height; + DirtyLayout(); + } +} + + +bool UISurfaceElement::GetIntrinsicDimensions(Rml::Vector2f & dimensions, float & ratio) +{ + UISurfaceProviderClass const * const provider = Find_Provider(GetAttribute("src", "")); + + // A surface is sized in game logical units, which is what one authored density + // independent pixel is, so the provider's own extents are its intrinsic size. + float const width = (provider != nullptr) ? (float)provider->Get_Width() : 0.0f; + float const height = (provider != nullptr) ? (float)provider->Get_Height() : 0.0f; + + if (height > 0.0f) { + ratio = width / height; + } + + // A surface's pixels are game logical units, and one authored density independent pixel + // is one of those, so the extents follow the document's scale rather than staying at + // their own pixel count. + dimensions = Rml::Vector2f(width, height) * Rml::ElementUtilities::GetDensityIndependentPixelRatio(this); + + return(true); +} + + +void UISurfaceElement::OnAttributeChange(Rml::ElementAttributes const & changed) +{ + Rml::Element::OnAttributeChange(changed); + + if (changed.find("src") != changed.end()) { + Pixels.Release(); + Uploaded = 0; + GeometryIsStale = true; + DirtyLayout(); + } +} + + +void UISurfaceElement::Generate_Geometry(void) +{ + Rml::Mesh mesh = Quad.Release(Rml::Geometry::ReleaseMode::ClearMesh); + + Rml::ComputedValues const & computed = GetComputedValues(); + Rml::ColourbPremultiplied const colour = computed.image_color().ToPremultiplied(computed.opacity()); + Rml::RenderBox const box = GetRenderBox(Rml::BoxArea::Content); + + Rml::MeshUtilities::GenerateQuad(mesh, box.GetFillOffset(), box.GetFillSize(), colour, + Rml::Vector2f(0.0f, 0.0f), Rml::Vector2f(1.0f, 1.0f)); + + if (Rml::RenderManager * manager = GetRenderManager()) { + Quad = manager->MakeGeometry(std::move(mesh)); + } + + GeometryIsStale = false; +} + + +/// +/// Brings the texture up to the provider's current pixels, uploading only when they moved. +/// +/// bool; Is there a texture to draw? +bool UISurfaceElement::Refresh_Texture(UISurfaceProviderClass & provider) +{ + unsigned int const generation = provider.Get_Generation(); + if (Uploaded == generation && Pixels) { + return(true); + } + + Rml::RenderManager * const manager = GetRenderManager(); + if (manager == nullptr) { + return(false); + } + + int const width = provider.Get_Width(); + int const height = provider.Get_Height(); + if (width <= 0 || height <= 0) { + return(false); + } + + // The callback runs when the texture is first needed for drawing, which keeps the + // conversion off the path that only lays the document out. + Pixels.Release(); + Pixels = manager->MakeCallbackTexture( + [&provider, width, height](Rml::CallbackTextureInterface const & texture) -> bool { + std::vector rgba((std::size_t)width * height * 4, 0); + if (!provider.Read_Pixels(rgba.data())) { + return(false); + } + return(texture.GenerateTexture(Rml::Span(rgba.data(), rgba.size()), + Rml::Vector2i(width, height))); + }); + + Uploaded = generation; + return(true); +} + + +void UISurfaceElement::OnRender(void) +{ + std::string const source = GetAttribute("src", ""); + if (source != Source) { + Source = source; + Uploaded = 0; + } + + UISurfaceProviderClass * const provider = Find_Provider(Source); + if (provider == nullptr) { + return; + } + + if (!Refresh_Texture(*provider)) { + return; + } + + if (GeometryIsStale) { + Generate_Geometry(); + } + + Quad.Render(GetAbsoluteOffset(Rml::BoxArea::Border), Pixels); +} + + +static Rml::ElementInstancerGeneric _Instancer; + +} // namespace + + +void UI_Surface_Element_Init(void) +{ + Rml::Factory::RegisterElementInstancer("surface", &_Instancer); +} + + +void UI_Surface_Element_Shutdown(void) +{ + _Providers.clear(); +} diff --git a/code/ui/uisurface.h b/code/ui/uisurface.h new file mode 100644 index 000000000..8f7799f0d --- /dev/null +++ b/code/ui/uisurface.h @@ -0,0 +1,75 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// Pixels the engine draws at run time, shown inside a document by the element. +// A provider is registered under a name and a document names it: . +// No RmlUi type appears here, so a screen's behavior half and the engine can own a provider +// without carrying the toolkit. +// +// docs/UI_DESIGN.md, "Assets and strings", owns the routing. + +#pragma once + +class Surface; + + +class UISurfaceProviderClass +{ + public: + virtual ~UISurfaceProviderClass(void); + + virtual int Get_Width(void) const = 0; + virtual int Get_Height(void) const = 0; + + // Writes Get_Width() by Get_Height() premultiplied RGBA8 pixels, top row first. + virtual bool Read_Pixels(unsigned char * pixels) const = 0; + + // Says the pixels changed. The element uploads them again before it next draws them + // and does nothing at all while this stands still, so a redraw costs no upload. + void Mark_Dirty(void) { Generation++; } + unsigned int Get_Generation(void) const { return(Generation); } + + private: + unsigned int Generation = 1; +}; + + +// A provider backed by an engine surface. A screen draws into it with the engine's ordinary +// drawing calls and marks it dirty; the conversion to what a texture wants happens here. +// Pixels matching the transparent color are written fully transparent, which is how the +// game's own artwork carries its mask. +class UISurfaceBufferClass : public UISurfaceProviderClass +{ + public: + UISurfaceBufferClass(int width, int height); + virtual ~UISurfaceBufferClass(void) override; + + Surface & Get_Surface(void) const { return(*Buffer); } + + void Set_Transparent_Color(int color) { Transparent = color; } + + // Fills the whole buffer with the transparent color and marks it dirty. + void Clear(void); + + virtual int Get_Width(void) const override { return(Width); } + virtual int Get_Height(void) const override { return(Height); } + virtual bool Read_Pixels(unsigned char * pixels) const override; + + private: + Surface * Buffer = nullptr; + int Width = 0; + int Height = 0; + int Transparent = 0; +}; + + +// Names a provider so a document can reach it. A name is unique among live providers, and a +// registration is dropped before its provider is destroyed. +void UI_Register_Surface(char const * name, UISurfaceProviderClass * provider); +void UI_Unregister_Surface(char const * name); From c74e05302790a72b4c8423e95b420943ce4a021f Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 03:00:25 +0100 Subject: [PATCH 088/179] refactor(progress): announce loading milestones outside the draw path Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/progress.cpp | 105 ++++++++++++++++++++++++++++++---------------- code/progress.h | 3 ++ code/scenario.cpp | 1 + 3 files changed, 74 insertions(+), 35 deletions(-) diff --git a/code/progress.cpp b/code/progress.cpp index 3fc416d72..3b4ea1e8e 100644 --- a/code/progress.cpp +++ b/code/progress.cpp @@ -197,15 +197,15 @@ double ProgressScreenClass::Get_Current_Progress(void) const /// -/// Draws the progress screen. -/// This routine paints a progress bar for every player being tracked, and in the single -/// player case announces the next loading message as the work passes each milestone. The -/// progress percent routines and the dialog's paint handler call it. +/// Announces the loading messages the job has just passed. +/// Each message is printed and its notification sound played once, when the progress first +/// reaches the threshold that names it. This is an effect of the progress moving rather +/// than of the screen being drawn, so a repaint cannot repeat a message and a presentation +/// that repaints a different number of times cannot lose one. /// -/// The screen position to draw at, or Point2D(-1,-1) to use the -/// position established by Set_Graphic_Data. -/// Nothing is drawn until Initialize has been called. -void ProgressScreenClass::Display_Progress(Point2D xpt) +/// The full screen single player presentation is the only one that shows these; +/// the dialog presentation and the multiplayer bars carry no messages. +void ProgressScreenClass::Announce_Milestones(void) { static struct { int Progress; @@ -221,6 +221,42 @@ void ProgressScreenClass::Display_Progress(Point2D xpt) { 100, TXT_LOADING_GAME1H } }; + if (!IsActive || PlayerCount != 1 || Dialog != 0 || Shape == NULL) { + return; + } + + int const progress = PlayerProgress[0]; + int const percent = Percentage; + + if (progress <= percent) { + return; + } + + for (int j = 0; j < ARRAY_SIZE(_progress_messages); j++) { + if (_progress_messages[j].Progress <= progress && _progress_messages[j].Progress > percent) { + Fancy_Text_Print(Fetch_String(_progress_messages[j].Text), *HiddenSurface, HiddenSurface->Get_Rect(), Pos + Point2D(0, 10 * j), Fetch_Scheme_By_Name("Green"), 0, TextPrintType(TPF_NOSHADOW|TPF_EFNT)); + Sound_Effect(VocClass::From_Name("Notify"), 0.4f); + Percentage = _progress_messages[j].Progress; + Update_Visible_Surface(); + break; + } + } +} + + +/// +/// Draws the progress screen. +/// This routine paints a progress bar for every player being tracked. Drawing it again +/// changes nothing else: the loading messages and the clamp that used to happen here now +/// belong to the progress moving. +/// +/// The screen position to draw at, or Point2D(-1,-1) to use the +/// position established by Set_Graphic_Data. +/// Nothing is drawn until Initialize has been called. The full screen single +/// player presentation draws no bar at all, which is what it has always done -- its +/// progress is shown by the messages Announce_Milestones prints. +void ProgressScreenClass::Display_Progress(Point2D xpt) +{ if (IsActive) { Point2D pt = xpt; @@ -233,9 +269,6 @@ void ProgressScreenClass::Display_Progress(Point2D xpt) ConvertClass * drawer = NormalDrawer; for (int i = 0; i < PlayerCount; i++) { - if (PlayerProgress[i] > MainProgress) { - PlayerProgress[i] = MainProgress; - } if (Shape != NULL) { if (pt == Point2D(-1,-1)) { if (PlayerCount == 1) { @@ -244,21 +277,6 @@ void ProgressScreenClass::Display_Progress(Point2D xpt) Get_Display_Rect(GetDlgItem(Dialog, IDC_PROGRESS_BAR_FRAME), &crect); pt = Point2D(crect.left + (crect.right - crect.left) / 2, crect.top + (crect.bottom - crect.top) / 2); } else { - int progress = PlayerProgress[i]; - int percent = Percentage; - if (progress > percent) { - for (int j = 0; j < ARRAY_SIZE(_progress_messages); j++) { - if (_progress_messages[j].Progress <= progress && _progress_messages[j].Progress > percent) { - Fancy_Text_Print(Fetch_String(_progress_messages[j].Text), *HiddenSurface, HiddenSurface->Get_Rect(), Pos + Point2D(0, 10 * j), Fetch_Scheme_By_Name("Green"), 0, TextPrintType(TPF_NOSHADOW|TPF_EFNT)); - Sound_Effect(VocClass::From_Name("Notify"), 0.4f); - Percentage = _progress_messages[j].Progress; - if (surface == HiddenSurface) { - Update_Visible_Surface(); - } - break; - } - } - } return; } } else { @@ -318,11 +336,7 @@ void ProgressScreenClass::Set_Progress_Percent(int index, double value, Point2D PlayerProgress[index] = (MainProgress / 100.0) * value; if (PlayerProgress[index] != prog1) { - if (Dialog != NULL) { - SendMessage(Dialog, WM_PAINT, 0, 0); - } else { - Display_Progress(pt); - } + Progress_Changed(pt); } } @@ -341,12 +355,33 @@ void ProgressScreenClass::Add_Progress_Percent(int index, double value, Point2D PlayerProgress[index] += (MainProgress / 100.0) * value; if (PlayerProgress[index] != prog1) { - if (Dialog != NULL) { - SendMessage(Dialog, WM_PAINT, 0, 0); - } else { - Display_Progress(pt); + Progress_Changed(pt); + } +} + + +/// +/// Carries out what a moved gauge asks for: the job may not be more than finished, the +/// messages it has just passed are announced, and the screen is drawn again. +/// +/// The screen position the caller asked the bars be drawn at. +void ProgressScreenClass::Progress_Changed(Point2D pt) +{ + for (int i = 0; i < PlayerCount; i++) { + if (PlayerProgress[i] > MainProgress) { + PlayerProgress[i] = MainProgress; } } + + if (pt == Point2D(-1,-1)) { + Announce_Milestones(); + } + + if (Dialog != NULL) { + SendMessage(Dialog, WM_PAINT, 0, 0); + } else { + Display_Progress(pt); + } } diff --git a/code/progress.h b/code/progress.h index 094397b12..6a020f413 100644 --- a/code/progress.h +++ b/code/progress.h @@ -40,6 +40,9 @@ class ProgressScreenClass int Get_Bar_Width(void) const; + void Announce_Milestones(void); + void Progress_Changed(Point2D pt = Point2D(-1,-1)); + void Set_Graphic_Data(const char * progbar, const char * background = NULL, const char * string = NULL, Point2D pt=Point2D(-1,-1)); void Display_Progress(Point2D pt = Point2D(-1,-1)); diff --git a/code/scenario.cpp b/code/scenario.cpp index 3ed5af5a4..93a676079 100644 --- a/code/scenario.cpp +++ b/code/scenario.cpp @@ -716,6 +716,7 @@ bool Read_Scenario(char const * fname) } Progress.Set_Graphic_Data((players > 1) ? "PROGBARM.SHP" : "PROGBAR.SHP", background, prog_msg, prog_bar_pos); + Progress.Announce_Milestones(); Progress.Display_Progress(); if (PacketTransport != NULL && Ipx.Transport_Mode() == IPXManagerClass::TRANSPORT_DIRECT && Session.Players.Count() > 1) { From 983e6de3bde9e7eb2857908459ef7c304461d9b7 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 03:00:49 +0100 Subject: [PATCH 089/179] feat(ui): show the progress and wait box through RmlUi Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/progress.cpp | 30 ++++- code/progress.h | 11 ++ code/ui/uiinternal.h | 7 ++ code/ui/uiprogress.cpp | 265 +++++++++++++++++++++++++++++++++++++++++ code/ui/uiprogress.h | 31 +++++ code/ui/uishell.cpp | 21 ++++ ui/progresswait.rcss | 84 +++++++++++++ ui/progresswait.rml | 12 ++ 8 files changed, 458 insertions(+), 3 deletions(-) create mode 100644 code/ui/uiprogress.cpp create mode 100644 code/ui/uiprogress.h create mode 100644 ui/progresswait.rcss create mode 100644 ui/progresswait.rml diff --git a/code/progress.cpp b/code/progress.cpp index 3b4ea1e8e..dc15cd603 100644 --- a/code/progress.cpp +++ b/code/progress.cpp @@ -28,6 +28,8 @@ #include "session.h" #include "shapeset.h" #include "surface.h" +#include "ui/uiprogress.h" +#include "ui/uishell.h" #include "voc.h" #include "windlg.h" @@ -49,6 +51,7 @@ ProgressScreenClass::ProgressScreenClass(void) Shape = NULL; Background = NULL; IsActive = false; + IsOverlay = false; for (int i = 0; i < MAX_PLAYERS; i++) { PlayerProgress[i] = 0; } @@ -83,7 +86,7 @@ void ProgressScreenClass::Initialize(double progress, int count, bool usedialog) IsActive = true; if (usedialog) { - if (Dialog == NULL) { + if (!Has_Dialog()) { Begin_Dialog(); } } else { @@ -135,6 +138,10 @@ void ProgressScreenClass::Set_Graphic_Data(const char * progbar, const char * ba Pos.Y = pt.Y; } + if (progbar != NULL && IsOverlay) { + UI_Progress_Wait_Set_Bar(progbar); + } + if (progbar != NULL) { Shape = (ShapeSet *)MFCD::Retrieve(progbar); if (Shape != NULL) { @@ -221,7 +228,7 @@ void ProgressScreenClass::Announce_Milestones(void) { 100, TXT_LOADING_GAME1H } }; - if (!IsActive || PlayerCount != 1 || Dialog != 0 || Shape == NULL) { + if (!IsActive || PlayerCount != 1 || Has_Dialog() || Shape == NULL) { return; } @@ -257,6 +264,10 @@ void ProgressScreenClass::Announce_Milestones(void) /// progress is shown by the messages Announce_Milestones prints. void ProgressScreenClass::Display_Progress(Point2D xpt) { + if (IsOverlay) { + return; + } + if (IsActive) { Point2D pt = xpt; @@ -377,7 +388,9 @@ void ProgressScreenClass::Progress_Changed(Point2D pt) Announce_Milestones(); } - if (Dialog != NULL) { + if (IsOverlay) { + UI_Progress_Wait_Set_Progress(Get_Current_Progress(0)); + } else if (Dialog != NULL) { SendMessage(Dialog, WM_PAINT, 0, 0); } else { Display_Progress(pt); @@ -393,6 +406,11 @@ void ProgressScreenClass::Progress_Changed(Point2D pt) ///
void ProgressScreenClass::Begin_Dialog(void) { + if (UI_Use_Rml() && UI_Progress_Wait_Open()) { + IsOverlay = true; + return; + } + Dialog = OwnerDraw::Begin_Dialog(IDD_PROGRESS_WAIT, ProgressScreenClass::Dialog_Proc); if (Dialog != NULL) { SetWindowLongPtr(Dialog, DWLP_USER, (LONG_PTR)this); @@ -409,6 +427,12 @@ void ProgressScreenClass::Begin_Dialog(void) /// void ProgressScreenClass::End_Dialog(void) { + if (IsOverlay) { + UI_Progress_Wait_Close(); + IsOverlay = false; + return; + } + if (Dialog != NULL) { OwnerDraw::End_Dialog(Dialog); Dialog = NULL; diff --git a/code/progress.h b/code/progress.h index 6a020f413..cb8cb7e9c 100644 --- a/code/progress.h +++ b/code/progress.h @@ -48,6 +48,10 @@ class ProgressScreenClass void Begin_Dialog(void); void End_Dialog(void); + + // Is the dialog presentation up, whichever of the two it is? + bool Has_Dialog(void) const { return(Dialog != NULL || IsOverlay); } + private: static INT_PTR CALLBACK Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); @@ -106,6 +110,13 @@ class ProgressScreenClass */ HWND Dialog; + /* + * If the dialog presentation is a document rather than a window, then this flag will + * be true and Dialog stays NULL. The document draws its own frame and bar, so the + * routines that paint into the game's surfaces stand aside for it. + */ + bool IsOverlay; + /* * This is the center of the progress bar display, expressed in screen pixels. A job * that names no spot of its own is centered on the hidden surface. diff --git a/code/ui/uiinternal.h b/code/ui/uiinternal.h index b48522a49..974276486 100644 --- a/code/ui/uiinternal.h +++ b/code/ui/uiinternal.h @@ -55,6 +55,13 @@ void UI_Message_Box_Service(void); void UI_Surface_Element_Init(void); void UI_Surface_Element_Shutdown(void); +// uishell.cpp. Puts what the shell draws on screen, which is the synchronous repaint a +// modeless dialog got from SendMessage(WM_PAINT). Only a screen with no loop of its own +// needs it; a screen inside UI_Run_Modal is presented by every pass. An immediate paint +// ignores the present pacing, which a box that must be seen before a long operation begins +// cannot afford to be skipped by. +void UI_Paint_Now(bool immediate); + // uisystem.cpp Rml::SystemInterface * UI_System_Interface(void); diff --git a/code/ui/uiprogress.cpp b/code/ui/uiprogress.cpp new file mode 100644 index 000000000..5a68fffce --- /dev/null +++ b/code/ui/uiprogress.cpp @@ -0,0 +1,265 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The progress and wait box. It is the first screen whose picture the engine draws: the bar +// is the game's own artwork clipped to the part of the job that is finished, so it reaches +// the document through the element rather than as a file the document names. +// +// What is preserved from IDD_PROGRESS_WAIT, and where each came from: the bar is the first +// frame of the shape the caller named, drawn from its left edge and cut off at the fraction +// finished, which is what ProgressScreenClass::Display_Progress does with +// Shape->Get_Rect(0); the bar is centered on the frame the template calls +// IDC_PROGRESS_BAR_FRAME; and the caption is the template's own literal, because that +// template holds the text rather than a string identifier. +// +// The box is not modal. Its callers -- the map generator and the file transfer -- keep +// running their own loops underneath it and close it when the job ends. +// +// docs/UI_DESIGN.md, "Screens" and "Assets and strings", own the contracts this keeps to. + +#include "always.h" + +#include "uiprogress.h" + +#include "uiinternal.h" +#include "uirmlview.h" +#include "uisurface.h" + +#include "_convert.h" +#include "dbgprint.h" +#include "_mixfile.h" +#include "convert.h" +#include "draw.h" +#include "mixfile.h" +#include "shapeset.h" + +#include + +#include +#include +#include + + +// The caption IDD_PROGRESS_WAIT names. The template carries the text itself rather than a +// string identifier, so there is nothing to look up. +static char const * const DEFAULT_CAPTION = "Working - Please Wait"; + +// The name the document gives the bar's pixels. +static char const * const BAR_SURFACE = "progressbar"; + + +/// +/// The toolkit-free half of the box. It has no actions: the job underneath moves the bar +/// and nothing the player does reaches the screen. +/// +class ProgressWaitPresenterClass : public UIPresenterClass +{ + public: + std::string Caption = DEFAULT_CAPTION; + + // The part of the job that is finished, 0 to 1. + double Fraction = 0.0; + + // The name of the bar artwork, as Set_Graphic_Data names it. + std::string BarShape; + + virtual void Execute(UIIntent const &) override {} + virtual void Refresh(void) override {} +}; + + +/// +/// The bar's pixels. The shape is drawn into an engine surface exactly as the dialog drew +/// it, and the element converts and uploads that when it changes. +/// +class ProgressBarSurfaceClass : public UISurfaceBufferClass +{ + public: + ProgressBarSurfaceClass(ShapeSet * shape, int width, int height) : + UISurfaceBufferClass(width, height), + Shape(shape) + { + } + + void Draw(double fraction); + + private: + ShapeSet * Shape = nullptr; +}; + + +void ProgressBarSurfaceClass::Draw(double fraction) +{ + Clear(); + + if (Shape == nullptr) { + return; + } + + // The dialog cut the bar off at the fraction finished by shrinking the shape's own + // rectangle, which is what keeps a part-drawn bar the same pixels as a full one. + Rect rect = Shape->Get_Rect(0); + rect.Width = (int)(rect.Width * std::clamp(fraction, 0.0, 1.0)); + rect.X = 0; + rect.Y = 0; + + Draw_Shape(Get_Surface(), *NormalDrawer, Shape, 0, Point2D(0, 0), rect, SHAPE_WIN_REL); + Mark_Dirty(); +} + + +class ProgressWaitViewClass : public UIRmlViewClass +{ + public: + ProgressWaitViewClass(ProgressWaitPresenterClass & presenter); + virtual ~ProgressWaitViewClass(void) override; + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + // Attaches the artwork the presenter named and draws the bar at its current value. + void Attach_Bar(void); + + private: + ProgressWaitPresenterClass & Screen; + std::unique_ptr Bar; +}; + + +ProgressWaitViewClass::ProgressWaitViewClass(ProgressWaitPresenterClass & presenter) : + UIRmlViewClass(presenter, "progresswait.rml"), + Screen(presenter) +{ +} + + +ProgressWaitViewClass::~ProgressWaitViewClass(void) +{ + // The registration goes before the provider does, so nothing can be asked for pixels + // that have been freed. + UI_Unregister_Surface(BAR_SURFACE); +} + + +void ProgressWaitViewClass::Bind(Rml::DataModelConstructor & model) +{ + model.Bind("caption", &Screen.Caption); +} + + +void ProgressWaitViewClass::Sync(void) +{ + if (Bar != nullptr) { + Bar->Draw(Screen.Fraction); + } +} + + +void ProgressWaitViewClass::Attach_Bar(void) +{ + UI_Unregister_Surface(BAR_SURFACE); + Bar.reset(); + + if (Screen.BarShape.empty()) { + return; + } + + ShapeSet * const shape = (ShapeSet *)MFCD::Retrieve(Screen.BarShape.c_str()); + if (shape == nullptr) { + DebugString("[UI] The progress bar artwork %s is not in the mix files.\n", Screen.BarShape.c_str()); + return; + } + + Rect const rect = shape->Get_Rect(0); + if (rect.Width <= 0 || rect.Height <= 0) { + return; + } + + Bar = std::make_unique(shape, rect.Width, rect.Height); + Bar->Draw(Screen.Fraction); + + // The element takes its size from the provider, and notices the registration on the next + // context update. + UI_Register_Surface(BAR_SURFACE, Bar.get()); +} + + +// The one progress box. Every caller opens one, holds it for the length of a job and closes +// it, and no caller opens a second while one is up. +static std::unique_ptr _Presenter; +static std::unique_ptr _View; + + +bool UI_Progress_Wait_Open(void) +{ + if (_View != nullptr) { + return(false); + } + + // The presenter is created first so that it is destroyed last, because the data model + // the view binds reads the presenter's view-model. + _Presenter = std::make_unique(); + _View = std::make_unique(*_Presenter); + + if (!_View->Prepare(false)) { + _View.reset(); + _Presenter.reset(); + return(false); + } + + // The box must be on screen before the job underneath begins, or a caller that never + // pumps again shows nothing at all. + UI_Paint_Now(true); + return(true); +} + + +void UI_Progress_Wait_Set_Bar(char const * shape) +{ + if (_View == nullptr) { + return; + } + + _Presenter->BarShape = (shape != nullptr) ? shape : ""; + _View->Attach_Bar(); + UI_Paint_Now(true); +} + + +void UI_Progress_Wait_Set_Progress(double fraction) +{ + if (_View == nullptr) { + return; + } + + _Presenter->Fraction = fraction; + _View->Sync(); + + // The dialog repainted itself where the gauge moved, through a synchronous WM_PAINT. + UI_Paint_Now(false); +} + + +void UI_Progress_Wait_Close(void) +{ + if (_View == nullptr) { + return; + } + + _View->Close(); + _View.reset(); + _Presenter.reset(); +} + + +bool UI_Progress_Wait_Is_Open(void) +{ + return(_View != nullptr); +} + diff --git a/code/ui/uiprogress.h b/code/ui/uiprogress.h new file mode 100644 index 000000000..96443b20d --- /dev/null +++ b/code/ui/uiprogress.h @@ -0,0 +1,31 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The progress and wait box, IDD_PROGRESS_WAIT. It stands over a job the caller drives, so +// it is not modal and the caller's own loop keeps running underneath it, the way the wait +// box does. Only plain values cross this header. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + + +// Opens the box. A false return means nothing was shown, which is the caller's cue to open +// the legacy dialog instead. The box carries no bar until one is named. +bool UI_Progress_Wait_Open(void); + +// Names the artwork the bar is drawn from, as ProgressScreenClass::Set_Graphic_Data names +// it. An unknown name leaves the box with no bar rather than failing. +void UI_Progress_Wait_Set_Bar(char const * shape); + +// Moves the bar. The fraction is the part of the job that is finished, 0 to 1. +void UI_Progress_Wait_Set_Progress(double fraction); + +void UI_Progress_Wait_Close(void); +bool UI_Progress_Wait_Is_Open(void); diff --git a/code/ui/uishell.cpp b/code/ui/uishell.cpp index cfcddeb11..da3b3e7ea 100644 --- a/code/ui/uishell.cpp +++ b/code/ui/uishell.cpp @@ -348,6 +348,27 @@ bool UI_Overlay_Is_Dirty(void) return(_OverlayIsDirty); } + +/// +/// Lays out and presents what the shell draws. +/// A screen that has no loop of its own -- the wait box, the progress box -- is on screen +/// only when something else pumps, so this is the equivalent of the synchronous WM_PAINT +/// those boxes were repainted with. +/// +/// Present whether or not the pacing is ready for another frame. +/// A box that must be seen before a long operation begins gets no second chance. +void UI_Paint_Now(bool immediate) +{ + UI_Tick(); + + if (immediate) { + Video_Present(); + } else { + Video_Present_If_Dirty(); + } +} + + bool UI_Document_Is_Visible(void) { return(_Initialized && _Context != nullptr && _Context->GetNumDocuments() > 0); diff --git a/ui/progresswait.rcss b/ui/progresswait.rcss new file mode 100644 index 000000000..c71216f19 --- /dev/null +++ b/ui/progresswait.rcss @@ -0,0 +1,84 @@ +/* The progress and wait box. Its geometry is the IDD_PROGRESS_WAIT template's, converted + from dialog units at the 8 point MS Sans Serif the template names: 1.5 pixels across and + 1.625 down, with a child's offset taken from the panel's content box and a bordered + element's declared size taken inside its own border. + + The bar itself is not styled here. It is the game's own artwork, drawn into a surface the + engine owns and shown by the element, which takes its size from that surface; + the frame around it centres whatever size arrives. */ + +body +{ + font-family: LatoLatin; + font-size: 12dp; + color: #d6d8cc; + + width: 100%; + height: 100%; +} + +/* 192 x 53 dialog units. The panel is centred on the frame and re-centres itself when the + frame is resized, because the offsets are a proportion of the context. */ +#dialog +{ + display: block; + position: absolute; + left: 50%; + top: 50%; + margin-left: -144dp; + margin-top: -43.0625dp; + + width: 284dp; + height: 82.125dp; + + background-color: #23261fF2; + border-width: 2dp; + border-top-color: #6e7360; + border-left-color: #6e7360; + border-right-color: #10120e; + border-bottom-color: #10120e; +} + +/* 22, 12, 148 x 11 dialog units. CTEXT with SS_CENTERIMAGE, so the caption is centred both + ways within its own extents. */ +#text +{ + display: block; + position: absolute; + left: 31dp; + top: 17.5dp; + width: 222dp; + height: 17.875dp; + line-height: 17.875dp; + + text-align: center; + white-space: nowrap; + overflow: hidden; +} + +/* 46, 26, 100 x 15 dialog units: the IDC_PROGRESS_BAR_FRAME group box. The bar is centred + in it, which is where Display_Progress put it from the control's own rectangle. */ +#frame +{ + display: block; + position: absolute; + left: 67dp; + top: 40.25dp; + width: 148dp; + height: 22.375dp; + line-height: 22.375dp; + + text-align: center; + + border-width: 1dp; + border-top-color: #10120e; + border-left-color: #10120e; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +surface +{ + display: inline-block; + vertical-align: middle; +} diff --git a/ui/progresswait.rml b/ui/progresswait.rml new file mode 100644 index 000000000..e96dc9a85 --- /dev/null +++ b/ui/progresswait.rml @@ -0,0 +1,12 @@ + + + Working + + + +
+
{{ caption }}
+
+
+ +
From 8b38239be65a785948ce81be7bc8d095431dc000 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 03:00:54 +0100 Subject: [PATCH 090/179] fix(ui): paint the wait box before the operation it stands over Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/ui/uimessagebox.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/code/ui/uimessagebox.cpp b/code/ui/uimessagebox.cpp index 27e470753..725da970d 100644 --- a/code/ui/uimessagebox.cpp +++ b/code/ui/uimessagebox.cpp @@ -357,6 +357,9 @@ bool UI_Wait_Box_Open(char const * message, char const * cancelcaption, bool * c return(false); } + // The box must be on screen before the operation underneath begins. A caller that saves + // a game and never pumps again would otherwise show nothing at all. + UI_Paint_Now(true); return(true); } @@ -369,6 +372,9 @@ void UI_Wait_Box_Set_Text(char const * message) _WaitPresenter->Message = (message != nullptr) ? message : ""; _WaitView->Sync(); + + // Set_Custom_Message_Box_Text repainted the box before it returned, through UpdateWindow. + UI_Paint_Now(true); } From ce4f174ef5b1e9c914443c34380d254a8522be46 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 03:04:49 +0100 Subject: [PATCH 091/179] docs: record step 6 of the UI migration Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/UI_DESIGN.md | 56 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index 1512eda68..5e1899afb 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -1,7 +1,7 @@ # UI system design -Status: in progress. Steps 1 to 5 of the migration plan have landed; nothing -from step 6 onward is implemented. Everything outside the migration plan +Status: in progress. Steps 1 to 6 of the migration plan have landed; nothing +from step 7 onward is implemented. Everything outside the migration plan remains a proposal informed by source inspection and upstream documentation. This page owns the UI architecture and migration; [Building OpenTS](BUILDING.md) owns build support and [Project @@ -13,9 +13,10 @@ transient one, because the program the overlays share is bgfx's embedded imgui shader, whose vertex stage multiplies by `u_viewProj` alone and so ignores the per-draw model transform; a program with a model transform restores the static vertex buffer the renderer table describes. `uitexture.cpp` reads PNG and -TGA only, so PCX, SHP and the `` element wait for the first screen -that shows game art, and the cursor and clipboard requests are recorded rather -than acted on. +TGA only, so PCX and SHP files wait for the first screen that shows game art, +and the cursor and clipboard requests are recorded rather than acted on. Step 6 +brought the `` element, which is the other route to game art: pixels +the engine draws rather than a file a document names. Step 3 exercised the rest. `UI_Run_Modal` now runs a screen, and the input hook gained the modal scope its rules always described: while an exclusive document @@ -52,6 +53,18 @@ from the model cannot preview a volume the player did not move; that is what data-model name, which `Context::CreateDataModel` refuses; a second screen of the same kind therefore fails preparation rather than opening. +Step 6 put engine-drawn pixels in a document. The `` element resolves +a provider by name at render time, takes its intrinsic size from that provider +scaled by the document's density-independent pixel ratio, and uploads only +when the provider's generation moves, so a document holding a surface costs a +quad per present while nothing changes. Two boxes without a loop of their own +gained the paint the dialogs got from `SendMessage(WM_PAINT)`: the wait box and +the progress box are presented as they open, unpaced, because the operation +they stand over may never pump again. Screens of different kinds do coexist, +which the progress box opening over the wait box shows; only a second screen of +the same kind is refused, and the coexistence rule still forbids a legacy +dialog underneath either. + ## Where the UI stands today OpenTS has four UI systems plus a few bespoke screens. They share the software @@ -137,8 +150,10 @@ Facts elsewhere in the tree that bind the design: `Slid`, and `LastSlid` beside `TopIndex` and `Buildables`. - `SidebarClass::Reposition_Sidebar` registers the cameo tooltips itself, independent of gadget registration; `CCToolTip` paints into game surfaces. -- `ProgressScreenClass::Set_Progress_Percent` sends `WM_PAINT` synchronously, - and `Display_Progress` plays the milestone sound from the draw path. +- `ProgressScreenClass::Set_Progress_Percent` sends `WM_PAINT` synchronously. + `Display_Progress` used to print the loading message, play its sound and + clamp the gauge from inside the draw path; step 6 moved all three onto the + progress-changed path. Three consequences shape the design. A new UI must fit the blocking-loop shape, or every driver has to be rewritten in the same change; the loop shape @@ -560,9 +575,18 @@ with the palette named in the source string. SHP frames use a zero transparent. Surfaces the engine draws at runtime (the map preview, the desync host icons, a progress bar) reach a document through a `` custom element bound to a named provider; the shell re-uploads the texture -when the provider marks it dirty. Original game art stays local runtime data +when the provider marks it dirty. A document writes `` +and the name is resolved at render time, so a document may be shown before the +screen that owns its pixels registers them and an element whose provider went +away draws nothing rather than failing to lay out. An element with no width or +height of its own takes the provider's extents, scaled by the document's +density-independent pixel ratio, because a provider's pixels are game logical +units. `UISurfaceBufferClass` is the provider a screen wants when the engine +already knows how to draw the thing: it owns a 16-bit surface the screen draws +into with the engine's ordinary calls, and converts it to premultiplied RGBA +with a color key for the mask. Original game art stays local runtime data outside version control; documents receive artwork identities, never engine -pointers. +pointers, and a presenter never holds a provider. ### Fonts @@ -676,7 +700,11 @@ Invariants the split preserves: Progress tracking, clamping, milestone text and sound, and the readiness queries that `scenario.cpp` consumes move out of the draw path into shared behavior, so a repaint cannot repeat a milestone sound and a hidden -presentation cannot lose one. The screen exposes phase, progress, status, and +presentation cannot lose one. Step 6 did the move: +`ProgressScreenClass::Progress_Changed` runs the clamp and +`Announce_Milestones` where the gauge moves, `Display_Progress` draws and +nothing else, and the loading screen announces its first message itself rather +than getting it from a repaint. The screen exposes phase, progress, status, and the operations the loader supports; no cancellation is added to a loader that cannot cancel. Loading stays on its thread with explicit cooperative service points that drain nothing unrelated while scenario objects are being @@ -767,7 +795,13 @@ text beyond an ASCII test document. children no width to be a proportion of. 6. **Progress and wait** (S, leaf). `IDD_PROGRESS_WAIT`, the saving and loading boxes in `savemgr.cpp`, the `` element, milestone effects - moved out of drawing. + moved out of drawing. Landed: `code/ui/uiprogress.{h,cpp}` with + `ui/progresswait.rml` and `ui/progresswait.rcss`, the geometry converted + from the `IDD_PROGRESS_WAIT` template; `code/ui/uisurface.{h,cpp}` with the + element and the provider contract; and the milestone move in + `code/progress.cpp`. The saving and loading boxes reach the wait box step 4 + built, through `OwnerDraw::Custom_Message_Box`, and needed the paint at open + rather than a screen of their own. 7. **Options family** (L, two changes each). Main options, display with its timed rollback, game controls (three variants), keyboard with the hotkey capture control, the display-mode confirmation, abort and surrender. From 7e69cd3c35e226f0c6193e912ba457cb65462da3 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 03:11:53 +0100 Subject: [PATCH 092/179] refactor(ui): put the in-game options screen behind a presenter Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/goptions.cpp | 297 +++++++++++++++++++------------------- code/ui/uigameoptions.cpp | 228 +++++++++++++++++++++++++++++ code/ui/uigameoptions.h | 99 +++++++++++++ 3 files changed, 474 insertions(+), 150 deletions(-) create mode 100644 code/ui/uigameoptions.cpp create mode 100644 code/ui/uigameoptions.h diff --git a/code/goptions.cpp b/code/goptions.cpp index 865a0b35d..2245ed933 100644 --- a/code/goptions.cpp +++ b/code/goptions.cpp @@ -48,14 +48,69 @@ #include "savemgr.h" #include "scenario.h" #include "stats.h" +#include "ui/uigameoptions.h" #include "special.hh" -void Game_Options_On_INITDIALOG(HWND window); +void Game_Options_On_INITDIALOG(HWND window, UIGameOptionsPresenterClass const & screen); INT_PTR CALLBACK Game_Options_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); INT_PTR CALLBACK Abort_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); void Abort_Dialog_On_COMMAND(HWND window, UINT message, WPARAM wparam, LPARAM lparam); + +// The screen the dialog procedure reads and writes. The driver owns it for the whole life +// of the dialog, which is the same lifetime DWLP_USER gave the result pointer it replaces. +static UIGameOptionsPresenterClass * _Screen = NULL; + + +static void Game_Options_Queue(UIGameOptionsPresenterClass & screen, char const * action, int value = 0) +{ + UIIntent intent; + intent.Action = action; + intent.Value = value; + screen.Queue(intent); +} + + +/// +/// Puts the view-model's enabled states into the controls the dialog enabled by hand. +/// A control the template left alone for a session type is left alone here too, so styling +/// adds no restriction the dialog did not have. +/// +static void Game_Options_Sync_Controls(HWND window, UIGameOptionsPresenterClass const & screen) +{ + HWND handle; + + if (!screen.IsMultiplayer) { + handle = GetDlgItem(window, IDC_LOAD_GAME); + if (handle) { + EnableWindow(handle, screen.CanLoad); + } + + handle = GetDlgItem(window, IDC_DELETE_GAME); + if (handle) { + EnableWindow(handle, screen.CanDelete); + } + } else { + handle = GetDlgItem(window, IDC_SAVE_GAME); + if (handle) { + EnableWindow(handle, screen.CanSave); + } + + handle = GetDlgItem(window, IDC_LOAD_GAME); + if (handle) { + EnableWindow(handle, screen.CanLoad); + } + } + + if (!screen.CanBrief) { + handle = GetDlgItem(window, IDC_BRIEFING); + if (handle) { + EnableWindow(handle, FALSE); + } + } +} + /// /// Displays the in game options dialog. /// This routine is used by the special dialog handler when the player calls up the options @@ -65,7 +120,10 @@ void Abort_Dialog_On_COMMAND(HWND window, UINT message, WPARAM wparam, LPARAM lp /// void Game_Options_Dialog(void) { - int rc = 0; + UIGameOptionsPresenterClass screen; + screen.Refresh(); + + _Screen = &screen; HWND dialog; if (Session.Type == GAME_NORMAL || Session.Type == GAME_SKIRMISH) { @@ -81,33 +139,57 @@ void Game_Options_Dialog(void) if (dialog) { - SetWindowLongPtr(dialog, DWLP_USER, (LONG_PTR)&rc); - OwnerDraw::Display_Dialog(dialog); - while (rc == 0) { + while (!screen.Result.has_value()) { if (OwnerDraw::Dialog_Message_Handler() == true) { - rc = IDOK; + // A session that ended underneath the screen leaves it as though the player + // had resumed, which is the IDOK the driver used to write. + UIResult ended; + ended.Outcome = UIResult::OUTCOME_SESSION_ENDED; + ended.GameEnded = true; + screen.Choice = UIGameOptionsPresenterClass::CHOICE_RESUME; + screen.Result = ended; + break; + } + + // A control handler queues rather than acts, so the queue is executed here, + // after the pump has returned. + screen.Drain(); + + // Getting out of the way of a screen this one opens is the view's work; what + // running it means is the presenter's. + if (screen.Pending != UIGameOptionsPresenterClass::SUB_NONE) { + ShowWindow(dialog, SW_HIDE); + UpdateWindow(MainWindow); + screen.Run_Pending(); + if (!screen.Result.has_value()) { + ShowWindow(dialog, SW_SHOW); + UpdateWindow(dialog); + } } + + Game_Options_Sync_Controls(dialog, screen); } + OwnerDraw::End_Dialog(dialog); } + _Screen = nullptr; + Keyboard->Clear(); - if (rc == IDC_BRIEFING) { + if (screen.Choice == UIGameOptionsPresenterClass::CHOICE_BRIEFING) { Restate_Mission(Scen); } IgnoreInput = Scen->IsInputLocked; - if (rc == IDC_LOAD_GAME) { - if (IDC_LOAD_GAME) { - if (MouseCursor->Is_Hidden() == false && Scen->IsInputLocked == 1) { - Hide_Mouse(); - } else if (MouseCursor->Is_Hidden() == true && Scen->IsInputLocked == 0) { - Show_Mouse(); - } + if (screen.Choice == UIGameOptionsPresenterClass::CHOICE_LOADED) { + if (MouseCursor->Is_Hidden() == false && Scen->IsInputLocked == 1) { + Hide_Mouse(); + } else if (MouseCursor->Is_Hidden() == true && Scen->IsInputLocked == 0) { + Show_Mouse(); } } @@ -117,139 +199,79 @@ void Game_Options_Dialog(void) /// /// Handles messages for the in game options dialog. -/// This routine offers every message to the owner draw system first. What is left it uses -/// to service the option buttons -- save, load, delete, briefing, resume, abort and -/// settings -- either acting on them directly or noting the player's choice for -/// Game_Options_Dialog to deal with once the dialog comes down. Dragging the game speed or -/// connection quality slider updates the label beside it. +/// The procedure reads the view-model and queues what the player asked for; the driver +/// executes the queue after the pump returns, as docs/UI_DESIGN.md requires of every +/// screen. Dragging the game speed or connection quality slider updates the label beside it, +/// which is the view's own business. /// /// Returns with TRUE if the owner draw system consumed the message. INT_PTR CALLBACK Game_Options_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) { - static int GameConnectionQualityNames[] = { - TXT_WORST_CONNECTION, - TXT_POOR_CONNECTION, - TXT_GOOD_CONNECTION, - TXT_BEST_CONNECTION - }; - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - HWND handle; - if (rc) { return(rc); } + // The driver owns the screen for the whole life of the dialog, so a message that arrives + // without one has nothing to act on. + if (_Screen == nullptr) { + return(FALSE); + } + + UIGameOptionsPresenterClass & screen = *_Screen; + HWND handle; + switch (message) { case WM_INITDIALOG: - Game_Options_On_INITDIALOG(window); + Game_Options_On_INITDIALOG(window, screen); break; case WM_COMMAND: { int code = HIWORD(wparam); - int* retval = (int *)GetWindowLongPtr(window, DWLP_USER); switch (LOWORD(wparam)) { case IDC_SAVE_GAME: - if (!code) { - if (Session.Type == GAME_NORMAL || Session.Type == GAME_SKIRMISH) { - ShowWindow(window, SW_HIDE); - UpdateWindow(MainWindow); - char description[512]; - strcpy(description, Scen->Description); - LoadOptionsClass().Save(description); - Game_Options_On_INITDIALOG(window); - ShowWindow(window, SW_SHOW); - UpdateWindow(window); - } else if (SaveManager.Is_Multiplayer_Saving_Allowed()) { - OutList.push_back(EventClass(PlayerPtr->HeapID, EventClass::SAVEGAME)); - *retval = IDC_SAVE_GAME; - } - } + if (!code) Game_Options_Queue(screen, UI_GAMEOPT_SAVE); break; case IDC_LOAD_GAME: - if (!code) { - if (Session.Type == GAME_NORMAL || Session.Type == GAME_SKIRMISH) { - ShowWindow(window, SW_HIDE); - UpdateWindow(MainWindow); - if (LoadOptionsClass().Load()) { - *retval = IDC_LOAD_GAME; - } else { - ShowWindow(window, SW_SHOW); - UpdateWindow(window); - } - } else if (SaveManager.Multiplayer_Load_Is_Allowed()) { - // A list opened from in here would sit inside the main loop and stall the - // match; the menu loop opens it between frames instead. - SpecialDialog = SDLG_LOAD; - *retval = IDC_LOAD_GAME; - } - } + if (!code) Game_Options_Queue(screen, UI_GAMEOPT_LOAD); break; case IDC_BRIEFING: - if (!code) { - *retval = IDC_BRIEFING; - } + if (!code) Game_Options_Queue(screen, UI_GAMEOPT_BRIEFING); break; case IDC_DELETE_GAME: - if (!code) { - ShowWindow(window, SW_HIDE); - UpdateWindow(MainWindow); - LoadOptionsClass().Delete(); - Game_Options_On_INITDIALOG(window); - ShowWindow(window, SW_SHOW); - UpdateWindow(window); - } + if (!code) Game_Options_Queue(screen, UI_GAMEOPT_DELETE); break; case IDC_RESUME_MISSION: if (!code) { - if (Session.Type == GAME_INTERNET) { - handle = GetDlgItem(window, IDC_CTRLWOL_CONNECTION); - if (handle) { - int fudge = 3 - SendMessage(handle, TBM_GETPOS, 0, 0); - if (fudge != Session.LatencyFudge) { - OutList.push_back(EventClass(PlayerPtr->HeapID, EventClass::LATENCYFUDGE, fudge)); - DebugString("LATENCYFUDGE event created - %d\n", fudge); - } - } - handle = GetDlgItem(window, IDC_GAME_SPEED_SLIDER); - if (handle) { - int speed = (OptionsClass::MAX_SPEED_SETTING-1) - SendMessage(handle, TBM_GETPOS, 0, 0); - if (Options.GameSpeed != speed) { - OutList.push_back(EventClass(PlayerPtr->HeapID, EventClass::GAMESPEED, speed)); - } - } + // The sliders are read here rather than tracked, because a keyboard + // or page move changes a track bar without raising WM_HSCROLL's + // thumb notification, and resume is where the dialog read them. + handle = GetDlgItem(window, IDC_CTRLWOL_CONNECTION); + if (handle) { + Game_Options_Queue(screen, UI_GAMEOPT_CONNECTION, SendMessage(handle, TBM_GETPOS, 0, 0)); } - *retval = IDOK; + handle = GetDlgItem(window, IDC_GAME_SPEED_SLIDER); + if (handle) { + Game_Options_Queue(screen, UI_GAMEOPT_SPEED, SendMessage(handle, TBM_GETPOS, 0, 0)); + } + Game_Options_Queue(screen, UI_GAMEOPT_RESUME); } break; case IDC_ABORT_MISSION: - if (!code) { - if (Session.Type == GAME_INTERNET) { - SpecialDialog = SDLG_SURRENDER; - if (!WestwoodOnline_Tournament) { - SpecialDialog = SDLG_ABORT; - } - } else { - SpecialDialog = SDLG_ABORT; - } - *retval = IDCANCEL; - } + if (!code) Game_Options_Queue(screen, UI_GAMEOPT_ABORT); break; case IDC_GAME_CONTROLS: - if (!code) { - SpecialDialog = SDLG_SETTINGS; - *retval = IDOK; - } + if (!code) Game_Options_Queue(screen, UI_GAMEOPT_SETTINGS); break; default: @@ -261,20 +283,28 @@ INT_PTR CALLBACK Game_Options_Dialog_Proc(HWND window, UINT message, WPARAM wpar case WM_HSCROLL: { if (LOWORD(wparam) == SB_THUMBTRACK) { int pos = HIWORD(wparam); - int textid; + char const * label = NULL; if ((HWND)lparam == GetDlgItem(window, IDC_GAME_SPEED_SLIDER)) { - textid = GameSpeedNames[pos]; + if (pos >= 0 && pos < (int)screen.SpeedLabels.size()) { + label = screen.SpeedLabels[pos].c_str(); + } handle = GetDlgItem(window, IDC_GAME_SPEED_LABEL); + Game_Options_Queue(screen, UI_GAMEOPT_SPEED, pos); } else if ((HWND)lparam == GetDlgItem(window, IDC_CTRLWOL_CONNECTION)) { - textid = GameConnectionQualityNames[pos]; + if (pos >= 0 && pos < (int)screen.ConnectionLabels.size()) { + label = screen.ConnectionLabels[pos].c_str(); + } + // The connection label shares the scroll speed label's identifier, which + // is what the IDD_OPT_CTRL_WOL template names it. handle = GetDlgItem(window, IDC_SCROLL_SPEED_LABEL); + Game_Options_Queue(screen, UI_GAMEOPT_CONNECTION, pos); } else { break; } - if (handle) { - Static_SetText(handle, Fetch_String(textid)); + if (handle && label != NULL) { + Static_SetText(handle, label); } } break; @@ -290,61 +320,28 @@ INT_PTR CALLBACK Game_Options_Dialog_Proc(HWND window, UINT message, WPARAM wpar /// /// Prepares the controls of the game options dialog. -/// This routine is called when the dialog is created, and again whenever a save or delete -/// has changed what is on disk. It decides which buttons the current game type allows the -/// player to use and primes the game speed and connection quality sliders. +/// Everything here comes out of the view-model, which the presenter refreshed before the +/// dialog was created and again whenever a save or a delete changed what is on disk. /// -void Game_Options_On_INITDIALOG(HWND window) +void Game_Options_On_INITDIALOG(HWND window, UIGameOptionsPresenterClass const & screen) { HWND handle; - if (Session.Type == GAME_NORMAL || Session.Type == GAME_SKIRMISH) { - bool present = LoadOptionsClass().Files_Present(); + Game_Options_Sync_Controls(window, screen); - handle = GetDlgItem(window, IDC_LOAD_GAME); - if (handle) { - EnableWindow(handle, present); - } - - handle = GetDlgItem(window, IDC_DELETE_GAME); - if (handle) { - EnableWindow(handle, present); - } - } - - if (Session.Type != GAME_NORMAL && Session.Type != GAME_SKIRMISH) { - handle = GetDlgItem(window, IDC_SAVE_GAME); - if (handle) { - EnableWindow(handle, SaveManager.Is_Multiplayer_Saving_Allowed()); - } - - handle = GetDlgItem(window, IDC_LOAD_GAME); - if (handle) { - EnableWindow(handle, SaveManager.Multiplayer_Load_Is_Allowed() && MultiplayerLoadOptionsClass().Files_Present()); - } - } - - if (Session.Type == GAME_INTERNET) { + if (screen.HasSliders) { handle = GetDlgItem(window, IDC_CTRLWOL_CONNECTION); if (handle) { - SetSliderRangeAndPos(handle, 0, 3, 3 - Session.LatencyFudge); + SetSliderRangeAndPos(handle, 0, 3, screen.ConnectionStep); } handle = GetDlgItem(window, IDC_GAME_SPEED_SLIDER); if (handle) { Slider_SetRange(handle, 0, OptionsClass::MAX_SPEED_SETTING-1); - Slider_SetPos(handle, (OptionsClass::MAX_SPEED_SETTING-1) - Options.GameSpeed); - } - } - - if (Session.Type == GAME_SKIRMISH) { - handle = GetDlgItem(window, IDC_BRIEFING); - if (handle) { - EnableWindow(handle, FALSE); + Slider_SetPos(handle, screen.SpeedStep); } } - } diff --git a/code/ui/uigameoptions.cpp b/code/ui/uigameoptions.cpp new file mode 100644 index 000000000..819368c86 --- /dev/null +++ b/code/ui/uigameoptions.cpp @@ -0,0 +1,228 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The in-game options screen. Behavior traced out of goptions.cpp and kept where it was, +// with the window handling left behind in the view. +// +// What the extraction fixes in place, each of which the dialog decided rather than the +// template: save and load mean different things in a solo game and in a session, and only +// the solo ones open a browser at all; delete never ends the screen, it just changes what +// is on disk and the screen is refreshed; a skirmish has no briefing to restate; resume is +// where the two sliders are applied, because dragging one only moved its label; abort +// asks for the surrender box rather than the abort box in a tournament session; and the +// screen answers with a choice rather than with a control identifier. +// +// docs/UI_DESIGN.md, "Screens", owns the contracts this keeps to. + +#include "always.h" + +#include "uigameoptions.h" + +#include "data.h" +#include "dbgprint.h" +#include "event.h" +#include "gamedlg.h" +#include "globals.h" +#include "house.h" +#include "language/language.h" +#include "loaddlg.h" +#include "options.h" +#include "savemgr.h" +#include "scenario.h" +#include "session.h" +#include "stats.h" + +#include "special.hh" + +#include + + +// The connection quality labels, best first, which is the order the slider counts in. +static int const _ConnectionNames[] = { + TXT_WORST_CONNECTION, + TXT_POOR_CONNECTION, + TXT_GOOD_CONNECTION, + TXT_BEST_CONNECTION +}; + +static int const CONNECTION_STEPS = 4; + + +static bool Is_Solo_Session(void) +{ + return(Session.Type == GAME_NORMAL || Session.Type == GAME_SKIRMISH); +} + + +/// +/// Copies the state the screen shows out of the game. +/// Called at open and again whenever a sub-screen has changed what is on disk, which is what +/// the dialog's second call to its own WM_INITDIALOG handler did. +/// +void UIGameOptionsPresenterClass::Refresh(void) +{ + IsMultiplayer = !Is_Solo_Session(); + HasSliders = (Session.Type == GAME_INTERNET); + + if (Is_Solo_Session()) { + bool const present = LoadOptionsClass().Files_Present(); + CanSave = true; + CanLoad = present; + CanDelete = present; + } else { + CanSave = SaveManager.Is_Multiplayer_Saving_Allowed(); + CanLoad = SaveManager.Multiplayer_Load_Is_Allowed() && MultiplayerLoadOptionsClass().Files_Present(); + CanDelete = true; + } + + CanBrief = (Session.Type != GAME_SKIRMISH); + + SpeedStep = (OptionsClass::MAX_SPEED_SETTING - 1) - Options.GameSpeed; + ConnectionStep = (CONNECTION_STEPS - 1) - Session.LatencyFudge; + + SpeedLabels.clear(); + for (int step = 0; step < OptionsClass::MAX_SPEED_SETTING; step++) { + SpeedLabels.push_back(Fetch_String(GameSpeedNames[step])); + } + + ConnectionLabels.clear(); + for (int step = 0; step < CONNECTION_STEPS; step++) { + ConnectionLabels.push_back(Fetch_String(_ConnectionNames[step])); + } +} + + +void UIGameOptionsPresenterClass::Finish(ChoiceType choice, UIResult::OutcomeType outcome) +{ + Choice = choice; + + UIResult result; + result.Outcome = outcome; + Result = result; +} + + +void UIGameOptionsPresenterClass::Execute(UIIntent const & intent) +{ + if (intent.Action == UI_GAMEOPT_SPEED) { + // Dragging only moves the label. The setting is applied when the player resumes, + // which is where the dialog read the slider back. + SpeedStep = intent.Value; + return; + } + + if (intent.Action == UI_GAMEOPT_CONNECTION) { + ConnectionStep = intent.Value; + return; + } + + // The two checks below are the dialog's own, made when the button was pressed rather + // than when it was enabled, because a session can withdraw permission while the screen + // is up. The view-model's CanSave and CanLoad say what to show, not what to allow. + if (intent.Action == UI_GAMEOPT_SAVE) { + if (Is_Solo_Session()) { + Pending = SUB_SAVE; + } else if (SaveManager.Is_Multiplayer_Saving_Allowed()) { + OutList.push_back(EventClass(PlayerPtr->HeapID, EventClass::SAVEGAME)); + Finish(CHOICE_SAVE_REQUESTED, UIResult::OUTCOME_ACCEPTED); + } + return; + } + + if (intent.Action == UI_GAMEOPT_LOAD) { + if (Is_Solo_Session()) { + Pending = SUB_LOAD; + } else if (SaveManager.Multiplayer_Load_Is_Allowed()) { + // A list opened from in here would sit inside the main loop and stall the match; + // the menu loop opens it between frames instead. + SpecialDialog = SDLG_LOAD; + Finish(CHOICE_LOADED, UIResult::OUTCOME_ACCEPTED); + } + return; + } + + if (intent.Action == UI_GAMEOPT_DELETE) { + Pending = SUB_DELETE; + return; + } + + if (intent.Action == UI_GAMEOPT_BRIEFING) { + Finish(CHOICE_BRIEFING, UIResult::OUTCOME_ACCEPTED); + return; + } + + if (intent.Action == UI_GAMEOPT_RESUME) { + if (Session.Type == GAME_INTERNET) { + int const fudge = (CONNECTION_STEPS - 1) - ConnectionStep; + if (fudge != Session.LatencyFudge) { + OutList.push_back(EventClass(PlayerPtr->HeapID, EventClass::LATENCYFUDGE, fudge)); + DebugString("LATENCYFUDGE event created - %d\n", fudge); + } + + int const speed = (OptionsClass::MAX_SPEED_SETTING - 1) - SpeedStep; + if (Options.GameSpeed != speed) { + OutList.push_back(EventClass(PlayerPtr->HeapID, EventClass::GAMESPEED, speed)); + } + } + Finish(CHOICE_RESUME, UIResult::OUTCOME_ACCEPTED); + return; + } + + if (intent.Action == UI_GAMEOPT_ABORT) { + if (Session.Type == GAME_INTERNET) { + SpecialDialog = WestwoodOnline_Tournament ? SDLG_SURRENDER : SDLG_ABORT; + } else { + SpecialDialog = SDLG_ABORT; + } + Finish(CHOICE_ABORT, UIResult::OUTCOME_CANCELLED); + return; + } + + if (intent.Action == UI_GAMEOPT_SETTINGS) { + SpecialDialog = SDLG_SETTINGS; + Finish(CHOICE_SETTINGS, UIResult::OUTCOME_ACCEPTED); + return; + } +} + + +/// +/// Runs the browser an executed intent asked for. +/// The caller has already got its own presentation out of the way, which is all a view has +/// to do about a screen opening on top of this one. +/// +void UIGameOptionsPresenterClass::Run_Pending(void) +{ + SubScreenType const pending = Pending; + Pending = SUB_NONE; + + switch (pending) { + case SUB_SAVE: { + char description[512]; + std::strcpy(description, Scen->Description); + LoadOptionsClass().Save(description); + Refresh(); + } + break; + + case SUB_LOAD: + if (LoadOptionsClass().Load()) { + Finish(CHOICE_LOADED, UIResult::OUTCOME_ACCEPTED); + } + break; + + case SUB_DELETE: + LoadOptionsClass().Delete(); + Refresh(); + break; + + default: + break; + } +} diff --git a/code/ui/uigameoptions.h b/code/ui/uigameoptions.h new file mode 100644 index 000000000..3c88a0169 --- /dev/null +++ b/code/ui/uigameoptions.h @@ -0,0 +1,99 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The in-game options screen's behavior, with no toolkit in it. It is the door to save, +// load, the mission briefing, the game settings and abort, so what it decides is worth +// having in one toolkit-free place before either view is written. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + +#include +#include + + +// What a view raises. +inline constexpr char const * UI_GAMEOPT_SAVE = "save"; +inline constexpr char const * UI_GAMEOPT_LOAD = "load"; +inline constexpr char const * UI_GAMEOPT_DELETE = "delete"; +inline constexpr char const * UI_GAMEOPT_BRIEFING = "briefing"; +inline constexpr char const * UI_GAMEOPT_RESUME = "resume"; +inline constexpr char const * UI_GAMEOPT_ABORT = "abort"; +inline constexpr char const * UI_GAMEOPT_SETTINGS = "settings"; +inline constexpr char const * UI_GAMEOPT_SPEED = "speed"; // Value: slider step +inline constexpr char const * UI_GAMEOPT_CONNECTION = "connection"; // Value: slider step + + +class UIGameOptionsPresenterClass : public UIPresenterClass +{ + public: + // What the player settled on. The driver maps this onto the value its own caller + // expects; no control identifier reaches this class. + enum ChoiceType { + CHOICE_NONE, + CHOICE_RESUME, + CHOICE_BRIEFING, + CHOICE_ABORT, + CHOICE_SETTINGS, + CHOICE_SAVE_REQUESTED, + CHOICE_LOADED, + }; + + // A screen this one opens on top of itself. The view hides whatever it has to hide, + // then asks for the pending one to run; only the view knows how to get out of the + // way, and only this class knows what running it means. + enum SubScreenType { + SUB_NONE, + SUB_SAVE, + SUB_LOAD, + SUB_DELETE, + }; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + + // Runs the sub-screen an executed intent asked for and clears the request. Safe to + // call with nothing pending. + void Run_Pending(void); + + /* + ** The view-model. Plain values, and the only thing a view reads. + */ + bool CanSave = false; + bool CanLoad = false; + bool CanDelete = false; + bool CanBrief = false; + + // Does the screen carry the game speed and connection quality sliders? Only the + // template the game shows for an internet session does. + bool HasSliders = false; + + // Is this a session the player saves and loads through the multiplayer path? The two + // paths differ in what the buttons mean, not only in whether they are enabled. + bool IsMultiplayer = false; + + // Slider steps, counted the way the templates count them: the fastest game speed and + // the best connection sit at step zero, so a step is the setting counted backward. A + // view shows steps; only this class knows what they mean. + int SpeedStep = 0; + int ConnectionStep = 0; + + // The label beside each slider, indexed by step. + std::vector SpeedLabels; + std::vector ConnectionLabels; + + ChoiceType Choice = CHOICE_NONE; + SubScreenType Pending = SUB_NONE; + + private: + void Finish(ChoiceType choice, UIResult::OutcomeType outcome); +}; From f533cbeb5e69e8aa726d85673f527af2291d4928 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 03:11:59 +0100 Subject: [PATCH 093/179] refactor(ui): put the abort and surrender screen behind a presenter Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/goptions.cpp | 91 ++++++++++++++++++++++++++++++++------------- code/ui/uiabort.cpp | 59 +++++++++++++++++++++++++++++ code/ui/uiabort.h | 55 +++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 25 deletions(-) create mode 100644 code/ui/uiabort.cpp create mode 100644 code/ui/uiabort.h diff --git a/code/goptions.cpp b/code/goptions.cpp index 2245ed933..78fc6a2e0 100644 --- a/code/goptions.cpp +++ b/code/goptions.cpp @@ -48,6 +48,7 @@ #include "savemgr.h" #include "scenario.h" #include "stats.h" +#include "ui/uiabort.h" #include "ui/uigameoptions.h" #include "special.hh" @@ -62,6 +63,9 @@ void Abort_Dialog_On_COMMAND(HWND window, UINT message, WPARAM wparam, LPARAM lp // of the dialog, which is the same lifetime DWLP_USER gave the result pointer it replaces. static UIGameOptionsPresenterClass * _Screen = NULL; +// The abort screen, owned by Abort_Dialog for the life of its dialog. +static UIAbortPresenterClass * _Abort = NULL; + static void Game_Options_Queue(UIGameOptionsPresenterClass & screen, char const * action, int value = 0) { @@ -355,32 +359,60 @@ void Game_Options_On_INITDIALOG(HWND window, UIGameOptionsPresenterClass const & /// IDCANCEL to carry on playing. int Abort_Dialog(void) { - int rc = 0; + UIAbortPresenterClass screen; + screen.Refresh(); + + _Abort = &screen; HWND dialog = OwnerDraw::Begin_Dialog(IDD_MISSION_ABORT, Abort_Dialog_Proc); if (dialog) { - SetWindowLongPtr(dialog, DWLP_USER, (LONG_PTR)&rc); - OwnerDraw::Display_Dialog(dialog); - while (rc == 0) { + while (!screen.Result.has_value()) { if (OwnerDraw::Dialog_Message_Handler() == true) { - rc = IDOK; + // A session that ended underneath the box answers as though the player chose + // to quit, which is the IDOK the driver used to write. + UIResult ended; + ended.Outcome = UIResult::OUTCOME_SESSION_ENDED; + ended.GameEnded = true; + screen.Choice = UIAbortPresenterClass::CHOICE_QUIT; + screen.Result = ended; + break; } + + screen.Drain(); } + OwnerDraw::End_Dialog(dialog); } - return(rc); + + _Abort = NULL; + + switch (screen.Choice) { + case UIAbortPresenterClass::CHOICE_QUIT: + return(IDOK); + + case UIAbortPresenterClass::CHOICE_RESTART: + return(IDABORT); + + case UIAbortPresenterClass::CHOICE_CANCEL: + return(IDCANCEL); + + default: + break; + } + + // The dialog could not be created, which left its driver's result at zero. + return(0); } /// /// Handles messages for the abort mission dialog. -/// This routine offers every message to the owner draw system first. What is left it uses -/// to relabel the restart button as a surrender for a multiplayer game, and to pass button -/// presses along to Abort_Dialog_On_COMMAND. +/// The procedure relabels and disables the middle button from the view-model, and queues +/// what the player pressed for the driver to execute after the pump. /// /// Returns with the result of the owner draw default dialog handler. INT_PTR CALLBACK Abort_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) @@ -389,12 +421,20 @@ INT_PTR CALLBACK Abort_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPA INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); if (rc == 0) { + if (_Abort == NULL) { + return(0); + } + + UIAbortPresenterClass & screen = *_Abort; + switch (message) { case WM_INITDIALOG: handle = GetDlgItem(window, IDC_RESTART_MISSION); - if (Session.Type != GAME_NORMAL) { - SetWindowText(handle, Fetch_String(TXT_SURRENDER)); - if (PlayerPtr->IsDefeated || PlayerPtr->IsToWin || PlayerPtr->IsToLose || PlayerPtr->IsToDie) { + if (handle) { + if (!screen.RestartCaption.empty()) { + SetWindowText(handle, screen.RestartCaption.c_str()); + } + if (!screen.CanRestart) { EnableWindow(handle, FALSE); } } @@ -411,34 +451,35 @@ INT_PTR CALLBACK Abort_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPA /// -/// Handles a button press in the abort mission dialog. -/// This routine records the player's choice in the result variable that Abort_Dialog -/// attached to the dialog window, which is what ends the dialog's message pump. +/// Queues what the player pressed in the abort mission dialog. /// /// The control identifier of the button that was pressed. /// The notification code that came with the button press. void Abort_Dialog_On_COMMAND(HWND window, UINT message, WPARAM wparam, LPARAM lparam) { - int* retval = (int *)GetWindowLongPtr(window, DWLP_USER); + if (_Abort == NULL || lparam != 0) { + return; + } + + UIIntent intent; switch ((int)message) { case IDC_ABORT_MISSION: - if (lparam == 0) { - *retval = IDOK; - } + intent.Action = UI_ABORT_QUIT; break; case IDC_RESTART_MISSION: - if (lparam == 0) { - *retval = IDABORT; - } + intent.Action = UI_ABORT_RESTART; break; case IDOK: case IDCANCEL: - if (lparam == 0) { - *retval = IDCANCEL; - } + intent.Action = UI_ABORT_CANCEL; break; + + default: + return; } + + _Abort->Queue(intent); } diff --git a/code/ui/uiabort.cpp b/code/ui/uiabort.cpp new file mode 100644 index 000000000..922bb8635 --- /dev/null +++ b/code/ui/uiabort.cpp @@ -0,0 +1,59 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The abort and surrender screen. Behavior traced out of goptions.cpp. +// +// Two things the dialog decided rather than the template: the middle button is relabelled +// a surrender for anything but a solo mission, and it is disabled for a player whose fate +// is already settled, so a defeated player is offered no surrender. Only the surrender +// caption comes from the string table; the restart caption stays the template's, which is +// where its translation lives, so the view-model asks for an override rather than naming +// both. + +#include "always.h" + +#include "uiabort.h" + +#include "data.h" +#include "house.h" +#include "language/language.h" +#include "session.h" + + +void UIAbortPresenterClass::Refresh(void) +{ + if (Session.Type == GAME_NORMAL) { + RestartCaption.clear(); + CanRestart = true; + } else { + RestartCaption = Fetch_String(TXT_SURRENDER); + CanRestart = !(PlayerPtr->IsDefeated || PlayerPtr->IsToWin || PlayerPtr->IsToLose || PlayerPtr->IsToDie); + } +} + + +void UIAbortPresenterClass::Execute(UIIntent const & intent) +{ + UIResult result; + + if (intent.Action == UI_ABORT_QUIT) { + Choice = CHOICE_QUIT; + result.Outcome = UIResult::OUTCOME_ACCEPTED; + } else if (intent.Action == UI_ABORT_RESTART) { + Choice = CHOICE_RESTART; + result.Outcome = UIResult::OUTCOME_ACCEPTED; + } else if (intent.Action == UI_ABORT_CANCEL) { + Choice = CHOICE_CANCEL; + result.Outcome = UIResult::OUTCOME_CANCELLED; + } else { + return; + } + + Result = result; +} diff --git a/code/ui/uiabort.h b/code/ui/uiabort.h new file mode 100644 index 000000000..f0ae6214f --- /dev/null +++ b/code/ui/uiabort.h @@ -0,0 +1,55 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The abort and surrender screen's behavior, with no toolkit in it. One screen serves both: +// the middle choice is a restart in a solo mission and a surrender in a session, which is +// what IDD_MISSION_ABORT's own procedure decided at WM_INITDIALOG. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + +#include + + +inline constexpr char const * UI_ABORT_QUIT = "quit"; +inline constexpr char const * UI_ABORT_RESTART = "restart"; +inline constexpr char const * UI_ABORT_CANCEL = "cancel"; + + +class UIAbortPresenterClass : public UIPresenterClass +{ + public: + enum ChoiceType { + CHOICE_NONE, + CHOICE_QUIT, + CHOICE_RESTART, + CHOICE_CANCEL, + }; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + + /* + ** The view-model. Plain values, and the only thing a view reads. + */ + + // What the middle button should be relabelled to, or empty to leave the caption the + // view already carries. Only the surrender overrides it; the restart is the caption + // the template holds, and the template is the localized resource. + std::string RestartCaption; + + // Is the middle choice offered at all? A player whose fate is already settled cannot + // surrender, which is what the dialog disabled the button for. + bool CanRestart = true; + + ChoiceType Choice = CHOICE_NONE; +}; From 32213b0e12ee1562113f206e012a170dcdc17c98 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 03:16:14 +0100 Subject: [PATCH 094/179] refactor(ui): put the game controls screen behind a presenter Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/gamedlg.cpp | 253 ++++++++++++++++++------------------- code/gamedlg.h | 7 +- code/ui/uigamecontrols.cpp | 205 ++++++++++++++++++++++++++++++ code/ui/uigamecontrols.h | 110 ++++++++++++++++ 4 files changed, 443 insertions(+), 132 deletions(-) create mode 100644 code/ui/uigamecontrols.cpp create mode 100644 code/ui/uigamecontrols.h diff --git a/code/gamedlg.cpp b/code/gamedlg.cpp index 4e1cc50e6..587c1cca5 100644 --- a/code/gamedlg.cpp +++ b/code/gamedlg.cpp @@ -48,9 +48,13 @@ #include "queue.h" #include "session.h" #include "techno.h" +#include "ui/uigamecontrols.h" #include "special.hh" +#include +#include + int GameSpeedNames[OptionsClass::MAX_SPEED_SETTING] = { TXT_SLOWEST, TXT_SLOWER, @@ -87,6 +91,62 @@ int GameDifficultyNames[OptionsClass::MAX_DIFFICULTY_SETTING] = { INT_PTR CALLBACK Game_Controls_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); void Game_Controls_Dialog_On_COMMAND(HWND window, UINT message, WPARAM wparam, LPARAM lparam); + +// The screen the dialog procedure reads and writes. The driver owns it for the whole life +// of the dialog, which is the lifetime DWLP_USER gave the result pointer it replaces. +static UIGameControlsPresenterClass * _Screen = NULL; + + +static void Game_Controls_Queue(UIGameControlsPresenterClass & screen, char const * action, int value = 0) +{ + UIIntent intent; + intent.Action = action; + intent.Value = value; + screen.Queue(intent); +} + + +// Reads every control back into the view-model. The dialog read them at IDOK rather than +// tracking them, because a keyboard or page move changes a track bar without raising the +// thumb notification the label follows. +static void Game_Controls_Read_Back(HWND window, UIGameControlsPresenterClass & screen) +{ + static struct { + int Control; + char const * Action; + } const _sliders[] = { + { IDC_GAME_SPEED_SLIDER, UI_GAMECTRL_SPEED }, + { IDC_SCROLL_SPEED_SLIDER, UI_GAMECTRL_SCROLL }, + { IDC_DETAIL_LEVEL_SLIDER, UI_GAMECTRL_DETAIL }, + { IDC_DIFFICULTY_SLIDER, UI_GAMECTRL_DIFFICULTY }, + }; + + static struct { + int Control; + char const * Action; + } const _checks[] = { + { IDC_SIDEBAR_TEXT, UI_GAMECTRL_CAMEO_TEXT }, + { IDC_TARGET_LINES, UI_GAMECTRL_ACTION_LINES }, + { IDC_TOOLTIPS, UI_GAMECTRL_TOOLTIPS }, + { IDC_SCROLL_COASTING, UI_GAMECTRL_COASTING }, + { IDC_EDGE_SCROLL, UI_GAMECTRL_EDGE_SCROLL }, + }; + + for (auto const & slider : _sliders) { + HWND handle = GetDlgItem(window, slider.Control); + if (handle) { + Game_Controls_Queue(screen, slider.Action, Slider_GetPos(handle)); + } + } + + for (auto const & check : _checks) { + HWND handle = GetDlgItem(window, check.Control); + if (handle) { + Game_Controls_Queue(screen, check.Action, Button_GetCheck(handle) == TRUE ? 1 : 0); + } + } +} + /*********************************************************************************************** * OptionsClass::Process -- Handles all the options graphic interface. * * * @@ -101,10 +161,13 @@ void Game_Controls_Dialog_On_COMMAND(HWND window, UINT message, WPARAM wparam, L *=============================================================================================*/ void GameControlsClass::Dialog(void) { - int res = -1; - DebugString("GameControls: GameSpeed = %d, ScrollRate = %d, Detail = %d\n", Options.GameSpeed, Options.ScrollRate, Options.DetailLevel); + UIGameControlsPresenterClass screen; + screen.Refresh(); + + _Screen = &screen; + if (GameActive == true) { if (Session.Type == GAME_INTERNET) { _Dialog = OwnerDraw::Begin_Dialog(IDD_OPT_CTRL_GAME_WOL, Game_Controls_Dialog_Proc); @@ -117,116 +180,42 @@ void GameControlsClass::Dialog(void) if (_Dialog) { - SetWindowLongPtr(_Dialog, DWLP_USER, (LONG_PTR)&res); - OwnerDraw::Display_Dialog(_Dialog); - while (res == -1) { + while (!screen.Result.has_value()) { if (OwnerDraw::Dialog_Message_Handler() == true) { - res = 2; - } - if (!GameActive) { - Title_Screen_Restore(); - } - } - if (res == 1) { - Set(); - Options.Save_Settings(); - } - - OwnerDraw::End_Dialog(_Dialog); - } - - DebugString("GameControls: GameSpeed = %d, ScrollRate = %d, Detail = %d\n", Options.GameSpeed, Options.ScrollRate, Options.DetailLevel); -} - - -/// -/// Sets the game options from the game controls dialog. -/// This routine is called when the player accepts the dialog. Each control is asked for -/// its current value and the answer is handed to the option it governs, along with any -/// notification the rest of the game needs -- the map is told to rebuild its cell drawers -/// when the detail level changes, and a game speed change during a network game is issued -/// as an event so that every player stays in step. -/// -void GameControlsClass::Set(void) -{ - HWND handle; - - handle = GetDlgItem(_Dialog, IDC_GAME_SPEED_SLIDER); - if (handle) { - int gamespeed = (OptionsClass::MAX_SPEED_SETTING-1) - Slider_GetPos(handle); - if (Options.GameSpeed != gamespeed) { - if (GameActive == true && Session.Type != GAME_NORMAL && Session.Type != GAME_SKIRMISH) { - OutList.push_back(EventClass(PlayerPtr->HeapID, EventClass::GAMESPEED, gamespeed)); - } else { - Options.GameSpeed = gamespeed; + // A session that ended underneath the screen leaves the settings alone, + // which is what the driver's own result of two did. + UIResult ended; + ended.Outcome = UIResult::OUTCOME_SESSION_ENDED; + ended.GameEnded = true; + screen.Result = ended; + break; } - } - } - - handle = GetDlgItem(_Dialog, IDC_SCROLL_SPEED_SLIDER); - if (handle) { - Options.ScrollRate = (OptionsClass::MAX_SCROLL_SETTING-1) - Slider_GetPos(handle); - } - handle = GetDlgItem(_Dialog, IDC_DETAIL_LEVEL_SLIDER); - if (handle) { - int detailevel = Slider_GetPos(handle); - if (Options.DetailLevel != detailevel) { - Options.DetailLevel = detailevel; - Map.Reinit_Cell_Drawers(); + screen.Drain(); + screen.Service(); } - } - - handle = GetDlgItem(_Dialog, IDC_SIDEBAR_TEXT); - if (handle) { - bool cameotext = Button_GetCheck(handle) == TRUE; - if (Options.SidebarCameoText != cameotext) { - Options.SidebarCameoText = cameotext; - Map.Toggle_Cameo_Text(cameotext); - } - } - handle = GetDlgItem(_Dialog, IDC_TARGET_LINES); - if (handle) { - Options.ActionLines = Button_GetCheck(handle) == TRUE; - TechnoClass::Set_Action_Lines(Options.ActionLines); - } - - handle = GetDlgItem(_Dialog, IDC_TOOLTIPS); - if (handle) { - Options.ToolTips = Button_GetCheck(handle) == TRUE; - if (ToolTips != NULL && GameActive == true) { - ToolTips->Activate(Options.ToolTips); + if (screen.Commits()) { + screen.Apply(); + Options.Save_Settings(); } - } - handle = GetDlgItem(_Dialog, IDC_SCROLL_COASTING); - if (handle) { - Options.ScrollMethod = Button_GetCheck(handle) == TRUE ? 0 : 1; + OwnerDraw::End_Dialog(_Dialog); } - handle = GetDlgItem(_Dialog, IDC_EDGE_SCROLL); - if (handle) { - Options.AutoScroll = Button_GetCheck(handle) == TRUE; - } + _Screen = NULL; - if (GameActive == false) { - handle = GetDlgItem(_Dialog, IDC_DIFFICULTY_SLIDER); - if (handle) { - Options.Difficulty = Slider_GetPos(handle); - } - } + DebugString("GameControls: GameSpeed = %d, ScrollRate = %d, Detail = %d\n", Options.GameSpeed, Options.ScrollRate, Options.DetailLevel); } /// /// Handles the messages sent to the game controls dialog. -/// This routine gives the ownerdraw layer first refusal on every message. Anything it -/// leaves alone is used to prime the sliders and check boxes from the current options, to -/// track the label alongside a slider the player is dragging, and to route commands on to -/// Game_Controls_Dialog_On_COMMAND. +/// The procedure primes its controls from the view-model, tracks the label alongside a +/// slider the player is dragging, and queues what the player pressed for the driver to +/// execute after the pump. /// /// Returns with a non-zero value if the message was consumed by the ownerdraw /// layer. @@ -237,65 +226,71 @@ INT_PTR CALLBACK Game_Controls_Dialog_Proc(HWND window, UINT message, WPARAM wpa INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); if (rc == 0) { + if (_Screen == NULL) { + return(0); + } + + UIGameControlsPresenterClass & screen = *_Screen; + switch (message) { case WM_INITDIALOG: handle = GetDlgItem(window, IDC_GAME_SPEED_SLIDER); if (handle) { SendMessage(handle, OD_TRACKNUMBERS, 0, 0); Slider_SetRange(handle, 0, (OptionsClass::MAX_SPEED_SETTING-1)); - Slider_SetPos(handle, (OptionsClass::MAX_SPEED_SETTING-1) - Options.GameSpeed); + Slider_SetPos(handle, screen.SpeedStep); } handle = GetDlgItem(window, IDC_SCROLL_SPEED_SLIDER); if (handle) { SendMessage(handle, OD_TRACKNUMBERS, 0, 0); Slider_SetRange(handle, 0, (OptionsClass::MAX_SCROLL_SETTING-1)); - Slider_SetPos(handle, (OptionsClass::MAX_SCROLL_SETTING-1) - Options.ScrollRate); + Slider_SetPos(handle, screen.ScrollStep); } handle = GetDlgItem(window, IDC_DETAIL_LEVEL_SLIDER); if (handle) { SendMessage(handle, OD_TRACKNUMBERS, 0, 0); Slider_SetRange(handle, 0, (OptionsClass::MAX_DETAIL_SETTING-1)); - Slider_SetPos(handle, Options.DetailLevel); + Slider_SetPos(handle, screen.DetailStep); } handle = GetDlgItem(window, IDC_SIDEBAR_TEXT); if (handle) { - Button_SetCheck(handle, Options.SidebarCameoText != false); + Button_SetCheck(handle, screen.CameoText); } handle = GetDlgItem(window, IDC_TARGET_LINES); if (handle) { - Button_SetCheck(handle, Options.ActionLines != false); + Button_SetCheck(handle, screen.ActionLines); } handle = GetDlgItem(window, IDC_TOOLTIPS); if (handle) { - Button_SetCheck(handle, Options.ToolTips != false); + Button_SetCheck(handle, screen.ShowToolTips); } handle = GetDlgItem(window, IDC_SCROLL_COASTING); if (handle) { - Button_SetCheck(handle, Options.ScrollMethod == 0); + Button_SetCheck(handle, screen.Coasting); } handle = GetDlgItem(window, IDC_EDGE_SCROLL); if (handle) { - Button_SetCheck(handle, Options.AutoScroll != false); + Button_SetCheck(handle, screen.EdgeScroll); } - if (GameActive == true) { + if (screen.Has_Sub_Screens()) { handle = GetDlgItem(window, IDC_OPT_SOUND_BTN); if (handle) { - EnableWindow(handle, AudioEngine.Is_Available()); + EnableWindow(handle, screen.SoundAvailable); } } else { handle = GetDlgItem(window, IDC_DIFFICULTY_SLIDER); if (handle) { SendMessage(handle, OD_TRACKNUMBERS, 0, 0); Slider_SetRange(handle, 0, (OptionsClass::MAX_DIFFICULTY_SETTING-1)); - Slider_SetPos(handle, Options.Difficulty); + Slider_SetPos(handle, screen.DifficultyStep); } } break; @@ -307,24 +302,24 @@ INT_PTR CALLBACK Game_Controls_Dialog_Proc(HWND window, UINT message, WPARAM wpa case WM_HSCROLL: if (LOWORD(wparam) == SB_THUMBTRACK) { index = HIWORD(wparam); - int name; + std::vector const * labels = NULL; handle = 0; if ((HWND)lparam == GetDlgItem(window, IDC_GAME_SPEED_SLIDER)) { - name = GameSpeedNames[index]; + labels = &screen.SpeedLabels; handle = GetDlgItem(window, IDC_GAME_SPEED_LABEL); } else if ((HWND)lparam == GetDlgItem(window, IDC_SCROLL_SPEED_SLIDER)) { - name = GameScrollSpeedNames[index]; + labels = &screen.ScrollLabels; handle = GetDlgItem(window, IDC_SCROLL_SPEED_LABEL); } else if ((HWND)lparam == GetDlgItem(window, IDC_DETAIL_LEVEL_SLIDER)) { - name = GameDetailLevelNames[index]; + labels = &screen.DetailLabels; handle = GetDlgItem(window, IDC_DETAIL_LEVEL_LABEL); - } else if (GameActive == false && (HWND)lparam == GetDlgItem(window, IDC_DIFFICULTY_SLIDER)) { - name = GameDifficultyNames[index]; + } else if (!screen.Has_Sub_Screens() && (HWND)lparam == GetDlgItem(window, IDC_DIFFICULTY_SLIDER)) { + labels = &screen.DifficultyLabels; handle = GetDlgItem(window, IDC_DIFFICULTY_LABEL); } - if (handle) { - SetWindowText(handle, Fetch_String(name)); + if (handle && labels != NULL && index >= 0 && index < (int)labels->size()) { + SetWindowText(handle, (*labels)[index].c_str()); } } break; @@ -336,41 +331,45 @@ INT_PTR CALLBACK Game_Controls_Dialog_Proc(HWND window, UINT message, WPARAM wpa /// -/// Handles the button presses of the game controls dialog. -/// This routine is called by the dialog procedure whenever a control notifies it. The -/// answer is stored back through the result pointer the dialog was created with, which is -/// what releases GameControlsClass::Dialog from its message loop. +/// Queues what the player pressed in the game controls dialog. +/// Leaving through the sound or the keyboard button reads the controls back as the accept +/// button does, because the dialog answered with the same IDOK for all three. /// /// The game controls dialog window. /// The identifier of the control that was activated. /// The notification code the control sent. void Game_Controls_Dialog_On_COMMAND(HWND window, UINT message, WPARAM wparam, LPARAM lparam) { - int* retval = (int *)GetWindowLongPtr(window, DWLP_USER); + if (_Screen == NULL) { + return; + } + + UIGameControlsPresenterClass & screen = *_Screen; switch ((INT)message) { case IDC_OPT_KEYBOARD_BTN: - if (lparam == 0 && GameActive == true) { - SpecialDialog = SDLG_KEYBOARD; - *retval = IDOK; + if (lparam == 0 && screen.Has_Sub_Screens()) { + Game_Controls_Read_Back(window, screen); + Game_Controls_Queue(screen, UI_GAMECTRL_KEYBOARD); } break; case IDC_OPT_SOUND_BTN: - if (lparam == 0 && GameActive == true) { - SpecialDialog = SDLG_SOUND; - *retval = IDOK; + if (lparam == 0 && screen.Has_Sub_Screens()) { + Game_Controls_Read_Back(window, screen); + Game_Controls_Queue(screen, UI_GAMECTRL_SOUND); } break; case IDOK: if (lparam == 0) { - *retval = IDOK; + Game_Controls_Read_Back(window, screen); + Game_Controls_Queue(screen, UI_GAMECTRL_ACCEPT); } break; case IDCANCEL: - *retval = IDCANCEL; + Game_Controls_Queue(screen, UI_GAMECTRL_CANCEL); break; } } diff --git a/code/gamedlg.h b/code/gamedlg.h index 959267548..6afe494e9 100644 --- a/code/gamedlg.h +++ b/code/gamedlg.h @@ -51,14 +51,11 @@ class GameControlsClass return(GameDifficultyNames[difficulty]); } - private: - void Set(void); - private: /* * This is the window handle of the game controls dialog while it is displayed. The - * player's settings are read back off its controls, so the handle is only - * meaningful between the dialog being created and destroyed. + * dialog is primed from it, so the handle is only meaningful between the dialog + * being created and destroyed. */ HWND _Dialog; }; diff --git a/code/ui/uigamecontrols.cpp b/code/ui/uigamecontrols.cpp new file mode 100644 index 000000000..674e41ea6 --- /dev/null +++ b/code/ui/uigamecontrols.cpp @@ -0,0 +1,205 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The game controls screen. Behavior traced out of gamedlg.cpp. +// +// What the extraction fixes in place, none of it obvious from the templates: leaving +// through the sound or the keyboard button applies and saves the settings, because both +// wrote the same IDOK the accept button did; the difficulty is applied only with no game +// running, so the slider the in-game template also carries is never read; a game speed +// change during a network session is issued as an event instead of being written, so every +// player stays in step; and the internet variant carries no game speed slider at all. +// +// docs/UI_DESIGN.md, "Screens", owns the contracts this keeps to. + +#include "always.h" + +#include "uigamecontrols.h" + +#include "_map.h" +#include "audio/audioengine.h" +#include "_tooltip.h" +#include "cctooltip.h" +#include "data.h" +#include "event.h" +#include "gamedlg.h" +#include "globals.h" +#include "house.h" +#include "init.h" +#include "language/language.h" +#include "options.h" +#include "session.h" +#include "techno.h" + +#include "special.hh" + + +static void Fill_Labels(std::vector & labels, int const * names, int count) +{ + labels.clear(); + for (int index = 0; index < count; index++) { + labels.push_back(Fetch_String(names[index])); + } +} + + +void UIGameControlsPresenterClass::Refresh(void) +{ + if (GameActive) { + Variant = (Session.Type == GAME_INTERNET) ? VARIANT_INTERNET : VARIANT_SESSION; + } else { + Variant = VARIANT_FRONTEND; + } + + SpeedStep = (OptionsClass::MAX_SPEED_SETTING - 1) - Options.GameSpeed; + ScrollStep = (OptionsClass::MAX_SCROLL_SETTING - 1) - Options.ScrollRate; + DetailStep = Options.DetailLevel; + DifficultyStep = Options.Difficulty; + + CameoText = Options.SidebarCameoText; + ActionLines = Options.ActionLines; + ShowToolTips = Options.ToolTips; + Coasting = (Options.ScrollMethod == 0); + EdgeScroll = Options.AutoScroll; + + SoundAvailable = AudioEngine.Is_Available(); + + Fill_Labels(SpeedLabels, GameSpeedNames, OptionsClass::MAX_SPEED_SETTING); + Fill_Labels(ScrollLabels, GameScrollSpeedNames, OptionsClass::MAX_SCROLL_SETTING); + Fill_Labels(DetailLabels, GameDetailLevelNames, OptionsClass::MAX_DETAIL_SETTING); + Fill_Labels(DifficultyLabels, GameDifficultyNames, OptionsClass::MAX_DIFFICULTY_SETTING); +} + + +/// +/// The maintenance the dialog driver ran on every pass of its own loop. +/// +void UIGameControlsPresenterClass::Service(void) +{ + if (!GameActive) { + Title_Screen_Restore(); + } +} + + +bool UIGameControlsPresenterClass::Commits(void) const +{ + return(Choice == CHOICE_ACCEPT || Choice == CHOICE_SOUND || Choice == CHOICE_KEYBOARD); +} + + +void UIGameControlsPresenterClass::Execute(UIIntent const & intent) +{ + if (intent.Action == UI_GAMECTRL_SPEED) { + SpeedStep = intent.Value; + return; + } + if (intent.Action == UI_GAMECTRL_SCROLL) { + ScrollStep = intent.Value; + return; + } + if (intent.Action == UI_GAMECTRL_DETAIL) { + DetailStep = intent.Value; + return; + } + if (intent.Action == UI_GAMECTRL_DIFFICULTY) { + DifficultyStep = intent.Value; + return; + } + if (intent.Action == UI_GAMECTRL_CAMEO_TEXT) { + CameoText = (intent.Value != 0); + return; + } + if (intent.Action == UI_GAMECTRL_ACTION_LINES) { + ActionLines = (intent.Value != 0); + return; + } + if (intent.Action == UI_GAMECTRL_TOOLTIPS) { + ShowToolTips = (intent.Value != 0); + return; + } + if (intent.Action == UI_GAMECTRL_COASTING) { + Coasting = (intent.Value != 0); + return; + } + if (intent.Action == UI_GAMECTRL_EDGE_SCROLL) { + EdgeScroll = (intent.Value != 0); + return; + } + + UIResult result; + + if (intent.Action == UI_GAMECTRL_ACCEPT) { + Choice = CHOICE_ACCEPT; + result.Outcome = UIResult::OUTCOME_ACCEPTED; + } else if (intent.Action == UI_GAMECTRL_CANCEL) { + Choice = CHOICE_CANCEL; + result.Outcome = UIResult::OUTCOME_CANCELLED; + } else if (intent.Action == UI_GAMECTRL_SOUND) { + if (!Has_Sub_Screens()) { + return; + } + SpecialDialog = SDLG_SOUND; + Choice = CHOICE_SOUND; + result.Outcome = UIResult::OUTCOME_ACCEPTED; + } else if (intent.Action == UI_GAMECTRL_KEYBOARD) { + if (!Has_Sub_Screens()) { + return; + } + SpecialDialog = SDLG_KEYBOARD; + Choice = CHOICE_KEYBOARD; + result.Outcome = UIResult::OUTCOME_ACCEPTED; + } else { + return; + } + + Result = result; +} + + +void UIGameControlsPresenterClass::Apply(void) +{ + if (Has_Speed()) { + int const gamespeed = (OptionsClass::MAX_SPEED_SETTING - 1) - SpeedStep; + if (Options.GameSpeed != gamespeed) { + if (GameActive && Session.Type != GAME_NORMAL && Session.Type != GAME_SKIRMISH) { + OutList.push_back(EventClass(PlayerPtr->HeapID, EventClass::GAMESPEED, gamespeed)); + } else { + Options.GameSpeed = gamespeed; + } + } + } + + Options.ScrollRate = (OptionsClass::MAX_SCROLL_SETTING - 1) - ScrollStep; + + if (Options.DetailLevel != DetailStep) { + Options.DetailLevel = DetailStep; + Map.Reinit_Cell_Drawers(); + } + + if (Options.SidebarCameoText != CameoText) { + Options.SidebarCameoText = CameoText; + Map.Toggle_Cameo_Text(CameoText); + } + + Options.ActionLines = ActionLines; + TechnoClass::Set_Action_Lines(Options.ActionLines); + + Options.ToolTips = ShowToolTips; + if (ToolTips != NULL && GameActive) { + ToolTips->Activate(Options.ToolTips); + } + + Options.ScrollMethod = Coasting ? 0 : 1; + Options.AutoScroll = EdgeScroll; + + if (Has_Difficulty()) { + Options.Difficulty = DifficultyStep; + } +} diff --git a/code/ui/uigamecontrols.h b/code/ui/uigamecontrols.h new file mode 100644 index 000000000..43dfd44c3 --- /dev/null +++ b/code/ui/uigamecontrols.h @@ -0,0 +1,110 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The game controls screen's behavior, with no toolkit in it. Three templates share it and +// they carry different controls, so the variant is part of the view-model rather than +// something a view works out for itself. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + +#include +#include + + +// What a view raises. Value carries the slider step or the check state. +inline constexpr char const * UI_GAMECTRL_SPEED = "speed"; +inline constexpr char const * UI_GAMECTRL_SCROLL = "scroll"; +inline constexpr char const * UI_GAMECTRL_DETAIL = "detail"; +inline constexpr char const * UI_GAMECTRL_DIFFICULTY = "difficulty"; +inline constexpr char const * UI_GAMECTRL_CAMEO_TEXT = "cameotext"; +inline constexpr char const * UI_GAMECTRL_ACTION_LINES = "actionlines"; +inline constexpr char const * UI_GAMECTRL_TOOLTIPS = "tooltips"; +inline constexpr char const * UI_GAMECTRL_COASTING = "coasting"; +inline constexpr char const * UI_GAMECTRL_EDGE_SCROLL = "edgescroll"; +inline constexpr char const * UI_GAMECTRL_SOUND = "sound"; +inline constexpr char const * UI_GAMECTRL_KEYBOARD = "keyboard"; +inline constexpr char const * UI_GAMECTRL_ACCEPT = "accept"; +inline constexpr char const * UI_GAMECTRL_CANCEL = "cancel"; + + +class UIGameControlsPresenterClass : public UIPresenterClass +{ + public: + // Which of the three templates the state below belongs to. The names are the + // game's own, and they do not mean what they look like: the screen shown with no + // game running is IDD_OPT_CTRL_GAME_SP and the one shown during any local game is + // IDD_OPT_CTRL_GAME_MP. + enum VariantType { + VARIANT_FRONTEND, + VARIANT_SESSION, + VARIANT_INTERNET, + }; + + enum ChoiceType { + CHOICE_NONE, + CHOICE_ACCEPT, + CHOICE_CANCEL, + CHOICE_SOUND, + CHOICE_KEYBOARD, + }; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + virtual void Service(void) override; + + // Writes the staged settings back into the game and saves them. The driver calls + // this where the dialog called Set, which is after its loop and before the screen + // comes down. + void Apply(void); + + // Does the way the player left the screen commit the settings? Leaving through the + // sound or keyboard button does, which is not obvious and is the dialog's own + // behavior: both wrote IDOK. + bool Commits(void) const; + + /* + ** The view-model. Plain values, and the only thing a view reads. + */ + VariantType Variant = VARIANT_FRONTEND; + + // Slider steps. Game speed and scroll rate count backward, so the fastest sits at + // step zero; detail and difficulty count forward. A view shows steps; only this + // class knows what they mean. + int SpeedStep = 0; + int ScrollStep = 0; + int DetailStep = 0; + int DifficultyStep = 0; + + bool CameoText = false; + bool ActionLines = false; + bool ShowToolTips = false; + bool Coasting = false; + bool EdgeScroll = false; + + // Is there an audio device to talk to? With none the sound button is disabled, as + // the dialog disabled it. + bool SoundAvailable = false; + + std::vector SpeedLabels; + std::vector ScrollLabels; + std::vector DetailLabels; + std::vector DifficultyLabels; + + // What each variant carries. A setting whose control the template omits is not + // applied, which is what the dialog's own null checks on GetDlgItem amounted to. + bool Has_Speed(void) const { return(Variant != VARIANT_INTERNET); } + bool Has_Difficulty(void) const { return(Variant == VARIANT_FRONTEND); } + bool Has_Sub_Screens(void) const { return(Variant != VARIANT_FRONTEND); } + + ChoiceType Choice = CHOICE_NONE; +}; From cd5fcc595b30c56d330d1488992d734bbe03e964 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 03:16:30 +0100 Subject: [PATCH 095/179] docs: record how far the options family extraction has come Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/UI_DESIGN.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index 5e1899afb..55c875f10 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -805,7 +805,13 @@ text beyond an ASCII test document. 7. **Options family** (L, two changes each). Main options, display with its timed rollback, game controls (three variants), keyboard with the hotkey capture control, the display-mode confirmation, abort and surrender. - Evidence: settings round-trip through `SUN.INI` unchanged. + Evidence: settings round-trip through `SUN.INI` unchanged. Started: the + first change of three of them has landed, classified preserved, with the + legacy view still selected --- `code/ui/uigameoptions.{h,cpp}` for the + in-game options screen, `code/ui/uiabort.{h,cpp}` for abort and surrender, + and `code/ui/uigamecontrols.{h,cpp}` for the game controls. Main options, + display with its rollback, the display-mode confirmation and the keyboard + screen are not extracted, and no RmlUi view exists for any of them. 8. **Main menu family** (M). `IDD_MAIN_MENU`, campaign choice, game type, multiplayer game selection. The `NewMenuClass` drivers keep their loops. 9. **Load, save, delete** (M, two changes). From af3c3298254baa916428a976fffdc69501b9d602 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 03:31:45 +0100 Subject: [PATCH 096/179] refactor(ui): put the display mode trial behind a presenter Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/mainopt.cpp | 74 ++++++++++++++++++++++----------- code/ui/uidisplayconfirm.cpp | 80 ++++++++++++++++++++++++++++++++++++ code/ui/uidisplayconfirm.h | 58 ++++++++++++++++++++++++++ 3 files changed, 187 insertions(+), 25 deletions(-) create mode 100644 code/ui/uidisplayconfirm.cpp create mode 100644 code/ui/uidisplayconfirm.h diff --git a/code/mainopt.cpp b/code/mainopt.cpp index 4534159ad..f84ef1776 100644 --- a/code/mainopt.cpp +++ b/code/mainopt.cpp @@ -35,6 +35,7 @@ #include "stimer.h" #include "surface.h" #include "wwmouse.h" +#include "ui/uidisplayconfirm.h" #include "color.hh" @@ -48,6 +49,21 @@ INT_PTR CALLBACK Test_Display_Mode_Dialog_Proc(HWND window, UINT message, WPARAM GameOptionsClass TempOptions; +// The screen the mode confirmation's procedure reads and writes. The driver owns it for the +// whole life of that dialog, which is the lifetime DWLP_USER gave the result pointer it +// replaces. +static UIDisplayConfirmPresenterClass * _ConfirmScreen = NULL; + + +static void Options_Queue(UIPresenterClass & screen, char const * action, int value = 0) +{ + UIIntent intent; + intent.Action = action; + intent.Value = value; + screen.Queue(intent); +} + + /// /// Brings up the main options dialog. /// This routine drives the options menu, dispatching to the sound, display, network, @@ -321,8 +337,6 @@ bool Change_Display_Mode(int width, int height) /// bool; Was the new display mode accepted and left in place? bool Test_Display_Mode_Dialog(int width, int height) { - int rc = -1; - DebugString("Testing display mode @ %dx%d\n", width, height); Hide_Mouse(); HiddenSurface->Fill(TBLACK); @@ -337,30 +351,38 @@ bool Test_Display_Mode_Dialog(int width, int height) Show_Mouse(); Draw_Menu_Background(); + UIDisplayConfirmPresenterClass screen; + screen.Refresh(); + + _ConfirmScreen = &screen; + + bool accepted = true; + HWND dialog = OwnerDraw::Begin_Dialog(IDD_OPT_CONFIRM_MODE, Test_Display_Mode_Dialog_Proc); if (dialog) { - SetWindowLongPtr(dialog, DWLP_USER, (LONG_PTR)&rc); OwnerDraw::Display_Dialog(dialog); - CDTimerClass timer = 10 * TIMER_SECOND; - while (rc < 0) { + while (!screen.Result.has_value()) { if (OwnerDraw::Dialog_Message_Handler() == true) { break; } - Title_Screen_Restore(); - if (timer <= 0) { - PostMessage(dialog, WM_COMMAND, WM_DESTROY, 0); - timer = 5 * TIMER_SECOND; - } + + screen.Drain(); + screen.Service(); } OwnerDraw::End_Dialog(dialog); - if (rc != IDOK) { - DebugString("Resetting display mode @ %dx%d\n", Options.ScreenWidth, Options.ScreenHeight); - Change_Display_Mode(Options.ScreenWidth, Options.ScreenHeight); - LogicalSurface = HiddenSurface; - return(false); - } + + accepted = (screen.Choice == UIDisplayConfirmPresenterClass::CHOICE_ACCEPT); + } + + _ConfirmScreen = NULL; + + if (!accepted) { + DebugString("Resetting display mode @ %dx%d\n", Options.ScreenWidth, Options.ScreenHeight); + Change_Display_Mode(Options.ScreenWidth, Options.ScreenHeight); + LogicalSurface = HiddenSurface; + return(false); } DebugString("Keeping display mode @ %dx%d\n", width, height); @@ -371,24 +393,26 @@ bool Test_Display_Mode_Dialog(int width, int height) /// /// Handles the mode confirmation dialog. -/// This routine records the button the player pressed so that the mode test can tell -/// whether the new resolution was accepted or rejected. +/// The procedure queues what the player pressed. Anything that is not the accept button is +/// a refusal, which is what the driver's test against IDOK made of every other identifier +/// the dialog could produce. /// INT_PTR CALLBACK Test_Display_Mode_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) { - int * result; - int id; - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); if (rc == 0) { - result = (int *)GetWindowLongPtr(window, DWLP_USER); + if (_ConfirmScreen == NULL) { + return(0); + } + switch (message) { - case WM_COMMAND: - id = LOWORD(wparam); + case WM_COMMAND: { + int const id = LOWORD(wparam); if (id > 0 && id <= IDCANCEL) { - *result = LOWORD(wparam); + Options_Queue(*_ConfirmScreen, (id == IDOK) ? UI_MODECONFIRM_ACCEPT : UI_MODECONFIRM_CANCEL); } break; + } } return(0); } diff --git a/code/ui/uidisplayconfirm.cpp b/code/ui/uidisplayconfirm.cpp new file mode 100644 index 000000000..eb9ba3cdb --- /dev/null +++ b/code/ui/uidisplayconfirm.cpp @@ -0,0 +1,80 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The display mode confirmation. Behavior traced out of Test_Display_Mode_Dialog and +// Test_Display_Mode_Dialog_Proc in mainopt.cpp. +// +// The timeout was expressed there as a posted WM_COMMAND carrying WM_DESTROY, which is 2, +// which the procedure recorded because it accepted any identifier from one to IDCANCEL, and +// IDCANCEL is also 2. So the timeout was a cancel spelled awkwardly, and it is a cancel +// here. The driver re-armed its timer to five seconds after firing because the posted +// message took another pass to arrive; this produces the result directly, and re-arms for +// the same reason: a caller that keeps servicing must not be handed the answer twice. +// +// docs/UI_DESIGN.md, "Screens", owns the contracts this keeps to. + +#include "always.h" + +#include "uidisplayconfirm.h" + + +void UIDisplayConfirmPresenterClass::Refresh(void) +{ + Choice = CHOICE_NONE; + Timer = TIMEOUT_SECONDS * TIMER_SECOND; +} + + +int UIDisplayConfirmPresenterClass::Seconds_Remaining(void) const +{ + int const ticks = (int)Timer.Value(); + if (ticks <= 0) { + return(0); + } + return((ticks + TIMER_SECOND - 1) / TIMER_SECOND); +} + + +/// +/// Takes the mode back when the player says nothing. +/// +void UIDisplayConfirmPresenterClass::Service(void) +{ + if (Result.has_value()) { + return; + } + + if (Timer <= 0) { + Timer = REARM_SECONDS * TIMER_SECOND; + + Choice = CHOICE_CANCEL; + + UIResult result; + result.Outcome = UIResult::OUTCOME_CANCELLED; + Result = result; + } +} + + +void UIDisplayConfirmPresenterClass::Execute(UIIntent const & intent) +{ + UIResult result; + + if (intent.Action == UI_MODECONFIRM_ACCEPT) { + Choice = CHOICE_ACCEPT; + result.Outcome = UIResult::OUTCOME_ACCEPTED; + } else if (intent.Action == UI_MODECONFIRM_CANCEL) { + Choice = CHOICE_CANCEL; + result.Outcome = UIResult::OUTCOME_CANCELLED; + } else { + return; + } + + Result = result; +} diff --git a/code/ui/uidisplayconfirm.h b/code/ui/uidisplayconfirm.h new file mode 100644 index 000000000..80f95b375 --- /dev/null +++ b/code/ui/uidisplayconfirm.h @@ -0,0 +1,58 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The display mode confirmation's behavior, with no toolkit in it. The screen exists to +// take back a resolution the player cannot see, so its timeout is the whole point of it and +// belongs here rather than in a view: a mode that leaves the screen unreadable is answered +// by saying nothing, and saying nothing has to mean no. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + +#include "stimer.h" +#include "timer.h" + + +inline constexpr char const * UI_MODECONFIRM_ACCEPT = "accept"; +inline constexpr char const * UI_MODECONFIRM_CANCEL = "cancel"; + + +class UIDisplayConfirmPresenterClass : public UIPresenterClass +{ + public: + enum { + // What the dialog driver's own CDTimerClass was set to, and what it re-armed to + // after firing. + TIMEOUT_SECONDS = 10, + REARM_SECONDS = 5, + }; + + enum ChoiceType { + CHOICE_NONE, + CHOICE_ACCEPT, + CHOICE_CANCEL, + }; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + virtual void Service(void) override; + + // Whole seconds left before the mode is taken back, counted down for a view that + // wants to show them. Nothing depends on the number; the rollback is driven by the + // timer itself. + int Seconds_Remaining(void) const; + + ChoiceType Choice = CHOICE_NONE; + + private: + CDTimerClass Timer; +}; From 3042ace129ff759b77bd27be780213a311d10b74 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 03:32:19 +0100 Subject: [PATCH 097/179] refactor(ui): put the display options screen behind a presenter Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/mainopt.cpp | 179 +++++++++++++++++------------------ code/mainopt.h | 5 + code/ui/uidisplayoptions.cpp | 130 +++++++++++++++++++++++++ code/ui/uidisplayoptions.h | 83 ++++++++++++++++ 4 files changed, 303 insertions(+), 94 deletions(-) create mode 100644 code/ui/uidisplayoptions.cpp create mode 100644 code/ui/uidisplayoptions.h diff --git a/code/mainopt.cpp b/code/mainopt.cpp index f84ef1776..cace23940 100644 --- a/code/mainopt.cpp +++ b/code/mainopt.cpp @@ -36,6 +36,7 @@ #include "surface.h" #include "wwmouse.h" #include "ui/uidisplayconfirm.h" +#include "ui/uidisplayoptions.h" #include "color.hh" @@ -46,12 +47,11 @@ bool Change_Display_Mode(int width, int height); bool Test_Display_Mode_Dialog(int width, int height); INT_PTR CALLBACK Test_Display_Mode_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -GameOptionsClass TempOptions; -// The screen the mode confirmation's procedure reads and writes. The driver owns it for the -// whole life of that dialog, which is the lifetime DWLP_USER gave the result pointer it -// replaces. +// The screens the dialog procedures read and write. A driver owns one for the whole life of +// its dialog, which is the lifetime DWLP_USER gave the result pointer each replaces. +static UIDisplayOptionsPresenterClass * _DisplayScreen = NULL; static UIDisplayConfirmPresenterClass * _ConfirmScreen = NULL; @@ -106,44 +106,9 @@ void Main_Options_Dialog(void) SoundControlsClass().Dialog(); break; - case IDC_OPTMAIN_DISPLAY: { - while (true) { - do { - TempOptions = Options; - in_rc = -1; - in_handle = OwnerDraw::Begin_Dialog(IDD_OPT_DISPLAY, Display_Options_Dialog_Proc); - } while (in_handle == 0); - SetWindowLongPtr(in_handle, DWLP_USER, (LONG_PTR)&in_rc); - OwnerDraw::Display_Dialog(in_handle); - - while (in_rc < 0) { - if (OwnerDraw::Dialog_Message_Handler() == true) { - break; - } - Title_Screen_Restore(); - } - - OwnerDraw::End_Dialog(in_handle); - - if (in_rc != 1) { - break; - } - if (TempOptions.ScreenWidth == Options.ScreenWidth && TempOptions.ScreenHeight == Options.ScreenHeight) { - break; - } - - if (WWMessageBox().Process(TXT_ABOUT_TO_TRY_MODE, TXT_OK, TXT_CANCEL) == 0) { - if (!Test_Display_Mode_Dialog(TempOptions.ScreenWidth, TempOptions.ScreenHeight)) { - continue; - } - Options.ScreenWidth = TempOptions.ScreenWidth; - Options.ScreenHeight = TempOptions.ScreenHeight; - } - - break; - } - } - break; + case IDC_OPTMAIN_DISPLAY: + Display_Options_Dialog(); + break; case IDC_OPTMAIN_KEYBOARD: Options.Hotkey_Dialog(); @@ -196,6 +161,60 @@ INT_PTR CALLBACK Main_Options_Dialog_Proc(HWND window, UINT message, WPARAM wpar } +/// +/// Brings up the display options and offers a chosen resolution as a trial. +/// A mode the player refuses, or does not answer for, brings the screen straight back up +/// with the old resolution in force; anything else leaves. +/// +void Display_Options_Dialog(void) +{ + while (true) { + UIDisplayOptionsPresenterClass screen; + screen.Refresh(); + + _DisplayScreen = &screen; + + HWND handle; + do { + handle = OwnerDraw::Begin_Dialog(IDD_OPT_DISPLAY, Display_Options_Dialog_Proc); + } while (handle == 0); + OwnerDraw::Display_Dialog(handle); + + while (!screen.Result.has_value()) { + if (OwnerDraw::Dialog_Message_Handler() == true) { + UIResult ended; + ended.Outcome = UIResult::OUTCOME_SESSION_ENDED; + ended.GameEnded = true; + screen.Result = ended; + break; + } + + screen.Drain(); + screen.Service(); + } + + OwnerDraw::End_Dialog(handle); + _DisplayScreen = NULL; + + if (screen.Choice != UIDisplayOptionsPresenterClass::CHOICE_ACCEPT) { + break; + } + if (!screen.Wants_Mode_Change()) { + break; + } + + if (WWMessageBox().Process(TXT_ABOUT_TO_TRY_MODE, TXT_OK, TXT_CANCEL) == 0) { + if (!Test_Display_Mode_Dialog(screen.StagedWidth, screen.StagedHeight)) { + continue; + } + screen.Commit(); + } + + break; + } +} + + /// /// Switches the game over to a new render resolution. /// Every drawing surface is destroyed and recreated at the new size, so any pointer held @@ -422,26 +441,18 @@ INT_PTR CALLBACK Test_Display_Mode_Dialog_Proc(HWND window, UINT message, WPARAM /// /// Handles the display options dialog messages. -/// This routine fills the resolution list with the display modes the hardware reports, -/// remembers which one the player picked, and tracks the movie stretching preference. The -/// chosen resolution is staged in the temporary options so that it can be tested before -/// being made permanent. +/// The procedure fills the resolution list from the view-model, queues the row and the +/// movie stretching preference the player left it on, and hands the driver what the player +/// pressed to execute after the pump. /// static __forceinline BOOL Display_Options_Dialog_Body(HWND window, UINT message, WPARAM wparam) { - enum { - MIN_WIDTH = 640, - MIN_HEIGHT = 400, - MAX_WIDTH = 4096, - MAX_HEIGHT = 4096, - }; - - static int * _modes = NULL; - static int _current_mode = -1; - static int _previous_mode = -1; - static bool _initialized = true; - - int * result = (int *)GetWindowLongPtr(window, DWLP_USER); + if (_DisplayScreen == NULL) { + return(0); + } + + UIDisplayOptionsPresenterClass & screen = *_DisplayScreen; + switch (message) { case WM_COMMAND: switch (LOWORD(wparam)) { @@ -450,65 +461,45 @@ static __forceinline BOOL Display_Options_Dialog_Body(HWND window, UINT message, case IDC_DISPLAY_RESLIST: { HWND list = GetDlgItem(window, IDC_DISPLAY_RESLIST); - _current_mode = ListBox_GetCurSel(list); + if (list) { + Options_Queue(screen, UI_DISPLAY_SELECT, ListBox_GetCurSel(list)); + } } return(0); case IDOK: { - if (_previous_mode != _current_mode) { + HWND list = GetDlgItem(window, IDC_DISPLAY_RESLIST); + if (list) { Center_Window_Within_Window(window, MainWindow); - HWND list = GetDlgItem(window, IDC_DISPLAY_RESLIST); - if (list) { - int index = ListBox_GetItemData(list, _current_mode); - int * modes = &_modes[2 * index]; - TempOptions.ScreenWidth = modes[0]; - TempOptions.ScreenHeight = modes[1]; - } + Options_Queue(screen, UI_DISPLAY_SELECT, ListBox_GetCurSel(list)); } HWND button = GetDlgItem(window, IDC_STRETCH_MOVIES); if (button) { - Options.StretchMovies = Button_GetCheck(button) == BST_CHECKED; + Options_Queue(screen, UI_DISPLAY_STRETCH, Button_GetCheck(button) == BST_CHECKED ? 1 : 0); } + Options_Queue(screen, UI_DISPLAY_ACCEPT); } break; case IDCANCEL: + Options_Queue(screen, UI_DISPLAY_CANCEL); break; } - delete [] _modes; - *result = LOWORD(wparam); break; case WM_INITDIALOG: { HWND list = GetDlgItem(window, IDC_DISPLAY_RESLIST); - _modes = EnumDisplayModes(MIN_WIDTH, MIN_HEIGHT, MAX_WIDTH, MAX_HEIGHT); - int * modes = _modes; - int item_index = 0; - int initial_mode = -1; - int mode_index = 0; - if (modes != NULL) { - while (*modes != 0) { - int width = *modes++; - int height = *modes++; - if (width == TempOptions.ScreenWidth && height == TempOptions.ScreenHeight) { - initial_mode = mode_index; - } - char buffer[64]; - sprintf(buffer, "%d x %d", width, height); - int index = ListBox_AddString(list, buffer); - ListBox_SetItemData(list, index, item_index); - mode_index++; - item_index++; + if (list) { + for (UIDisplayOptionsPresenterClass::ModeType const & mode : screen.Modes) { + int const index = ListBox_AddString(list, mode.Label.c_str()); + ListBox_SetItemData(list, index, index); } + ListBox_SetCurSel(list, screen.Selected); } - ListBox_SetCurSel(list, initial_mode); - _initialized = true; - _current_mode = initial_mode; - _previous_mode = initial_mode; HWND button = GetDlgItem(window, IDC_STRETCH_MOVIES); if (button) { - Button_SetCheck(button, Options.StretchMovies != false); + Button_SetCheck(button, screen.StretchMovies != false); } } break; diff --git a/code/mainopt.h b/code/mainopt.h index 10c4a7726..9bf10cefb 100644 --- a/code/mainopt.h +++ b/code/mainopt.h @@ -11,3 +11,8 @@ bool Change_Display_Mode(int width, int height); void Main_Options_Dialog(void); + +// The display options and the mode trial the family reaches through them. The loop stays +// with the driver, because only a view knows how to bring the screen back up after a mode +// the player refused. +void Display_Options_Dialog(void); diff --git a/code/ui/uidisplayoptions.cpp b/code/ui/uidisplayoptions.cpp new file mode 100644 index 000000000..5650dfb76 --- /dev/null +++ b/code/ui/uidisplayoptions.cpp @@ -0,0 +1,130 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The display options screen. Behavior traced out of Display_Options_Dialog_Body in +// mainopt.cpp. +// +// What the extraction fixes in place: the resolution is staged and only a trial the player +// confirms writes it to the settings, while the movie stretching preference is written +// straight to the settings at accept and left alone at cancel; and the staged resolution +// moves only when the player leaves the screen on a row other than the one it opened on, so +// re-picking the row already in force stages nothing and skips the trial. +// +// EnumDisplayModes reports nothing on a platform without host mode enumeration, and this +// screen then offers an empty list, which is what the dialog did with the same answer. +// +// docs/UI_DESIGN.md, "Screens", owns the contracts this keeps to. + +#include "always.h" + +#include "uidisplayoptions.h" + +#include "globals.h" +#include "init.h" +#include "goptions.h" +#include "options.h" +#include "video.h" + +#include + + +void UIDisplayOptionsPresenterClass::Refresh(void) +{ + Modes.clear(); + Selected = -1; + + StagedWidth = Options.ScreenWidth; + StagedHeight = Options.ScreenHeight; + StretchMovies = Options.StretchMovies; + + int * const modes = EnumDisplayModes(MIN_WIDTH, MIN_HEIGHT, MAX_WIDTH, MAX_HEIGHT); + if (modes != NULL) { + for (int * mode = modes; *mode != 0; mode += 2) { + ModeType entry; + entry.Width = mode[0]; + entry.Height = mode[1]; + + char buffer[64]; + std::snprintf(buffer, sizeof(buffer), "%d x %d", entry.Width, entry.Height); + entry.Label = buffer; + + if (entry.Width == StagedWidth && entry.Height == StagedHeight) { + Selected = (int)Modes.size(); + } + + Modes.push_back(entry); + } + delete [] modes; + } + + Opened = Selected; +} + + +/// +/// The maintenance the dialog driver ran on every pass of its own loop. +/// +void UIDisplayOptionsPresenterClass::Service(void) +{ + Title_Screen_Restore(); +} + + +bool UIDisplayOptionsPresenterClass::Wants_Mode_Change(void) const +{ + return(StagedWidth != Options.ScreenWidth || StagedHeight != Options.ScreenHeight); +} + + +void UIDisplayOptionsPresenterClass::Commit(void) +{ + Options.ScreenWidth = StagedWidth; + Options.ScreenHeight = StagedHeight; +} + + +void UIDisplayOptionsPresenterClass::Execute(UIIntent const & intent) +{ + if (intent.Action == UI_DISPLAY_SELECT) { + if (intent.Value >= 0 && intent.Value < (int)Modes.size()) { + Selected = intent.Value; + } + return; + } + + if (intent.Action == UI_DISPLAY_STRETCH) { + StretchMovies = (intent.Value != 0); + return; + } + + UIResult result; + + if (intent.Action == UI_DISPLAY_ACCEPT) { + if (Selected != Opened && Selected >= 0 && Selected < (int)Modes.size()) { + StagedWidth = Modes[Selected].Width; + StagedHeight = Modes[Selected].Height; + } + + // The stretching preference is not staged. The dialog wrote it at IDOK and left it + // alone at IDCANCEL, so it survives a resolution the player then refuses. + Options.StretchMovies = StretchMovies; + + Choice = CHOICE_ACCEPT; + result.Outcome = UIResult::OUTCOME_ACCEPTED; + + } else if (intent.Action == UI_DISPLAY_CANCEL) { + Choice = CHOICE_CANCEL; + result.Outcome = UIResult::OUTCOME_CANCELLED; + + } else { + return; + } + + Result = result; +} diff --git a/code/ui/uidisplayoptions.h b/code/ui/uidisplayoptions.h new file mode 100644 index 000000000..486bf4e6e --- /dev/null +++ b/code/ui/uidisplayoptions.h @@ -0,0 +1,83 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The display options screen's behavior, with no toolkit in it. The resolution it settles +// on is staged rather than applied, because a mode is tried and confirmed before the +// settings remember it; the file-scope TempOptions copy the dialog used for that staging +// lives here now. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + +#include +#include + + +inline constexpr char const * UI_DISPLAY_SELECT = "select"; // Value: row +inline constexpr char const * UI_DISPLAY_STRETCH = "stretch"; // Value: check state +inline constexpr char const * UI_DISPLAY_ACCEPT = "accept"; +inline constexpr char const * UI_DISPLAY_CANCEL = "cancel"; + + +class UIDisplayOptionsPresenterClass : public UIPresenterClass +{ + public: + // The bounds the dialog asked the display for. + enum { + MIN_WIDTH = 640, + MIN_HEIGHT = 400, + MAX_WIDTH = 4096, + MAX_HEIGHT = 4096, + }; + + struct ModeType + { + std::string Label; + int Width = 0; + int Height = 0; + }; + + enum ChoiceType { + CHOICE_NONE, + CHOICE_ACCEPT, + CHOICE_CANCEL, + }; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + virtual void Service(void) override; + + // Is a resolution staged that the game is not already running at? Only one that is + // gets tried, and only a tried one is ever written to the settings. + bool Wants_Mode_Change(void) const; + + // Writes the staged resolution into the settings, once its trial was accepted. + void Commit(void); + + /* + ** The view-model. Plain values, and the only thing a view reads. + */ + std::vector Modes; + int Selected = -1; + bool StretchMovies = false; + + // The resolution the screen is staging. It starts at the one in force and moves + // only when the player accepts a row other than the one the screen opened on, which + // is what the dialog's own previous-against-current comparison amounted to. + int StagedWidth = 0; + int StagedHeight = 0; + + ChoiceType Choice = CHOICE_NONE; + + private: + int Opened = -1; +}; From 70ab4ed1004185b2bf7a2398a8d3ea4be499b9b0 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 03:32:36 +0100 Subject: [PATCH 098/179] refactor(ui): put the main options screen behind a presenter Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/mainopt.cpp | 151 ++++++++++++++++++++++---------------- code/ui/uimainoptions.cpp | 120 ++++++++++++++++++++++++++++++ code/ui/uimainoptions.h | 73 ++++++++++++++++++ 3 files changed, 279 insertions(+), 65 deletions(-) create mode 100644 code/ui/uimainoptions.cpp create mode 100644 code/ui/uimainoptions.h diff --git a/code/mainopt.cpp b/code/mainopt.cpp index cace23940..f248f9272 100644 --- a/code/mainopt.cpp +++ b/code/mainopt.cpp @@ -37,6 +37,7 @@ #include "wwmouse.h" #include "ui/uidisplayconfirm.h" #include "ui/uidisplayoptions.h" +#include "ui/uimainoptions.h" #include "color.hh" @@ -48,9 +49,9 @@ bool Test_Display_Mode_Dialog(int width, int height); INT_PTR CALLBACK Test_Display_Mode_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); - // The screens the dialog procedures read and write. A driver owns one for the whole life of // its dialog, which is the lifetime DWLP_USER gave the result pointer each replaces. +static UIMainOptionsPresenterClass * _MainScreen = NULL; static UIDisplayOptionsPresenterClass * _DisplayScreen = NULL; static UIDisplayConfirmPresenterClass * _ConfirmScreen = NULL; @@ -73,91 +74,54 @@ static void Options_Queue(UIPresenterClass & screen, char const * action, int va /// Game logic is suspended for the duration of this routine. void Main_Options_Dialog(void) { - bool old_game_active = GameActive; - GameActive = false; - - HWND main_handle; - LONG main_rc; + UIMainOptionsPresenterClass screen; + screen.Begin(); + screen.Refresh(); - HWND in_handle; - LONG in_rc; + _MainScreen = &screen; while (true) { + screen.Result.reset(); + screen.Choice = UIMainOptionsPresenterClass::CHOICE_NONE; + + HWND main_handle; do { - main_rc = -1; main_handle = OwnerDraw::Begin_Dialog(IDD_OPT_MAIN, Main_Options_Dialog_Proc); } while (main_handle == 0); - SetWindowLongPtr(main_handle, DWLP_USER, (LONG_PTR)&main_rc); OwnerDraw::Move_Dialog(main_handle, -1, (HiddenSurface->Get_Height() - 400) / 2 + 147); OwnerDraw::Display_Dialog(main_handle); - while (main_rc < 0) { + while (!screen.Result.has_value()) { if (OwnerDraw::Dialog_Message_Handler() == true) { + // A session that ended underneath the screen leaves the family, which is + // what the driver's own unanswered result did on the way to its default arm. + UIResult ended; + ended.Outcome = UIResult::OUTCOME_SESSION_ENDED; + ended.GameEnded = true; + screen.Choice = UIMainOptionsPresenterClass::CHOICE_EXIT; + screen.Result = ended; break; } - Title_Screen_Restore(); + + screen.Drain(); + screen.Service(); } OwnerDraw::End_Dialog(main_handle); - switch (main_rc) { - case IDC_OPTMAIN_SOUND: - SoundControlsClass().Dialog(); - break; - - case IDC_OPTMAIN_DISPLAY: - Display_Options_Dialog(); - break; - - case IDC_OPTMAIN_KEYBOARD: - Options.Hotkey_Dialog(); - break; - - case IDC_OPTMAIN_GAME_SETTINGS: - GameControlsClass().Dialog(); - break; - - default: - Options.Save_Settings(); - GameActive = old_game_active; - return; + if (screen.Exits()) { + break; } - } -} - - -/// -/// Handles the main options dialog. -/// This routine reports the button the player pressed back to the options dialog driver so -/// that it can bring up the appropriate sub dialog. The sound button is disabled when there -/// is no audio hardware to talk to. -/// -INT_PTR CALLBACK Main_Options_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - int *result; - HWND handle; - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - if (rc == 0) { - result = (int *)GetWindowLongPtr(window, DWLP_USER); - switch (message) { + // The sub-screen runs with this one destroyed, which is the coexistence rule the + // driver already kept. + screen.Run_Pending(); + } - case WM_COMMAND: - *result = LOWORD(wparam); - break; + _MainScreen = NULL; - case WM_INITDIALOG: - handle = GetDlgItem(window, IDC_OPTMAIN_SOUND); - if (handle) { - EnableWindow(handle, AudioEngine.Is_Available()); - } - break; - - } - return(0); - } - return(rc); + screen.End(); } @@ -215,6 +179,63 @@ void Display_Options_Dialog(void) } +/// +/// Handles the main options dialog. +/// The procedure queues what the player pressed for the driver to execute after the pump, +/// and disables the sound button when there is no audio hardware to talk to. +/// +INT_PTR CALLBACK Main_Options_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) +{ + HWND handle; + + INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); + if (rc == 0) { + if (_MainScreen == NULL) { + return(0); + } + + UIMainOptionsPresenterClass & screen = *_MainScreen; + + switch (message) { + + case WM_COMMAND: + switch (LOWORD(wparam)) { + case IDC_OPTMAIN_SOUND: + Options_Queue(screen, UI_MAINOPT_SOUND); + break; + + case IDC_OPTMAIN_DISPLAY: + Options_Queue(screen, UI_MAINOPT_DISPLAY); + break; + + case IDC_OPTMAIN_KEYBOARD: + Options_Queue(screen, UI_MAINOPT_KEYBOARD); + break; + + case IDC_OPTMAIN_GAME_SETTINGS: + Options_Queue(screen, UI_MAINOPT_SETTINGS); + break; + + default: + Options_Queue(screen, UI_MAINOPT_EXIT); + break; + } + break; + + case WM_INITDIALOG: + handle = GetDlgItem(window, IDC_OPTMAIN_SOUND); + if (handle) { + EnableWindow(handle, screen.SoundAvailable); + } + break; + + } + return(0); + } + return(rc); +} + + /// /// Switches the game over to a new render resolution. /// Every drawing surface is destroyed and recreated at the new size, so any pointer held diff --git a/code/ui/uimainoptions.cpp b/code/ui/uimainoptions.cpp new file mode 100644 index 000000000..157bffffa --- /dev/null +++ b/code/ui/uimainoptions.cpp @@ -0,0 +1,120 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The main options screen. Behavior traced out of Main_Options_Dialog and +// Main_Options_Dialog_Proc in mainopt.cpp. +// +// What the extraction fixes in place: the driver cleared GameActive around the whole family +// and put it back on the way out, so every screen the family opens sees a game that is not +// running whether or not one is, and both the sound and the game controls screens choose +// their layout from exactly that; and the settings are written once, as the player leaves +// the family, not as each sub-screen closes. +// +// docs/UI_DESIGN.md, "Screens", owns the contracts this keeps to. + +#include "always.h" + +#include "uimainoptions.h" + +#include "audio/audioengine.h" +#include "gamedlg.h" +#include "globals.h" +#include "init.h" +#include "mainopt.h" +#include "goptions.h" +#include "options.h" +#include "sounddlg.h" + + +void UIMainOptionsPresenterClass::Refresh(void) +{ + SoundAvailable = AudioEngine.Is_Available(); +} + + +void UIMainOptionsPresenterClass::Begin(void) +{ + WasGameActive = GameActive; + GameActive = false; +} + + +void UIMainOptionsPresenterClass::End(void) +{ + Options.Save_Settings(); + GameActive = WasGameActive; +} + + +/// +/// The maintenance the dialog driver ran on every pass of its own loop. +/// +void UIMainOptionsPresenterClass::Service(void) +{ + Title_Screen_Restore(); +} + + +void UIMainOptionsPresenterClass::Execute(UIIntent const & intent) +{ + UIResult result; + result.Outcome = UIResult::OUTCOME_ACCEPTED; + + if (intent.Action == UI_MAINOPT_SOUND) { + if (!SoundAvailable) { + return; + } + Choice = CHOICE_SOUND; + } else if (intent.Action == UI_MAINOPT_DISPLAY) { + Choice = CHOICE_DISPLAY; + } else if (intent.Action == UI_MAINOPT_KEYBOARD) { + Choice = CHOICE_KEYBOARD; + } else if (intent.Action == UI_MAINOPT_SETTINGS) { + Choice = CHOICE_SETTINGS; + } else { + // Anything else leaves, which is the dialog's own default arm: it took whatever + // identifier arrived, and every identifier that was not a sub-screen ended the + // family. + Choice = CHOICE_EXIT; + result.Outcome = UIResult::OUTCOME_CANCELLED; + } + + Result = result; +} + + +/// +/// Runs the screen the player asked for. +/// +void UIMainOptionsPresenterClass::Run_Pending(void) +{ + ChoiceType const pending = Choice; + Choice = CHOICE_NONE; + + switch (pending) { + case CHOICE_SOUND: + SoundControlsClass().Dialog(); + break; + + case CHOICE_DISPLAY: + Display_Options_Dialog(); + break; + + case CHOICE_KEYBOARD: + Options.Hotkey_Dialog(); + break; + + case CHOICE_SETTINGS: + GameControlsClass().Dialog(); + break; + + default: + break; + } +} diff --git a/code/ui/uimainoptions.h b/code/ui/uimainoptions.h new file mode 100644 index 000000000..8109e5dc9 --- /dev/null +++ b/code/ui/uimainoptions.h @@ -0,0 +1,73 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The main options screen's behavior, with no toolkit in it. The screen is five buttons and +// almost no state of its own, but it owns the family: it suspends the game while any +// options screen is up and writes the settings out when the player leaves, and the sound +// and game controls screens each pick their own layout from the suspension it holds. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + + +inline constexpr char const * UI_MAINOPT_SOUND = "sound"; +inline constexpr char const * UI_MAINOPT_DISPLAY = "display"; +inline constexpr char const * UI_MAINOPT_KEYBOARD = "keyboard"; +inline constexpr char const * UI_MAINOPT_SETTINGS = "settings"; +inline constexpr char const * UI_MAINOPT_EXIT = "exit"; + + +class UIMainOptionsPresenterClass : public UIPresenterClass +{ + public: + enum ChoiceType { + CHOICE_NONE, + CHOICE_SOUND, + CHOICE_DISPLAY, + CHOICE_KEYBOARD, + CHOICE_SETTINGS, + CHOICE_EXIT, + }; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + virtual void Service(void) override; + + // Suspends the game for as long as the options family is up. What this holds is + // read by the screens the family opens, so it is behavior rather than bookkeeping: + // the sound and game controls screens each choose their layout from it. + void Begin(void); + + // Writes the settings out and puts the game back the way Begin found it. + void End(void); + + // Runs the screen the last choice asked for, then clears the request. Safe to call + // with nothing pending. + void Run_Pending(void); + + // Does the last choice leave the options family? Anything the screen does not + // recognize leaves it, which is what the dialog's default arm did. + bool Exits(void) const { return(Choice == CHOICE_EXIT); } + + /* + ** The view-model. Plain values, and the only thing a view reads. + */ + + // Is there an audio device to talk to? With none the sound button is disabled, as + // the dialog disabled it. + bool SoundAvailable = false; + + ChoiceType Choice = CHOICE_NONE; + + private: + bool WasGameActive = false; +}; From 645566ecd0d115fa6dadecd7d16eb53489aa2c6c Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 03:35:52 +0100 Subject: [PATCH 099/179] refactor(ui): put the keyboard screen behind a presenter Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/options.cpp | 260 ++++++++++++++-------------------- code/ui/uikeyboard.cpp | 313 +++++++++++++++++++++++++++++++++++++++++ code/ui/uikeyboard.h | 91 ++++++++++++ 3 files changed, 508 insertions(+), 156 deletions(-) create mode 100644 code/ui/uikeyboard.cpp create mode 100644 code/ui/uikeyboard.h diff --git a/code/options.cpp b/code/options.cpp index 615ad4398..26751a12d 100644 --- a/code/options.cpp +++ b/code/options.cpp @@ -84,6 +84,7 @@ #include "vector.h" #include "video.h" #include "vox.h" +#include "ui/uikeyboard.h" #include "diff.hh" @@ -593,68 +594,100 @@ int OptionsClass::Normalize_Volume(int volume) const } -/* - * Internal state-machine messages the hotkey-configuration dialog posts to - * itself. wParam/lParam are unused; each just triggers the matching refresh. - */ -#define HKD_FILL_COMMANDS (WM_USER + 100) /// rebuild the command listbox for the selected category -#define HKD_SHOW_COMMAND (WM_USER + 101) /// refresh the description / assigned-key panel for the selected command -#define HKD_APPLY_HOTKEY (WM_USER + 102) /// assign the hotkey edit's key to the selected command -#define HKD_REINIT (WM_USER + 103) /// full refresh: repopulate the category combo and reset +// The screen the hotkey dialog procedure reads and writes. The driver owns it for the whole +// life of the dialog, which is the lifetime DWLP_USER gave the result pointer it replaces. +static UIKeyboardPresenterClass * _KeyboardScreen = NULL; + + +static void Hotkey_Queue(UIKeyboardPresenterClass & screen, char const * action, int value = 0) +{ + UIIntent intent; + intent.Action = action; + intent.Value = value; + screen.Queue(intent); +} + + +// Puts the view-model's text and lists back on the controls. The dialog did this from its +// own private messages; the driver now does it after the queue has been executed, so a +// handler that only queues still leaves the screen looking right. +static void Hotkey_Sync_Controls(HWND window, UIKeyboardPresenterClass const & screen) +{ + HWND handle; + + handle = GetDlgItem(window, IDC_KEY_COMMANDS); + if (handle && ListBox_GetCount(handle) != (int)screen.Commands.size()) { + ListBox_ResetContent(handle); + for (UIKeyboardPresenterClass::CommandType const & command : screen.Commands) { + ListBox_AddString(handle, command.Label.c_str()); + } + ListBox_SetCurSel(handle, screen.SelectedCommand); + } + + handle = GetDlgItem(window, IDC_KEY_DESCRIPTION); + if (handle) { + SetWindowText(handle, screen.Description.c_str()); + } + + handle = GetDlgItem(window, IDC_KEY_CURRENT_SHORTCUT); + if (handle) { + SetWindowText(handle, screen.CurrentShortcut.c_str()); + } + + handle = GetDlgItem(window, IDC_KEY_ASSIGNED_TO); + if (handle) { + SetWindowText(handle, screen.AssignedTo.c_str()); + } + + handle = GetDlgItem(window, IDC_KEY_HOTKEY); + if (handle && SendMessage(handle, HKM_GETHOTKEY, 0, 0) != screen.CapturedKey) { + SendMessage(handle, HKM_SETHOTKEY, screen.CapturedKey, 0); + } +} /// /// Handles the messages for the keyboard configuration dialog. -/// This routine drives the category, command and hotkey controls, and hands the reassigned -/// keys back to the hotkey command list. Accepting the dialog writes the assignments out to -/// KEYBOARD.INI; canceling puts the previous assignments back. +/// The procedure primes its controls from the view-model and queues what the player did for +/// the driver to execute after the pump. /// /// Returns with TRUE if the message was consumed by this dialog. INT_PTR CALLBACK Hotkey_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) { - char buffer[64]; - int * retval; - static int current_selection = -1; - INT_PTR result = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); if (result) { return(result); } - retval = (int *)GetWindowLongPtr(window, DWLP_USER); + if (_KeyboardScreen == NULL) { + return(FALSE); + } + + UIKeyboardPresenterClass & screen = *_KeyboardScreen; switch (message) { case WM_COMMAND: switch (LOWORD(wparam)) { case IDOK: if (HIWORD(wparam) == BN_CLICKED) { - CCINIClass ini; - ini.Clear(); - - for (int i = 0; i < HotkeyCommands.Count(); i++) { - CommandClass const * cmd = HotkeyCommands.Fetch_By_Position(i); - int key = HotkeyCommands.Fetch_ID_By_Position(i); - ini.Put_Int("Hotkey", cmd->Get_Unique_Name(), key); - } - - CDFileClass file("Keyboard.ini"); - ini.Save(file, false); - *retval = IDOK; + Hotkey_Queue(screen, UI_KEYBOARD_ACCEPT); return(TRUE); } break; case IDCANCEL: if (HIWORD(wparam) == BN_CLICKED) { - Init_Hotkeys(); - *retval = 2; + Hotkey_Queue(screen, UI_KEYBOARD_CANCEL); return(TRUE); } break; case IDC_KEY_COMMANDS: if (HIWORD(wparam) == LBN_SELCHANGE) { - SendMessage(window, HKD_SHOW_COMMAND, 0, 0); + HWND list = GetDlgItem(window, IDC_KEY_COMMANDS); + if (list) { + Hotkey_Queue(screen, UI_KEYBOARD_COMMAND, ListBox_GetCurSel(list)); + } HWND hotkey = GetDlgItem(window, IDC_KEY_HOTKEY); if (hotkey != NULL) { SetFocus(hotkey); @@ -664,149 +697,55 @@ INT_PTR CALLBACK Hotkey_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LP break; case IDC_KEY_ASSIGN: - SendMessage(window, HKD_APPLY_HOTKEY, 0, 0); - SendMessage(window, HKD_SHOW_COMMAND, 0, 0); + Hotkey_Queue(screen, UI_KEYBOARD_ASSIGN); return(TRUE); case IDC_KEY_HOTKEY: if (HIWORD(wparam) == EN_CHANGE) { - int key = SendMessage((HWND)lparam, HKM_GETHOTKEY, 0, 0); - char const * key_name; - if (HotkeyCommands.Is_Present(key)) { - key_name = HotkeyCommands[key]->Get_Display_Name(); - if (key_name == NULL) { - key_name = ""; - } - } else { - key_name = ""; - } - HWND hotkey_name = GetDlgItem(window, IDC_KEY_ASSIGNED_TO); - SetWindowText(hotkey_name, key_name); + Hotkey_Queue(screen, UI_KEYBOARD_CAPTURE, (int)SendMessage((HWND)lparam, HKM_GETHOTKEY, 0, 0)); return(TRUE); } break; case IDC_KEY_RESET_ALL: if (HIWORD(wparam) == BN_CLICKED) { - if (WWMessageBox()._Process(TXT_RESET_HOTKEYS, IDOK, TXT_YES, TXT_NO, TXT_NONE, false) == 0) { - DebugString("Deleting users KEYBOARD.INI\n"); - // Only the player's own file is discarded; the defaults a - // deployment ships are what the reset falls back on. - CCFileClass file("KEYBOARD.INI"); - file.Delete(); - Init_Hotkeys(); - SendMessage(window, HKD_REINIT, 0, 0); - return(TRUE); - } + Hotkey_Queue(screen, UI_KEYBOARD_RESET); + return(TRUE); } break; case IDC_KEY_CATEGORY: if (HIWORD(wparam) == CBN_SELCHANGE) { - SendMessage(window, HKD_FILL_COMMANDS, 0, 0); + HWND combo = GetDlgItem(window, IDC_KEY_CATEGORY); + if (combo) { + Hotkey_Queue(screen, UI_KEYBOARD_CATEGORY, ComboBox_GetCurSel(combo)); + } return(TRUE); } break; } return(TRUE); - case HKD_APPLY_HOTKEY: { - HWND list_commands = GetDlgItem(window, IDC_KEY_COMMANDS); - int selection = ListBox_GetCurSel(list_commands); - if (selection != LB_ERR) { - CommandClass const * cmd = (CommandClass const *)ListBox_GetItemData(list_commands, selection); - for (int i = 0; i < HotkeyCommands.Count(); i++) { - if (HotkeyCommands.Fetch_By_Position(i) == cmd) { - HotkeyCommands.Remove_Index(HotkeyCommands.Fetch_ID_By_Position(i)); - break; - } - } - HWND hotkey = GetDlgItem(window, IDC_KEY_HOTKEY); - int key = SendMessage(hotkey, HKM_GETHOTKEY, 0, 0); - if (key != 0) { - HotkeyCommands.Remove_Index(key); - HotkeyCommands.Add_Index(key, cmd); - return(TRUE); - } - } - return(TRUE); - } - - case HKD_SHOW_COMMAND: { - HWND list_commands = GetDlgItem(window, IDC_KEY_COMMANDS); - int selection = ListBox_GetCurSel(list_commands); - if (selection != LB_ERR) { - CommandClass const * cmd = (CommandClass const *)ListBox_GetItemData(list_commands, selection); - HWND description = GetDlgItem(window, IDC_KEY_DESCRIPTION); - SetWindowText(description, cmd->Get_Description()); - - int key = 0; - for (int i = 0; i < HotkeyCommands.Count(); i++) { - if (HotkeyCommands.Fetch_By_Position(i) == cmd) { - key = HotkeyCommands.Fetch_ID_By_Position(i); - break; - } + case WM_INITDIALOG: { + HWND combo = GetDlgItem(window, IDC_KEY_CATEGORY); + if (combo) { + ComboBox_ResetContent(combo); + for (std::string const & category : screen.Categories) { + ComboBox_AddString(combo, category.c_str()); } - - HWND key_label = GetDlgItem(window, IDC_KEY_CURRENT_SHORTCUT); - Build_Hotkey_String((KeyNumType)key, buffer); - SetWindowText(key_label, buffer); - - HWND hotkey = GetDlgItem(window, IDC_KEY_HOTKEY); - SendMessage(hotkey, HKM_SETHOTKEY, 0, 0); - - HWND hotkey_name = GetDlgItem(window, IDC_KEY_ASSIGNED_TO); - SetWindowText(hotkey_name, ""); - return(TRUE); + ComboBox_SetCurSel(combo, screen.SelectedCategory); } - return(TRUE); - } - case HKD_FILL_COMMANDS: { - HWND cmb_category = GetDlgItem(window, IDC_KEY_CATEGORY); - if (ComboBox_GetCurSel(cmb_category) != current_selection) { - current_selection = ComboBox_GetCurSel(cmb_category); - GetWindowText(cmb_category, buffer, sizeof(buffer)); - HWND list_commands = GetDlgItem(window, IDC_KEY_COMMANDS); - ListBox_ResetContent(list_commands); - for (int i = 0; i < AllCommands.Count(); i++) { - CommandClass const * cmd = AllCommands[i]; - if (stricmp(cmd->Get_Category(), buffer) == 0) { - int index = ListBox_AddString(list_commands, cmd->Get_Display_Name()); - if (index != LB_ERR) { - ListBox_SetItemData(list_commands, index, (LPARAM)cmd); - } - } + HWND list = GetDlgItem(window, IDC_KEY_COMMANDS); + if (list) { + ListBox_ResetContent(list); + for (UIKeyboardPresenterClass::CommandType const & command : screen.Commands) { + ListBox_AddString(list, command.Label.c_str()); } - HWND description = GetDlgItem(window, IDC_KEY_DESCRIPTION); - SetWindowText(description, ""); - ListBox_SetCurSel(description, 0); - SendMessage(window, HKD_SHOW_COMMAND, 0, 0); - return(TRUE); + ListBox_SetCurSel(list, screen.SelectedCommand); } - return(TRUE); - } - - case HKD_REINIT: { - HWND cmb_category = GetDlgItem(window, IDC_KEY_CATEGORY); - ComboBox_ResetContent(cmb_category); - for (int i = 0; i < AllCommands.Count(); i++) { - CommandClass const * cmd = AllCommands[i]; - const char * s = cmd->Get_Category(); - if (ComboBox_FindString(cmb_category, 0, s) == CB_ERR) { - s = cmd->Get_Category(); - ComboBox_AddString(cmb_category, s); - } - } - ComboBox_SetCurSel(cmb_category, 0); - SendMessage(window, HKD_FILL_COMMANDS, 0, 0); - current_selection = -1; - return(TRUE); - } - - case WM_INITDIALOG: - SendMessage(window, HKD_REINIT, 0, 0); return(FALSE); + } } return(FALSE); @@ -821,26 +760,35 @@ INT_PTR CALLBACK Hotkey_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LP /// bool OptionsClass::Hotkey_Dialog(void) { - HWND handle; - int res = -1; + UIKeyboardPresenterClass screen; + screen.Refresh(); - handle = OwnerDraw::Begin_Dialog(IDD_OPT_KEYBOARD, Hotkey_Dialog_Proc); + _KeyboardScreen = &screen; + + HWND handle = OwnerDraw::Begin_Dialog(IDD_OPT_KEYBOARD, Hotkey_Dialog_Proc); if (handle != NULL) { - SetWindowLongPtr(handle, DWLP_USER, (LONG_PTR)&res); OwnerDraw::Display_Dialog(handle); - while (res < 0) { + while (!screen.Result.has_value()) { if (OwnerDraw::Dialog_Message_Handler() == true) { - res = 2; - } - if (!GameActive) { - Title_Screen_Restore(); + UIResult ended; + ended.Outcome = UIResult::OUTCOME_SESSION_ENDED; + ended.GameEnded = true; + screen.Result = ended; + break; } + + screen.Drain(); + Hotkey_Sync_Controls(handle, screen); + screen.Service(); } + OwnerDraw::End_Dialog(handle); } + _KeyboardScreen = NULL; + return(true); } diff --git a/code/ui/uikeyboard.cpp b/code/ui/uikeyboard.cpp new file mode 100644 index 000000000..a893b61cc --- /dev/null +++ b/code/ui/uikeyboard.cpp @@ -0,0 +1,313 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The keyboard screen. Behavior traced out of Hotkey_Dialog_Proc and its four private +// messages in options.cpp. +// +// What the extraction fixes in place, none of it obvious from the template: assigning with +// nothing captured CLEARS the selected command's key, because the dialog removed the old +// binding before it looked at the new one and only re-added when the key was not zero; +// assigning a key another command already holds TAKES it, because the key is removed from +// the index before it is added; cancel is not a no-op but a reload, since every assignment +// was already made against the live index; and reset deletes only the player's own +// KEYBOARD.INI, so the defaults a deployment ships are what is left. +// +// One inherited defect is preserved rather than repaired: changing the category left the +// command list with no selection, because the dialog handed ListBox_SetCurSel the +// description control instead of the list. The description therefore clears and the +// shortcut, the capture and the assigned-to text all keep whatever they were showing, until +// the player picks a command. +// +// docs/UI_DESIGN.md, "Screens", owns the contracts this keeps to. + +#include "always.h" + +#include "uikeyboard.h" + +#include "_command.h" +#include "ccfile.h" +#include "ccini.h" +#include "cdfile.h" +#include "command.h" +#include "dbgprint.h" +#include "globals.h" +#include "init.h" +#include "language/language.h" +#include "msgbox.h" +#include "ownrdraw.h" +#include "vector.h" + +#include +#include + + +// Build_Hotkey_String lives in ownrdraw.cpp and is the only thing this screen wants from +// there. It spells a key, not a control, and moves with the rest of the keyboard support +// when OwnerDraw is retired. +std::string UIKeyboardPresenterClass::Key_Name(int key) +{ + char buffer[64]; + buffer[0] = '\0'; + Build_Hotkey_String((KeyNumType)key, buffer); + return(std::string(buffer)); +} + + +static CommandClass const * Find_Command(std::string const & unique) +{ + for (int index = 0; index < AllCommands.Count(); index++) { + if (unique == AllCommands[index]->Get_Unique_Name()) { + return(AllCommands[index]); + } + } + return(NULL); +} + + +// The key a command answers to now, or zero when it answers to none. +static int Key_Of_Command(CommandClass const * command) +{ + for (int index = 0; index < HotkeyCommands.Count(); index++) { + if (HotkeyCommands.Fetch_By_Position(index) == command) { + return(HotkeyCommands.Fetch_ID_By_Position(index)); + } + } + return(0); +} + + +static bool Less_Ignoring_Case(std::string const & left, std::string const & right) +{ + return(stricmp(left.c_str(), right.c_str()) < 0); +} + + +/// +/// Rebuilds the whole screen from the command list and the live key assignments. +/// +void UIKeyboardPresenterClass::Refresh(void) +{ + Categories.clear(); + + for (int index = 0; index < AllCommands.Count(); index++) { + char const * const category = AllCommands[index]->Get_Category(); + if (category == NULL) continue; + + bool present = false; + for (std::string const & known : Categories) { + if (stricmp(known.c_str(), category) == 0) { + present = true; + break; + } + } + if (!present) { + Categories.push_back(category); + } + } + + // The combo box carried CBS_SORT, so the player sees the categories in order rather + // than in the order the command list was built. + std::sort(Categories.begin(), Categories.end(), Less_Ignoring_Case); + + SelectedCategory = Categories.empty() ? -1 : 0; + + CapturedKey = 0; + CapturedText.clear(); + AssignedTo.clear(); + CurrentShortcut.clear(); + + Fill_Commands(); +} + + +/// +/// The maintenance the dialog driver ran on every pass of its own loop. +/// +void UIKeyboardPresenterClass::Service(void) +{ + if (!GameActive) { + Title_Screen_Restore(); + } +} + + +void UIKeyboardPresenterClass::Fill_Commands(void) +{ + Commands.clear(); + SelectedCommand = -1; + Description.clear(); + + if (SelectedCategory < 0 || SelectedCategory >= (int)Categories.size()) { + return; + } + + std::string const & category = Categories[SelectedCategory]; + + for (int index = 0; index < AllCommands.Count(); index++) { + CommandClass const * const command = AllCommands[index]; + if (command->Get_Category() == NULL) continue; + if (stricmp(command->Get_Category(), category.c_str()) != 0) continue; + + CommandType entry; + entry.Label = (command->Get_Display_Name() != NULL) ? command->Get_Display_Name() : ""; + entry.Description = (command->Get_Description() != NULL) ? command->Get_Description() : ""; + entry.UniqueName = command->Get_Unique_Name(); + Commands.push_back(entry); + } + + // The list box carried LBS_SORT. + std::sort(Commands.begin(), Commands.end(), + [](CommandType const & left, CommandType const & right) { + return(Less_Ignoring_Case(left.Label, right.Label)); + }); +} + + +void UIKeyboardPresenterClass::Show_Command(void) +{ + if (SelectedCommand < 0 || SelectedCommand >= (int)Commands.size()) { + return; + } + + CommandType const & entry = Commands[SelectedCommand]; + CommandClass const * const command = Find_Command(entry.UniqueName); + + Description = entry.Description; + CurrentShortcut = Key_Name(Key_Of_Command(command)); + + // The capture control was emptied every time a command was shown, so a key captured for + // one command cannot be assigned to the next by accident. + CapturedKey = 0; + CapturedText.clear(); + AssignedTo.clear(); +} + + +void UIKeyboardPresenterClass::Apply_Hotkey(void) +{ + if (SelectedCommand < 0 || SelectedCommand >= (int)Commands.size()) { + return; + } + + CommandClass const * const command = Find_Command(Commands[SelectedCommand].UniqueName); + if (command == NULL) { + return; + } + + for (int index = 0; index < HotkeyCommands.Count(); index++) { + if (HotkeyCommands.Fetch_By_Position(index) == command) { + HotkeyCommands.Remove_Index(HotkeyCommands.Fetch_ID_By_Position(index)); + break; + } + } + + if (CapturedKey != 0) { + HotkeyCommands.Remove_Index(CapturedKey); + HotkeyCommands.Add_Index(CapturedKey, command); + } +} + + +void UIKeyboardPresenterClass::Reset_All(void) +{ + DebugString("Deleting users KEYBOARD.INI\n"); + + // Only the player's own file is discarded; the defaults a deployment ships are what the + // reset falls back on. + CCFileClass file("KEYBOARD.INI"); + file.Delete(); + + Init_Hotkeys(); + Refresh(); +} + + +void UIKeyboardPresenterClass::Save_Assignments(void) const +{ + CCINIClass ini; + ini.Clear(); + + for (int index = 0; index < HotkeyCommands.Count(); index++) { + CommandClass const * const command = HotkeyCommands.Fetch_By_Position(index); + int const key = HotkeyCommands.Fetch_ID_By_Position(index); + ini.Put_Int("Hotkey", command->Get_Unique_Name(), key); + } + + CDFileClass file("Keyboard.ini"); + ini.Save(file, false); +} + + +void UIKeyboardPresenterClass::Execute(UIIntent const & intent) +{ + if (intent.Action == UI_KEYBOARD_CATEGORY) { + if (intent.Value >= 0 && intent.Value < (int)Categories.size() && intent.Value != SelectedCategory) { + SelectedCategory = intent.Value; + Fill_Commands(); + } + return; + } + + if (intent.Action == UI_KEYBOARD_COMMAND) { + if (intent.Value >= 0 && intent.Value < (int)Commands.size()) { + SelectedCommand = intent.Value; + Show_Command(); + } + return; + } + + if (intent.Action == UI_KEYBOARD_CAPTURE) { + CapturedKey = intent.Value; + CapturedText = Key_Name(CapturedKey); + + AssignedTo.clear(); + if (HotkeyCommands.Is_Present(CapturedKey)) { + CommandClass const * const holder = HotkeyCommands[CapturedKey]; + if (holder != NULL && holder->Get_Display_Name() != NULL) { + AssignedTo = holder->Get_Display_Name(); + } + } + return; + } + + if (intent.Action == UI_KEYBOARD_ASSIGN) { + Apply_Hotkey(); + Show_Command(); + return; + } + + if (intent.Action == UI_KEYBOARD_RESET) { + // The second argument is the default response, a button position rather than a + // control identifier, and the dialog passed one: Enter answers No. + if (WWMessageBox()._Process(TXT_RESET_HOTKEYS, 1, TXT_YES, TXT_NO, TXT_NONE, false) == 0) { + Reset_All(); + } + return; + } + + UIResult result; + + if (intent.Action == UI_KEYBOARD_ACCEPT) { + Save_Assignments(); + Choice = CHOICE_ACCEPT; + result.Outcome = UIResult::OUTCOME_ACCEPTED; + + } else if (intent.Action == UI_KEYBOARD_CANCEL) { + // Every assignment was made against the live index, so leaving without accepting + // has to put the file's assignments back. + Init_Hotkeys(); + Choice = CHOICE_CANCEL; + result.Outcome = UIResult::OUTCOME_CANCELLED; + + } else { + return; + } + + Result = result; +} diff --git a/code/ui/uikeyboard.h b/code/ui/uikeyboard.h new file mode 100644 index 000000000..421105e84 --- /dev/null +++ b/code/ui/uikeyboard.h @@ -0,0 +1,91 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The keyboard screen's behavior, with no toolkit in it. The screen rebinds the game's +// commands, so what it holds is a staged view of HotkeyCommands: assignments are made +// against the live index as the player works and are written to KEYBOARD.INI only on +// accept, while cancel throws the index away and loads the file again. +// +// The captured key is a plain integer in the game's own encoding, low byte the virtual key +// and the shift, control and alt bits above it, which is the encoding HotkeyCommands is +// indexed by. A view decides how to capture one; this class never sees the keypress. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + +#include +#include + + +inline constexpr char const * UI_KEYBOARD_CATEGORY = "category"; // Value: row +inline constexpr char const * UI_KEYBOARD_COMMAND = "command"; // Value: row +inline constexpr char const * UI_KEYBOARD_CAPTURE = "capture"; // Value: encoded key +inline constexpr char const * UI_KEYBOARD_ASSIGN = "assign"; +inline constexpr char const * UI_KEYBOARD_RESET = "reset"; +inline constexpr char const * UI_KEYBOARD_ACCEPT = "accept"; +inline constexpr char const * UI_KEYBOARD_CANCEL = "cancel"; + + +class UIKeyboardPresenterClass : public UIPresenterClass +{ + public: + // A command as the screen shows it. The unique name is the identity an intent is + // resolved against, because the command list is rebuilt whenever the category + // changes and a row number outlives nothing. + struct CommandType + { + std::string Label; + std::string Description; + std::string UniqueName; + }; + + enum ChoiceType { + CHOICE_NONE, + CHOICE_ACCEPT, + CHOICE_CANCEL, + }; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + virtual void Service(void) override; + + // Spells a key out the way the player's own keyboard layout names it, modifiers + // first. Empty for a key of zero, which is what an unbound command carries. + static std::string Key_Name(int key); + + /* + ** The view-model. Plain values, and the only thing a view reads. + */ + std::vector Categories; + int SelectedCategory = -1; + + std::vector Commands; + int SelectedCommand = -1; + + // The selected command's description and the key it answers to now. + std::string Description; + std::string CurrentShortcut; + + // What the capture control is holding, and the command that key already belongs to. + int CapturedKey = 0; + std::string CapturedText; + std::string AssignedTo; + + ChoiceType Choice = CHOICE_NONE; + + private: + void Fill_Commands(void); + void Show_Command(void); + void Apply_Hotkey(void); + void Reset_All(void); + void Save_Assignments(void) const; +}; From f7c48e4286f8574996f92adb07ab21ad44b9f581 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 03:55:55 +0100 Subject: [PATCH 100/179] feat(ui): let a view step aside for a screen it opens Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/ui/uirmlview.h | 5 +++++ code/ui/uishell.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/code/ui/uirmlview.h b/code/ui/uirmlview.h index 7fa6fd332..677070b60 100644 --- a/code/ui/uirmlview.h +++ b/code/ui/uirmlview.h @@ -36,6 +36,11 @@ class UIRmlViewClass // model is removed while its storage still lives. void Close(void); + // Takes the document off the screen without releasing it, and puts it back, so a + // screen opened on top of this one has the region and the input scope to itself. + void Hide(void); + void Show(void); + bool Is_Visible(void) const; // Fills in the view-model's fields and events. Called once, before the document is diff --git a/code/ui/uishell.cpp b/code/ui/uishell.cpp index da3b3e7ea..d76c02635 100644 --- a/code/ui/uishell.cpp +++ b/code/ui/uishell.cpp @@ -293,6 +293,7 @@ void UI_On_Resize(void) /// void UI_Tick(void) { + // The service points nest: a dialog driver's Call_Back runs inside a loop that already // ticked. A nested request is dropped rather than updating the context twice, which is // what keeps a pump reached from inside an update out of it. @@ -704,6 +705,47 @@ bool UIRmlViewClass::Prepare(bool modal) } +/// +/// Takes the document off the screen without releasing it. +/// A screen this one opens draws where this one is, and the coexistence rule in +/// docs/UI_DESIGN.md wants only one presentation over a region; the modal scope goes with +/// it, so the screen underneath takes input while it is away. +/// +void UIRmlViewClass::Hide(void) +{ + if (Element == nullptr || !Element->IsVisible()) { + return; + } + + Element->Hide(); + + if (IsModal) { + Leave_Modal_Scope(); + } + + Mark_Overlay_Dirty(); +} + + +/// +/// Puts the document back on the screen with the scope it had. +/// +void UIRmlViewClass::Show(void) +{ + if (Element == nullptr || Element->IsVisible()) { + return; + } + + Element->Show(IsModal ? Rml::ModalFlag::Modal : Rml::ModalFlag::None); + + if (IsModal) { + Enter_Modal_Scope(); + } + + Mark_Overlay_Dirty(); +} + + void UIRmlViewClass::Close(void) { if (_Context == nullptr || Element == nullptr) { From 1c755a08615a52b9db41cd5ef92f0c265a448c44 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 03:55:55 +0100 Subject: [PATCH 101/179] feat(ui): show the in-game options and abort box through RmlUi Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/goptions.cpp | 109 ++++++++++++------- code/ui/uiabort.cpp | 79 ++++++++++++++ code/ui/uiabort.h | 5 + code/ui/uigameoptions.cpp | 177 +++++++++++++++++++++++++++++++ code/ui/uigameoptions.h | 5 + ui/abort.rcss | 41 +++++++ ui/abort.rml | 15 +++ ui/gameoptions.rcss | 33 ++++++ ui/gameoptions.rml | 18 ++++ ui/gameoptionsmp.rcss | 27 +++++ ui/gameoptionsmp.rml | 14 +++ ui/gameoptionswol.rcss | 89 ++++++++++++++++ ui/gameoptionswol.rml | 26 +++++ ui/optionsbase.rcss | 218 ++++++++++++++++++++++++++++++++++++++ 14 files changed, 820 insertions(+), 36 deletions(-) create mode 100644 ui/abort.rcss create mode 100644 ui/abort.rml create mode 100644 ui/gameoptions.rcss create mode 100644 ui/gameoptions.rml create mode 100644 ui/gameoptionsmp.rcss create mode 100644 ui/gameoptionsmp.rml create mode 100644 ui/gameoptionswol.rcss create mode 100644 ui/gameoptionswol.rml create mode 100644 ui/optionsbase.rcss diff --git a/code/goptions.cpp b/code/goptions.cpp index 78fc6a2e0..80bdc738d 100644 --- a/code/goptions.cpp +++ b/code/goptions.cpp @@ -50,6 +50,7 @@ #include "stats.h" #include "ui/uiabort.h" #include "ui/uigameoptions.h" +#include "ui/uishell.h" #include "special.hh" @@ -115,6 +116,30 @@ static void Game_Options_Sync_Controls(HWND window, UIGameOptionsPresenterClass } } +// What the driver does on the way out, whichever view was shown. The briefing is restated +// after the screen has gone, which is where the dialog driver restated it. +static void Game_Options_Finish(UIGameOptionsPresenterClass const & screen) +{ + Keyboard->Clear(); + + if (screen.Choice == UIGameOptionsPresenterClass::CHOICE_BRIEFING) { + Restate_Mission(Scen); + } + + IgnoreInput = Scen->IsInputLocked; + + if (screen.Choice == UIGameOptionsPresenterClass::CHOICE_LOADED) { + if (MouseCursor->Is_Hidden() == false && Scen->IsInputLocked == 1) { + Hide_Mouse(); + } else if (MouseCursor->Is_Hidden() == true && Scen->IsInputLocked == 0) { + Show_Mouse(); + } + } + + Map.Flag_To_Redraw(GS_REDRAW_ALL); +} + + /// /// Displays the in game options dialog. /// This routine is used by the special dialog handler when the player calls up the options @@ -127,6 +152,21 @@ void Game_Options_Dialog(void) UIGameOptionsPresenterClass screen; screen.Refresh(); + IgnoreInput = true; + Keyboard->Clear(); + + // The selection is latched here, at screen entry, and the legacy dialog opens only when + // the document could not be prepared. + if (UI_Use_Rml()) { + UIResult const result = UI_Game_Options_Screen(screen); + if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { + Game_Options_Finish(screen); + return; + } + screen.IsClosing = false; + screen.Result.reset(); + } + _Screen = &screen; HWND dialog; @@ -138,9 +178,6 @@ void Game_Options_Dialog(void) dialog = OwnerDraw::Begin_Dialog(IDD_OPT_CTRL_MP, Game_Options_Dialog_Proc); } - IgnoreInput = true; - Keyboard->Clear(); - if (dialog) { OwnerDraw::Display_Dialog(dialog); @@ -181,23 +218,7 @@ void Game_Options_Dialog(void) _Screen = nullptr; - Keyboard->Clear(); - - if (screen.Choice == UIGameOptionsPresenterClass::CHOICE_BRIEFING) { - Restate_Mission(Scen); - } - - IgnoreInput = Scen->IsInputLocked; - - if (screen.Choice == UIGameOptionsPresenterClass::CHOICE_LOADED) { - if (MouseCursor->Is_Hidden() == false && Scen->IsInputLocked == 1) { - Hide_Mouse(); - } else if (MouseCursor->Is_Hidden() == true && Scen->IsInputLocked == 0) { - Show_Mouse(); - } - } - - Map.Flag_To_Redraw(GS_REDRAW_ALL); + Game_Options_Finish(screen); } @@ -349,6 +370,26 @@ void Game_Options_On_INITDIALOG(HWND window, UIGameOptionsPresenterClass const & } +// Maps the screen's choice onto the value the special dialog handler expects. A screen that +// never opened answers zero, which is what the driver's own result was left at. +static int Abort_Choice_Result(UIAbortPresenterClass const & screen) +{ + switch (screen.Choice) { + case UIAbortPresenterClass::CHOICE_QUIT: + return(IDOK); + + case UIAbortPresenterClass::CHOICE_RESTART: + return(IDABORT); + + case UIAbortPresenterClass::CHOICE_CANCEL: + return(IDCANCEL); + + default: + return(0); + } +} + + /// /// Displays the abort mission dialog and waits for an answer. /// This routine is used by the special dialog handler when the player asks to abandon or @@ -362,6 +403,17 @@ int Abort_Dialog(void) UIAbortPresenterClass screen; screen.Refresh(); + // The selection is latched here, at screen entry, and the legacy dialog opens only when + // the document could not be prepared. + if (UI_Use_Rml()) { + UIResult const result = UI_Abort_Screen(screen); + if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { + return(Abort_Choice_Result(screen)); + } + screen.IsClosing = false; + screen.Result.reset(); + } + _Abort = &screen; HWND dialog = OwnerDraw::Begin_Dialog(IDD_MISSION_ABORT, Abort_Dialog_Proc); @@ -390,22 +442,7 @@ int Abort_Dialog(void) _Abort = NULL; - switch (screen.Choice) { - case UIAbortPresenterClass::CHOICE_QUIT: - return(IDOK); - - case UIAbortPresenterClass::CHOICE_RESTART: - return(IDABORT); - - case UIAbortPresenterClass::CHOICE_CANCEL: - return(IDCANCEL); - - default: - break; - } - - // The dialog could not be created, which left its driver's result at zero. - return(0); + return(Abort_Choice_Result(screen)); } diff --git a/code/ui/uiabort.cpp b/code/ui/uiabort.cpp index 922bb8635..d1fe666e7 100644 --- a/code/ui/uiabort.cpp +++ b/code/ui/uiabort.cpp @@ -20,11 +20,17 @@ #include "uiabort.h" +#include "uirmlview.h" + #include "data.h" #include "house.h" #include "language/language.h" #include "session.h" +#include +#include +#include + void UIAbortPresenterClass::Refresh(void) { @@ -57,3 +63,76 @@ void UIAbortPresenterClass::Execute(UIIntent const & intent) Result = result; } + + +//--------------------------------------------------------------------------------------- +// The RmlUi view. +//--------------------------------------------------------------------------------------- + +/// +/// The RmlUi half of the abort and surrender screen. +/// +class AbortViewClass : public UIRmlViewClass +{ + public: + AbortViewClass(UIAbortPresenterClass & presenter) : + UIRmlViewClass(presenter, "abort.rml"), + Screen(presenter) + { + } + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override {} + + private: + UIAbortPresenterClass & Screen; + + // The middle button's caption. The document carries the restart wording the + // template carried, and this replaces it only where the screen asks. + Rml::String RestartCaption; +}; + + +void AbortViewClass::Bind(Rml::DataModelConstructor & model) +{ + RestartCaption = Screen.RestartCaption.empty() ? "Restart" : Rml::String(Screen.RestartCaption); + + model.Bind("restartcaption", &RestartCaption); + model.Bind("canrestart", &Screen.CanRestart); + + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{arguments[0].Get(), "", 0}); + }); + + // Escape carries on playing, which is the IDCANCEL the dialog answered with its cancel + // arm. Enter does the same: the template names no default push button, so Windows sent + // IDOK, and the dialog had no arm for it. + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key == Rml::Input::KI_ESCAPE) { + Screen.Queue(UIIntent{UI_ABORT_CANCEL, "", 0}); + } + }); +} + + +/// +/// Shows the abort box and waits for the player to choose. +/// +UIResult UI_Abort_Screen(UIAbortPresenterClass & presenter) +{ + AbortViewClass view(presenter); + + if (!view.Prepare(true)) { + UIResult result; + result.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + return(result); + } + + UIResult const result = UI_Run_Modal(presenter, view); + view.Close(); + return(result); +} diff --git a/code/ui/uiabort.h b/code/ui/uiabort.h index f0ae6214f..b5fc34480 100644 --- a/code/ui/uiabort.h +++ b/code/ui/uiabort.h @@ -53,3 +53,8 @@ class UIAbortPresenterClass : public UIPresenterClass ChoiceType Choice = CHOICE_NONE; }; + + +// Shows the screen through its RmlUi view. FAILED_TO_OPEN leaves nothing shown and the +// caller falls through to the legacy dialog. +UIResult UI_Abort_Screen(UIAbortPresenterClass & presenter); diff --git a/code/ui/uigameoptions.cpp b/code/ui/uigameoptions.cpp index 819368c86..e69acaa85 100644 --- a/code/ui/uigameoptions.cpp +++ b/code/ui/uigameoptions.cpp @@ -24,6 +24,8 @@ #include "uigameoptions.h" +#include "uirmlview.h" + #include "data.h" #include "dbgprint.h" #include "event.h" @@ -40,6 +42,10 @@ #include "special.hh" +#include +#include +#include + #include @@ -226,3 +232,174 @@ void UIGameOptionsPresenterClass::Run_Pending(void) break; } } + + +//--------------------------------------------------------------------------------------- +// The RmlUi view. One document per dialog template, because the three templates differ by +// which controls exist rather than by how one is arranged. +//--------------------------------------------------------------------------------------- + +/// +/// The RmlUi half of the in-game options screen. +/// +class GameOptionsViewClass : public UIRmlViewClass +{ + public: + GameOptionsViewClass(UIGameOptionsPresenterClass & presenter, char const * document) : + UIRmlViewClass(presenter, document), + Screen(presenter) + { + } + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + // A slider takes its position from the model as the document loads, and that raises + // a change event of its own. Nothing is queued until this is set. + void Settle(void) { Settled = true; } + + private: + void Move(char const * which, int step); + void Update_Labels(void); + + UIGameOptionsPresenterClass & Screen; + bool Settled = false; + + // The caption beside each slider. Held here because the labels the presenter + // carries are indexed by step and a document binds a value, not a lookup. + Rml::String SpeedLabel; + Rml::String ConnectionLabel; +}; + + +void GameOptionsViewClass::Update_Labels(void) +{ + SpeedLabel.clear(); + if (Screen.SpeedStep >= 0 && Screen.SpeedStep < (int)Screen.SpeedLabels.size()) { + SpeedLabel = Screen.SpeedLabels[Screen.SpeedStep]; + } + + ConnectionLabel.clear(); + if (Screen.ConnectionStep >= 0 && Screen.ConnectionStep < (int)Screen.ConnectionLabels.size()) { + ConnectionLabel = Screen.ConnectionLabels[Screen.ConnectionStep]; + } +} + + +void GameOptionsViewClass::Move(char const * which, int step) +{ + if (!Settled) return; + + // A position the screen already holds raises no intent, so setting a slider from the + // model cannot look like a move the player did not make. + if (which == UI_GAMEOPT_SPEED && step == Screen.SpeedStep) return; + if (which == UI_GAMEOPT_CONNECTION && step == Screen.ConnectionStep) return; + + Screen.Queue(UIIntent{which, "", step}); +} + + +void GameOptionsViewClass::Bind(Rml::DataModelConstructor & model) +{ + Update_Labels(); + + model.Bind("cansave", &Screen.CanSave); + model.Bind("canload", &Screen.CanLoad); + model.Bind("candelete", &Screen.CanDelete); + model.Bind("canbrief", &Screen.CanBrief); + model.Bind("speed", &Screen.SpeedStep); + model.Bind("connection", &Screen.ConnectionStep); + model.Bind("speedlabel", &SpeedLabel); + model.Bind("connectionlabel", &ConnectionLabel); + + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{arguments[0].Get(), "", 0}); + }); + + model.BindEventCallback("move", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + + Rml::String const which = arguments[0].Get(); + int const step = (int)(event.GetParameter("value", 0.0f) + 0.5f); + + if (which == UI_GAMEOPT_SPEED) Move(UI_GAMEOPT_SPEED, step); + else if (which == UI_GAMEOPT_CONNECTION) Move(UI_GAMEOPT_CONNECTION, step); + }); + + // Escape resumes, which is the IDCANCEL the dialog answered with its resume arm. Enter + // resumes too: the template names no default push button, so Windows sent IDOK, and the + // dialog treated that as the resume button. + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key == Rml::Input::KI_ESCAPE || key == Rml::Input::KI_RETURN || key == Rml::Input::KI_NUMPADENTER) { + Screen.Queue(UIIntent{UI_GAMEOPT_RESUME, "", 0}); + } + }); +} + + +void GameOptionsViewClass::Sync(void) +{ + if (!Model) return; + + Update_Labels(); + + // The slider positions are not dirtied, because a slider already carries the position + // its own change event reported. + Model.DirtyVariable("cansave"); + Model.DirtyVariable("canload"); + Model.DirtyVariable("candelete"); + Model.DirtyVariable("canbrief"); + Model.DirtyVariable("speedlabel"); + Model.DirtyVariable("connectionlabel"); +} + + +/// +/// Shows the in-game options and waits for the player to choose. +/// +UIResult UI_Game_Options_Screen(UIGameOptionsPresenterClass & presenter) +{ + char const * document = "gameoptionsmp.rml"; + if (!presenter.IsMultiplayer) { + document = "gameoptions.rml"; + } else if (presenter.HasSliders) { + document = "gameoptionswol.rml"; + } + + GameOptionsViewClass view(presenter, document); + + if (!view.Prepare(true)) { + UIResult result; + result.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + return(result); + } + + view.Settle(); + + // A browser this screen opens is a screen of a different kind, so it nests; getting out + // of the way of it is hiding this document, which is what the dialog's ShowWindow did. + while (!presenter.Result.has_value()) { + UI_Run_Modal(presenter, view); + + if (presenter.Pending == UIGameOptionsPresenterClass::SUB_NONE) { + break; + } + + view.Hide(); + presenter.Run_Pending(); + if (presenter.Result.has_value()) { + break; + } + view.Show(); + view.Sync(); + } + + UIResult const result = presenter.Result.value_or(UIResult{}); + view.Close(); + return(result); +} diff --git a/code/ui/uigameoptions.h b/code/ui/uigameoptions.h index 3c88a0169..0416785ec 100644 --- a/code/ui/uigameoptions.h +++ b/code/ui/uigameoptions.h @@ -97,3 +97,8 @@ class UIGameOptionsPresenterClass : public UIPresenterClass private: void Finish(ChoiceType choice, UIResult::OutcomeType outcome); }; + + +// Shows the screen through its RmlUi view. FAILED_TO_OPEN leaves nothing shown and the +// caller falls through to the legacy dialog. +UIResult UI_Game_Options_Screen(UIGameOptionsPresenterClass & presenter); diff --git a/ui/abort.rcss b/ui/abort.rcss new file mode 100644 index 000000000..3920dd2eb --- /dev/null +++ b/ui/abort.rcss @@ -0,0 +1,41 @@ +/* The abort and surrender box. Geometry from the IDD_MISSION_ABORT template, converted from + dialog units at the 8 point MS Sans Serif the template names: 1.5 pixels across and 1.625 + down, with a child's offset taken from the panel's content box and the panel's declared + size taken inside its own border. */ + +/* 256 x 63 dialog units, so 384 x 102.375 pixels, centred on the frame. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -192dp; + margin-top: -51.1875dp; + + width: 380dp; + height: 98.375dp; +} + +/* CTEXT with SS_CENTERIMAGE, 212 x 19 dialog units at 22, 12: centred both ways within its + own extents. */ +#question +{ + left: 31dp; + top: 17.5dp; + width: 318dp; + height: 30.875dp; + line-height: 30.875dp; + text-align: center; +} + +/* Three buttons, all 60 x 14 dialog units on the same row. */ +.button +{ + top: 58.125dp; + width: 90dp; + height: 22.75dp; + line-height: 22.75dp; +} + +#quit { left: 31dp; } +#restart { left: 145dp; } +#cancel { left: 259dp; } diff --git a/ui/abort.rml b/ui/abort.rml new file mode 100644 index 000000000..f39ba21ab --- /dev/null +++ b/ui/abort.rml @@ -0,0 +1,15 @@ + + + Abort mission + + + + +
+
Do you want to abort the mission?
+
Abort
+
{{ restartcaption }}
+
[[TXT_CANCEL]]
+
+ +
diff --git a/ui/gameoptions.rcss b/ui/gameoptions.rcss new file mode 100644 index 000000000..96eae2437 --- /dev/null +++ b/ui/gameoptions.rcss @@ -0,0 +1,33 @@ +/* The in-game options a solo mission or a skirmish shows. Geometry from the IDD_OPT_CTRL_SP + template, converted from dialog units at the 8 point MS Sans Serif the template names: + 1.5 pixels across and 1.625 down, with a child's offset taken from the panel's content + box and the panel's declared size taken inside its own border. */ + +/* 209 x 140 dialog units, so 313.5 x 227.5 pixels, centred on the frame. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -156.75dp; + margin-top: -113.75dp; + + width: 309.5dp; + height: 223.5dp; +} + +/* Seven buttons, all 115 x 14 dialog units at x 47. */ +.button +{ + left: 68.5dp; + width: 172.5dp; + height: 22.75dp; + line-height: 22.75dp; +} + +#settings { top: 17.5dp; } +#briefing { top: 45.125dp; } +#load { top: 72.75dp; } +#save { top: 100.375dp; } +#delete { top: 128dp; } +#abort { top: 155.625dp; } +#resume { top: 183.25dp; } diff --git a/ui/gameoptions.rml b/ui/gameoptions.rml new file mode 100644 index 000000000..1c8d4f7a9 --- /dev/null +++ b/ui/gameoptions.rml @@ -0,0 +1,18 @@ + + + In-game options + + + + +
+
Game Controls
+
Restate Briefing
+
Load Game
+
Save Game
+
Delete Game
+
Abort Mission
+
[[TXT_RESUME_MISSION]]
+
+ +
diff --git a/ui/gameoptionsmp.rcss b/ui/gameoptionsmp.rcss new file mode 100644 index 000000000..04e2b1de5 --- /dev/null +++ b/ui/gameoptionsmp.rcss @@ -0,0 +1,27 @@ +/* The in-game options a local session shows: three buttons and no saving. Geometry from the + IDD_OPT_CTRL_MP template, converted at 1.5 pixels across and 1.625 down. */ + +/* 209 x 75 dialog units, so 313.5 x 121.875 pixels, centred on the frame. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -156.75dp; + margin-top: -60.9375dp; + + width: 309.5dp; + height: 117.875dp; +} + +/* Three buttons, all 99 x 14 dialog units at x 55. */ +.button +{ + left: 80.5dp; + width: 148.5dp; + height: 22.75dp; + line-height: 22.75dp; +} + +#settings { top: 17.5dp; } +#abort { top: 46.75dp; } +#resume { top: 76dp; } diff --git a/ui/gameoptionsmp.rml b/ui/gameoptionsmp.rml new file mode 100644 index 000000000..e70d5e59b --- /dev/null +++ b/ui/gameoptionsmp.rml @@ -0,0 +1,14 @@ + + + In-game options + + + + +
+
Game Controls
+
Abort Mission
+
[[TXT_RESUME_MISSION]]
+
+ +
diff --git a/ui/gameoptionswol.rcss b/ui/gameoptionswol.rcss new file mode 100644 index 000000000..a525de1be --- /dev/null +++ b/ui/gameoptionswol.rcss @@ -0,0 +1,89 @@ +/* The in-game options an internet session shows: the five buttons plus the game speed and + connection quality sliders. Geometry from the IDD_OPT_CTRL_WOL template, converted at 1.5 + pixels across and 1.625 down. */ + +/* 340 x 185 dialog units, so 510 x 300.625 pixels, centred on the frame. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -255dp; + margin-top: -150.3125dp; + + width: 506dp; + height: 296.625dp; +} + +/* Five buttons, all 99 x 14 dialog units at x 120. */ +.button +{ + left: 178dp; + width: 148.5dp; + height: 22.75dp; + line-height: 22.75dp; +} + +#settings { top: 11dp; } +#load { top: 38.625dp; } +#save { top: 66.25dp; } +#abort { top: 93.875dp; } +#resume { top: 121.5dp; } + +/* The group box around the two sliders, 283 x 68 dialog units at 28, 92. Its caption sits + on the top edge, which is how the owner-draw group box drew it. */ +#group +{ + display: block; + position: absolute; + box-sizing: border-box; + left: 40dp; + top: 147.5dp; + width: 424.5dp; + height: 110.5dp; + line-height: 14dp; + padding-left: 8dp; + + color: #b9bcae; + border-width: 1dp; + border-top-color: #10120e; + border-left-color: #10120e; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +/* Two track bars, 148 x 13 dialog units at x 95. */ +.slider +{ + left: 140.5dp; + width: 222dp; + height: 21.125dp; +} + +.slider slidertrack { margin-top: 7.5dp; } + +#connection { top: 176.75dp; } +#speed { top: 212.5dp; } + +/* The left captions are LTEXT with SS_CENTERIMAGE, 58 x 13 at x 39; the value captions are + RTEXT, 45 x 13 at x 247. */ +.caption +{ + height: 21.125dp; + line-height: 21.125dp; +} + +#connectionlabel, #speedlabel +{ + left: 56.5dp; + width: 87dp; +} + +.value +{ + left: 368.5dp; + width: 67.5dp; + text-align: right; +} + +#connectionlabel, #connectionvalue { top: 176.75dp; } +#speedlabel, #speedvalue { top: 212.5dp; } diff --git a/ui/gameoptionswol.rml b/ui/gameoptionswol.rml new file mode 100644 index 000000000..d820acab9 --- /dev/null +++ b/ui/gameoptionswol.rml @@ -0,0 +1,26 @@ + + + In-game options + + + + +
+
Game Controls
+
Load Game
+
Save Game
+
Abort Mission
+
[[TXT_RESUME_MISSION]]
+ +
Internet Game Controls
+ +
Connection
+ +
{{ connectionlabel }}
+ +
Game Speed
+ +
{{ speedlabel }}
+
+ +
diff --git a/ui/optionsbase.rcss b/ui/optionsbase.rcss new file mode 100644 index 000000000..255876966 --- /dev/null +++ b/ui/optionsbase.rcss @@ -0,0 +1,218 @@ +/* What every options family document looks like. Only the look lives here; each document's + own stylesheet carries its geometry, converted from its dialog template's units. + + The palette and the raised and sunken borders are the ones ui/sound.rcss established for + the same family of dialogs. The panel is drawn rather than blitted because the dialogs' + own background is a PCX, and PCX decoding arrives with the first screen that shows game + art. Everything here stays inside the styling profile docs/UI_DESIGN.md declares: text, + ordinary layout, borders and basic decorators, with no filter, layer, shader or + transform. */ + +body +{ + font-family: LatoLatin; + font-size: 12dp; + color: #d6d8cc; + + width: 100%; + height: 100%; +} + +/* A dialog panel. A document states its own size and where it sits. */ +.panel +{ + display: block; + position: absolute; + + background-color: #23261fF2; + border-width: 2dp; + border-top-color: #6e7360; + border-left-color: #6e7360; + border-right-color: #10120e; + border-bottom-color: #10120e; +} + +/* An owner-draw push button. */ +.button +{ + display: block; + position: absolute; + + /* A control's declared size is the template's, borders included, because a dialog + control's rectangle includes the frame drawn around it. */ + box-sizing: border-box; + text-align: center; + white-space: nowrap; + overflow: hidden; + + color: #e4e6da; + background-color: #33372c; + border-width: 2dp; + border-top-color: #767c68; + border-left-color: #767c68; + border-right-color: #14160f; + border-bottom-color: #14160f; +} + +.button:hover { background-color: #474d3d; } + +.button:active +{ + background-color: #2a2e24; + border-top-color: #14160f; + border-left-color: #14160f; + border-right-color: #767c68; + border-bottom-color: #767c68; +} + +.button.disabled, +.disabled .button +{ + color: #6b6e63; + background-color: #2a2d24; + pointer-events: none; +} + +/* A BS_FLAT check box, which reads as pressed in rather than as a tick beside a caption. */ +.check +{ + display: block; + position: absolute; + box-sizing: border-box; + text-align: center; + white-space: nowrap; + overflow: hidden; + + color: #b9bcae; + background-color: #2b2f25; + border-width: 2dp; + border-top-color: #5c6152; + border-left-color: #5c6152; + border-right-color: #14160f; + border-bottom-color: #14160f; +} + +.check:hover { background-color: #3b4032; } + +.check.ticked +{ + color: #e4e6da; + background-color: #4a5140; + border-top-color: #14160f; + border-left-color: #14160f; + border-right-color: #767c68; + border-bottom-color: #767c68; +} + +/* A static caption. A document says where it sits and how it is aligned. */ +.caption +{ + display: block; + position: absolute; + white-space: nowrap; + overflow: hidden; +} + +/* A TBS_NOTICKS track bar: a plain groove with a thumb. */ +.slider +{ + display: block; + position: absolute; +} + +.slider slider +{ + width: 100%; + height: 100%; +} + +.slider sliderbar +{ + width: 14dp; + height: 100%; + + background-color: #5c6152; + border-width: 2dp; + border-top-color: #949a84; + border-left-color: #949a84; + border-right-color: #14160f; + border-bottom-color: #14160f; +} + +.slider sliderbar:hover { background-color: #6f7563; } +.slider sliderbar:active { background-color: #4a4e41; } + +.slider slidertrack +{ + width: 100%; + height: 6dp; + + background-color: #14160f; + border-width: 1dp; + border-top-color: #10120e; + border-left-color: #10120e; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +.slider sliderarrowdec, +.slider sliderarrowinc +{ + width: 0dp; + height: 0dp; +} + +/* A list box. A row states its own width, because a scrolling container gives its children + no width to be a proportion of. */ +.list +{ + display: block; + position: absolute; + + background-color: #14160f; + overflow-y: auto; + overflow-x: hidden; +} + +.list .row +{ + display: block; + box-sizing: border-box; + padding: 1dp 4dp; + white-space: nowrap; + color: #b9bcae; +} + +.list .row:hover { color: #e4e6da; } + +.list .row.picked +{ + background-color: #3f4536; + color: #e4e6da; +} + +.list scrollbarvertical +{ + width: 12dp; + background-color: #23261f; +} + +.list scrollbarvertical slidertrack +{ + width: 12dp; + margin-top: 0dp; + background-color: #14160f; + border-width: 0dp; +} + +.list scrollbarvertical sliderbar +{ + width: 12dp; + min-height: 20dp; + background-color: #5c6152; + border-width: 2dp; + border-top-color: #949a84; + border-left-color: #949a84; + border-right-color: #14160f; + border-bottom-color: #14160f; +} From d4aaadfcab131515dca1a94b99e704ee9a3e8c17 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 03:55:55 +0100 Subject: [PATCH 102/179] feat(ui): show the game controls through RmlUi Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/gamedlg.cpp | 17 ++++ code/ui/uigamecontrols.cpp | 189 +++++++++++++++++++++++++++++++++++++ code/ui/uigamecontrols.h | 5 + ui/gamecontrols.rcss | 80 ++++++++++++++++ ui/gamecontrols.rml | 34 +++++++ ui/gamecontrolsmp.rcss | 79 ++++++++++++++++ ui/gamecontrolsmp.rml | 32 +++++++ ui/gamecontrolswol.rcss | 79 ++++++++++++++++ ui/gamecontrolswol.rml | 28 ++++++ 9 files changed, 543 insertions(+) create mode 100644 ui/gamecontrols.rcss create mode 100644 ui/gamecontrols.rml create mode 100644 ui/gamecontrolsmp.rcss create mode 100644 ui/gamecontrolsmp.rml create mode 100644 ui/gamecontrolswol.rcss create mode 100644 ui/gamecontrolswol.rml diff --git a/code/gamedlg.cpp b/code/gamedlg.cpp index 587c1cca5..d85b4fd49 100644 --- a/code/gamedlg.cpp +++ b/code/gamedlg.cpp @@ -49,6 +49,7 @@ #include "session.h" #include "techno.h" #include "ui/uigamecontrols.h" +#include "ui/uishell.h" #include "special.hh" @@ -166,6 +167,22 @@ void GameControlsClass::Dialog(void) UIGameControlsPresenterClass screen; screen.Refresh(); + // The selection is latched here, at screen entry, and the legacy dialog opens only when + // the document could not be prepared. + if (UI_Use_Rml()) { + UIResult const result = UI_Game_Controls_Screen(screen); + if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { + if (screen.Commits()) { + screen.Apply(); + Options.Save_Settings(); + } + DebugString("GameControls: GameSpeed = %d, ScrollRate = %d, Detail = %d\n", Options.GameSpeed, Options.ScrollRate, Options.DetailLevel); + return; + } + screen.IsClosing = false; + screen.Result.reset(); + } + _Screen = &screen; if (GameActive == true) { diff --git a/code/ui/uigamecontrols.cpp b/code/ui/uigamecontrols.cpp index 674e41ea6..f4f8c1640 100644 --- a/code/ui/uigamecontrols.cpp +++ b/code/ui/uigamecontrols.cpp @@ -22,6 +22,8 @@ #include "uigamecontrols.h" +#include "uirmlview.h" + #include "_map.h" #include "audio/audioengine.h" #include "_tooltip.h" @@ -39,6 +41,10 @@ #include "special.hh" +#include +#include +#include + static void Fill_Labels(std::vector & labels, int const * names, int count) { @@ -203,3 +209,186 @@ void UIGameControlsPresenterClass::Apply(void) Options.Difficulty = DifficultyStep; } } + + +//--------------------------------------------------------------------------------------- +// The RmlUi view. One document per dialog template, because the three templates differ by +// which controls exist rather than by how one is arranged. +//--------------------------------------------------------------------------------------- + +/// +/// The RmlUi half of the game controls screen. +/// +class GameControlsViewClass : public UIRmlViewClass +{ + public: + GameControlsViewClass(UIGameControlsPresenterClass & presenter, char const * document) : + UIRmlViewClass(presenter, document), + Screen(presenter) + { + } + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + // A slider takes its position from the model as the document loads, and that raises + // a change event of its own. Nothing is queued until this is set. + void Settle(void) { Settled = true; } + + private: + void Move(char const * which, int step); + void Update_Labels(void); + + UIGameControlsPresenterClass & Screen; + bool Settled = false; + + Rml::String SpeedText; + Rml::String ScrollText; + Rml::String DetailText; + Rml::String DifficultyText; +}; + + +static Rml::String Label_At(std::vector const & labels, int step) +{ + if (step < 0 || step >= (int)labels.size()) { + return(Rml::String()); + } + return(Rml::String(labels[step])); +} + + +void GameControlsViewClass::Update_Labels(void) +{ + SpeedText = Label_At(Screen.SpeedLabels, Screen.SpeedStep); + ScrollText = Label_At(Screen.ScrollLabels, Screen.ScrollStep); + DetailText = Label_At(Screen.DetailLabels, Screen.DetailStep); + DifficultyText = Label_At(Screen.DifficultyLabels, Screen.DifficultyStep); +} + + +void GameControlsViewClass::Move(char const * which, int step) +{ + if (!Settled) return; + + if (which == UI_GAMECTRL_SPEED && step == Screen.SpeedStep) return; + if (which == UI_GAMECTRL_SCROLL && step == Screen.ScrollStep) return; + if (which == UI_GAMECTRL_DETAIL && step == Screen.DetailStep) return; + if (which == UI_GAMECTRL_DIFFICULTY && step == Screen.DifficultyStep) return; + + Screen.Queue(UIIntent{which, "", step}); +} + + +void GameControlsViewClass::Bind(Rml::DataModelConstructor & model) +{ + Update_Labels(); + + model.Bind("speed", &Screen.SpeedStep); + model.Bind("scroll", &Screen.ScrollStep); + model.Bind("detail", &Screen.DetailStep); + model.Bind("difficulty", &Screen.DifficultyStep); + model.Bind("speedtext", &SpeedText); + model.Bind("scrolltext", &ScrollText); + model.Bind("detailtext", &DetailText); + model.Bind("difficultytext", &DifficultyText); + + model.Bind("cameotext", &Screen.CameoText); + model.Bind("actionlines", &Screen.ActionLines); + model.Bind("tooltips", &Screen.ShowToolTips); + model.Bind("coasting", &Screen.Coasting); + model.Bind("edgescroll", &Screen.EdgeScroll); + model.Bind("soundavailable", &Screen.SoundAvailable); + + model.BindEventCallback("move", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + + Rml::String const which = arguments[0].Get(); + int const step = (int)(event.GetParameter("value", 0.0f) + 0.5f); + + if (which == UI_GAMECTRL_SPEED) Move(UI_GAMECTRL_SPEED, step); + else if (which == UI_GAMECTRL_SCROLL) Move(UI_GAMECTRL_SCROLL, step); + else if (which == UI_GAMECTRL_DETAIL) Move(UI_GAMECTRL_DETAIL, step); + else if (which == UI_GAMECTRL_DIFFICULTY) Move(UI_GAMECTRL_DIFFICULTY, step); + }); + + // A check box is a class plus a click that queues a toggle, not a two-way bound control. + model.BindEventCallback("toggle", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + + Rml::String const which = arguments[0].Get(); + if (which == UI_GAMECTRL_CAMEO_TEXT) Screen.Queue(UIIntent{UI_GAMECTRL_CAMEO_TEXT, "", Screen.CameoText ? 0 : 1}); + else if (which == UI_GAMECTRL_ACTION_LINES) Screen.Queue(UIIntent{UI_GAMECTRL_ACTION_LINES, "", Screen.ActionLines ? 0 : 1}); + else if (which == UI_GAMECTRL_TOOLTIPS) Screen.Queue(UIIntent{UI_GAMECTRL_TOOLTIPS, "", Screen.ShowToolTips ? 0 : 1}); + else if (which == UI_GAMECTRL_COASTING) Screen.Queue(UIIntent{UI_GAMECTRL_COASTING, "", Screen.Coasting ? 0 : 1}); + else if (which == UI_GAMECTRL_EDGE_SCROLL) Screen.Queue(UIIntent{UI_GAMECTRL_EDGE_SCROLL, "", Screen.EdgeScroll ? 0 : 1}); + }); + + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{arguments[0].Get(), "", 0}); + }); + + // Escape cancels, which is the IDCANCEL the dialog's own cancel arm took. Enter accepts, + // because the template names no default push button and Windows then sent IDOK. + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key == Rml::Input::KI_ESCAPE) { + Screen.Queue(UIIntent{UI_GAMECTRL_CANCEL, "", 0}); + } else if (key == Rml::Input::KI_RETURN || key == Rml::Input::KI_NUMPADENTER) { + Screen.Queue(UIIntent{UI_GAMECTRL_ACCEPT, "", 0}); + } + }); +} + + +void GameControlsViewClass::Sync(void) +{ + if (!Model) return; + + Update_Labels(); + + // The slider positions are not dirtied, because a slider already carries the position + // its own change event reported. + Model.DirtyVariable("speedtext"); + Model.DirtyVariable("scrolltext"); + Model.DirtyVariable("detailtext"); + Model.DirtyVariable("difficultytext"); + Model.DirtyVariable("cameotext"); + Model.DirtyVariable("actionlines"); + Model.DirtyVariable("tooltips"); + Model.DirtyVariable("coasting"); + Model.DirtyVariable("edgescroll"); +} + + +/// +/// Shows the game controls and waits for the player to leave them. +/// +UIResult UI_Game_Controls_Screen(UIGameControlsPresenterClass & presenter) +{ + char const * document = "gamecontrols.rml"; + if (presenter.Variant == UIGameControlsPresenterClass::VARIANT_SESSION) { + document = "gamecontrolsmp.rml"; + } else if (presenter.Variant == UIGameControlsPresenterClass::VARIANT_INTERNET) { + document = "gamecontrolswol.rml"; + } + + GameControlsViewClass view(presenter, document); + + if (!view.Prepare(true)) { + UIResult result; + result.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + return(result); + } + + view.Settle(); + + UIResult const result = UI_Run_Modal(presenter, view); + view.Close(); + return(result); +} diff --git a/code/ui/uigamecontrols.h b/code/ui/uigamecontrols.h index 43dfd44c3..5d6077748 100644 --- a/code/ui/uigamecontrols.h +++ b/code/ui/uigamecontrols.h @@ -108,3 +108,8 @@ class UIGameControlsPresenterClass : public UIPresenterClass ChoiceType Choice = CHOICE_NONE; }; + + +// Shows the screen through its RmlUi view. FAILED_TO_OPEN leaves nothing shown and the +// caller falls through to the legacy dialog. +UIResult UI_Game_Controls_Screen(UIGameControlsPresenterClass & presenter); diff --git a/ui/gamecontrols.rcss b/ui/gamecontrols.rcss new file mode 100644 index 000000000..faffa6bb3 --- /dev/null +++ b/ui/gamecontrols.rcss @@ -0,0 +1,80 @@ +/* The game controls shown with no game running. Geometry from the IDD_OPT_CTRL_GAME_SP + template, whose name says single player but which is the screen the front end shows, + converted from dialog units at the 8 point MS Sans Serif the template names: 1.5 pixels + across and 1.625 down. */ + +/* 292 x 179 dialog units, so 438 x 290.875 pixels, centred on the frame. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -219dp; + margin-top: -145.4375dp; + + width: 434dp; + height: 286.875dp; +} + +/* Four track bars, 148 x 13 dialog units at x 80. */ +.slider +{ + left: 118dp; + width: 222dp; + height: 21.125dp; +} + +.slider slidertrack { margin-top: 7.5dp; } + +#speed { top: 17.5dp; } +#scroll { top: 53.25dp; } +#detail { top: 89dp; } +#difficulty { top: 124.75dp; } + +/* The left captions are LTEXT with SS_CENTERIMAGE, 58 x 13 at x 22; the value captions are + RTEXT, 45 x 13 at x 229. */ +.caption +{ + height: 21.125dp; + line-height: 21.125dp; +} + +#speedlabel, #scrolllabel, #detaillabel, #difficultylabel +{ + left: 31dp; + width: 87dp; +} + +.value +{ + left: 341.5dp; + width: 67.5dp; + text-align: right; +} + +#speedlabel, #speedvalue { top: 17.5dp; } +#scrolllabel, #scrollvalue { top: 53.25dp; } +#detaillabel, #detailvalue { top: 89dp; } +#difficultylabel, #difficultyvalue { top: 124.75dp; } + +/* Five BS_FLAT check boxes in two columns. */ +.check +{ + height: 16.25dp; + line-height: 16.25dp; +} + +#cameotext { left: 31dp; top: 165.375dp; width: 186dp; } +#actionlines { left: 31dp; top: 191.375dp; width: 186dp; } +#edgescroll { left: 31dp; top: 217.375dp; width: 186dp; } +#tooltips { left: 217dp; top: 165.375dp; width: 192dp; } +#coasting { left: 217dp; top: 191.375dp; width: 192dp; } + +/* The one button, 130 x 14 dialog units at 81, 153. */ +#mainmenu +{ + left: 119.5dp; + top: 246.625dp; + width: 195dp; + height: 22.75dp; + line-height: 22.75dp; +} diff --git a/ui/gamecontrols.rml b/ui/gamecontrols.rml new file mode 100644 index 000000000..3ad32b20c --- /dev/null +++ b/ui/gamecontrols.rml @@ -0,0 +1,34 @@ + + + Game controls + + + + +
+
Game Speed
+ +
{{ speedtext }}
+ +
Scroll Rate
+ +
{{ scrolltext }}
+ +
Visual Details
+ +
{{ detailtext }}
+ +
Difficulty
+ +
{{ difficultytext }}
+ +
Cameo Text
+
Target Lines
+
Tooltips
+
Scroll Coasting
+
Edge Scrolling
+ + +
+ +
diff --git a/ui/gamecontrolsmp.rcss b/ui/gamecontrolsmp.rcss new file mode 100644 index 000000000..c37fe478a --- /dev/null +++ b/ui/gamecontrolsmp.rcss @@ -0,0 +1,79 @@ +/* The game controls shown during a local game. Geometry from the IDD_OPT_CTRL_GAME_MP + template, converted at 1.5 pixels across and 1.625 down. */ + +/* 292 x 175 dialog units, so 438 x 284.375 pixels, centred on the frame. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -219dp; + margin-top: -142.1875dp; + + width: 434dp; + height: 280.375dp; +} + +/* Three track bars, 128 x 15 dialog units at x 90. */ +.slider +{ + left: 133dp; + width: 192dp; + height: 24.375dp; +} + +.slider slidertrack { margin-top: 9.2dp; } + +#speed { top: 17.5dp; } +#scroll { top: 67.875dp; } +#detail { top: 118.25dp; } + +/* The left captions are RTEXT, 63 x 15 at x 22; the value captions are LTEXT, 50 x 15 at + x 224. Both carry SS_CENTERIMAGE. */ +.caption +{ + height: 24.375dp; + line-height: 24.375dp; +} + +#speedlabel, #scrolllabel, #detaillabel +{ + left: 31dp; + width: 94.5dp; + text-align: right; +} + +.value +{ + left: 334dp; + width: 75dp; +} + +#speedlabel, #speedvalue { top: 17.5dp; } +#scrolllabel, #scrollvalue { top: 67.875dp; } +#detaillabel, #detailvalue { top: 118.25dp; } + +/* Five BS_FLAT check boxes in two columns. */ +.check +{ + height: 16.25dp; + line-height: 16.25dp; +} + +#cameotext { left: 31dp; top: 150.75dp; width: 178.5dp; } +#actionlines { left: 31dp; top: 180dp; width: 178.5dp; } +#edgescroll { left: 31dp; top: 209.25dp; width: 178.5dp; } +#tooltips { left: 218.5dp; top: 150.75dp; width: 190.5dp; } +#coasting { left: 218.5dp; top: 180dp; width: 190.5dp; } + +/* Three buttons, all 77 x 14 dialog units on the same row. */ +.button +{ + top: 240.125dp; + width: 115.5dp; + height: 22.75dp; + line-height: 22.75dp; +} + +#sound { left: 31dp; } +#keyboard { left: 161.5dp; } +#accept { left: 292dp; } diff --git a/ui/gamecontrolsmp.rml b/ui/gamecontrolsmp.rml new file mode 100644 index 000000000..e4d2f9216 --- /dev/null +++ b/ui/gamecontrolsmp.rml @@ -0,0 +1,32 @@ + + + Game controls + + + + +
+
Game Speed:
+ +
{{ speedtext }}
+ +
Scroll Rate:
+ +
{{ scrolltext }}
+ +
Visual Details:
+ +
{{ detailtext }}
+ +
Sidebar Text
+
Target Lines
+
Tooltips
+
Scroll Coasting
+
Edge Scrolling
+ +
Sound
+
Keyboard
+
[[TXT_OPTIONS_MENU]]
+
+ +
diff --git a/ui/gamecontrolswol.rcss b/ui/gamecontrolswol.rcss new file mode 100644 index 000000000..7d10bc4d0 --- /dev/null +++ b/ui/gamecontrolswol.rcss @@ -0,0 +1,79 @@ +/* The game controls shown during an internet game. Geometry from the IDD_OPT_CTRL_GAME_WOL + template, converted at 1.5 pixels across and 1.625 down. The template carries no game + speed slider, because a speed change in an internet session is an event every player has + to agree on rather than a setting one of them holds. */ + +/* 294 x 144 dialog units, so 441 x 234 pixels, centred on the frame. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -220.5dp; + margin-top: -117dp; + + width: 437dp; + height: 230dp; +} + +/* Two track bars, 128 x 15 dialog units at x 90. */ +.slider +{ + left: 133dp; + width: 192dp; + height: 24.375dp; +} + +.slider slidertrack { margin-top: 9.2dp; } + +#scroll { top: 17.5dp; } +#detail { top: 67.875dp; } + +/* The left captions are RTEXT, 63 x 15 at x 22; the value captions are LTEXT, 50 x 15 at + x 226. Both carry SS_CENTERIMAGE. */ +.caption +{ + height: 24.375dp; + line-height: 24.375dp; +} + +#scrolllabel, #detaillabel +{ + left: 31dp; + width: 94.5dp; + text-align: right; +} + +.value +{ + left: 337dp; + width: 75dp; +} + +#scrolllabel, #scrollvalue { top: 17.5dp; } +#detaillabel, #detailvalue { top: 67.875dp; } + +/* Five BS_FLAT check boxes in two columns. */ +.check +{ + height: 16.25dp; + line-height: 16.25dp; +} + +#cameotext { left: 31dp; top: 100.375dp; width: 178.5dp; } +#actionlines { left: 31dp; top: 129.625dp; width: 178.5dp; } +#edgescroll { left: 31dp; top: 158.875dp; width: 178.5dp; } +#tooltips { left: 218.5dp; top: 100.375dp; width: 193.5dp; } +#coasting { left: 218.5dp; top: 129.625dp; width: 193.5dp; } + +/* Three buttons, all 77 x 14 dialog units on the same row. */ +.button +{ + top: 189.75dp; + width: 115.5dp; + height: 22.75dp; + line-height: 22.75dp; +} + +#sound { left: 31dp; } +#keyboard { left: 161.5dp; } +#accept { left: 292dp; } diff --git a/ui/gamecontrolswol.rml b/ui/gamecontrolswol.rml new file mode 100644 index 000000000..dc96ded61 --- /dev/null +++ b/ui/gamecontrolswol.rml @@ -0,0 +1,28 @@ + + + Game controls + + + + +
+
Scroll Rate:
+ +
{{ scrolltext }}
+ +
Visual Details:
+ +
{{ detailtext }}
+ +
Sidebar Text
+
Target Lines
+
Tooltips
+
Scroll Coasting
+
Edge Scrolling
+ +
Sound
+
Keyboard
+
[[TXT_OPTIONS_MENU]]
+
+ +
From 091295b406bfb3b5d57a37510e66d57e4a2e7620 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 03:55:55 +0100 Subject: [PATCH 103/179] feat(ui): show the main options, display and mode trial through RmlUi Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/mainopt.cpp | 109 ++++++++++++++++++++++++++--------- code/ui/uidisplayconfirm.cpp | 88 ++++++++++++++++++++++++++++ code/ui/uidisplayconfirm.h | 5 ++ code/ui/uidisplayoptions.cpp | 100 ++++++++++++++++++++++++++++++++ code/ui/uidisplayoptions.h | 5 ++ code/ui/uimainoptions.cpp | 72 +++++++++++++++++++++++ code/ui/uimainoptions.h | 5 ++ ui/display.rcss | 61 ++++++++++++++++++++ ui/display.rml | 19 ++++++ ui/modeconfirm.rcss | 42 ++++++++++++++ ui/modeconfirm.rml | 14 +++++ ui/options.rcss | 35 +++++++++++ ui/options.rml | 16 +++++ 13 files changed, 543 insertions(+), 28 deletions(-) create mode 100644 ui/display.rcss create mode 100644 ui/display.rml create mode 100644 ui/modeconfirm.rcss create mode 100644 ui/modeconfirm.rml create mode 100644 ui/options.rcss create mode 100644 ui/options.rml diff --git a/code/mainopt.cpp b/code/mainopt.cpp index f248f9272..1acafc140 100644 --- a/code/mainopt.cpp +++ b/code/mainopt.cpp @@ -38,6 +38,7 @@ #include "ui/uidisplayconfirm.h" #include "ui/uidisplayoptions.h" #include "ui/uimainoptions.h" +#include "ui/uishell.h" #include "color.hh" @@ -81,9 +82,27 @@ void Main_Options_Dialog(void) _MainScreen = &screen; while (true) { + // The screen is opened again on each pass round the family, so what the last close + // left behind is cleared first. screen.Result.reset(); + screen.IsClosing = false; screen.Choice = UIMainOptionsPresenterClass::CHOICE_NONE; + // The selection is latched here, at screen entry, and the legacy dialog opens only + // when the document could not be prepared. + if (UI_Use_Rml()) { + UIResult const result = UI_Main_Options_Screen(screen); + if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { + if (screen.Exits()) { + break; + } + screen.Run_Pending(); + continue; + } + screen.IsClosing = false; + screen.Result.reset(); + } + HWND main_handle; do { main_handle = OwnerDraw::Begin_Dialog(IDD_OPT_MAIN, Main_Options_Dialog_Proc); @@ -136,30 +155,45 @@ void Display_Options_Dialog(void) UIDisplayOptionsPresenterClass screen; screen.Refresh(); - _DisplayScreen = &screen; + // The selection is latched here, at screen entry, and the legacy dialog opens only + // when the document could not be prepared. + bool shown = false; + if (UI_Use_Rml()) { + UIResult const result = UI_Display_Options_Screen(screen); + if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { + shown = true; + } else { + screen.IsClosing = false; + screen.Result.reset(); + } + } - HWND handle; - do { - handle = OwnerDraw::Begin_Dialog(IDD_OPT_DISPLAY, Display_Options_Dialog_Proc); - } while (handle == 0); - OwnerDraw::Display_Dialog(handle); + if (!shown) { + _DisplayScreen = &screen; + + HWND handle; + do { + handle = OwnerDraw::Begin_Dialog(IDD_OPT_DISPLAY, Display_Options_Dialog_Proc); + } while (handle == 0); + OwnerDraw::Display_Dialog(handle); + + while (!screen.Result.has_value()) { + if (OwnerDraw::Dialog_Message_Handler() == true) { + UIResult ended; + ended.Outcome = UIResult::OUTCOME_SESSION_ENDED; + ended.GameEnded = true; + screen.Result = ended; + break; + } - while (!screen.Result.has_value()) { - if (OwnerDraw::Dialog_Message_Handler() == true) { - UIResult ended; - ended.Outcome = UIResult::OUTCOME_SESSION_ENDED; - ended.GameEnded = true; - screen.Result = ended; - break; + screen.Drain(); + screen.Service(); } - screen.Drain(); - screen.Service(); + OwnerDraw::End_Dialog(handle); + _DisplayScreen = NULL; } - OwnerDraw::End_Dialog(handle); - _DisplayScreen = NULL; - if (screen.Choice != UIDisplayOptionsPresenterClass::CHOICE_ACCEPT) { break; } @@ -366,6 +400,22 @@ bool Change_Display_Mode(int width, int height) } +// Leaves the tried mode in place or puts the old one back, whichever view answered. +static bool Keep_Or_Reset_Display_Mode(int width, int height, bool accepted) +{ + if (!accepted) { + DebugString("Resetting display mode @ %dx%d\n", Options.ScreenWidth, Options.ScreenHeight); + Change_Display_Mode(Options.ScreenWidth, Options.ScreenHeight); + LogicalSurface = HiddenSurface; + return(false); + } + + DebugString("Keeping display mode @ %dx%d\n", width, height); + LogicalSurface = HiddenSurface; + return(true); +} + + /// /// Tries a display mode out and asks the player to confirm it. /// This routine switches to the requested mode and puts up a confirmation dialog. If the @@ -394,6 +444,18 @@ bool Test_Display_Mode_Dialog(int width, int height) UIDisplayConfirmPresenterClass screen; screen.Refresh(); + // The selection is latched here, at screen entry, and the legacy dialog opens only when + // the document could not be prepared. + if (UI_Use_Rml()) { + UIResult const result = UI_Display_Confirm_Screen(screen); + if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { + return(Keep_Or_Reset_Display_Mode(width, height, + screen.Choice == UIDisplayConfirmPresenterClass::CHOICE_ACCEPT)); + } + screen.IsClosing = false; + screen.Result.reset(); + } + _ConfirmScreen = &screen; bool accepted = true; @@ -418,16 +480,7 @@ bool Test_Display_Mode_Dialog(int width, int height) _ConfirmScreen = NULL; - if (!accepted) { - DebugString("Resetting display mode @ %dx%d\n", Options.ScreenWidth, Options.ScreenHeight); - Change_Display_Mode(Options.ScreenWidth, Options.ScreenHeight); - LogicalSurface = HiddenSurface; - return(false); - } - - DebugString("Keeping display mode @ %dx%d\n", width, height); - LogicalSurface = HiddenSurface; - return(true); + return(Keep_Or_Reset_Display_Mode(width, height, accepted)); } diff --git a/code/ui/uidisplayconfirm.cpp b/code/ui/uidisplayconfirm.cpp index eb9ba3cdb..2bee96c78 100644 --- a/code/ui/uidisplayconfirm.cpp +++ b/code/ui/uidisplayconfirm.cpp @@ -23,6 +23,12 @@ #include "uidisplayconfirm.h" +#include "uirmlview.h" + +#include +#include +#include + void UIDisplayConfirmPresenterClass::Refresh(void) { @@ -78,3 +84,85 @@ void UIDisplayConfirmPresenterClass::Execute(UIIntent const & intent) Result = result; } + + +//--------------------------------------------------------------------------------------- +// The RmlUi view. +//--------------------------------------------------------------------------------------- + +/// +/// The RmlUi half of the display mode confirmation. +/// +class DisplayConfirmViewClass : public UIRmlViewClass +{ + public: + DisplayConfirmViewClass(UIDisplayConfirmPresenterClass & presenter) : + UIRmlViewClass(presenter, "modeconfirm.rml"), + Screen(presenter) + { + } + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + private: + UIDisplayConfirmPresenterClass & Screen; + int Remaining = 0; +}; + + +void DisplayConfirmViewClass::Bind(Rml::DataModelConstructor & model) +{ + Remaining = Screen.Seconds_Remaining(); + model.Bind("seconds", &Remaining); + + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{arguments[0].Get(), "", 0}); + }); + + // Escape refuses the mode, as does saying nothing. Enter keeps it, because the template + // names no default push button and Windows then sent the dialog IDOK, which its + // procedure recorded and its driver compared against IDOK. + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key == Rml::Input::KI_ESCAPE) { + Screen.Queue(UIIntent{UI_MODECONFIRM_CANCEL, "", 0}); + } else if (key == Rml::Input::KI_RETURN || key == Rml::Input::KI_NUMPADENTER) { + Screen.Queue(UIIntent{UI_MODECONFIRM_ACCEPT, "", 0}); + } + }); +} + + +void DisplayConfirmViewClass::Sync(void) +{ + if (!Model) return; + + int const remaining = Screen.Seconds_Remaining(); + if (remaining != Remaining) { + Remaining = remaining; + Model.DirtyVariable("seconds"); + } +} + + +/// +/// Shows the mode confirmation and waits for the player, or for the timeout. +/// +UIResult UI_Display_Confirm_Screen(UIDisplayConfirmPresenterClass & presenter) +{ + DisplayConfirmViewClass view(presenter); + + if (!view.Prepare(true)) { + UIResult result; + result.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + return(result); + } + + UIResult const result = UI_Run_Modal(presenter, view); + view.Close(); + return(result); +} diff --git a/code/ui/uidisplayconfirm.h b/code/ui/uidisplayconfirm.h index 80f95b375..d0cd12818 100644 --- a/code/ui/uidisplayconfirm.h +++ b/code/ui/uidisplayconfirm.h @@ -56,3 +56,8 @@ class UIDisplayConfirmPresenterClass : public UIPresenterClass private: CDTimerClass Timer; }; + + +// Shows the screen through its RmlUi view. FAILED_TO_OPEN leaves nothing shown and the +// caller falls through to the legacy dialog. +UIResult UI_Display_Confirm_Screen(UIDisplayConfirmPresenterClass & presenter); diff --git a/code/ui/uidisplayoptions.cpp b/code/ui/uidisplayoptions.cpp index 5650dfb76..2a9e19893 100644 --- a/code/ui/uidisplayoptions.cpp +++ b/code/ui/uidisplayoptions.cpp @@ -25,6 +25,8 @@ #include "uidisplayoptions.h" +#include "uirmlview.h" + #include "globals.h" #include "init.h" #include "goptions.h" @@ -33,6 +35,10 @@ #include +#include +#include +#include + void UIDisplayOptionsPresenterClass::Refresh(void) { @@ -128,3 +134,97 @@ void UIDisplayOptionsPresenterClass::Execute(UIIntent const & intent) Result = result; } + + +//--------------------------------------------------------------------------------------- +// The RmlUi view. +//--------------------------------------------------------------------------------------- + +/// +/// The RmlUi half of the display options screen. +/// +class DisplayOptionsViewClass : public UIRmlViewClass +{ + public: + DisplayOptionsViewClass(UIDisplayOptionsPresenterClass & presenter) : + UIRmlViewClass(presenter, "display.rml"), + Screen(presenter) + { + } + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + private: + UIDisplayOptionsPresenterClass & Screen; +}; + + +void DisplayOptionsViewClass::Bind(Rml::DataModelConstructor & model) +{ + if (auto mode = model.RegisterStruct()) { + mode.RegisterMember("label", &UIDisplayOptionsPresenterClass::ModeType::Label); + } + model.RegisterArray>(); + + model.Bind("modes", &Screen.Modes); + model.Bind("selected", &Screen.Selected); + model.Bind("stretch", &Screen.StretchMovies); + + model.BindEventCallback("pick", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{UI_DISPLAY_SELECT, "", (int)arguments[0].Get()}); + }); + + model.BindEventCallback("toggle", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const &) { + Screen.Queue(UIIntent{UI_DISPLAY_STRETCH, "", Screen.StretchMovies ? 0 : 1}); + }); + + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{arguments[0].Get(), "", 0}); + }); + + // Escape cancels and Enter accepts, which is what IsDialogMessage delivered to a dialog + // whose template names no default push button. + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key == Rml::Input::KI_ESCAPE) { + Screen.Queue(UIIntent{UI_DISPLAY_CANCEL, "", 0}); + } else if (key == Rml::Input::KI_RETURN || key == Rml::Input::KI_NUMPADENTER) { + Screen.Queue(UIIntent{UI_DISPLAY_ACCEPT, "", 0}); + } + }); +} + + +void DisplayOptionsViewClass::Sync(void) +{ + if (!Model) return; + + Model.DirtyVariable("selected"); + Model.DirtyVariable("stretch"); +} + + +/// +/// Shows the display options and waits for the player to leave them. +/// +UIResult UI_Display_Options_Screen(UIDisplayOptionsPresenterClass & presenter) +{ + DisplayOptionsViewClass view(presenter); + + if (!view.Prepare(true)) { + UIResult result; + result.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + return(result); + } + + UIResult const result = UI_Run_Modal(presenter, view); + view.Close(); + return(result); +} diff --git a/code/ui/uidisplayoptions.h b/code/ui/uidisplayoptions.h index 486bf4e6e..676f3d617 100644 --- a/code/ui/uidisplayoptions.h +++ b/code/ui/uidisplayoptions.h @@ -81,3 +81,8 @@ class UIDisplayOptionsPresenterClass : public UIPresenterClass private: int Opened = -1; }; + + +// Shows the screen through its RmlUi view. FAILED_TO_OPEN leaves nothing shown and the +// caller falls through to the legacy dialog. +UIResult UI_Display_Options_Screen(UIDisplayOptionsPresenterClass & presenter); diff --git a/code/ui/uimainoptions.cpp b/code/ui/uimainoptions.cpp index 157bffffa..d05c70b30 100644 --- a/code/ui/uimainoptions.cpp +++ b/code/ui/uimainoptions.cpp @@ -22,6 +22,8 @@ #include "uimainoptions.h" +#include "uirmlview.h" + #include "audio/audioengine.h" #include "gamedlg.h" #include "globals.h" @@ -31,6 +33,10 @@ #include "options.h" #include "sounddlg.h" +#include +#include +#include + void UIMainOptionsPresenterClass::Refresh(void) { @@ -118,3 +124,69 @@ void UIMainOptionsPresenterClass::Run_Pending(void) break; } } + + +//--------------------------------------------------------------------------------------- +// The RmlUi view. +//--------------------------------------------------------------------------------------- + +/// +/// The RmlUi half of the main options screen. +/// +class MainOptionsViewClass : public UIRmlViewClass +{ + public: + MainOptionsViewClass(UIMainOptionsPresenterClass & presenter) : + UIRmlViewClass(presenter, "options.rml"), + Screen(presenter) + { + } + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override {} + + private: + UIMainOptionsPresenterClass & Screen; +}; + + +void MainOptionsViewClass::Bind(Rml::DataModelConstructor & model) +{ + model.Bind("available", &Screen.SoundAvailable); + + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{arguments[0].Get(), "", 0}); + }); + + // Escape leaves, which is the IDCANCEL the dialog's default arm took as an exit. Enter + // leaves too, because the template names no default push button and Windows then sent + // the dialog IDOK, which that same arm did not recognize either. + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key == Rml::Input::KI_ESCAPE || key == Rml::Input::KI_RETURN || key == Rml::Input::KI_NUMPADENTER) { + Screen.Queue(UIIntent{UI_MAINOPT_EXIT, "", 0}); + } + }); +} + + +/// +/// Shows the main options menu and waits for the player to choose. +/// +UIResult UI_Main_Options_Screen(UIMainOptionsPresenterClass & presenter) +{ + MainOptionsViewClass view(presenter); + + if (!view.Prepare(true)) { + UIResult result; + result.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + return(result); + } + + UIResult const result = UI_Run_Modal(presenter, view); + view.Close(); + return(result); +} diff --git a/code/ui/uimainoptions.h b/code/ui/uimainoptions.h index 8109e5dc9..cc2bafc30 100644 --- a/code/ui/uimainoptions.h +++ b/code/ui/uimainoptions.h @@ -71,3 +71,8 @@ class UIMainOptionsPresenterClass : public UIPresenterClass private: bool WasGameActive = false; }; + + +// Shows the screen through its RmlUi view. FAILED_TO_OPEN leaves nothing shown and the +// caller falls through to the legacy dialog. +UIResult UI_Main_Options_Screen(UIMainOptionsPresenterClass & presenter); diff --git a/ui/display.rcss b/ui/display.rcss new file mode 100644 index 000000000..6a95b0266 --- /dev/null +++ b/ui/display.rcss @@ -0,0 +1,61 @@ +/* The display options. Geometry from the IDD_OPT_DISPLAY template, converted from dialog + units at the 8 point MS Sans Serif the template names: 1.5 pixels across and 1.625 down, + with a child's offset taken from the panel's content box and the panel's declared size + taken inside its own border. */ + +/* 229 x 196 dialog units, so 343.5 x 318.5 pixels, centred on the frame. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -171.75dp; + margin-top: -159.25dp; + + width: 339.5dp; + height: 314.5dp; +} + +/* Two CTEXT captions with SS_CENTERIMAGE, 185 dialog units wide at x 22. */ +.caption +{ + left: 31dp; + width: 277.5dp; + text-align: center; +} + +#title { top: 17.5dp; height: 14.625dp; line-height: 14.625dp; } +#reslabel { top: 38.625dp; height: 16.25dp; line-height: 16.25dp; } + +/* The resolution list, 185 x 110 dialog units at 22, 37. */ +#reslist +{ + left: 31dp; + top: 58.125dp; + width: 277.5dp; + height: 178.75dp; +} + +/* A row spans the list less its scrollbar. */ +#reslist .row { width: 265.5dp; } + +/* The movie stretching check box, 185 x 10 dialog units at 22, 153. */ +#stretch +{ + left: 31dp; + top: 246.625dp; + width: 277.5dp; + height: 16.25dp; + line-height: 16.25dp; +} + +/* OK and Cancel, both 62 x 14 dialog units on the same row. */ +.button +{ + top: 274.25dp; + width: 93dp; + height: 22.75dp; + line-height: 22.75dp; +} + +#ok { left: 31dp; } +#cancel { left: 215.5dp; } diff --git a/ui/display.rml b/ui/display.rml new file mode 100644 index 000000000..c3e213ed0 --- /dev/null +++ b/ui/display.rml @@ -0,0 +1,19 @@ + + + Display options + + + + +
+
Display Options:
+
Resolution Modes
+
+
{{ mode.label }}
+
+
Stretch movies to fit resolution
+
[[TXT_OK]]
+
[[TXT_CANCEL]]
+
+ +
diff --git a/ui/modeconfirm.rcss b/ui/modeconfirm.rcss new file mode 100644 index 000000000..66caf66d1 --- /dev/null +++ b/ui/modeconfirm.rcss @@ -0,0 +1,42 @@ +/* The display mode confirmation. Geometry from the IDD_OPT_CONFIRM_MODE template, converted + at 1.5 pixels across and 1.625 down. + + Nothing here counts the timeout down. The screen takes the mode back on its own after ten + seconds, because the mode being confirmed may have left this document unreadable, and a + countdown a player cannot see is not what decides it. */ + +/* 239 x 70 dialog units, so 358.5 x 113.75 pixels, centred on the frame. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -179.25dp; + margin-top: -56.875dp; + + width: 354.5dp; + height: 109.75dp; +} + +/* LTEXT with no SS_CENTERIMAGE, 195 x 26 dialog units at 22, 12: left aligned, top aligned + and wrapping, which is how the sentence filled its two lines. */ +#text +{ + left: 31dp; + top: 17.5dp; + width: 292.5dp; + height: 42.25dp; + white-space: normal; + line-height: 16dp; +} + +/* OK and Cancel, both 50 x 14 dialog units on the same row. */ +.button +{ + top: 69.5dp; + width: 75dp; + height: 22.75dp; + line-height: 22.75dp; +} + +#ok { left: 164.5dp; } +#cancel { left: 248.5dp; } diff --git a/ui/modeconfirm.rml b/ui/modeconfirm.rml new file mode 100644 index 000000000..3c2711f23 --- /dev/null +++ b/ui/modeconfirm.rml @@ -0,0 +1,14 @@ + + + Confirm display mode + + + + +
+
Click OK to keep this display mode or wait and your old display settings will be restored.
+
[[TXT_OK]]
+
[[TXT_CANCEL]]
+
+ +
diff --git a/ui/options.rcss b/ui/options.rcss new file mode 100644 index 000000000..a9b056a10 --- /dev/null +++ b/ui/options.rcss @@ -0,0 +1,35 @@ +/* The main options menu. Geometry from the IDD_OPT_MAIN template, converted from dialog + units at the 8 point MS Sans Serif the template names: 1.5 pixels across and 1.625 down, + with a child's offset taken from the panel's content box and the panel's declared size + taken inside its own border. */ + +/* 200 x 148 dialog units, so 300 x 240.5 pixels. + + The panel is not centred vertically. Its driver moved it with + Move_Dialog(handle, -1, (HiddenSurface->Get_Height() - 400) / 2 + 147), which is half the + frame height less 53, so it sits 53 dp above the middle whatever the frame height is. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -150dp; + margin-top: -53dp; + + width: 296dp; + height: 236.5dp; +} + +/* Five buttons, all 126 x 18 dialog units at x 37. */ +.button +{ + left: 53.5dp; + width: 189dp; + height: 29.25dp; + line-height: 29.25dp; +} + +#settings { top: 14.25dp; } +#display { top: 50dp; } +#sound { top: 85.75dp; } +#keyboard { top: 121.5dp; } +#exit { top: 193dp; } diff --git a/ui/options.rml b/ui/options.rml new file mode 100644 index 000000000..d3dbb77c0 --- /dev/null +++ b/ui/options.rml @@ -0,0 +1,16 @@ + + + Options + + + + +
+
Game Settings
+
Display
+
Sound
+
Keyboard
+
Main Menu
+
+ +
From c916aad758209b4b20cbbcfbc1a38697a144c596 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 03:56:21 +0100 Subject: [PATCH 104/179] docs: record the options family's screens and views --- docs/UI_DESIGN.md | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index 55c875f10..07b3e77ce 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -1,7 +1,7 @@ # UI system design -Status: in progress. Steps 1 to 6 of the migration plan have landed; nothing -from step 7 onward is implemented. Everything outside the migration plan +Status: in progress. Steps 1 to 6 of the migration plan have landed and step 7 +is most of the way through; nothing from step 8 onward is implemented. Everything outside the migration plan remains a proposal informed by source inspection and upstream documentation. This page owns the UI architecture and migration; [Building OpenTS](BUILDING.md) owns build support and [Project @@ -65,6 +65,14 @@ which the progress box opening over the wait box shows; only a second screen of the same kind is refused, and the coexistence rule still forbids a legacy dialog underneath either. +Step 7 gave a view the ability to step aside. `UIRmlViewClass` gained `Hide` +and `Show`, which take a document off the screen and put it back with the modal +scope it had, because the in-game options screen opens the save and load +browsers where it is drawn and the legacy dialog got out of their way with +`ShowWindow`. A screen family whose members are opened one after another +resets `IsClosing` and the held result before each pass, since a close marks +the presenter closing and a marked presenter drains nothing. + ## Where the UI stands today OpenTS has four UI systems plus a few bespoke screens. They share the software @@ -805,13 +813,28 @@ text beyond an ASCII test document. 7. **Options family** (L, two changes each). Main options, display with its timed rollback, game controls (three variants), keyboard with the hotkey capture control, the display-mode confirmation, abort and surrender. - Evidence: settings round-trip through `SUN.INI` unchanged. Started: the - first change of three of them has landed, classified preserved, with the - legacy view still selected --- `code/ui/uigameoptions.{h,cpp}` for the - in-game options screen, `code/ui/uiabort.{h,cpp}` for abort and surrender, - and `code/ui/uigamecontrols.{h,cpp}` for the game controls. Main options, - display with its rollback, the display-mode confirmation and the keyboard - screen are not extracted, and no RmlUi view exists for any of them. + Evidence: settings round-trip through `SUN.INI` unchanged. Every screen in + the family is extracted: `code/ui/uigameoptions.{h,cpp}`, + `code/ui/uiabort.{h,cpp}`, `code/ui/uigamecontrols.{h,cpp}`, + `code/ui/uimainoptions.{h,cpp}`, `code/ui/uidisplayoptions.{h,cpp}`, + `code/ui/uidisplayconfirm.{h,cpp}` and `code/ui/uikeyboard.{h,cpp}`. Six of + the seven have their RmlUi view: `ui/options.rml`, `ui/gameoptions.rml` with + its `mp` and `wol` variants, `ui/abort.rml`, `ui/gamecontrols.rml` with its + `mp` and `wol` variants, `ui/display.rml` and `ui/modeconfirm.rml`, sharing + the family's look through `ui/optionsbase.rcss` and each carrying its own + geometry. The keyboard screen still has only its legacy view. + + The mode trial's timeout is the presenter's, not a view's: the driver + expressed it as a posted `WM_COMMAND` carrying `WM_DESTROY`, which is two, + which its procedure recorded because `IDCANCEL` is also two, so the timeout + was a cancel spelled awkwardly. `UIDisplayConfirmPresenterClass::Service` + counts a `CDTimerClass` down from ten seconds and produces + the cancel itself, which is what takes an unreadable mode back. + + The templates carry the button captions and the string table does not, so a + document repeats the template's caption where no `TXT_` name exists. That + leaves those captions untranslated until names are added to `language.rc`, + which is where the strings are owned. 8. **Main menu family** (M). `IDD_MAIN_MENU`, campaign choice, game type, multiplayer game selection. The `NewMenuClass` drivers keep their loops. 9. **Load, save, delete** (M, two changes). From 1250baa182d3011d997bc8ca24a3e26be10d684b Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 04:08:06 +0100 Subject: [PATCH 105/179] fix(ui): map the whole keyboard between Win32 and RmlUi Key_Identifier named letters, digits, F1 to F12 and eighteen more keys and answered KI_UNKNOWN for everything else, and an unknown key is never handed to RmlUi at all, so a document could only ever act on part of the keyboard. The table is now complete and is read in both directions, because a screen that records a keypress has to turn RmlUi's identifier back into the virtual key the game's own encoding is built from. Where two virtual keys share an identifier or two identifiers share a virtual key the first entry wins, so the inverse yields the code this engine's keyboard queue is written against: keyboard.h has no name for 0xA0 to 0xA5 and platform/win32compat reports 0x10, 0x11 and 0x12 for both sides of a modifier. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/ui/uiinternal.h | 6 ++ code/ui/uishell.cpp | 198 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 182 insertions(+), 22 deletions(-) diff --git a/code/ui/uiinternal.h b/code/ui/uiinternal.h index 974276486..fddf99d9f 100644 --- a/code/ui/uiinternal.h +++ b/code/ui/uiinternal.h @@ -62,6 +62,12 @@ void UI_Surface_Element_Shutdown(void); // cannot afford to be skipped by. void UI_Paint_Now(bool immediate); +// Turns an identifier RmlUi reports back into the Win32 virtual key it came from. A screen +// that records a keypress needs it, because an RmlUi key event carries the identifier and +// the game's own key encoding is a virtual key with its modifier bits above it. Zero for an +// identifier no key produces. +int UI_Virtual_Key(int identifier); + // uisystem.cpp Rml::SystemInterface * UI_System_Interface(void); diff --git a/code/ui/uishell.cpp b/code/ui/uishell.cpp index d76c02635..f799ee517 100644 --- a/code/ui/uishell.cpp +++ b/code/ui/uishell.cpp @@ -73,45 +73,199 @@ static Rml::ElementDocument * _TestDocument = nullptr; #endif +// The keys RmlUi names that are not part of one of the four runs above. The pairing is +// read in both directions, so an entry's order matters where two virtual keys share an +// identifier or two identifiers share a virtual key: the first entry naming a virtual key +// is the identifier that key produces, and the first entry naming an identifier is the +// virtual key it converts back to. +// +// Where a modifier has both a general and a sided code the general one comes first, because +// that is what this engine's keyboard queue is written against: keyboard.h has no name for +// 0xA0 to 0xA5 at all, and platform/win32compat reports 0x10, 0x11 and 0x12 for both sides. +struct KeyPairType +{ + unsigned short VirtualKey; + Rml::Input::KeyIdentifier Identifier; +}; + +static KeyPairType const _KeyPairs[] = { + { 0x08, Rml::Input::KI_BACK }, + { 0x09, Rml::Input::KI_TAB }, + { 0x0C, Rml::Input::KI_CLEAR }, + { 0x0D, Rml::Input::KI_RETURN }, + { 0x10, Rml::Input::KI_LSHIFT }, + { 0x11, Rml::Input::KI_LCONTROL }, + { 0x12, Rml::Input::KI_LMENU }, + { 0x13, Rml::Input::KI_PAUSE }, + { 0x14, Rml::Input::KI_CAPITAL }, + { 0x15, Rml::Input::KI_KANA }, + { 0x15, Rml::Input::KI_HANGUL }, + { 0x17, Rml::Input::KI_JUNJA }, + { 0x18, Rml::Input::KI_FINAL }, + { 0x19, Rml::Input::KI_HANJA }, + { 0x19, Rml::Input::KI_KANJI }, + { 0x1B, Rml::Input::KI_ESCAPE }, + { 0x1C, Rml::Input::KI_CONVERT }, + { 0x1D, Rml::Input::KI_NONCONVERT }, + { 0x1E, Rml::Input::KI_ACCEPT }, + { 0x1F, Rml::Input::KI_MODECHANGE }, + { 0x20, Rml::Input::KI_SPACE }, + { 0x21, Rml::Input::KI_PRIOR }, + { 0x22, Rml::Input::KI_NEXT }, + { 0x23, Rml::Input::KI_END }, + { 0x24, Rml::Input::KI_HOME }, + { 0x25, Rml::Input::KI_LEFT }, + { 0x26, Rml::Input::KI_UP }, + { 0x27, Rml::Input::KI_RIGHT }, + { 0x28, Rml::Input::KI_DOWN }, + { 0x29, Rml::Input::KI_SELECT }, + { 0x2A, Rml::Input::KI_PRINT }, + { 0x2B, Rml::Input::KI_EXECUTE }, + { 0x2C, Rml::Input::KI_SNAPSHOT }, + { 0x2D, Rml::Input::KI_INSERT }, + { 0x2E, Rml::Input::KI_DELETE }, + { 0x2F, Rml::Input::KI_HELP }, + { 0x5B, Rml::Input::KI_LWIN }, + { 0x5C, Rml::Input::KI_RWIN }, + { 0x5D, Rml::Input::KI_APPS }, + { 0x5F, Rml::Input::KI_SLEEP }, + { 0x6A, Rml::Input::KI_MULTIPLY }, + { 0x6B, Rml::Input::KI_ADD }, + { 0x6C, Rml::Input::KI_SEPARATOR }, + { 0x6D, Rml::Input::KI_SUBTRACT }, + { 0x6E, Rml::Input::KI_DECIMAL }, + { 0x6F, Rml::Input::KI_DIVIDE }, + { 0x90, Rml::Input::KI_NUMLOCK }, + { 0x91, Rml::Input::KI_SCROLL }, + { 0x92, Rml::Input::KI_OEM_NEC_EQUAL }, + { 0x92, Rml::Input::KI_OEM_FJ_JISHO }, + { 0x93, Rml::Input::KI_OEM_FJ_MASSHOU }, + { 0x94, Rml::Input::KI_OEM_FJ_TOUROKU }, + { 0x95, Rml::Input::KI_OEM_FJ_LOYA }, + { 0x96, Rml::Input::KI_OEM_FJ_ROYA }, + { 0xA0, Rml::Input::KI_LSHIFT }, + { 0xA1, Rml::Input::KI_RSHIFT }, + { 0xA2, Rml::Input::KI_LCONTROL }, + { 0xA3, Rml::Input::KI_RCONTROL }, + { 0xA4, Rml::Input::KI_LMENU }, + { 0xA5, Rml::Input::KI_RMENU }, + { 0xA6, Rml::Input::KI_BROWSER_BACK }, + { 0xA7, Rml::Input::KI_BROWSER_FORWARD }, + { 0xA8, Rml::Input::KI_BROWSER_REFRESH }, + { 0xA9, Rml::Input::KI_BROWSER_STOP }, + { 0xAA, Rml::Input::KI_BROWSER_SEARCH }, + { 0xAB, Rml::Input::KI_BROWSER_FAVORITES }, + { 0xAC, Rml::Input::KI_BROWSER_HOME }, + { 0xAD, Rml::Input::KI_VOLUME_MUTE }, + { 0xAE, Rml::Input::KI_VOLUME_DOWN }, + { 0xAF, Rml::Input::KI_VOLUME_UP }, + { 0xB0, Rml::Input::KI_MEDIA_NEXT_TRACK }, + { 0xB1, Rml::Input::KI_MEDIA_PREV_TRACK }, + { 0xB2, Rml::Input::KI_MEDIA_STOP }, + { 0xB3, Rml::Input::KI_MEDIA_PLAY_PAUSE }, + { 0xB4, Rml::Input::KI_LAUNCH_MAIL }, + { 0xB5, Rml::Input::KI_LAUNCH_MEDIA_SELECT }, + { 0xB6, Rml::Input::KI_LAUNCH_APP1 }, + { 0xB7, Rml::Input::KI_LAUNCH_APP2 }, + { 0xBA, Rml::Input::KI_OEM_1 }, + { 0xBB, Rml::Input::KI_OEM_PLUS }, + { 0xBC, Rml::Input::KI_OEM_COMMA }, + { 0xBD, Rml::Input::KI_OEM_MINUS }, + { 0xBE, Rml::Input::KI_OEM_PERIOD }, + { 0xBF, Rml::Input::KI_OEM_2 }, + { 0xC0, Rml::Input::KI_OEM_3 }, + { 0xDB, Rml::Input::KI_OEM_4 }, + { 0xDC, Rml::Input::KI_OEM_5 }, + { 0xDD, Rml::Input::KI_OEM_6 }, + { 0xDE, Rml::Input::KI_OEM_7 }, + { 0xDF, Rml::Input::KI_OEM_8 }, + { 0xE1, Rml::Input::KI_OEM_AX }, + { 0xE2, Rml::Input::KI_OEM_102 }, + { 0xE3, Rml::Input::KI_ICO_HELP }, + { 0xE4, Rml::Input::KI_ICO_00 }, + { 0xE5, Rml::Input::KI_PROCESSKEY }, + { 0xE6, Rml::Input::KI_ICO_CLEAR }, + { 0xF6, Rml::Input::KI_ATTN }, + { 0xF7, Rml::Input::KI_CRSEL }, + { 0xF8, Rml::Input::KI_EXSEL }, + { 0xF9, Rml::Input::KI_EREOF }, + { 0xFA, Rml::Input::KI_PLAY }, + { 0xFB, Rml::Input::KI_ZOOM }, + { 0xFD, Rml::Input::KI_PA1 }, + { 0xFE, Rml::Input::KI_OEM_CLEAR }, +}; + + /// /// Turns a Win32 virtual key into the identifier RmlUi names it by. -/// Only the keys a document can act on are mapped; an unmapped key is left to the game. +/// Every key RmlUi has a name for is mapped, because a key nothing maps is never delivered +/// to a document at all and a screen that binds a shortcut has to see the whole keyboard. /// static Rml::Input::KeyIdentifier Key_Identifier(WPARAM key) { using namespace Rml::Input; + // The four runs where the two enumerations march in step. if (key >= 'A' && key <= 'Z') { return((KeyIdentifier)(KI_A + (int)(key - 'A'))); } if (key >= '0' && key <= '9') { return((KeyIdentifier)(KI_0 + (int)(key - '0'))); } - if (key >= VK_F1 && key <= VK_F12) { + if (key >= 0x60 && key <= 0x69) { + return((KeyIdentifier)(KI_NUMPAD0 + (int)(key - 0x60))); + } + if (key >= VK_F1 && key <= VK_F24) { return((KeyIdentifier)(KI_F1 + (int)(key - VK_F1))); } - switch (key) { - case VK_BACK: return(KI_BACK); - case VK_TAB: return(KI_TAB); - case VK_RETURN: return(KI_RETURN); - case VK_ESCAPE: return(KI_ESCAPE); - case VK_SPACE: return(KI_SPACE); - case VK_PRIOR: return(KI_PRIOR); - case VK_NEXT: return(KI_NEXT); - case VK_END: return(KI_END); - case VK_HOME: return(KI_HOME); - case VK_LEFT: return(KI_LEFT); - case VK_UP: return(KI_UP); - case VK_RIGHT: return(KI_RIGHT); - case VK_DOWN: return(KI_DOWN); - case VK_INSERT: return(KI_INSERT); - case VK_DELETE: return(KI_DELETE); - case VK_SHIFT: return(KI_LSHIFT); - case VK_CONTROL: return(KI_LCONTROL); - case VK_MENU: return(KI_LMENU); - default: return(KI_UNKNOWN); + for (KeyPairType const & pair : _KeyPairs) { + if (pair.VirtualKey == key) { + return(pair.Identifier); + } + } + + return(KI_UNKNOWN); +} + + +/// +/// Turns an identifier RmlUi reports back into the Win32 virtual key it came from. +/// A screen that records a keypress needs this, because an RmlUi key event carries the +/// identifier and the game's own encoding is a virtual key. +/// +/// int; The virtual key, or zero for an identifier no key produces. +int UI_Virtual_Key(int identifier) +{ + using namespace Rml::Input; + + if (identifier >= KI_A && identifier <= KI_Z) { + return('A' + (identifier - KI_A)); + } + if (identifier >= KI_0 && identifier <= KI_9) { + return('0' + (identifier - KI_0)); + } + if (identifier >= KI_NUMPAD0 && identifier <= KI_NUMPAD9) { + return(0x60 + (identifier - KI_NUMPAD0)); + } + if (identifier >= KI_F1 && identifier <= KI_F24) { + return(VK_F1 + (identifier - KI_F1)); } + + // The numeric keypad's Enter is a Return as far as Win32 is concerned; only the + // extended-key bit in a message's own parameters tells them apart, and that bit is + // gone by the time RmlUi has named the key. + if (identifier == KI_NUMPADENTER) { + return(VK_RETURN); + } + + for (KeyPairType const & pair : _KeyPairs) { + if (pair.Identifier == identifier) { + return(pair.VirtualKey); + } + } + + return(0); } From ed5a8e8e27f71167d8dd63907b01bb5014f9aecb Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 04:26:08 +0100 Subject: [PATCH 106/179] fix(platform): report the host's display modes EnumDisplaySettings answered FALSE, so EnumDisplayModes reported nothing and the display options screen offered an empty list: no resolution could be picked, and with none picked the mode trial and its ten second rollback were unreachable through the screen that owns them. The shim now answers from the host's own mode list, and ENUM_CURRENT_SETTINGS from the desktop mode. A mode is reported at the size a caller would ask the display for rather than at its pixel count, because the host states a mode in logical points and carries the pixel density separately, and the density belongs to the presentation, which already has it. video.cpp keeps one code path now that both platforms answer. Measured on this machine: 22 sizes between 960x600 and 3456x2234, deduplicated across six refresh rates each. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/video.cpp | 7 ----- platform/win32compat/src/window.cpp | 45 +++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/code/video.cpp b/code/video.cpp index ea95bbcc1..9c0492bc9 100644 --- a/code/video.cpp +++ b/code/video.cpp @@ -354,12 +354,6 @@ static int __cdecl Compare_Modes(void const * left, void const * right) /// when nothing matched. int * EnumDisplayModes(int minwidth, int minheight, int maxwidth, int maxheight) { -#ifndef _WIN32 - // No host mode enumeration on this platform yet. Reporting nothing is already a supported - // answer, and the caller falls back to the sizes it knows. - (void)minwidth; (void)minheight; (void)maxwidth; (void)maxheight; - return(NULL); -#else DEVMODE devmode; int count = 0; int capacity = 0; @@ -423,5 +417,4 @@ int * EnumDisplayModes(int minwidth, int minheight, int maxwidth, int maxheight) modes[unique * 2] = 0; modes[unique * 2 + 1] = 0; return(modes); -#endif } diff --git a/platform/win32compat/src/window.cpp b/platform/win32compat/src/window.cpp index 9d28b730c..12144fe16 100644 --- a/platform/win32compat/src/window.cpp +++ b/platform/win32compat/src/window.cpp @@ -726,12 +726,51 @@ extern "C" BOOL GetMonitorInfo(HMONITOR monitor, LPMONITORINFO info) } +// The host states a display mode in logical points and reports the pixel density beside +// it, which is the unit the engine sizes its frame in on this platform; the density belongs +// to the presentation and the shell's scale information already carries it. A mode is +// therefore reported at the size the caller would ask the display for, not at its pixel +// count, which is what EnumDisplaySettings means on the platform this shim stands in for. extern "C" BOOL EnumDisplaySettings(LPCSTR device, DWORD mode, DEVMODE * settings) { (void)device; - (void)mode; - (void)settings; - return(FALSE); + + if (settings == NULL) { + return(FALSE); + } + + SDL_DisplayID const display = SDL_GetPrimaryDisplay(); + SDL_DisplayMode const * found = NULL; + SDL_DisplayMode ** modes = NULL; + + // ENUM_CURRENT_SETTINGS asks for the mode in force rather than for one of the list. + if (mode == (DWORD)-1 || mode == (DWORD)-2) { + found = SDL_GetDesktopDisplayMode(display); + } else { + int count = 0; + modes = SDL_GetFullscreenDisplayModes(display, &count); + + if (modes != NULL && (int)mode < count) { + found = modes[mode]; + } + } + + if (found == NULL) { + SDL_free(modes); + return(FALSE); + } + + SDL_PixelFormatDetails const * const format = SDL_GetPixelFormatDetails(found->format); + + std::memset(settings, 0, sizeof(*settings)); + settings->dmSize = (WORD)sizeof(*settings); + settings->dmPelsWidth = (DWORD)found->w; + settings->dmPelsHeight = (DWORD)found->h; + settings->dmBitsPerPel = (format != NULL) ? (DWORD)format->bits_per_pixel : 32; + settings->dmDisplayFrequency = (DWORD)(found->refresh_rate + 0.5f); + + SDL_free(modes); + return(TRUE); } From aa5870af83487b8db3cf35f208fe0e586f229d39 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 04:26:09 +0100 Subject: [PATCH 107/179] feat(ui): show the keyboard screen through RmlUi ui/keyboard.rml and ui/keyboard.rcss, the last of the options family, with the geometry converted from the IDD_OPT_KEYBOARD template and the family's look taken from ui/optionsbase.rcss. Two controls this family had not needed before. The category combo is RmlUi's select, bound one way like every other form control here, with the closed rectangle sized the way an owner-draw CBS_DROPDOWNLIST is sized -- from the item height ownrdraw.cpp sets, not from the template's dropped extent. The capture control stands where msctls_hotkey32 stood. It takes a keypress while it holds the focus and builds the game's own encoding from it, the virtual key with its modifier bits above it, which needs the inverse of the shell's key table. It leaves alone the keys IsDialogMessage took from that control -- Escape and Enter still leave the screen and Tab still moves the focus -- and a modifier pressed on its own is not a capture, because the hotkey control held nothing until a real key arrived. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/options.cpp | 13 +++ code/ui/uikeyboard.cpp | 162 ++++++++++++++++++++++++++++++++ code/ui/uikeyboard.h | 5 + ui/keyboard.rcss | 207 +++++++++++++++++++++++++++++++++++++++++ ui/keyboard.rml | 38 ++++++++ 5 files changed, 425 insertions(+) create mode 100644 ui/keyboard.rcss create mode 100644 ui/keyboard.rml diff --git a/code/options.cpp b/code/options.cpp index 26751a12d..06be04d16 100644 --- a/code/options.cpp +++ b/code/options.cpp @@ -85,6 +85,7 @@ #include "video.h" #include "vox.h" #include "ui/uikeyboard.h" +#include "ui/uishell.h" #include "diff.hh" @@ -763,6 +764,18 @@ bool OptionsClass::Hotkey_Dialog(void) UIKeyboardPresenterClass screen; screen.Refresh(); + // The selection is latched here, at screen entry, and the legacy dialog opens only when + // the document could not be prepared. + if (UI_Use_Rml()) { + UIResult const result = UI_Keyboard_Screen(screen); + if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { + return(true); + } + + screen.IsClosing = false; + screen.Result.reset(); + } + _KeyboardScreen = &screen; HWND handle = OwnerDraw::Begin_Dialog(IDD_OPT_KEYBOARD, Hotkey_Dialog_Proc); diff --git a/code/ui/uikeyboard.cpp b/code/ui/uikeyboard.cpp index a893b61cc..e300d0fff 100644 --- a/code/ui/uikeyboard.cpp +++ b/code/ui/uikeyboard.cpp @@ -30,6 +30,9 @@ #include "uikeyboard.h" +#include "uiinternal.h" +#include "uirmlview.h" + #include "_command.h" #include "ccfile.h" #include "ccini.h" @@ -43,9 +46,16 @@ #include "ownrdraw.h" #include "vector.h" +#include "keyboard.h" + #include +#include #include +#include +#include +#include + // Build_Hotkey_String lives in ownrdraw.cpp and is the only thing this screen wants from // there. It spells a key, not a control, and moves with the rest of the keyboard support @@ -311,3 +321,155 @@ void UIKeyboardPresenterClass::Execute(UIIntent const & intent) Result = result; } + + +//--------------------------------------------------------------------------------------- +// The RmlUi view. +//--------------------------------------------------------------------------------------- + +/// +/// The RmlUi half of the keyboard screen. +/// +class KeyboardViewClass : public UIRmlViewClass +{ + public: + KeyboardViewClass(UIKeyboardPresenterClass & presenter) : + UIRmlViewClass(presenter, "keyboard.rml"), + Screen(presenter) + { + } + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + private: + UIKeyboardPresenterClass & Screen; +}; + + +// Is this the virtual key of a modifier on its own? A hotkey control holds nothing while +// only modifiers are down and takes the binding when a real key arrives, so a modifier +// pressed by itself is not a capture. +static bool Is_Modifier_Key(int key) +{ + return(key == VK_SHIFT || key == VK_CONTROL || key == VK_MENU + || (key >= 0xA0 && key <= 0xA5)); +} + + +void KeyboardViewClass::Bind(Rml::DataModelConstructor & model) +{ + if (auto command = model.RegisterStruct()) { + command.RegisterMember("label", &UIKeyboardPresenterClass::CommandType::Label); + } + model.RegisterArray>(); + model.RegisterArray>(); + + model.Bind("categories", &Screen.Categories); + model.Bind("category", &Screen.SelectedCategory); + model.Bind("commands", &Screen.Commands); + model.Bind("selectedcommand", &Screen.SelectedCommand); + model.Bind("description", &Screen.Description); + model.Bind("shortcut", &Screen.CurrentShortcut); + model.Bind("capturedtext", &Screen.CapturedText); + model.Bind("assignedto", &Screen.AssignedTo); + + // The combo box is bound one way, as every form control in this family is, so the + // category the model holds cannot be re-queued as a change the player did not make. + model.BindEventCallback("choose", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + Rml::String const value = event.GetParameter("value", Rml::String()); + if (value.empty()) return; + + int const row = std::atoi(value.c_str()); + if (row == Screen.SelectedCategory) return; + + Screen.Queue(UIIntent{UI_KEYBOARD_CATEGORY, "", row}); + }); + + model.BindEventCallback("pick", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{UI_KEYBOARD_COMMAND, "", (int)arguments[0].Get()}); + }); + + // The capture control. It stands where msctls_hotkey32 stood, so it takes the key + // itself and leaves the keys IsDialogMessage took from that control alone: Escape and + // Enter still leave the screen and Tab still moves the focus. + model.BindEventCallback("capture", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const identifier = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + + if (identifier == Rml::Input::KI_ESCAPE || identifier == Rml::Input::KI_RETURN + || identifier == Rml::Input::KI_NUMPADENTER || identifier == Rml::Input::KI_TAB) { + return; + } + + int const key = UI_Virtual_Key(identifier); + if (key == 0 || Is_Modifier_Key(key)) { + return; + } + + // The game's encoding is the virtual key with its modifier bits above it, and + // those bits are the HOTKEYF_ values the hotkey control reported byte for byte. + int encoded = key; + if (event.GetParameter("shift_key", false)) encoded |= WWKEY_SHIFT_BIT; + if (event.GetParameter("ctrl_key", false)) encoded |= WWKEY_CTRL_BIT; + if (event.GetParameter("alt_key", false)) encoded |= WWKEY_ALT_BIT; + + event.StopPropagation(); + Screen.Queue(UIIntent{UI_KEYBOARD_CAPTURE, "", encoded}); + }); + + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{arguments[0].Get(), "", 0}); + }); + + // Escape cancels and Enter accepts, which is what IsDialogMessage delivered to a dialog + // whose template names no default push button. + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key == Rml::Input::KI_ESCAPE) { + Screen.Queue(UIIntent{UI_KEYBOARD_CANCEL, "", 0}); + } else if (key == Rml::Input::KI_RETURN || key == Rml::Input::KI_NUMPADENTER) { + Screen.Queue(UIIntent{UI_KEYBOARD_ACCEPT, "", 0}); + } + }); +} + + +void KeyboardViewClass::Sync(void) +{ + if (!Model) return; + + Model.DirtyVariable("categories"); + Model.DirtyVariable("category"); + Model.DirtyVariable("commands"); + Model.DirtyVariable("selectedcommand"); + Model.DirtyVariable("description"); + Model.DirtyVariable("shortcut"); + Model.DirtyVariable("capturedtext"); + Model.DirtyVariable("assignedto"); +} + + +/// +/// Shows the keyboard screen and waits for the player to leave it. +/// +UIResult UI_Keyboard_Screen(UIKeyboardPresenterClass & presenter) +{ + KeyboardViewClass view(presenter); + + if (!view.Prepare(true)) { + UIResult result; + result.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + return(result); + } + + UIResult const result = UI_Run_Modal(presenter, view); + view.Close(); + return(result); +} diff --git a/code/ui/uikeyboard.h b/code/ui/uikeyboard.h index 421105e84..d54dc773a 100644 --- a/code/ui/uikeyboard.h +++ b/code/ui/uikeyboard.h @@ -89,3 +89,8 @@ class UIKeyboardPresenterClass : public UIPresenterClass void Reset_All(void); void Save_Assignments(void) const; }; + + +// Shows the screen through its RmlUi view. FAILED_TO_OPEN leaves nothing shown and the +// caller falls through to the legacy dialog. +UIResult UI_Keyboard_Screen(UIKeyboardPresenterClass & presenter); diff --git a/ui/keyboard.rcss b/ui/keyboard.rcss new file mode 100644 index 000000000..58953f7bf --- /dev/null +++ b/ui/keyboard.rcss @@ -0,0 +1,207 @@ +/* The keyboard screen. Geometry from the IDD_OPT_KEYBOARD template, converted from dialog + units at the 8 point MS Sans Serif the template names: 1.5 pixels across and 1.625 down, + with a child's offset taken from the panel's content box and the panel's declared size + taken inside its own border. */ + +/* 336 x 208 dialog units, so 504 x 338 pixels, centred on the frame. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -252dp; + margin-top: -169dp; + + width: 500dp; + height: 334dp; +} + +/* The CTEXT title, 292 x 11 dialog units at 22, 12. */ +#title +{ + left: 31dp; + top: 17.5dp; + width: 438dp; + height: 17.875dp; + line-height: 17.875dp; + text-align: center; +} + +/* The two column captions and the four label and value pairs, all LTEXT. */ +#categorylabel { left: 31dp; top: 41.875dp; width: 219dp; height: 14.625dp; line-height: 14.625dp; } +#commandlabel { left: 250dp; top: 41.875dp; width: 219dp; height: 13dp; line-height: 13dp; } +#capturelabel { left: 31dp; top: 191.375dp; width: 192dp; height: 14.625dp; line-height: 14.625dp; } +#assignedlabel { left: 31dp; top: 243.375dp; width: 219dp; height: 17.875dp; line-height: 17.875dp; } +#shortcutlabel { left: 250dp; top: 243.375dp; width: 219dp; height: 17.875dp; line-height: 17.875dp; } +#assignedto { left: 31dp; top: 267.75dp; width: 219dp; height: 16.25dp; line-height: 16.25dp; } +#shortcut { left: 250dp; top: 267.75dp; width: 219dp; height: 16.25dp; line-height: 16.25dp; } + +/* The category combo box, 138 dialog units wide at 22, 42. The template's 146 is how far + the list drops, not how tall the control is: an owner-draw CBS_DROPDOWNLIST sizes its + closed rectangle from the item height ownrdraw.cpp sets, which is the 14 pixel dialog + font plus two, inside a two pixel border. */ +#category +{ + display: block; + position: absolute; + box-sizing: border-box; + left: 31dp; + top: 66.25dp; + width: 207dp; + height: 20dp; + + color: #b9bcae; + background-color: #2b2f25; + border-width: 2dp; + border-top-color: #14160f; + border-left-color: #14160f; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +#category selectvalue +{ + width: auto; + margin-right: 16dp; + height: 16dp; + line-height: 16dp; + padding: 0dp 4dp; + white-space: nowrap; + overflow: hidden; +} + +#category selectarrow +{ + width: 16dp; + height: 16dp; + + background-color: #33372c; + border-width: 2dp; + border-top-color: #767c68; + border-left-color: #767c68; + border-right-color: #14160f; + border-bottom-color: #14160f; +} + +#category selectarrow:active +{ + border-top-color: #14160f; + border-left-color: #14160f; + border-right-color: #767c68; + border-bottom-color: #767c68; +} + +/* The dropped list, capped at the 146 dialog units the template gives it. */ +#category selectbox +{ + width: 203dp; + max-height: 237.25dp; + overflow-y: auto; + + background-color: #14160f; + border-width: 1dp; + border-top-color: #6e7360; + border-left-color: #6e7360; + border-right-color: #10120e; + border-bottom-color: #10120e; +} + +#category selectbox option +{ + width: auto; + height: 20dp; + line-height: 20dp; + padding: 0dp 4dp; + white-space: nowrap; + color: #b9bcae; +} + +#category selectbox option:hover { color: #e4e6da; } +#category selectbox option:checked +{ + background-color: #3f4536; + color: #e4e6da; +} + +/* The command list, 146 x 104 dialog units at 168, 41. */ +#commands +{ + left: 250dp; + top: 64.625dp; + width: 219dp; + height: 169dp; +} + +/* A row spans the list less its scrollbar. */ +#commands .row { width: 207dp; } + +/* The description group box, 138 x 57 dialog units at 22, 57, with its caption on the top + edge the way the owner-draw group box drew it, and the description text inside it at + 127 x 42 dialog units at 29, 68. */ +#descriptionbox +{ + display: block; + position: absolute; + box-sizing: border-box; + left: 31dp; + top: 90.625dp; + width: 207dp; + height: 92.625dp; + line-height: 14dp; + padding-left: 8dp; + + color: #b9bcae; + border-width: 1dp; + border-top-color: #10120e; + border-left-color: #10120e; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +#description +{ + display: block; + position: absolute; + box-sizing: border-box; + left: 41.5dp; + top: 108.5dp; + width: 190.5dp; + height: 68.25dp; + overflow: hidden; + color: #d6d8cc; +} + +/* The capture control, 85 x 14 dialog units at 22, 131. It stands where msctls_hotkey32 + stood: a bordered field that spells out the key it is holding and takes a keypress + whenever it has the focus. */ +#capture +{ + display: block; + position: absolute; + box-sizing: border-box; + left: 31dp; + top: 210.875dp; + width: 127.5dp; + height: 22.75dp; + line-height: 18.75dp; + padding: 0dp 4dp; + white-space: nowrap; + overflow: hidden; + + color: #e4e6da; + background-color: #14160f; + border-width: 2dp; + border-top-color: #10120e; + border-left-color: #10120e; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +#capture:focus { background-color: #1d2017; } + +/* Assign, Reset All, OK and Cancel. */ +.button { height: 22.75dp; line-height: 22.75dp; } + +#assign { left: 163dp; top: 212.5dp; width: 75dp; } +#reset { left: 31dp; top: 292.125dp; width: 81dp; } +#ok { left: 278.5dp; top: 292.125dp; width: 75dp; } +#cancel { left: 394dp; top: 292.125dp; width: 75dp; } diff --git a/ui/keyboard.rml b/ui/keyboard.rml new file mode 100644 index 000000000..a9cc1c90c --- /dev/null +++ b/ui/keyboard.rml @@ -0,0 +1,38 @@ + + + Customize keyboard + + + + +
+
Customize Keyboard
+ +
Category:
+ + +
Commands:
+
+
{{ command.label }}
+
+ +
Description:
+
{{ description }}
+ +
Press new shortcut key:
+
{{ capturedtext }}
+
Assign
+ +
Currently assigned to:
+
Current shortcut:
+
{{ assignedto }}
+
{{ shortcut }}
+ +
Reset All
+
[[TXT_OK]]
+
[[TXT_CANCEL]]
+
+ +
From f0b3aa0e8dd688ae100a3d62fb4e501b638c7dac Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 04:26:28 +0100 Subject: [PATCH 108/179] docs: record the options family as migrated Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/UI_DESIGN.md | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index 07b3e77ce..d2bd41038 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -1,7 +1,7 @@ # UI system design -Status: in progress. Steps 1 to 6 of the migration plan have landed and step 7 -is most of the way through; nothing from step 8 onward is implemented. Everything outside the migration plan +Status: in progress. Steps 1 to 7 of the migration plan have landed; nothing +from step 8 onward is implemented. Everything outside the migration plan remains a proposal informed by source inspection and upstream documentation. This page owns the UI architecture and migration; [Building OpenTS](BUILDING.md) owns build support and [Project @@ -817,12 +817,31 @@ text beyond an ASCII test document. the family is extracted: `code/ui/uigameoptions.{h,cpp}`, `code/ui/uiabort.{h,cpp}`, `code/ui/uigamecontrols.{h,cpp}`, `code/ui/uimainoptions.{h,cpp}`, `code/ui/uidisplayoptions.{h,cpp}`, - `code/ui/uidisplayconfirm.{h,cpp}` and `code/ui/uikeyboard.{h,cpp}`. Six of - the seven have their RmlUi view: `ui/options.rml`, `ui/gameoptions.rml` with + `code/ui/uidisplayconfirm.{h,cpp}` and `code/ui/uikeyboard.{h,cpp}`, and each + has its RmlUi view: `ui/options.rml`, `ui/gameoptions.rml` with its `mp` and `wol` variants, `ui/abort.rml`, `ui/gamecontrols.rml` with its - `mp` and `wol` variants, `ui/display.rml` and `ui/modeconfirm.rml`, sharing + `mp` and `wol` variants, `ui/display.rml`, `ui/modeconfirm.rml` and + `ui/keyboard.rml`, sharing the family's look through `ui/optionsbase.rcss` and each carrying its own - geometry. The keyboard screen still has only its legacy view. + geometry. + + The keyboard screen brought the family's two new controls. The category + combo is RmlUi's `select`, sized the way an owner-draw `CBS_DROPDOWNLIST` is + sized, from the item height `ownrdraw.cpp` sets rather than from the + template's dropped extent. The capture control stands where + `msctls_hotkey32` stood: it takes a keypress while it holds the focus and + builds the game's own encoding from it, and it leaves alone the keys + `IsDialogMessage` took from that control, so Escape and Enter still leave + the screen and Tab still moves the focus. Turning a keypress back into that + encoding needs the inverse of the shell's key table, which was a short list + in one direction only; it is now complete and paired, because a key nothing + maps is never delivered to a document at all. Step 9's save-name field wants + the same table. + + The display screen needed the host's mode list. `EnumDisplaySettings` + answered nothing on this platform, so the resolution list was empty and the + trial and its rollback could not be reached through the screen that owns + them; the shim answers from the host's own enumeration now. The mode trial's timeout is the presenter's, not a view's: the driver expressed it as a posted `WM_COMMAND` carrying `WM_DESTROY`, which is two, From 0c9afee88001ca690aa2dac22eb4fa82d5e3df6f Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 04:33:50 +0100 Subject: [PATCH 109/179] refactor(ui): put the main menu behind a presenter Classified preserved: the legacy dialog is still what is shown, and it now reads a view-model and queues intents the driver executes after the pump. Three things the extraction pins down, none of them visible in the template. The keys the driver watched for beside the buttons belong to the screen rather than to the window it was drawn in, so a typed character reaches Cheat_Key_Process through an intent and the version screen and the credits are choices like any other. The load button's permission is checked when the button is pressed, not when it was enabled, because a saved game can arrive or go while the screen is up. And the version screen is a screen of a different kind, so it nests: what running it means went to the presenter and getting out of its way stayed with the view, which is what the dialog's ShowWindow did. The campaign choice screen goes with it, in code/ui/uicampaign.{h,cpp}. Its availability test moved out of init.cpp, where it existed for this screen alone, and a row carries the campaign it stands for rather than its position, because the list skips a campaign the player cannot reach. The difficulty is read back from the slider at accept rather than tracked, for the reason the game controls screen reads its own back: a keyboard or page move changes a track bar without raising the thumb notification the label follows. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/init.cpp | 191 +++++++++++++++++++---------------------- code/init.h | 4 + code/ui/uicampaign.cpp | 159 ++++++++++++++++++++++++++++++++++ code/ui/uicampaign.h | 73 ++++++++++++++++ code/ui/uimainmenu.cpp | 136 +++++++++++++++++++++++++++++ code/ui/uimainmenu.h | 72 ++++++++++++++++ 6 files changed, 534 insertions(+), 101 deletions(-) create mode 100644 code/ui/uicampaign.cpp create mode 100644 code/ui/uicampaign.h create mode 100644 code/ui/uimainmenu.cpp create mode 100644 code/ui/uimainmenu.h diff --git a/code/init.cpp b/code/init.cpp index 4b3e7444a..72601c9c1 100644 --- a/code/init.cpp +++ b/code/init.cpp @@ -195,6 +195,8 @@ #include "bench.hh" #include "scrnsel.hh" +#include "ui/uicampaign.h" +#include "ui/uimainmenu.h" #include #include @@ -205,10 +207,6 @@ extern VoxelDataStruct DropPodVoxel; -struct ChooseCampaignStruct { - CampaignType ChosenCampaign; - bool ChoiceMade; -}; /********************************************************************** ** Optional parameter control for special options. @@ -278,7 +276,7 @@ static CheatEntryStruct CheatEntries[] = { }; static void Cheat_Disable(void); -static bool Cheat_Key_Process(char chr); +bool Cheat_Key_Process(char chr); static void Cheat_Version_Suffix(char * string); @@ -718,30 +716,20 @@ void Prepare_Side_Roster(void) } + +static UICampaignPresenterClass * _CampaignScreen = NULL; +static UIMainMenuPresenterClass * _MainMenuScreen = NULL; + + /// -/// Can this campaign be played with the addons that are enabled? -/// A base game campaign is offered only when no addon is running, and an addon's own -/// campaign only when that particular addon is running. +/// Puts the view-model on the campaign dialog's controls. /// -/// The campaign to be tested. -/// bool; Is the campaign available for the player to select? -static bool Campaign_Available(CampaignClass * campaign) +static void Campaign_Sync_Controls(HWND window, UICampaignPresenterClass const & screen) { - if (Addon_Enabled(ADDON_ANY) == true) { - if (campaign->RequiredAddon == ADDON_BASE_GAME) { - return(false); - } - if (Addon_Enabled((AddonType)campaign->RequiredAddon)) { - return(true); - } - return(false); - } - - if (campaign->RequiredAddon == ADDON_BASE_GAME) { - return(true); + HWND handle = GetDlgItem(window, IDC_DIFFICULTY_LABEL); + if (handle) { + SetWindowText(handle, screen.DifficultyLabel.c_str()); } - - return(false); } @@ -753,7 +741,6 @@ static bool Campaign_Available(CampaignClass * campaign) static INT_PTR CALLBACK Campaign_Choice_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) { HWND item; - struct ChooseCampaignStruct * state; INT_PTR rc; rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); @@ -762,6 +749,12 @@ static INT_PTR CALLBACK Campaign_Choice_Dialog_Proc(HWND window, UINT message, W return(rc); } + if (_CampaignScreen == NULL) { + return(FALSE); + } + + UICampaignPresenterClass & screen = *_CampaignScreen; + switch (message) { case WM_INITDIALOG: @@ -769,28 +762,18 @@ static INT_PTR CALLBACK Campaign_Choice_Dialog_Proc(HWND window, UINT message, W if (item != NULL) { DebugString("Initializing Choose_Campaign() Dialog.\n"); - for (int index = 0; index < Campaigns.Count(); index++) { - CampaignClass * campaign = Campaigns[index]; - - if (!Campaign_Available(campaign)) { - DebugString("\tSkipping Campaign [%d] - %s\n", index, campaign->Description); - continue; - } - - DebugString("\tAdding Campaign [%d] - %s\n", index, campaign->Description); - int pos = ListBox_AddString(item, campaign->Description); - ListBox_SetItemData(item, pos, index); + for (UICampaignPresenterClass::EntryType const & entry : screen.Campaigns) { + ListBox_AddString(item, entry.Label.c_str()); } - - ListBox_SetCurSel(item, 0); + ListBox_SetCurSel(item, screen.Selected); } item = GetDlgItem(window, IDC_DIFFICULTY_SLIDER); if (item != NULL) { SendMessage(item, OD_TRACKNUMBERS, 0, 0); - Slider_SetRange(item, 0,2); - Slider_SetPos(item, Options.Difficulty); + Slider_SetRange(item, 0, UICampaignPresenterClass::DIFFICULTY_STEPS - 1); + Slider_SetPos(item, screen.Difficulty); } break; @@ -798,34 +781,26 @@ static INT_PTR CALLBACK Campaign_Choice_Dialog_Proc(HWND window, UINT message, W switch (LOWORD(wparam)) { case IDOK: if (HIWORD(wparam) == BN_CLICKED) { - state = (ChooseCampaignStruct *)GetWindowLongPtr(window, DWLP_USER); - - if (state != NULL) { - item = GetDlgItem(window, IDC_LIST); - - if (item != NULL) { - int pos = ListBox_GetCurSel(item); - state->ChosenCampaign = (CampaignType)ListBox_GetItemData(item, pos); - state->ChoiceMade = true; - } + item = GetDlgItem(window, IDC_LIST); + if (item != NULL) { + screen.Queue(UIIntent{UI_CAMPAIGN_SELECT, "", ListBox_GetCurSel(item)}); } + // The slider is read back here rather than tracked, because a + // keyboard or page move changes a track bar without raising the + // thumb notification the label follows. item = GetDlgItem(window, IDC_DIFFICULTY_SLIDER); - if (item != NULL) { - Options.Difficulty = Slider_GetPos(item); + screen.Queue(UIIntent{UI_CAMPAIGN_DIFFICULTY, "", Slider_GetPos(item)}); } + + screen.Queue(UIIntent{UI_CAMPAIGN_ACCEPT, "", 0}); } break; case IDCANCEL: if (HIWORD(wparam) == BN_CLICKED) { - state = (ChooseCampaignStruct *)GetWindowLongPtr(window, DWLP_USER); - - if (state != NULL) { - state->ChosenCampaign = CAMPAIGN_NONE; - state->ChoiceMade = true; - } + screen.Queue(UIIntent{UI_CAMPAIGN_CANCEL, "", 0}); } break; @@ -833,13 +808,8 @@ static INT_PTR CALLBACK Campaign_Choice_Dialog_Proc(HWND window, UINT message, W break; case WM_HSCROLL: { - int diff = HIWORD(wparam); - int stringID = 0; - if ((HWND)lparam == GetDlgItem(window, IDC_DIFFICULTY_SLIDER)) { - stringID = GameDifficultyNames[diff]; - item = GetDlgItem(window, IDC_DIFFICULTY_LABEL); - Static_SetText(item, Fetch_String(stringID)); + screen.Queue(UIIntent{UI_CAMPAIGN_DIFFICULTY, "", (int)HIWORD(wparam)}); } break; } @@ -861,10 +831,6 @@ static INT_PTR CALLBACK Campaign_Choice_Dialog_Proc(HWND window, UINT message, W static CampaignType Choose_Campaign(void) { HWND dialog; - struct ChooseCampaignStruct state; - - state.ChoiceMade = false; - state.ChosenCampaign = CAMPAIGN_NONE; if (Campaigns.Count() == 0) { Init_Campaigns(); @@ -874,25 +840,33 @@ static CampaignType Choose_Campaign(void) } } + UICampaignPresenterClass screen; + screen.Refresh(); + + _CampaignScreen = &screen; + dialog = OwnerDraw::Begin_Dialog(IDD_CAMPAIGN, Campaign_Choice_Dialog_Proc); if (dialog != NULL) { - SetWindowLongPtr(dialog, DWLP_USER, (LONG_PTR) &state); - OwnerDraw::Move_Dialog(dialog, -1, (HiddenSurface->Get_Height() - 400) / 2 + 147); OwnerDraw::Display_Dialog(dialog); - while (state.ChoiceMade == false) { + while (!screen.Result.has_value()) { if (OwnerDraw::Dialog_Message_Handler() == true) { break; } - Title_Screen_Restore(); + + screen.Drain(); + Campaign_Sync_Controls(dialog, screen); + screen.Service(); } OwnerDraw::End_Dialog(dialog); } - return(state.ChosenCampaign); + _CampaignScreen = NULL; + + return((CampaignType)screen.Chosen()); } @@ -3131,11 +3105,15 @@ int Main_Menu(unsigned int timeout) timeout = 0; + UIMainMenuPresenterClass screen; + screen.Refresh(); + + _MainMenuScreen = &screen; + dialog = OwnerDraw::Begin_Dialog(IDD_MAIN_MENU, Main_Menu_Dialog_Proc); assert(dialog != NULL); if (dialog != NULL) { - SetWindowLongPtr(dialog, DWLP_USER, (LONG_PTR)&retval); char *menu = Get_New_Menu()->Background; Load_Title_Screen(menu, HiddenSurface, &CCPalette); Draw_Version_Text(HiddenSurface); @@ -3146,40 +3124,48 @@ int Main_Menu(unsigned int timeout) do { if (OwnerDraw::Dialog_Message_Handler() == true) { - retval = SEL_EXIT; + screen.Queue(UIIntent{UI_MAINMENU_EXIT, "", 0}); } - Title_Screen_Restore(); + screen.Drain(); + screen.Service(); if (Keyboard->Check()) { KeyNumType input = Keyboard->Get(); switch ((unsigned int)input) { case (KN_V | KN_CTRL_BIT): - ShowWindow(dialog, SW_HIDE); - UpdateWindow(MainWindow); - Version_Dialog(); - ShowWindow(dialog, SW_SHOW); - UpdateWindow(dialog); - SetFocus(MainWindow); + screen.Queue(UIIntent{UI_MAINMENU_VERSION, "", 0}); break; case VK_C | KN_CTRL_BIT | KN_ALT_BIT: - retval = SEL_VIEW_CREDITS; + screen.Queue(UIIntent{UI_MAINMENU_CREDITS, "", 0}); break; default: if ((input & KN_RLSE_BIT) == 0) { - if (Cheat_Key_Process((char)input) == true) { - Sound_Effect(Rule->OptionsChanged); - Title_Screen_Restore(true); - } + screen.Queue(UIIntent{UI_MAINMENU_TYPED, "", (int)(char)input}); } break; } + + screen.Drain(); + } + + // The version screen is a screen of a different kind, so it nests; getting out + // of the way of it is what the dialog's ShowWindow did. + if (screen.VersionPending) { + ShowWindow(dialog, SW_HIDE); + UpdateWindow(MainWindow); + screen.Run_Pending(); + ShowWindow(dialog, SW_SHOW); + UpdateWindow(dialog); + SetFocus(MainWindow); } } - while (retval == SEL_NONE); + while (!screen.Result.has_value()); + + retval = screen.Selection(); OwnerDraw::End_Dialog(dialog); @@ -3193,6 +3179,8 @@ int Main_Menu(unsigned int timeout) retval = SEL_EXIT; } + _MainMenuScreen = NULL; + SetFocus(MainWindow); return(retval); } @@ -3205,24 +3193,25 @@ int Main_Menu(unsigned int timeout) ///
INT_PTR CALLBACK Main_Menu_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) { - int * res; - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); if (rc) { return(rc); } - res = (int *) GetWindowLongPtr(window, DWLP_USER); + if (_MainMenuScreen == NULL) { + return(FALSE); + } + + UIMainMenuPresenterClass & screen = *_MainMenuScreen; switch (message) { case WM_INITDIALOG: { HWND control = GetDlgItem(window, IDC_LOAD_MISSION); if (control) { - if (LoadOptionsClass().Files_Present() == true) { - EnableWindow(control, TRUE); + EnableWindow(control, screen.CanLoad ? TRUE : FALSE); + if (screen.CanLoad) { return(FALSE); } - EnableWindow(control, FALSE); } } break; @@ -3230,27 +3219,27 @@ INT_PTR CALLBACK Main_Menu_Dialog_Proc(HWND window, UINT message, WPARAM wparam, case WM_COMMAND: { switch (LOWORD(wparam)) { case IDC_OPTIONS: - *res = SEL_OPTIONS; + screen.Queue(UIIntent{UI_MAINMENU_OPTIONS, "", 0}); break; case IDC_EXIT_GAME: - *res = SEL_EXIT; + screen.Queue(UIIntent{UI_MAINMENU_EXIT, "", 0}); break; case IDC_INTRO: - *res = SEL_INTRO; + screen.Queue(UIIntent{UI_MAINMENU_INTRO, "", 0}); break; case IDC_NEWCAMPAIGN: - *res = SEL_CAMPAIGN_GAME; + screen.Queue(UIIntent{UI_MAINMENU_CAMPAIGN, "", 0}); break; case IDC_MULTIPLAYER_GAME: - *res = SEL_MULTIPLAYER_GAME; + screen.Queue(UIIntent{UI_MAINMENU_MULTIPLAYER, "", 0}); break; case IDC_LOAD_MISSION: - *res = SEL_LOAD_GAME; + screen.Queue(UIIntent{UI_MAINMENU_LOAD, "", 0}); break; } } diff --git a/code/init.h b/code/init.h index ca3fd1ce2..c8d4ccb39 100644 --- a/code/init.h +++ b/code/init.h @@ -41,6 +41,10 @@ void Reset_Selection_Filters(void); void Title_Screen_Restore(bool force=false); +// Spells a cheat word out one character at a time and answers when one is completed. The +// main menu screen owns this; it is not a key binding and never reaches the command list. +bool Cheat_Key_Process(char chr); + void Init_Campaigns(void); void Prepare_Theater_Roster(void); diff --git a/code/ui/uicampaign.cpp b/code/ui/uicampaign.cpp new file mode 100644 index 000000000..b8025c474 --- /dev/null +++ b/code/ui/uicampaign.cpp @@ -0,0 +1,159 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The campaign choice screen. Behavior traced out of Choose_Campaign and +// Campaign_Choice_Dialog_Proc in init.cpp, including the availability test that decided +// which campaigns were listed at all. +// +// What the extraction fixes in place: a row carries the campaign it stands for rather than +// its position, because the list skips a campaign the player cannot reach; the difficulty +// is written to the settings only on accept, which is where the dialog read the slider +// back; and the difficulty caption starts as the template's own, because the dialog set it +// only when the slider moved. +// +// docs/UI_DESIGN.md, "Screens", owns the contracts this keeps to. + +#include "always.h" + +#include "uicampaign.h" + +#include "uirmlview.h" + +#include "addon.h" +#include "campaign.h" +#include "data.h" +#include "dbgprint.h" +#include "campaign.hh" +#include "gamedlg.h" +#include "globals.h" +#include "goptions.h" +#include "init.h" +#include "language/language.h" +#include "vector.h" + +#include +#include +#include + + +// A base game campaign is offered only when no addon is running, and an addon's own +// campaign only when that particular addon is running. Moved here from init.cpp, where it +// existed for this screen alone. +static bool Campaign_Available(CampaignClass * campaign) +{ + if (Addon_Enabled(ADDON_ANY) == true) { + if (campaign->RequiredAddon == ADDON_BASE_GAME) { + return(false); + } + if (Addon_Enabled((AddonType)campaign->RequiredAddon)) { + return(true); + } + return(false); + } + + if (campaign->RequiredAddon == ADDON_BASE_GAME) { + return(true); + } + + return(false); +} + + +void UICampaignPresenterClass::Refresh(void) +{ + Campaigns.clear(); + Selected = -1; + + for (int index = 0; index < ::Campaigns.Count(); index++) { + CampaignClass * const campaign = ::Campaigns[index]; + + if (!Campaign_Available(campaign)) { + DebugString("\tSkipping Campaign [%d] - %s\n", index, campaign->Description); + continue; + } + + DebugString("\tAdding Campaign [%d] - %s\n", index, campaign->Description); + + EntryType entry; + entry.Label = campaign->Description; + entry.Campaign = index; + Campaigns.push_back(entry); + } + + // The dialog selected the first row it had listed. + if (!Campaigns.empty()) { + Selected = 0; + } + + Difficulty = Options.Difficulty; + if (Difficulty < 0) Difficulty = 0; + if (Difficulty >= DIFFICULTY_STEPS) Difficulty = DIFFICULTY_STEPS - 1; + + // The template's own caption, which is what the dialog left showing until the slider + // was moved. + DifficultyLabel = "Harder"; +} + + +/// +/// The maintenance the dialog driver ran on every pass of its own loop. +/// +void UICampaignPresenterClass::Service(void) +{ + Title_Screen_Restore(); +} + + +int UICampaignPresenterClass::Chosen(void) const +{ + if (Choice != CHOICE_ACCEPT) { + return(CAMPAIGN_NONE); + } + if (Selected < 0 || Selected >= (int)Campaigns.size()) { + return(CAMPAIGN_NONE); + } + return(Campaigns[Selected].Campaign); +} + + +void UICampaignPresenterClass::Execute(UIIntent const & intent) +{ + if (intent.Action == UI_CAMPAIGN_SELECT) { + if (intent.Value >= 0 && intent.Value < (int)Campaigns.size()) { + Selected = intent.Value; + } + return; + } + + if (intent.Action == UI_CAMPAIGN_DIFFICULTY) { + if (intent.Value >= 0 && intent.Value < DIFFICULTY_STEPS) { + Difficulty = intent.Value; + DifficultyLabel = Fetch_String(GameDifficultyNames[Difficulty]); + } + return; + } + + UIResult result; + + if (intent.Action == UI_CAMPAIGN_ACCEPT) { + Options.Difficulty = Difficulty; + Choice = CHOICE_ACCEPT; + result.Outcome = UIResult::OUTCOME_ACCEPTED; + + } else if (intent.Action == UI_CAMPAIGN_CANCEL) { + Choice = CHOICE_CANCEL; + result.Outcome = UIResult::OUTCOME_CANCELLED; + + } else { + return; + } + + result.Value = Chosen(); + Result = result; +} diff --git a/code/ui/uicampaign.h b/code/ui/uicampaign.h new file mode 100644 index 000000000..81a014c92 --- /dev/null +++ b/code/ui/uicampaign.h @@ -0,0 +1,73 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The campaign choice screen's behavior, with no toolkit in it. A row carries the campaign +// it stands for rather than its position, because the list skips a campaign the player +// cannot reach and a row number then means nothing on its own. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + +#include +#include + + +inline constexpr char const * UI_CAMPAIGN_SELECT = "select"; // Value: row +inline constexpr char const * UI_CAMPAIGN_DIFFICULTY = "difficulty"; // Value: slider step +inline constexpr char const * UI_CAMPAIGN_ACCEPT = "accept"; +inline constexpr char const * UI_CAMPAIGN_CANCEL = "cancel"; + + +class UICampaignPresenterClass : public UIPresenterClass +{ + public: + // The three positions the difficulty track bar was given. + enum { DIFFICULTY_STEPS = 3 }; + + struct EntryType + { + std::string Label; + int Campaign = 0; + }; + + enum ChoiceType { + CHOICE_NONE, + CHOICE_ACCEPT, + CHOICE_CANCEL, + }; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + virtual void Service(void) override; + + // The campaign the player settled on, or the none campaign when they backed out. + int Chosen(void) const; + + /* + ** The view-model. Plain values, and the only thing a view reads. + */ + std::vector Campaigns; + int Selected = -1; + + int Difficulty = 0; + + // What the difficulty caption reads. The dialog left the template's own caption + // showing until the slider was moved, so that caption is where this starts. + std::string DifficultyLabel; + + ChoiceType Choice = CHOICE_NONE; +}; + + +// Shows the screen through its RmlUi view. FAILED_TO_OPEN leaves nothing shown and the +// caller falls through to the legacy dialog. +UIResult UI_Campaign_Screen(UICampaignPresenterClass & presenter); diff --git a/code/ui/uimainmenu.cpp b/code/ui/uimainmenu.cpp new file mode 100644 index 000000000..3b7bd807a --- /dev/null +++ b/code/ui/uimainmenu.cpp @@ -0,0 +1,136 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The main menu. Behavior traced out of Main_Menu and Main_Menu_Dialog_Proc in init.cpp. +// +// What the extraction fixes in place: the load button is disabled when there is no saved +// game to offer, and the test is made as the screen opens rather than once at startup; the +// keys the driver watched for beside the buttons belong to the screen, not to the window it +// was drawn in, so a typed character reaches Cheat_Key_Process through an intent and the +// version screen and the credits are choices like any other; and the version screen is a +// screen of a different kind, so it nests and the view steps aside for it, which is what +// the dialog's own ShowWindow did. +// +// docs/UI_DESIGN.md, "Screens", owns the contracts this keeps to. + +#include "always.h" + +#include "uimainmenu.h" + +#include "uirmlview.h" + +#include "voc.h" +#include "globals.h" +#include "init.h" +#include "loaddlg.h" +#include "_rules.h" +#include "rules.h" +#include "scrnsel.hh" + +#include +#include +#include + +void Version_Dialog(void); + + +void UIMainMenuPresenterClass::Refresh(void) +{ + CanLoad = LoadOptionsClass().Files_Present(); +} + + +/// +/// The maintenance the menu driver ran on every pass of its own loop. +/// +void UIMainMenuPresenterClass::Service(void) +{ + Title_Screen_Restore(); +} + + +/// +/// The selection the caller's own switch is written against. +/// +int UIMainMenuPresenterClass::Selection(void) const +{ + switch (Choice) { + case CHOICE_CAMPAIGN: return(SEL_CAMPAIGN_GAME); + case CHOICE_LOAD: return(SEL_LOAD_GAME); + case CHOICE_MULTIPLAYER: return(SEL_MULTIPLAYER_GAME); + case CHOICE_INTRO: return(SEL_INTRO); + case CHOICE_OPTIONS: return(SEL_OPTIONS); + case CHOICE_EXIT: return(SEL_EXIT); + case CHOICE_CREDITS: return(SEL_VIEW_CREDITS); + default: return(SEL_NONE); + } +} + + +/// +/// Runs the screen the last choice asked for, then clears the request. +/// +void UIMainMenuPresenterClass::Run_Pending(void) +{ + if (!VersionPending) { + return; + } + + VersionPending = false; + Version_Dialog(); +} + + +void UIMainMenuPresenterClass::Execute(UIIntent const & intent) +{ + if (intent.Action == UI_MAINMENU_VERSION) { + VersionPending = true; + return; + } + + if (intent.Action == UI_MAINMENU_TYPED) { + // A cheat word is spelled out one character at a time and only the word completing + // it answers, which is why nothing here is a choice. + if (Cheat_Key_Process((char)intent.Value)) { + Sound_Effect(Rule->OptionsChanged); + Title_Screen_Restore(true); + } + return; + } + + UIResult result; + result.Outcome = UIResult::OUTCOME_ACCEPTED; + + if (intent.Action == UI_MAINMENU_CAMPAIGN) { + Choice = CHOICE_CAMPAIGN; + } else if (intent.Action == UI_MAINMENU_LOAD) { + // The permission is checked when the button is pressed rather than when it was + // enabled, because a saved game can arrive or go while the screen is up. + if (!LoadOptionsClass().Files_Present()) { + return; + } + Choice = CHOICE_LOAD; + } else if (intent.Action == UI_MAINMENU_MULTIPLAYER) { + Choice = CHOICE_MULTIPLAYER; + } else if (intent.Action == UI_MAINMENU_INTRO) { + Choice = CHOICE_INTRO; + } else if (intent.Action == UI_MAINMENU_OPTIONS) { + Choice = CHOICE_OPTIONS; + } else if (intent.Action == UI_MAINMENU_CREDITS) { + Choice = CHOICE_CREDITS; + } else if (intent.Action == UI_MAINMENU_EXIT) { + Choice = CHOICE_EXIT; + result.Outcome = UIResult::OUTCOME_CANCELLED; + } else { + return; + } + + result.Value = Selection(); + Result = result; +} diff --git a/code/ui/uimainmenu.h b/code/ui/uimainmenu.h new file mode 100644 index 000000000..eb9555415 --- /dev/null +++ b/code/ui/uimainmenu.h @@ -0,0 +1,72 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The main menu's behavior, with no toolkit in it. The screen is six buttons and the keys +// the driver watched for beside them, which are part of the screen rather than of the +// window it was drawn in: the version screen, the credits, and the cheat words. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + + +inline constexpr char const * UI_MAINMENU_CAMPAIGN = "campaign"; +inline constexpr char const * UI_MAINMENU_LOAD = "load"; +inline constexpr char const * UI_MAINMENU_MULTIPLAYER = "multiplayer"; +inline constexpr char const * UI_MAINMENU_INTRO = "intro"; +inline constexpr char const * UI_MAINMENU_OPTIONS = "options"; +inline constexpr char const * UI_MAINMENU_EXIT = "exit"; +inline constexpr char const * UI_MAINMENU_VERSION = "version"; +inline constexpr char const * UI_MAINMENU_CREDITS = "credits"; +inline constexpr char const * UI_MAINMENU_TYPED = "typed"; // Value: the character typed + + +class UIMainMenuPresenterClass : public UIPresenterClass +{ + public: + enum ChoiceType { + CHOICE_NONE, + CHOICE_CAMPAIGN, + CHOICE_LOAD, + CHOICE_MULTIPLAYER, + CHOICE_INTRO, + CHOICE_OPTIONS, + CHOICE_EXIT, + CHOICE_CREDITS, + }; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + virtual void Service(void) override; + + // The selection the caller's own switch is written against. + int Selection(void) const; + + // Is the version screen waiting to be run? It is a screen of a different kind, so + // it nests, and the view gets out of its way the way the dialog's ShowWindow did. + bool VersionPending = false; + void Run_Pending(void); + + /* + ** The view-model. Plain values, and the only thing a view reads. + */ + + // Is there a saved game to offer? With none the load button is disabled, as the + // dialog disabled it. + bool CanLoad = false; + + ChoiceType Choice = CHOICE_NONE; +}; + + +// Shows the screen through its RmlUi view. FAILED_TO_OPEN leaves nothing shown and the +// caller falls through to the legacy dialog. +UIResult UI_Main_Menu_Screen(UIMainMenuPresenterClass & presenter); From 3404188992e20c2be1362a3b97fa3a1d799a1eeb Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 04:33:50 +0100 Subject: [PATCH 110/179] refactor(ui): put the game type and multiplayer choices behind presenters Classified preserved: both legacy dialogs are still what is shown, and each now reads a view-model and queues intents its driver executes after the pump. code/ui/uigametype.{h,cpp} carries the choice between the base game and Firestorm. What it fixes in place is the dialog's own default arm: every identifier that was not Firestorm and not a cancel is the base game, and only a cancel stops the game carrying on. Clearing the active addons before the choice is applied and setting the required addon afterwards are part of applying it, so they moved with it. code/ui/uimpselect.{h,cpp} carries the multiplayer game selection. Only network and skirmish answer; modem and serial falls into the same default arm as backing out, which is where the dialog sent it. The internet and world domination buttons stay where the template put them and are disabled, because neither the service they led to nor the tour it hosted can be reached, and that is now a view-model fact rather than an EnableWindow in a message handler. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/addon.cpp | 60 +++++++++++++++------------ code/mplayer.cpp | 88 ++++++++++++++++++++++------------------ code/ui/uigametype.cpp | 88 ++++++++++++++++++++++++++++++++++++++++ code/ui/uigametype.h | 50 +++++++++++++++++++++++ code/ui/uimpselect.cpp | 92 ++++++++++++++++++++++++++++++++++++++++++ code/ui/uimpselect.h | 68 +++++++++++++++++++++++++++++++ 6 files changed, 382 insertions(+), 64 deletions(-) create mode 100644 code/ui/uigametype.cpp create mode 100644 code/ui/uigametype.h create mode 100644 code/ui/uimpselect.cpp create mode 100644 code/ui/uimpselect.h diff --git a/code/addon.cpp b/code/addon.cpp index 4a97f3f84..701873745 100644 --- a/code/addon.cpp +++ b/code/addon.cpp @@ -16,9 +16,12 @@ #include "init.h" #include "language/language.h" #include "ownrdraw.h" +#include "ui/uigametype.h" INT_PTR CALLBACK Select_Game_Type_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); +static UIGameTypePresenterClass * _GameTypeScreen = NULL; + int AvailableAddOns = 1 << ADDON_BASE_GAME; int ActiveAddOns = 1 << ADDON_BASE_GAME; AddonType RequiredAddon = ADDON_BASE_GAME; @@ -55,46 +58,47 @@ AddonType operator--(AddonType & val) /// bool; Should the game carry on? Returns false if the player backed out. bool Select_Game_Type_Dialog(AddonType &type) { - int retval; - type = ADDON_BASE_GAME; if (Addon_Installed(ADDON_ANY)) { + UIGameTypePresenterClass screen; + screen.Refresh(); + + _GameTypeScreen = &screen; + HWND dialog = OwnerDraw::Begin_Dialog(IDD_SELECT_GAME_TYPE, Select_Game_Type_Dialog_Proc); if (dialog != 0) { - SetWindowLongPtr(dialog, DWLP_USER, (LONG_PTR)&retval); OwnerDraw::Display_Dialog(dialog); - retval = -1; - while (retval == -1) { + while (!screen.Result.has_value()) { if (OwnerDraw::Dialog_Message_Handler() == true) { break; } - Title_Screen_Restore(false); + screen.Drain(); + screen.Service(); } ShowWindow(dialog, SW_HIDE); UpdateWindow(MainWindow); OwnerDraw::End_Dialog(dialog); - ActiveAddOns = 1 << ADDON_BASE_GAME; - switch (retval) { - default: - type = ADDON_BASE_GAME; - break; + int addon = ADDON_BASE_GAME; + bool const carry_on = screen.Apply(addon); + type = (AddonType)addon; - case IDC_GAMETYPE_FIRESTORM: - Enable_Addon(ADDON_FIRESTORM); - type = ADDON_FIRESTORM; - break; + _GameTypeScreen = NULL; - case IDCANCEL: - return(false); + if (!carry_on) { + return(false); } + + return(true); } + _GameTypeScreen = NULL; + Set_Required_Addon(type); return(true); } @@ -110,18 +114,24 @@ bool Select_Game_Type_Dialog(AddonType &type) ///
INT_PTR CALLBACK Select_Game_Type_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) { - int * retval; - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - if (rc == 0) { - switch (message) { - case WM_COMMAND: - retval = (int *)GetWindowLongPtr(window, DWLP_USER); - *retval = LOWORD(wparam); + if (rc == 0 && _GameTypeScreen != NULL && message == WM_COMMAND) { + switch (LOWORD(wparam)) { + case IDC_GAMETYPE_FIRESTORM: + _GameTypeScreen->Queue(UIIntent{UI_GAMETYPE_FIRESTORM, "", 0}); + break; + + case IDCANCEL: + _GameTypeScreen->Queue(UIIntent{UI_GAMETYPE_BACK, "", 0}); + break; + + default: + // The dialog's own default arm: any identifier that was not Firestorm and + // not a cancel is the base game. + _GameTypeScreen->Queue(UIIntent{UI_GAMETYPE_ORIGINAL, "", 0}); break; } - rc = 0; } return(rc); diff --git a/code/mplayer.cpp b/code/mplayer.cpp index 9fbd31f15..c277f34f6 100644 --- a/code/mplayer.cpp +++ b/code/mplayer.cpp @@ -47,11 +47,14 @@ #include "msgbox.h" #include "ownrdraw.h" #include "session.h" +#include "ui/uimpselect.h" class ListClass; INT_PTR CALLBACK Select_MPlayer_Game_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); +static UIMPSelectPresenterClass * _MPSelectScreen = NULL; + /// /// Prompts the player for which kind of multiplayer game to start. /// @@ -64,9 +67,14 @@ GameType Select_MPlayer_Game (void) return(retval); } + UIMPSelectPresenterClass screen; + screen.Refresh(); + + _MPSelectScreen = &screen; + HWND dialog; - if (Addon_Installed(ADDON_FIRESTORM) == ADDON_FIRESTORM) { + if (screen.Variant == UIMPSelectPresenterClass::VARIANT_FIRESTORM) { dialog = OwnerDraw::Begin_Dialog(IDD_MPLAYER_SELECT_GAME_FS, Select_MPlayer_Game_Dialog_Proc); } else { dialog = OwnerDraw::Begin_Dialog(IDD_MPLAYER_SELECT_GAME, Select_MPlayer_Game_Dialog_Proc); @@ -75,43 +83,29 @@ GameType Select_MPlayer_Game (void) if (dialog) { - int rc; - SetWindowLongPtr(dialog, DWLP_USER, (LONG_PTR)&rc); - - bool process = true; - while (process) { - OwnerDraw::Move_Dialog(dialog, -1, (HiddenSurface->Get_Height() - 400) / 2 + 147); - OwnerDraw::Display_Dialog(dialog); - rc = -1; - while (rc == -1) { - if (OwnerDraw::Dialog_Message_Handler() == true) { - break; - } - Title_Screen_Restore(); - } + OwnerDraw::Move_Dialog(dialog, -1, (HiddenSurface->Get_Height() - 400) / 2 + 147); + OwnerDraw::Display_Dialog(dialog); - ShowWindow(dialog, SW_HIDE); - UpdateWindow(MainWindow); - switch (rc) { - case IDC_NETWORK: - retval = GAME_IPX; - break; - case IDC_SKIRMISH: - retval = GAME_SKIRMISH; - break; - default: - retval = GAME_NORMAL; - process = false; - break; - } - if (retval != GAME_NORMAL) { + while (!screen.Result.has_value()) { + if (OwnerDraw::Dialog_Message_Handler() == true) { break; } + + screen.Drain(); + screen.Service(); } + ShowWindow(dialog, SW_HIDE); + UpdateWindow(MainWindow); + + retval = (GameType)screen.Session_Type(); + OwnerDraw::End_Dialog(dialog); Session.Read_Scenario_Descriptions(); } + + _MPSelectScreen = NULL; + return(retval); } /* end of Select_MPlayer_Game */ @@ -123,21 +117,18 @@ GameType Select_MPlayer_Game (void) /// left unhandled. INT_PTR CALLBACK Select_MPlayer_Game_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) { - int * retval; HWND handle; INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - if (message == WM_INITDIALOG) { - // Neither the online service these led to nor the tour it hosted can be reached, - // so the buttons are left on the dialog but never answer. + if (message == WM_INITDIALOG && _MPSelectScreen != NULL) { handle = GetDlgItem(window, IDC_INTERNET); if (handle) { - EnableWindow(handle, FALSE); + EnableWindow(handle, _MPSelectScreen->InternetAvailable ? TRUE : FALSE); } handle = GetDlgItem(window, IDC_WORLDDOM); if (handle) { - EnableWindow(handle, FALSE); + EnableWindow(handle, _MPSelectScreen->WorldDominationAvailable ? TRUE : FALSE); } } @@ -145,9 +136,28 @@ INT_PTR CALLBACK Select_MPlayer_Game_Dialog_Proc(HWND window, UINT message, WPAR return(rc); } - if (message == WM_COMMAND) { - retval = (int *)GetWindowLongPtr(window, DWLP_USER); - *retval = LOWORD(wparam); + if (message == WM_COMMAND && _MPSelectScreen != NULL) { + switch (LOWORD(wparam)) { + case IDC_NETWORK: + _MPSelectScreen->Queue(UIIntent{UI_MPSELECT_NETWORK, "", 0}); + break; + + case IDC_SKIRMISH: + _MPSelectScreen->Queue(UIIntent{UI_MPSELECT_SKIRMISH, "", 0}); + break; + + case IDC_INTERNET: + _MPSelectScreen->Queue(UIIntent{UI_MPSELECT_INTERNET, "", 0}); + break; + + case IDC_WORLDDOM: + _MPSelectScreen->Queue(UIIntent{UI_MPSELECT_WORLDDOM, "", 0}); + break; + + default: + _MPSelectScreen->Queue(UIIntent{UI_MPSELECT_BACK, "", 0}); + break; + } } return(false); } diff --git a/code/ui/uigametype.cpp b/code/ui/uigametype.cpp new file mode 100644 index 000000000..f02a1a3f3 --- /dev/null +++ b/code/ui/uigametype.cpp @@ -0,0 +1,88 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The game type screen. Behavior traced out of Select_Game_Type_Dialog and its procedure in +// addon.cpp. +// +// What the extraction fixes in place: the addon state is cleared before the choice is +// applied and the required addon is set afterwards whichever way the player went, and only +// backing out stops the game carrying on, which is the dialog's own default arm reading any +// identifier that was not Firestorm as the base game. +// +// docs/UI_DESIGN.md, "Screens", owns the contracts this keeps to. + +#include "always.h" + +#include "uigametype.h" + +#include "uirmlview.h" + +#include "addon.h" +#include "init.h" + +#include +#include +#include + + +void UIGameTypePresenterClass::Refresh(void) +{ + Choice = CHOICE_NONE; +} + + +/// +/// The maintenance the dialog driver ran on every pass of its own loop. +/// +void UIGameTypePresenterClass::Service(void) +{ + Title_Screen_Restore(false); +} + + +bool UIGameTypePresenterClass::Apply(int & addon) +{ + // Every addon off before the choice is applied, which is what the dialog did by + // assigning the active set directly. + Disable_Addon(ADDON_ANY); + + if (Choice == CHOICE_BACK) { + return(false); + } + + if (Choice == CHOICE_FIRESTORM) { + Enable_Addon(ADDON_FIRESTORM); + addon = ADDON_FIRESTORM; + } else { + addon = ADDON_BASE_GAME; + } + + Set_Required_Addon((AddonType)addon); + return(true); +} + + +void UIGameTypePresenterClass::Execute(UIIntent const & intent) +{ + UIResult result; + result.Outcome = UIResult::OUTCOME_ACCEPTED; + + if (intent.Action == UI_GAMETYPE_FIRESTORM) { + Choice = CHOICE_FIRESTORM; + } else if (intent.Action == UI_GAMETYPE_ORIGINAL) { + Choice = CHOICE_ORIGINAL; + } else if (intent.Action == UI_GAMETYPE_BACK) { + Choice = CHOICE_BACK; + result.Outcome = UIResult::OUTCOME_CANCELLED; + } else { + return; + } + + Result = result; +} diff --git a/code/ui/uigametype.h b/code/ui/uigametype.h new file mode 100644 index 000000000..501a8961e --- /dev/null +++ b/code/ui/uigametype.h @@ -0,0 +1,50 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The game type screen's behavior, with no toolkit in it. Two buttons and a way back, shown +// only when an expansion is installed and there is a choice to make. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + + +inline constexpr char const * UI_GAMETYPE_ORIGINAL = "original"; +inline constexpr char const * UI_GAMETYPE_FIRESTORM = "firestorm"; +inline constexpr char const * UI_GAMETYPE_BACK = "back"; + + +class UIGameTypePresenterClass : public UIPresenterClass +{ + public: + enum ChoiceType { + CHOICE_NONE, + CHOICE_ORIGINAL, + CHOICE_FIRESTORM, + CHOICE_BACK, + }; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + virtual void Service(void) override; + + // Puts the addon system into the state the choice asks for, and says whether the + // game carries on. Anything but backing out carries on, which is the dialog's own + // default arm. + bool Apply(int & addon); + + ChoiceType Choice = CHOICE_NONE; +}; + + +// Shows the screen through its RmlUi view. FAILED_TO_OPEN leaves nothing shown and the +// caller falls through to the legacy dialog. +UIResult UI_Game_Type_Screen(UIGameTypePresenterClass & presenter); diff --git a/code/ui/uimpselect.cpp b/code/ui/uimpselect.cpp new file mode 100644 index 000000000..b96dab47e --- /dev/null +++ b/code/ui/uimpselect.cpp @@ -0,0 +1,92 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The multiplayer game selection screen. Behavior traced out of Select_MPlayer_Game and its +// procedure in mplayer.cpp. +// +// What the extraction fixes in place: only the network and skirmish buttons answer, and +// anything else leaves with no session chosen, which is the dialog's own default arm; the +// internet and world domination buttons stay where the template put them and are disabled, +// because neither the service they led to nor the tour it hosted can be reached; and the +// modem and serial button is on the template but reaches nothing, so it leaves as well. +// +// docs/UI_DESIGN.md, "Screens", owns the contracts this keeps to. + +#include "always.h" + +#include "uimpselect.h" + +#include "uirmlview.h" + +#include "addon.h" +#include "init.h" +#include "session.h" + +#include +#include +#include + + +void UIMPSelectPresenterClass::Refresh(void) +{ + Variant = (Addon_Installed(ADDON_FIRESTORM) == ADDON_FIRESTORM) ? VARIANT_FIRESTORM : VARIANT_BASE; + InternetAvailable = false; + WorldDominationAvailable = false; + Choice = CHOICE_NONE; +} + + +/// +/// The maintenance the dialog driver ran on every pass of its own loop. +/// +void UIMPSelectPresenterClass::Service(void) +{ + Title_Screen_Restore(); +} + + +/// +/// The session type the caller's own switch is written against. +/// +int UIMPSelectPresenterClass::Session_Type(void) const +{ + switch (Choice) { + case CHOICE_NETWORK: return(GAME_IPX); + case CHOICE_SKIRMISH: return(GAME_SKIRMISH); + default: return(GAME_NORMAL); + } +} + + +void UIMPSelectPresenterClass::Execute(UIIntent const & intent) +{ + if (intent.Action == UI_MPSELECT_INTERNET && !InternetAvailable) { + return; + } + if (intent.Action == UI_MPSELECT_WORLDDOM && !WorldDominationAvailable) { + return; + } + + UIResult result; + result.Outcome = UIResult::OUTCOME_ACCEPTED; + + if (intent.Action == UI_MPSELECT_NETWORK) { + Choice = CHOICE_NETWORK; + } else if (intent.Action == UI_MPSELECT_SKIRMISH) { + Choice = CHOICE_SKIRMISH; + } else { + // Modem and serial, and anything else the screen carries, leave with no session + // chosen, which is where the dialog's default arm sent them. + Choice = CHOICE_BACK; + result.Outcome = UIResult::OUTCOME_CANCELLED; + } + + result.Value = Session_Type(); + Result = result; +} diff --git a/code/ui/uimpselect.h b/code/ui/uimpselect.h new file mode 100644 index 000000000..6f490dd0f --- /dev/null +++ b/code/ui/uimpselect.h @@ -0,0 +1,68 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The multiplayer game selection screen's behavior, with no toolkit in it. Two templates +// share it and they differ by which buttons exist, so the variant is part of the view-model +// rather than something a view works out for itself. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + + +inline constexpr char const * UI_MPSELECT_INTERNET = "internet"; +inline constexpr char const * UI_MPSELECT_WORLDDOM = "worlddom"; +inline constexpr char const * UI_MPSELECT_MODEM = "modem"; +inline constexpr char const * UI_MPSELECT_NETWORK = "network"; +inline constexpr char const * UI_MPSELECT_SKIRMISH = "skirmish"; +inline constexpr char const * UI_MPSELECT_BACK = "back"; + + +class UIMPSelectPresenterClass : public UIPresenterClass +{ + public: + enum VariantType { + VARIANT_BASE, + VARIANT_FIRESTORM, + }; + + enum ChoiceType { + CHOICE_NONE, + CHOICE_NETWORK, + CHOICE_SKIRMISH, + CHOICE_BACK, + }; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + virtual void Service(void) override; + + // The session type the caller's own switch is written against. + int Session_Type(void) const; + + /* + ** The view-model. Plain values, and the only thing a view reads. + */ + VariantType Variant = VARIANT_BASE; + + // Neither the online service these led to nor the tour it hosted can be reached, so + // the buttons stay on the screen and never answer, which is what the dialog did by + // disabling them. + bool InternetAvailable = false; + bool WorldDominationAvailable = false; + + ChoiceType Choice = CHOICE_NONE; +}; + + +// Shows the screen through its RmlUi view. FAILED_TO_OPEN leaves nothing shown and the +// caller falls through to the legacy dialog. +UIResult UI_MPlayer_Select_Screen(UIMPSelectPresenterClass & presenter); From cfb837e5121faa4e2a6131983f8ccf54d09ef2b6 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 04:44:49 +0100 Subject: [PATCH 111/179] fix(ui): let the runner return for a screen that steps aside The in-game options screen sets a pending sub-screen without a result, and UI_Run_Modal only left its loop on a result, so the browsers it opens were unreachable through the RmlUi view. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/ui/uigameoptions.h | 1 + code/ui/uimainmenu.h | 1 + code/ui/uiscreen.h | 5 +++++ code/ui/uishell.cpp | 9 ++++++++- 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/code/ui/uigameoptions.h b/code/ui/uigameoptions.h index 0416785ec..65344d83a 100644 --- a/code/ui/uigameoptions.h +++ b/code/ui/uigameoptions.h @@ -60,6 +60,7 @@ class UIGameOptionsPresenterClass : public UIPresenterClass virtual void Execute(UIIntent const & intent) override; virtual void Refresh(void) override; + virtual bool Suspends(void) const override { return(Pending != SUB_NONE); } // Runs the sub-screen an executed intent asked for and clears the request. Safe to // call with nothing pending. diff --git a/code/ui/uimainmenu.h b/code/ui/uimainmenu.h index eb9555415..fa0f10998 100644 --- a/code/ui/uimainmenu.h +++ b/code/ui/uimainmenu.h @@ -46,6 +46,7 @@ class UIMainMenuPresenterClass : public UIPresenterClass virtual void Execute(UIIntent const & intent) override; virtual void Refresh(void) override; virtual void Service(void) override; + virtual bool Suspends(void) const override { return(VersionPending); } // The selection the caller's own switch is written against. int Selection(void) const; diff --git a/code/ui/uiscreen.h b/code/ui/uiscreen.h index 0a8bd5837..6c550ddc0 100644 --- a/code/ui/uiscreen.h +++ b/code/ui/uiscreen.h @@ -79,6 +79,11 @@ class UIPresenterClass // was rather than moved into the runner. virtual void Service(void) {} + // Does the screen want its runner to return before it has a result? A screen of a + // different kind opened over this one draws where this one is, so the owner takes + // this one off the screen and puts it back, which is what a dialog's ShowWindow did. + virtual bool Suspends(void) const { return(false); } + std::optional Result; bool IsClosing = false; diff --git a/code/ui/uishell.cpp b/code/ui/uishell.cpp index f799ee517..3d2f07722 100644 --- a/code/ui/uishell.cpp +++ b/code/ui/uishell.cpp @@ -961,7 +961,7 @@ UIResult UI_Run_Modal(UIPresenterClass & presenter, UIRmlViewClass & view) _RunningModal++; - while (!presenter.Result.has_value()) { + while (!presenter.Result.has_value() && !presenter.Suspends()) { Windows_Message_Handler(); if (Session.Type != GAME_NORMAL && Session.Type != GAME_SKIRMISH && !Session.NetOpen && !Session.Suspended) { @@ -993,5 +993,12 @@ UIResult UI_Run_Modal(UIPresenterClass & presenter, UIRmlViewClass & view) } _RunningModal--; + + // A screen that asked to be stepped aside has no result yet; its owner runs the screen + // it opened and calls back in. + if (!presenter.Result.has_value()) { + return(result); + } + return(presenter.Result.value()); } From c38d7545a7b75babf9c1518de6d51f1f1de799fc Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 04:49:16 +0100 Subject: [PATCH 112/179] feat(ui): show the main menu and campaign choice through RmlUi The main menu document carries Ctrl+V, Ctrl+Alt+C and the cheat words itself, because the shell's modal scope takes every key message before the game's own queue sees it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/init.cpp | 72 +++++++++++++++++++++---- code/ui/uicampaign.cpp | 113 +++++++++++++++++++++++++++++++++++++++ code/ui/uimainmenu.cpp | 118 +++++++++++++++++++++++++++++++++++++++++ ui/campaign.rcss | 79 +++++++++++++++++++++++++++ ui/campaign.rml | 23 ++++++++ ui/mainmenu.rcss | 36 +++++++++++++ ui/mainmenu.rml | 17 ++++++ 7 files changed, 448 insertions(+), 10 deletions(-) create mode 100644 ui/campaign.rcss create mode 100644 ui/campaign.rml create mode 100644 ui/mainmenu.rcss create mode 100644 ui/mainmenu.rml diff --git a/code/init.cpp b/code/init.cpp index 72601c9c1..74bd4b5f4 100644 --- a/code/init.cpp +++ b/code/init.cpp @@ -843,6 +843,17 @@ static CampaignType Choose_Campaign(void) UICampaignPresenterClass screen; screen.Refresh(); + // The selection is latched here, at screen entry, and the legacy dialog opens only when + // the document could not be prepared. + if (UI_Use_Rml()) { + if (UI_Campaign_Screen(screen).Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { + return((CampaignType)screen.Chosen()); + } + + screen.IsClosing = false; + screen.Result.reset(); + } + _CampaignScreen = &screen; dialog = OwnerDraw::Begin_Dialog(IDD_CAMPAIGN, Campaign_Choice_Dialog_Proc); @@ -3083,6 +3094,29 @@ void Version_Dialog(void) } +/// +/// Puts the title screen behind the menu. +/// +static void Draw_Title_Screen(void) +{ + char * menu = Get_New_Menu()->Background; + Load_Title_Screen(menu, HiddenSurface, &CCPalette); + Draw_Version_Text(HiddenSurface); + Update_Visible_Surface(); +} + + +/// +/// Seeds the cryptographic random number generator from the clock. +/// +static void Seed_Crypto_Random(void) +{ + SYSTEMTIME t; + GetSystemTime(&t); + CryptRandom.Seed_Byte(t.wMilliseconds); +} + + /*************************************************************************** * Main_Menu -- Menu processing * * * @@ -3110,14 +3144,37 @@ int Main_Menu(unsigned int timeout) _MainMenuScreen = &screen; + // The selection is latched here, at screen entry, and the legacy dialog opens only when + // the document could not be prepared. + if (UI_Use_Rml()) { + Draw_Title_Screen(); + + UIResult const result = UI_Main_Menu_Screen(screen); + + if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { + // A session that ended underneath the screen leaves the menu, which is what the + // driver's own exit intent did for the same condition. + if (result.Outcome == UIResult::OUTCOME_SESSION_ENDED) { + screen.Choice = UIMainMenuPresenterClass::CHOICE_EXIT; + } + + retval = screen.Selection(); + Seed_Crypto_Random(); + + _MainMenuScreen = NULL; + SetFocus(MainWindow); + return(retval); + } + + screen.IsClosing = false; + screen.Result.reset(); + } + dialog = OwnerDraw::Begin_Dialog(IDD_MAIN_MENU, Main_Menu_Dialog_Proc); assert(dialog != NULL); if (dialog != NULL) { - char *menu = Get_New_Menu()->Background; - Load_Title_Screen(menu, HiddenSurface, &CCPalette); - Draw_Version_Text(HiddenSurface); - Update_Visible_Surface(); + Draw_Title_Screen(); OwnerDraw::Move_Dialog(dialog, -1, (HiddenSurface->Get_Height() - 400) / 2 + 147); OwnerDraw::Display_Dialog(dialog); SetFocus(MainWindow); @@ -3169,12 +3226,7 @@ int Main_Menu(unsigned int timeout) OwnerDraw::End_Dialog(dialog); - /* - * Seed cryptographic random number generator. - */ - SYSTEMTIME t; - GetSystemTime(&t); - CryptRandom.Seed_Byte(t.wMilliseconds); + Seed_Crypto_Random(); } else { retval = SEL_EXIT; } diff --git a/code/ui/uicampaign.cpp b/code/ui/uicampaign.cpp index b8025c474..5f8fa053c 100644 --- a/code/ui/uicampaign.cpp +++ b/code/ui/uicampaign.cpp @@ -157,3 +157,116 @@ void UICampaignPresenterClass::Execute(UIIntent const & intent) result.Value = Chosen(); Result = result; } + + +//--------------------------------------------------------------------------------------- +// The RmlUi view. +//--------------------------------------------------------------------------------------- + +/// +/// The RmlUi half of the campaign choice screen. +/// +class CampaignViewClass : public UIRmlViewClass +{ + public: + CampaignViewClass(UICampaignPresenterClass & presenter) : + UIRmlViewClass(presenter, "campaign.rml"), + Screen(presenter) + { + } + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + // The track bar takes its position from the model as the document loads, and that + // raises a change event of its own. Nothing is queued until this is set. + void Settle(void) { Settled = true; } + + private: + UICampaignPresenterClass & Screen; + bool Settled = false; +}; + + +void CampaignViewClass::Bind(Rml::DataModelConstructor & model) +{ + if (auto entry = model.RegisterStruct()) { + entry.RegisterMember("label", &UICampaignPresenterClass::EntryType::Label); + } + model.RegisterArray>(); + + model.Bind("campaigns", &Screen.Campaigns); + model.Bind("selected", &Screen.Selected); + model.Bind("difficulty", &Screen.Difficulty); + model.Bind("difficultyname", &Screen.DifficultyLabel); + + model.BindEventCallback("pick", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{UI_CAMPAIGN_SELECT, "", (int)arguments[0].Get()}); + }); + + // The track bar is bound one way and a position the screen already holds raises no + // intent, so setting it from the model cannot move a difficulty the player did not. + // The dialog read its slider back at accept because a keyboard or page move raised no + // thumb notification; RmlUi raises a change for every move, so there is nothing left to + // read back. + model.BindEventCallback("slide", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + if (!Settled) return; + + int const step = (int)(event.GetParameter("value", 0.0f) + 0.5f); + if (step == Screen.Difficulty) return; + + Screen.Queue(UIIntent{UI_CAMPAIGN_DIFFICULTY, "", step}); + }); + + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{arguments[0].Get(), "", 0}); + }); + + // Escape cancels and Enter accepts, which is what IsDialogMessage delivered to a dialog + // whose template names no default push button. + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key == Rml::Input::KI_ESCAPE) { + Screen.Queue(UIIntent{UI_CAMPAIGN_CANCEL, "", 0}); + } else if (key == Rml::Input::KI_RETURN || key == Rml::Input::KI_NUMPADENTER) { + Screen.Queue(UIIntent{UI_CAMPAIGN_ACCEPT, "", 0}); + } + }); +} + + +void CampaignViewClass::Sync(void) +{ + if (!Model) return; + + Model.DirtyVariable("campaigns"); + Model.DirtyVariable("selected"); + Model.DirtyVariable("difficultyname"); +} + + +/// +/// Shows the campaign list and waits for the player to choose. +/// +UIResult UI_Campaign_Screen(UICampaignPresenterClass & presenter) +{ + CampaignViewClass view(presenter); + + if (!view.Prepare(true)) { + UIResult result; + result.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + return(result); + } + + view.Settle(); + + UIResult const result = UI_Run_Modal(presenter, view); + view.Close(); + return(result); +} diff --git a/code/ui/uimainmenu.cpp b/code/ui/uimainmenu.cpp index 3b7bd807a..3145cc694 100644 --- a/code/ui/uimainmenu.cpp +++ b/code/ui/uimainmenu.cpp @@ -134,3 +134,121 @@ void UIMainMenuPresenterClass::Execute(UIIntent const & intent) result.Value = Selection(); Result = result; } + + +//--------------------------------------------------------------------------------------- +// The RmlUi view. +//--------------------------------------------------------------------------------------- + +/// +/// The RmlUi half of the main menu. +/// +class MainMenuViewClass : public UIRmlViewClass +{ + public: + MainMenuViewClass(UIMainMenuPresenterClass & presenter) : + UIRmlViewClass(presenter, "mainmenu.rml"), + Screen(presenter) + { + } + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + private: + UIMainMenuPresenterClass & Screen; + + // Was a modifier held for the key that produced the character now arriving? The + // driver read the version and credits combinations off the queue before anything + // else saw them, so a character they produce is not a character the player typed. + bool Modified = false; +}; + + +void MainMenuViewClass::Bind(Rml::DataModelConstructor & model) +{ + model.Bind("canload", &Screen.CanLoad); + + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{arguments[0].Get(), "", 0}); + }); + + // The keys the driver watched for beside the buttons are the screen's, so the document + // carries them: the shell's modal scope takes every key message before the game's own + // queue sees it, and Keyboard->Check() never fires again while a document is shown. + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + bool const ctrl = event.GetParameter("ctrl_key", false); + bool const alt = event.GetParameter("alt_key", false); + + Modified = ctrl || alt; + + if (key == Rml::Input::KI_V && ctrl && !alt) { + Screen.Queue(UIIntent{UI_MAINMENU_VERSION, "", 0}); + } else if (key == Rml::Input::KI_C && ctrl && alt) { + Screen.Queue(UIIntent{UI_MAINMENU_CREDITS, "", 0}); + } + }); + + // A cheat word is spelled out, so the screen wants the character rather than the key + // that produced it. + model.BindEventCallback("typed", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + if (Modified) return; + + Rml::String const text = event.GetParameter("text", Rml::String()); + for (char const letter : text) { + Screen.Queue(UIIntent{UI_MAINMENU_TYPED, "", (int)letter}); + } + }); +} + + +void MainMenuViewClass::Sync(void) +{ + if (!Model) return; + + Model.DirtyVariable("canload"); +} + + +/// +/// Shows the main menu and waits for the player to choose. +/// +UIResult UI_Main_Menu_Screen(UIMainMenuPresenterClass & presenter) +{ + MainMenuViewClass view(presenter); + + if (!view.Prepare(true)) { + UIResult result; + result.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + return(result); + } + + // The version screen is a screen of a different kind, so it nests; getting out of the + // way of it is hiding this document, which is what the dialog's ShowWindow did. + while (!presenter.Result.has_value()) { + UIResult const pass = UI_Run_Modal(presenter, view); + + if (pass.GameEnded) { + view.Close(); + return(pass); + } + + if (!presenter.VersionPending) { + break; + } + + view.Hide(); + presenter.Run_Pending(); + view.Show(); + view.Sync(); + } + + UIResult const result = presenter.Result.value_or(UIResult{}); + view.Close(); + return(result); +} diff --git a/ui/campaign.rcss b/ui/campaign.rcss new file mode 100644 index 000000000..91dab2e0f --- /dev/null +++ b/ui/campaign.rcss @@ -0,0 +1,79 @@ +/* The campaign choice. Geometry from the IDD_CAMPAIGN template, converted from dialog units + at the 8 point MS Sans Serif the template names: 1.5 pixels across and 1.625 down, with a + child's offset taken from the panel's content box and the panel's declared size taken + inside its own border. */ + +/* 246 x 149 dialog units, so 369 x 242.125 pixels. + + The panel is not centred vertically. Its driver moved it with + Move_Dialog(handle, -1, (HiddenSurface->Get_Height() - 400) / 2 + 147), which is half the + frame height less 53, so it sits 53 dp above the middle whatever the frame height is. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -184.5dp; + margin-top: -53dp; + + width: 365dp; + height: 238.125dp; +} + +/* CTEXT with SS_CENTERIMAGE, 206 x 10 dialog units at 22, 12. */ +#question +{ + left: 31dp; + top: 17.5dp; + width: 309dp; + height: 16.25dp; + line-height: 16.25dp; + text-align: center; +} + +/* The campaign list, 206 x 51 dialog units at 22, 28. The template draws it with NOT + WS_BORDER, so it has none. */ +#campaignlist +{ + left: 31dp; + top: 43.5dp; + width: 309dp; + height: 82.875dp; +} + +/* A row spans the list less its scrollbar. */ +#campaignlist .row { width: 297dp; } + +/* The left caption is a static with SS_LEFTNOWORDWRAP and SS_CENTERIMAGE, 136 x 15 dialog + units at 22, 84; the value beside it is RTEXT, 66 x 15 at 162, 84. */ +.caption +{ + height: 24.375dp; + line-height: 24.375dp; +} + +#difficultylabel { left: 31dp; top: 134.5dp; width: 204dp; } +#difficultyvalue { left: 241dp; top: 134.5dp; width: 99dp; text-align: right; } + +/* The difficulty track bar, 206 x 15 dialog units at 22, 98. TBS_NOTICKS, so the bar is a + plain groove with a thumb. */ +#difficulty +{ + left: 31dp; + top: 157.25dp; + width: 309dp; + height: 24.375dp; +} + +.slider slidertrack { margin-top: 9.1875dp; } + +/* OK and Cancel, both 50 x 16 dialog units at y 121. */ +.button +{ + top: 194.625dp; + width: 75dp; + height: 26dp; + line-height: 26dp; +} + +#ok { left: 179.5dp; } +#cancel { left: 265dp; } diff --git a/ui/campaign.rml b/ui/campaign.rml new file mode 100644 index 000000000..c42d6bf3e --- /dev/null +++ b/ui/campaign.rml @@ -0,0 +1,23 @@ + + + Select campaign + + + + +
+
Select Campaign:
+ +
+
{{ entry.label }}
+
+ +
Difficulty
+
{{ difficultyname }}
+ + +
[[TXT_OK]]
+
[[TXT_CANCEL]]
+
+ +
diff --git a/ui/mainmenu.rcss b/ui/mainmenu.rcss new file mode 100644 index 000000000..f8c6ad4c3 --- /dev/null +++ b/ui/mainmenu.rcss @@ -0,0 +1,36 @@ +/* The main menu. Geometry from the IDD_MAIN_MENU template, converted from dialog units at + the 8 point MS Sans Serif the template names: 1.5 pixels across and 1.625 down, with a + child's offset taken from the panel's content box and the panel's declared size taken + inside its own border. */ + +/* 204 x 147 dialog units, so 306 x 238.875 pixels. + + The panel is not centred vertically. Its driver moved it with + Move_Dialog(handle, -1, (HiddenSurface->Get_Height() - 400) / 2 + 147), which is half the + frame height less 53, so it sits 53 dp above the middle whatever the frame height is. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -153dp; + margin-top: -53dp; + + width: 302dp; + height: 234.875dp; +} + +/* Six buttons, all 130 x 18 dialog units at x 37. */ +.button +{ + left: 53.5dp; + width: 195dp; + height: 29.25dp; + line-height: 29.25dp; +} + +#campaign { top: 17.5dp; } +#load { top: 51.625dp; } +#multiplayer { top: 85.75dp; } +#intro { top: 119.875dp; } +#options { top: 154dp; } +#exit { top: 188.125dp; } diff --git a/ui/mainmenu.rml b/ui/mainmenu.rml new file mode 100644 index 000000000..8d5e89561 --- /dev/null +++ b/ui/mainmenu.rml @@ -0,0 +1,17 @@ + + + Main menu + + + + +
+
New Campaign
+
Load Mission
+
Multiplayer Game
+
Intro / Sneak Peek
+
Options
+
Exit Game
+
+ +
From b8a1e885cd606bd7c5e79efb489758461a478fe4 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 04:49:16 +0100 Subject: [PATCH 113/179] feat(ui): show the game type and multiplayer choices through RmlUi Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/addon.cpp | 15 +++++++++ code/mplayer.cpp | 14 +++++++++ code/ui/uigametype.cpp | 65 ++++++++++++++++++++++++++++++++++++++ code/ui/uimpselect.cpp | 71 ++++++++++++++++++++++++++++++++++++++++++ ui/gametype.rcss | 41 ++++++++++++++++++++++++ ui/gametype.rml | 15 +++++++++ ui/mpselect.rcss | 46 +++++++++++++++++++++++++++ ui/mpselect.rml | 17 ++++++++++ ui/mpselectfs.rcss | 47 ++++++++++++++++++++++++++++ ui/mpselectfs.rml | 18 +++++++++++ 10 files changed, 349 insertions(+) create mode 100644 ui/gametype.rcss create mode 100644 ui/gametype.rml create mode 100644 ui/mpselect.rcss create mode 100644 ui/mpselect.rml create mode 100644 ui/mpselectfs.rcss create mode 100644 ui/mpselectfs.rml diff --git a/code/addon.cpp b/code/addon.cpp index 701873745..3e410f8a5 100644 --- a/code/addon.cpp +++ b/code/addon.cpp @@ -17,6 +17,7 @@ #include "language/language.h" #include "ownrdraw.h" #include "ui/uigametype.h" +#include "ui/uishell.h" INT_PTR CALLBACK Select_Game_Type_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); @@ -64,6 +65,20 @@ bool Select_Game_Type_Dialog(AddonType &type) UIGameTypePresenterClass screen; screen.Refresh(); + // The selection is latched here, at screen entry, and the legacy dialog opens only + // when the document could not be prepared. + if (UI_Use_Rml()) { + if (UI_Game_Type_Screen(screen).Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { + int addon = ADDON_BASE_GAME; + bool const carry_on = screen.Apply(addon); + type = (AddonType)addon; + return(carry_on); + } + + screen.IsClosing = false; + screen.Result.reset(); + } + _GameTypeScreen = &screen; HWND dialog = OwnerDraw::Begin_Dialog(IDD_SELECT_GAME_TYPE, Select_Game_Type_Dialog_Proc); diff --git a/code/mplayer.cpp b/code/mplayer.cpp index c277f34f6..2134c0661 100644 --- a/code/mplayer.cpp +++ b/code/mplayer.cpp @@ -48,6 +48,7 @@ #include "ownrdraw.h" #include "session.h" #include "ui/uimpselect.h" +#include "ui/uishell.h" class ListClass; @@ -70,6 +71,19 @@ GameType Select_MPlayer_Game (void) UIMPSelectPresenterClass screen; screen.Refresh(); + // The selection is latched here, at screen entry, and the legacy dialog opens only when + // the document could not be prepared. + if (UI_Use_Rml()) { + if (UI_MPlayer_Select_Screen(screen).Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { + retval = (GameType)screen.Session_Type(); + Session.Read_Scenario_Descriptions(); + return(retval); + } + + screen.IsClosing = false; + screen.Result.reset(); + } + _MPSelectScreen = &screen; HWND dialog; diff --git a/code/ui/uigametype.cpp b/code/ui/uigametype.cpp index f02a1a3f3..931121a50 100644 --- a/code/ui/uigametype.cpp +++ b/code/ui/uigametype.cpp @@ -86,3 +86,68 @@ void UIGameTypePresenterClass::Execute(UIIntent const & intent) Result = result; } + + +//--------------------------------------------------------------------------------------- +// The RmlUi view. +//--------------------------------------------------------------------------------------- + +/// +/// The RmlUi half of the game type screen. +/// +class GameTypeViewClass : public UIRmlViewClass +{ + public: + GameTypeViewClass(UIGameTypePresenterClass & presenter) : + UIRmlViewClass(presenter, "gametype.rml"), + Screen(presenter) + { + } + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override {} + + private: + UIGameTypePresenterClass & Screen; +}; + + +void GameTypeViewClass::Bind(Rml::DataModelConstructor & model) +{ + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{arguments[0].Get(), "", 0}); + }); + + // The Main Menu button is the template's IDCANCEL, so Escape is what it is, and Enter + // reaches the dialog's default arm, which is the base game. + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key == Rml::Input::KI_ESCAPE) { + Screen.Queue(UIIntent{UI_GAMETYPE_BACK, "", 0}); + } else if (key == Rml::Input::KI_RETURN || key == Rml::Input::KI_NUMPADENTER) { + Screen.Queue(UIIntent{UI_GAMETYPE_ORIGINAL, "", 0}); + } + }); +} + + +/// +/// Shows the game type choice and waits for the player to make it. +/// +UIResult UI_Game_Type_Screen(UIGameTypePresenterClass & presenter) +{ + GameTypeViewClass view(presenter); + + if (!view.Prepare(true)) { + UIResult result; + result.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + return(result); + } + + UIResult const result = UI_Run_Modal(presenter, view); + view.Close(); + return(result); +} diff --git a/code/ui/uimpselect.cpp b/code/ui/uimpselect.cpp index b96dab47e..67ce7e912 100644 --- a/code/ui/uimpselect.cpp +++ b/code/ui/uimpselect.cpp @@ -90,3 +90,74 @@ void UIMPSelectPresenterClass::Execute(UIIntent const & intent) result.Value = Session_Type(); Result = result; } + + +//--------------------------------------------------------------------------------------- +// The RmlUi view. One document per template, because the two differ by which buttons exist +// rather than by how one is arranged. +//--------------------------------------------------------------------------------------- + +/// +/// The RmlUi half of the multiplayer game selection screen. +/// +class MPSelectViewClass : public UIRmlViewClass +{ + public: + MPSelectViewClass(UIMPSelectPresenterClass & presenter, char const * document) : + UIRmlViewClass(presenter, document), + Screen(presenter) + { + } + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override {} + + private: + UIMPSelectPresenterClass & Screen; +}; + + +void MPSelectViewClass::Bind(Rml::DataModelConstructor & model) +{ + model.Bind("internet", &Screen.InternetAvailable); + model.Bind("worlddom", &Screen.WorldDominationAvailable); + + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{arguments[0].Get(), "", 0}); + }); + + // The Main Menu button is the template's IDCANCEL, and Enter reaches the same default + // arm the dialog sent every unhandled identifier to. + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key == Rml::Input::KI_ESCAPE || key == Rml::Input::KI_RETURN + || key == Rml::Input::KI_NUMPADENTER) { + Screen.Queue(UIIntent{UI_MPSELECT_BACK, "", 0}); + } + }); +} + + +/// +/// Shows the multiplayer game choices and waits for the player to make one. +/// +UIResult UI_MPlayer_Select_Screen(UIMPSelectPresenterClass & presenter) +{ + char const * const document = + (presenter.Variant == UIMPSelectPresenterClass::VARIANT_FIRESTORM) ? "mpselectfs.rml" : "mpselect.rml"; + + MPSelectViewClass view(presenter, document); + + if (!view.Prepare(true)) { + UIResult result; + result.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + return(result); + } + + UIResult const result = UI_Run_Modal(presenter, view); + view.Close(); + return(result); +} diff --git a/ui/gametype.rcss b/ui/gametype.rcss new file mode 100644 index 000000000..c77366751 --- /dev/null +++ b/ui/gametype.rcss @@ -0,0 +1,41 @@ +/* The game type choice. Geometry from the IDD_SELECT_GAME_TYPE template, converted from + dialog units at the 8 point MS Sans Serif the template names: 1.5 pixels across and 1.625 + down, with a child's offset taken from the panel's content box and the panel's declared + size taken inside its own border. */ + +/* 228 x 108 dialog units, so 342 x 175.5 pixels. This is the one screen of the family its + driver does not move, so it is centred both ways. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -171dp; + margin-top: -87.75dp; + + width: 338dp; + height: 171.5dp; +} + +/* CTEXT with SS_CENTERIMAGE, 184 x 19 dialog units at 22, 12. */ +#question +{ + left: 31dp; + top: 17.5dp; + width: 276dp; + height: 30.875dp; + line-height: 30.875dp; + text-align: center; +} + +/* Three buttons, all 104 x 14 dialog units at x 62. */ +.button +{ + left: 91dp; + width: 156dp; + height: 22.75dp; + line-height: 22.75dp; +} + +#original { top: 66.25dp; } +#firestorm { top: 98.75dp; } +#back { top: 131.25dp; } diff --git a/ui/gametype.rml b/ui/gametype.rml new file mode 100644 index 000000000..73b2d36e4 --- /dev/null +++ b/ui/gametype.rml @@ -0,0 +1,15 @@ + + + Select game type + + + + +
+
Select Game Type
+
Tiberian Sun (Original)
+
Firestorm
+
Main Menu
+
+ +
diff --git a/ui/mpselect.rcss b/ui/mpselect.rcss new file mode 100644 index 000000000..fb3692b96 --- /dev/null +++ b/ui/mpselect.rcss @@ -0,0 +1,46 @@ +/* The multiplayer game choice shown with the base game. Geometry from the + IDD_MPLAYER_SELECT_GAME template, converted from dialog units at the 8 point MS Sans + Serif the template names: 1.5 pixels across and 1.625 down, with a child's offset taken + from the panel's content box and the panel's declared size taken inside its own border. */ + +/* 204 x 144 dialog units, so 306 x 234 pixels. + + The panel is not centred vertically. Its driver moved it with + Move_Dialog(handle, -1, (HiddenSurface->Get_Height() - 400) / 2 + 147), which is half the + frame height less 53, so it sits 53 dp above the middle whatever the frame height is. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -153dp; + margin-top: -53dp; + + width: 302dp; + height: 230dp; +} + +/* CTEXT with SS_CENTERIMAGE, 130 x 12 dialog units at 37, 12. */ +#question +{ + left: 53.5dp; + top: 17.5dp; + width: 195dp; + height: 19.5dp; + line-height: 19.5dp; + text-align: center; +} + +/* Five buttons, all 130 x 18 dialog units at x 37. */ +.button +{ + left: 53.5dp; + width: 195dp; + height: 29.25dp; + line-height: 29.25dp; +} + +#internet { top: 43.5dp; } +#modem { top: 77.625dp; } +#network { top: 111.75dp; } +#skirmish { top: 145.875dp; } +#back { top: 180dp; } diff --git a/ui/mpselect.rml b/ui/mpselect.rml new file mode 100644 index 000000000..1351fc499 --- /dev/null +++ b/ui/mpselect.rml @@ -0,0 +1,17 @@ + + + Select multiplayer game + + + + +
+
Select Multiplayer Game
+
Internet
+
Modem / Serial
+
Network
+
Skirmish
+
Main Menu
+
+ +
diff --git a/ui/mpselectfs.rcss b/ui/mpselectfs.rcss new file mode 100644 index 000000000..8e8565d51 --- /dev/null +++ b/ui/mpselectfs.rcss @@ -0,0 +1,47 @@ +/* The multiplayer game choice shown with the expansion installed. Geometry from the + IDD_MPLAYER_SELECT_GAME_FS template, converted from dialog units at the 8 point MS Sans + Serif the template names: 1.5 pixels across and 1.625 down, with a child's offset taken + from the panel's content box and the panel's declared size taken inside its own border. */ + +/* 197 x 146 dialog units, so 295.5 x 237.25 pixels. + + The panel is not centred vertically. Its driver moved it with + Move_Dialog(handle, -1, (HiddenSurface->Get_Height() - 400) / 2 + 147), which is half the + frame height less 53, so it sits 53 dp above the middle whatever the frame height is. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -147.75dp; + margin-top: -53dp; + + width: 291.5dp; + height: 233.25dp; +} + +/* CTEXT with SS_CENTERIMAGE, 130 x 12 dialog units at 30, 8. */ +#question +{ + left: 43dp; + top: 11dp; + width: 195dp; + height: 19.5dp; + line-height: 19.5dp; + text-align: center; +} + +/* Six buttons, all 130 x 18 dialog units at x 30. */ +.button +{ + left: 43dp; + width: 195dp; + height: 29.25dp; + line-height: 29.25dp; +} + +#internet { top: 33.75dp; } +#worlddom { top: 64.625dp; } +#modem { top: 95.5dp; } +#network { top: 126.375dp; } +#skirmish { top: 157.25dp; } +#back { top: 189.75dp; } diff --git a/ui/mpselectfs.rml b/ui/mpselectfs.rml new file mode 100644 index 000000000..3f1cfbd28 --- /dev/null +++ b/ui/mpselectfs.rml @@ -0,0 +1,18 @@ + + + Select multiplayer game + + + + +
+
Select Multiplayer Game
+
Internet
+
World Domination! (Internet)
+
Modem / Serial
+
Network
+
Skirmish
+
Main Menu
+
+ +
From e5607e6a1315658972f12b29b2c043d70764d511 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 05:11:19 +0100 Subject: [PATCH 114/179] fix(ui): bind each variant document to its own data model A view names its model after the document's own file, so a variant document naming the base document's model got no bindings and no events at all. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- ui/gamecontrolsmp.rml | 2 +- ui/gamecontrolswol.rml | 2 +- ui/gameoptionsmp.rml | 2 +- ui/gameoptionswol.rml | 2 +- ui/mpselectfs.rml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/gamecontrolsmp.rml b/ui/gamecontrolsmp.rml index e4d2f9216..ad29f12a9 100644 --- a/ui/gamecontrolsmp.rml +++ b/ui/gamecontrolsmp.rml @@ -4,7 +4,7 @@ - +
Game Speed:
diff --git a/ui/gamecontrolswol.rml b/ui/gamecontrolswol.rml index dc96ded61..b34b2fa65 100644 --- a/ui/gamecontrolswol.rml +++ b/ui/gamecontrolswol.rml @@ -4,7 +4,7 @@ - +
Scroll Rate:
diff --git a/ui/gameoptionsmp.rml b/ui/gameoptionsmp.rml index e70d5e59b..cc3c74211 100644 --- a/ui/gameoptionsmp.rml +++ b/ui/gameoptionsmp.rml @@ -4,7 +4,7 @@ - +
Game Controls
Abort Mission
diff --git a/ui/gameoptionswol.rml b/ui/gameoptionswol.rml index d820acab9..914c1dd38 100644 --- a/ui/gameoptionswol.rml +++ b/ui/gameoptionswol.rml @@ -4,7 +4,7 @@ - +
Game Controls
Load Game
diff --git a/ui/mpselectfs.rml b/ui/mpselectfs.rml index 3f1cfbd28..0e9ea001b 100644 --- a/ui/mpselectfs.rml +++ b/ui/mpselectfs.rml @@ -4,7 +4,7 @@ - +
Select Multiplayer Game
Internet
From 12455d1ebdda20dadc6c3880addb73891171dbfb Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 05:11:19 +0100 Subject: [PATCH 115/179] fix(ui): type the cheat words again after a modified key The main menu view held the modifier flag until the next keypress, so every character after Ctrl+V was dropped. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/ui/uimainmenu.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/code/ui/uimainmenu.cpp b/code/ui/uimainmenu.cpp index 3145cc694..28717f7c3 100644 --- a/code/ui/uimainmenu.cpp +++ b/code/ui/uimainmenu.cpp @@ -197,7 +197,10 @@ void MainMenuViewClass::Bind(Rml::DataModelConstructor & model) // that produced it. model.BindEventCallback("typed", [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { - if (Modified) return; + // One character follows one key, so the flag answers for that character alone. + bool const modified = Modified; + Modified = false; + if (modified) return; Rml::String const text = event.GetParameter("text", Rml::String()); for (char const letter : text) { From 9c094997f319d551209c480fc982044b0a60bca3 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 05:11:38 +0100 Subject: [PATCH 116/179] docs: record the main menu family as migrated Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/UI_DESIGN.md | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index d2bd41038..d820efbd9 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -1,7 +1,7 @@ # UI system design -Status: in progress. Steps 1 to 7 of the migration plan have landed; nothing -from step 8 onward is implemented. Everything outside the migration plan +Status: in progress. Steps 1 to 8 of the migration plan have landed; nothing +from step 9 onward is implemented. Everything outside the migration plan remains a proposal informed by source inspection and upstream documentation. This page owns the UI architecture and migration; [Building OpenTS](BUILDING.md) owns build support and [Project @@ -65,6 +65,13 @@ which the progress box opening over the wait box shows; only a second screen of the same kind is refused, and the coexistence rule still forbids a legacy dialog underneath either. +Step 8 made a suspended runner possible. `UI_Run_Modal` returns when the screen +asks to be stepped aside as well as when it has a result, because a screen that +opens another one of a different kind has no result yet and its owner has to +hide it, run the other, and show it again. Without that the in-game options +screen's save and load browsers, and the main menu's version screen, could +never be reached through an RmlUi view. + Step 7 gave a view the ability to step aside. `UIRmlViewClass` gained `Hide` and `Show`, which take a document off the screen and put it back with the modal scope it had, because the in-game options screen opens the save and load @@ -856,6 +863,33 @@ text beyond an ASCII test document. which is where the strings are owned. 8. **Main menu family** (M). `IDD_MAIN_MENU`, campaign choice, game type, multiplayer game selection. The `NewMenuClass` drivers keep their loops. + Landed: `code/ui/uimainmenu.{h,cpp}`, `uicampaign.{h,cpp}`, + `uigametype.{h,cpp}` and `uimpselect.{h,cpp}` with `ui/mainmenu.rml`, + `ui/campaign.rml`, `ui/gametype.rml`, `ui/mpselect.rml` and + `ui/mpselectfs.rml`, sharing `ui/optionsbase.rcss` and each carrying its own + geometry. `NewMenuClass` is untouched: it is the MSEngine graphic menu. + + The main menu document carries the keys its driver watched for beside the + buttons, because those keys belong to the screen rather than to the window it + was drawn in: Ctrl+V and Ctrl+Alt+C on `keydown`, and the cheat words on + `textinput`, since the shell's modal scope takes every key message before the + `KN_` queue sees it and `Keyboard->Check()` never fires again while a + document is shown. A character that followed a modified key is dropped, the + way the driver's own switch answered those combinations before its default + arm saw them. + + A campaign row carries the campaign it stands for rather than its position, + because the list skips a campaign the player cannot reach. The difficulty + track bar needs no read-back at accept: the dialog read its slider back + because a keyboard or page move raised no thumb notification, and RmlUi + raises a change for every move. The game type screen's default arm is the + behavior, so anything but backing out carries on. + + A view names its data model after its own document, so a variant document + must name its own model rather than the one the base document names; a + document that names another's gets no bindings and no events at all. The + step 7 variants had that fault and it went unseen until a click was driven + through one. 9. **Load, save, delete** (M, two changes). 10. **Skirmish and map selection** (M, two changes). Includes the scenario picker templates and the preview surface. From e8e4fd8f06a13ea9be2c56289a443ae7b6570714 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 05:17:40 +0100 Subject: [PATCH 117/179] refactor(ui): put the save game browser behind a presenter The three command handlers read the view-model and queue intents, and the driver executes the queue after the pump. Fill_List keeps the control and Build_List reads the folder, so the file list has one owner. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/loaddlg.cpp | 353 ++++++++++++++++++-------------------- code/loaddlg.h | 11 +- code/ui/uisavebrowser.cpp | 320 ++++++++++++++++++++++++++++++++++ code/ui/uisavebrowser.h | 123 +++++++++++++ 4 files changed, 619 insertions(+), 188 deletions(-) create mode 100644 code/ui/uisavebrowser.cpp create mode 100644 code/ui/uisavebrowser.h diff --git a/code/loaddlg.cpp b/code/loaddlg.cpp index 7d5467f38..e575d1b7d 100644 --- a/code/loaddlg.cpp +++ b/code/loaddlg.cpp @@ -58,6 +58,8 @@ #include "savever.h" #include "scenario.h" #include "session.h" +#include "ui/uisavebrowser.h" +#include "ui/uishell.h" #include "win.h" #include @@ -173,18 +175,29 @@ bool LoadOptionsClass::Delete(void) /// The notification code that accompanied the control. void LoadOptionsClass::Load_Dialog_On_WM_COMMAND(HWND window, WPARAM wparam, LPARAM lparam, int id) { - LoadOptionsClass * _this = (LoadOptionsClass *)GetWindowLongPtr(window, DWLP_USER); + UISaveBrowserPresenterClass * screen = (UISaveBrowserPresenterClass *)GetWindowLongPtr(window, DWLP_USER); + if (screen == NULL) { + return; + } + switch ((int)wparam) { case IDC_MISSION_LOAD_LIST: if (id == 2 && ListBox_GetCount((HWND)lparam) > 0) { - _this->State = STATE_OK; + screen->Queue(UIIntent{UI_SAVEBROWSER_SELECT, "", ListBox_GetCurSel((HWND)lparam)}); + screen->Queue(UIIntent{UI_SAVEBROWSER_ACCEPT, "", 0}); } break; case IDOK: + if (id == 0) { + screen->Queue(UIIntent{UI_SAVEBROWSER_SELECT, "", ListBox_GetCurSel(GetDlgItem(window, IDC_MISSION_LOAD_LIST))}); + screen->Queue(UIIntent{UI_SAVEBROWSER_ACCEPT, "", 0}); + } + break; + case IDCANCEL: if (id == 0) { - _this->State = (LoadDialogState)wparam; + screen->Queue(UIIntent{UI_SAVEBROWSER_CANCEL, "", 0}); } break; } @@ -202,39 +215,35 @@ void LoadOptionsClass::Load_Dialog_On_WM_COMMAND(HWND window, WPARAM wparam, LPA /// The notification code that accompanied the control. void LoadOptionsClass::Save_Dialog_On_WM_COMMAND(HWND window, WPARAM wparam, LPARAM lparam, int id) { - LoadOptionsClass * _this = (LoadOptionsClass *)GetWindowLongPtr(window, DWLP_USER); + UISaveBrowserPresenterClass * screen = (UISaveBrowserPresenterClass *)GetWindowLongPtr(window, DWLP_USER); + if (screen == NULL) { + return; + } + switch ((int)wparam) { case IDC_MISSION_SAVE_LIST: - - /* - ** If the user clicks on the list, see if the there is a new current - ** item; if so, and if we're in SAVE mode, copy the list item into - ** the save-game description field. - */ if (id == 1 && ListBox_GetCount((HWND)lparam) > 0) { - int row = ListBox_GetCurSel((HWND)lparam); + int const row = ListBox_GetCurSel((HWND)lparam); if (row != LB_ERR) { - - /* - ** Copy the game's description, UNLESS it's the empty slot; if - ** it is, set the edit buffer to empty. - */ - FileEntryClass * fdata = (FileEntryClass *)ListBox_GetItemData((HWND)lparam, row); - if (fdata->Valid) { - SetWindowText(GetDlgItem(window, IDC_MISSION_SAVE_DESC), fdata->Descr); - } else if (_this->Description != NULL) { - SetWindowText(GetDlgItem(window, IDC_MISSION_SAVE_DESC), _this->Description); - } - SetFocus(GetDlgItem(window, IDC_MISSION_SAVE_DESC)); - Edit_SetSel(GetDlgItem(window, IDC_MISSION_SAVE_DESC), 0, -1); + screen->Queue(UIIntent{UI_SAVEBROWSER_SELECT, "", row}); } } break; case IDOK: + if (id == 0) { + // The field is read here rather than tracked, because the description the + // player typed is only ever wanted at the moment the button is pressed. + char buffer[256]; + GetWindowText(GetDlgItem(window, IDC_MISSION_SAVE_DESC), buffer, DESCRIP_MAX+36); + screen->Queue(UIIntent{UI_SAVEBROWSER_DESCRIBE, buffer, 0}); + screen->Queue(UIIntent{UI_SAVEBROWSER_ACCEPT, "", 0}); + } + break; + case IDCANCEL: if (id == 0) { - _this->State = (LoadDialogState)wparam; + screen->Queue(UIIntent{UI_SAVEBROWSER_CANCEL, "", 0}); } break; } @@ -250,12 +259,22 @@ void LoadOptionsClass::Save_Dialog_On_WM_COMMAND(HWND window, WPARAM wparam, LPA /// The notification code that accompanied the control. void LoadOptionsClass::Delete_Dialog_On_WM_COMMAND(HWND window, WPARAM wparam, LPARAM lparam, int id) { - LoadOptionsClass * _this = (LoadOptionsClass *)GetWindowLongPtr(window, DWLP_USER); + UISaveBrowserPresenterClass * screen = (UISaveBrowserPresenterClass *)GetWindowLongPtr(window, DWLP_USER); + if (screen == NULL) { + return; + } + switch ((int)wparam) { case IDOK: + if (id == 0) { + screen->Queue(UIIntent{UI_SAVEBROWSER_SELECT, "", ListBox_GetCurSel(GetDlgItem(window, IDC_MISSION_DELETE_LIST))}); + screen->Queue(UIIntent{UI_SAVEBROWSER_ACCEPT, "", 0}); + } + break; + case IDCANCEL: if (id == 0) { - _this->State = (LoadDialogState)wparam; + screen->Queue(UIIntent{UI_SAVEBROWSER_CANCEL, "", 0}); } break; } @@ -390,16 +409,70 @@ static bool Saved_Game_Exists(char const * name) * HISTORY: * * 02/14/1995 BR : Created. * *=============================================================================================*/ +/// +/// Puts the view-model on the dialog's own controls. +/// +static void Save_Browser_Sync_Controls(HWND window, UISaveBrowserPresenterClass & screen) +{ + if (screen.Style != UISaveBrowserPresenterClass::STYLE_SAVE) { + return; + } + + HWND const field = GetDlgItem(window, IDC_MISSION_SAVE_DESC); + if (field == NULL) { + return; + } + + char current[256]; + GetWindowText(field, current, sizeof(current)); + + if (strcmp(current, screen.Description.c_str()) != 0) { + SetWindowText(field, screen.Description.c_str()); + } + + if (screen.FocusDescription) { + screen.FocusDescription = false; + SetFocus(field); + Edit_SetSel(field, 0, -1); + } +} + + +/// +/// Rebuilds the list control when the view-model's list has moved. +/// +void LoadOptionsClass::Sync_List(HWND list, HWND dialog, UISaveBrowserPresenterClass & screen) +{ + if (list == 0 || !screen.ListChanged) { + return; + } + + screen.ListChanged = false; + Fill_List(list, screen.Selected); + EnableWindow(GetDlgItem(dialog, 1), screen.CanAct ? TRUE : FALSE); +} + + bool LoadOptionsClass::Dialog(void) { - /* - ** Dialog variables - */ + UISaveBrowserPresenterClass::StyleType style = UISaveBrowserPresenterClass::STYLE_LOAD; + if (Style == SAVE) { + style = UISaveBrowserPresenterClass::STYLE_SAVE; + } else if (Style == WWDELETE) { + style = UISaveBrowserPresenterClass::STYLE_DELETE; + } + + UISaveBrowserPresenterClass screen(*this, style); + + if (!screen.Can_Open()) { + return(false); + } + + screen.Refresh(); + HWND dialog = 0; HWND list = 0; - char buffer[256]; - switch (Style) { case LOAD: dialog = OwnerDraw::Begin_Dialog(IDD_MISSION_LOAD, Load_Dialog_Proc); @@ -407,10 +480,6 @@ bool LoadOptionsClass::Dialog(void) break; case SAVE: - if (Disk_Space_Available() < MinSpaceRequired) { - WWMessageBox().Process(TXT_DISKFULL, TXT_OK, TXT_NONE, TXT_NONE); - return(false); - } dialog = OwnerDraw::Begin_Dialog(IDD_MISSION_SAVE, Save_Dialog_Proc); list = GetDlgItem(dialog, IDC_MISSION_SAVE_LIST); break; @@ -419,156 +488,57 @@ bool LoadOptionsClass::Dialog(void) dialog = OwnerDraw::Begin_Dialog(IDD_MISSION_DELETE, Delete_Dialog_Proc); list = GetDlgItem(dialog, IDC_MISSION_DELETE_LIST); break; + + default: + break; } State = STATE_PENDING; if (dialog) { - /* - ** Initialize. - */ - SetWindowLongPtr(dialog, DWLP_USER, (LONG_PTR)this); + SetWindowLongPtr(dialog, DWLP_USER, (LONG_PTR)&screen); - if (list != 0) { - Fill_List(list); - EnableWindow(GetDlgItem(dialog, 1), bool(ListBox_GetCount(list) > 0)); - } + Sync_List(list, dialog, screen); + Save_Browser_Sync_Controls(dialog, screen); OwnerDraw::Display_Dialog(dialog); - /* - ** Main Processing Loop. - */ - do { - while (State == STATE_PENDING) { - if (OwnerDraw::Dialog_Message_Handler() == true) { - State = STATE_CLOSE; - } - - /* - ** Invoke game callback. - */ - if (Callback) { - Callback(); - } + while (!screen.Result.has_value()) { + if (OwnerDraw::Dialog_Message_Handler() == true) { + screen.Queue(UIIntent{UI_SAVEBROWSER_CANCEL, "", 0}); + } - /* - ** If we have just received input focus again after running in the background then - ** we need to redraw. - */ - if (!GameActive) { - Title_Screen_Restore(0); + // A control handler queues rather than acts, so the queue is executed here, + // after the pump has returned. + screen.Drain(); + + // A load draws where this screen is, so the dialog gets out of its way, which + // is what its own ShowWindow did. + if (screen.Pending != UISaveBrowserPresenterClass::SUB_NONE) { + ShowWindow(dialog, SW_HIDE); + UpdateWindow(MainWindow); + screen.Run_Pending(); + if (!screen.Result.has_value()) { + ShowWindow(dialog, SW_SHOW); + UpdateWindow(dialog); } } - if (State == STATE_OK) { - LRESULT row = ListBox_GetCurSel(list); + Sync_List(list, dialog, screen); + Save_Browser_Sync_Controls(dialog, screen); - if (row != LB_ERR) { - FileEntryClass * entry = (FileEntryClass *)ListBox_GetItemData(list, row); - - /* - ** Process input. - */ - switch (Style) { - /* - ** Load: if load fails, present a message, and stay in the dialog - ** to allow the user to try another game - */ - case LOAD: { - if (entry->Num != -1) { - Init_Campaigns(); - } - - ShowWindow(dialog, SW_HIDE); - UpdateWindow(MainWindow); - - if (!Load_File(entry->Filename)) { - WWMessageBox().Process(TXT_ERROR_LOADING_GAME, TXT_OK, TXT_NONE, TXT_NONE); - ShowWindow(dialog, SW_SHOW); - State = STATE_PENDING; - } - break; - } - - /* - ** Save: Save the game & exit the dialog - */ - case SAVE: { - GetWindowText(GetDlgItem(dialog, IDC_MISSION_SAVE_DESC), buffer, DESCRIP_MAX+36); - - if (strlen(buffer) == 0) { - WWMessageBox().Process(TXT_MUSTENTER_DESCRIPTION, TXT_OK, TXT_NONE, TXT_NONE); - SetFocus(GetDlgItem(dialog, IDC_MISSION_SAVE_DESC)); - Edit_SetSel(GetDlgItem(dialog, IDC_MISSION_SAVE_DESC), -1, -1); - State = STATE_PENDING; - break; - } - - const char * filename = NULL; - char test_filename[256]; - - if (entry && entry->Valid) { - filename = entry->Filename; - } else { - Pick_Filename(test_filename); - filename = test_filename; - } - - if (filename != NULL) { - bool exists = Saved_Game_Exists(filename); - if (exists && WWMessageBox()._Process(TXT_CONFIRM_SAVE, 1, TXT_YES, TXT_NO, TXT_NONE)) - State = STATE_PENDING; - else { - if (!Save_File(filename, buffer)) { - WWMessageBox().Process(TXT_ERROR_SAVING_GAME, TXT_OK, TXT_NONE, TXT_NONE); - State = STATE_PENDING; - } else { - int confirmation = Save_Confirmation(); - if (confirmation != TXT_NONE) { - WWMessageBox().Process(confirmation, TXT_OK, TXT_NONE, TXT_NONE); - } - if (Description) { - strcpy(Description, buffer); - } - } - } - } - break; - } - - /* - ** Delete: delete the file & stay in the dialog, to allow the user - ** to delete multiple files. - */ - case WWDELETE: { - sprintf(buffer, "%s\n%s", Fetch_String(TXT_DELETE_FILE_QUERY), entry->Descr); - - if (!WWMessageBox()._Process(buffer, 1, TXT_YES, TXT_NO, TXT_NONE)) { - Delete_File(entry->Filename); - ListBox_DeleteString(list, row); - ListBox_SetCurSel(list, 0); - if (ListBox_GetCount(list) > 0) { - State = STATE_PENDING; - break; - } - } else { - State = STATE_PENDING; - } - break; - } - } - } - } - } while (State == STATE_PENDING); + screen.Service(); + } Clear_List(); OwnerDraw::End_Dialog(dialog); } - return(State == STATE_OK ? true : false); + State = screen.Accepted() ? STATE_OK : STATE_CLOSE; + + return(screen.Accepted()); } @@ -617,7 +587,7 @@ void LoadOptionsClass::Clear_List(void) /*********************************************************************************************** - * LoadOptionsClass::Fill_List -- fills the list box & GameNum arrays * + * LoadOptionsClass::Build_List -- reads the folder into the file list * * * * INPUT: * * none. * @@ -632,9 +602,8 @@ void LoadOptionsClass::Clear_List(void) * 02/14/1995 BR : Created. * * 06/25/1995 JLB : Shows which saved games are "(old)". * *=============================================================================================*/ -void LoadOptionsClass::Fill_List(HWND window) +void LoadOptionsClass::Build_List(void) { - OwnerDraw::CellData thecell; FileEntryClass * fdata = NULL; // for adding entries to 'Files' WIN32_FIND_DATAA ff; // for FindFirstFile @@ -722,6 +691,33 @@ void LoadOptionsClass::Fill_List(HWND window) ** Now sort the list in order of Date/Time (newest first, oldest last) */ qsort((void *)(&Files[0]), Files.Count(), sizeof(class FileEntryClass *), LoadOptionsClass::Compare); + } +} + + +/*********************************************************************************************** + * LoadOptionsClass::Fill_List -- fills the list box & GameNum arrays * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * none. * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + * 06/25/1995 JLB : Shows which saved games are "(old)". * + *=============================================================================================*/ +void LoadOptionsClass::Fill_List(HWND window, int selected) +{ + OwnerDraw::CellData thecell; + FileEntryClass * fdata = NULL; + char buffer[128]; + + if (Files.Count() > 0) { ListBox_ResetContent(window); @@ -757,25 +753,8 @@ void LoadOptionsClass::Fill_List(HWND window) ListBox_SetItemData(window, row, (LPARAM)fdata); } - switch (Style) { - case LOAD: { - for (int i = 0; i < Files.Count(); i++) { - if (Files[i]->Valid) { - ListBox_SetCurSel(window, i); - ListBox_SetTopIndex(window, i); - break; - } - } - } - break; - - case SAVE: - case WWDELETE: - ListBox_SetCurSel(window, 0); - ListBox_SetTopIndex(window, 0); - break; - } - + ListBox_SetCurSel(window, selected); + ListBox_SetTopIndex(window, selected); } } diff --git a/code/loaddlg.h b/code/loaddlg.h index 24af0f976..9a04d5ec3 100644 --- a/code/loaddlg.h +++ b/code/loaddlg.h @@ -69,6 +69,10 @@ class FileEntryClass { class LoadOptionsClass { + // The screen's behavior, which reads the file list, the suggested description and the + // save confirmation this class owns. + friend class UISaveBrowserPresenterClass; + public: /* ** This defines the style of the dialog @@ -102,6 +106,10 @@ class LoadOptionsClass void Pick_Filename(char * file_name); bool Files_Present(void); + // Reads the folder into Files, newest first. The control the list is shown in is a + // view's business, so it is not touched here. + void Build_List(void); + virtual bool Load_File(const char * file_name); virtual bool Save_File(const char * file_name, const char * descr); virtual bool Delete_File(const char * file_name); @@ -112,7 +120,8 @@ class LoadOptionsClass ** Internal routines */ void Clear_List (void); // clears the list & game # array - void Fill_List (HWND window); // fills the list & game # array + void Fill_List (HWND window, int selected); // puts the list on the control + void Sync_List (HWND list, HWND dialog, class UISaveBrowserPresenterClass & screen); int Num_From_Ext (char *fname); // translates filename to file # static int __cdecl Compare(const void *p1, const void *p2); // for qsort() diff --git a/code/ui/uisavebrowser.cpp b/code/ui/uisavebrowser.cpp new file mode 100644 index 000000000..12a81b7ed --- /dev/null +++ b/code/ui/uisavebrowser.cpp @@ -0,0 +1,320 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The save game browser. Behavior traced out of LoadOptionsClass::Dialog and the three +// command handlers in loaddlg.cpp. +// +// What the extraction fixes in place: a row carries its entry rather than its position in +// the control; the action button is disabled with an empty list but every check that can +// refuse the operation is made when the button is pressed; a refused load, a refused save +// and a declined deletion all leave the screen standing, which is what putting the state +// back to pending did; and deleting the last game leaves the screen accepted, which is what +// the delete arm did by falling out of its own loop. +// +// docs/UI_DESIGN.md, "Screens", owns the contracts this keeps to. + +#include "always.h" + +#include "uisavebrowser.h" + +#include "uirmlview.h" + +#include "campaign.h" +#include "conquer.h" +#include "data.h" +#include "gamedirs.h" +#include "init.h" +#include "language/language.h" +#include "loaddlg.h" +#include "msgbox.h" +#include "saveload.h" +#include "vector.h" + +#include +#include +#include + +#include +#include + + +// Is a saved game of this name already there? Asked before one is written, since a name the +// folder holds is written over rather than added to. +static bool Saved_Game_Exists(char const * name) +{ + return(GetFileAttributes(Saved_Game_Name(name).c_str()) != INVALID_FILE_ATTRIBUTES); +} + + +UISaveBrowserPresenterClass::UISaveBrowserPresenterClass(LoadOptionsClass & options, StyleType style) : + Style(style), + Options(options) +{ +} + + +/// +/// Is there room on disk to save at all? +/// +bool UISaveBrowserPresenterClass::Can_Open(void) +{ + if (Style != STYLE_SAVE) { + return(true); + } + + if (Disk_Space_Available() >= Options.MinSpaceRequired) { + return(true); + } + + WWMessageBox().Process(TXT_DISKFULL, TXT_OK, TXT_NONE, TXT_NONE); + return(false); +} + + +void UISaveBrowserPresenterClass::Refresh(void) +{ + Options.Build_List(); + + Entries.clear(); + Selected = -1; + + for (int index = 0; index < Options.Files.Count(); index++) { + FileEntryClass const * const file = Options.Files[index]; + + EntryType entry; + entry.Description = file->Descr; + entry.Session = (file->Type != GAME_NORMAL); + entry.Valid = file->Valid; + + if (file->DateTime.dwHighDateTime != (DWORD)-1 && file->DateTime.dwLowDateTime != (DWORD)-1) { + FILETIME local; + SYSTEMTIME stamp; + char buffer[128]; + + FileTimeToLocalFileTime(&file->DateTime, &local); + FileTimeToSystemTime(&local, &stamp); + + GetDateFormat(LANG_USER_DEFAULT, TIME_NOMINUTESORSECONDS, &stamp, NULL, buffer, sizeof(buffer)); + entry.Date = buffer; + GetTimeFormat(LANG_USER_DEFAULT, TIME_NOSECONDS, &stamp, NULL, buffer, sizeof(buffer)); + entry.Time = buffer; + } + + Entries.push_back(entry); + } + + // The load list opens on the first game it could actually read; the other two open on + // their first row, which for a save is the empty slot. + if (!Entries.empty()) { + Selected = 0; + + if (Style == STYLE_LOAD) { + for (size_t index = 0; index < Entries.size(); index++) { + if (Entries[index].Valid) { + Selected = (int)index; + break; + } + } + } + } + + CanAct = !Entries.empty(); + ListChanged = true; + + Description.clear(); + if (Style == STYLE_SAVE && Options.Description != NULL) { + Description = Options.Description; + } +} + + +/// +/// The maintenance the dialog driver ran on every pass of its own loop. +/// +void UISaveBrowserPresenterClass::Service(void) +{ + if (Options.Callback != NULL) { + Options.Callback(); + } + + if (!GameActive) { + Title_Screen_Restore(false); + } +} + + +void UISaveBrowserPresenterClass::Finish(bool accepted) +{ + Outcome = accepted; + + UIResult result; + result.Outcome = accepted ? UIResult::OUTCOME_ACCEPTED : UIResult::OUTCOME_CANCELLED; + Result = result; +} + + +/// +/// Loads the game the player picked, with the screen already out of the way. +/// A load that fails leaves the screen standing so another game can be tried, which is what +/// putting the dialog's state back to pending did. +/// +void UISaveBrowserPresenterClass::Run_Pending(void) +{ + if (Pending != SUB_LOAD) { + return; + } + + Pending = SUB_NONE; + + if (Selected < 0 || Selected >= Options.Files.Count()) { + return; + } + + if (!Options.Load_File(Options.Files[Selected]->Filename)) { + WWMessageBox().Process(TXT_ERROR_LOADING_GAME, TXT_OK, TXT_NONE, TXT_NONE); + return; + } + + Finish(true); +} + + +void UISaveBrowserPresenterClass::Accept(void) +{ + // No row means no operation, which is the LB_ERR the driver tested for. + if (Selected < 0 || Selected >= Options.Files.Count()) { + return; + } + + FileEntryClass * const entry = Options.Files[Selected]; + + switch (Style) { + case STYLE_LOAD: + // The campaign list is read before the screen steps aside, where the dialog + // read it, because the load needs it and a mission save carries a campaign. + if (entry->Num != -1) { + Init_Campaigns(); + } + Pending = SUB_LOAD; + break; + + case STYLE_SAVE: { + if (Description.empty()) { + WWMessageBox().Process(TXT_MUSTENTER_DESCRIPTION, TXT_OK, TXT_NONE, TXT_NONE); + FocusDescription = true; + return; + } + + char picked[256]; + char const * filename = NULL; + + if (entry->Valid) { + filename = entry->Filename; + } else { + Options.Pick_Filename(picked); + filename = picked; + } + + if (filename == NULL) { + return; + } + + // A name the folder already holds is written over, so it is confirmed first. + if (Saved_Game_Exists(filename) + && WWMessageBox()._Process(TXT_CONFIRM_SAVE, 1, TXT_YES, TXT_NO, TXT_NONE)) { + return; + } + + if (!Options.Save_File(filename, Description.c_str())) { + WWMessageBox().Process(TXT_ERROR_SAVING_GAME, TXT_OK, TXT_NONE, TXT_NONE); + return; + } + + int const confirmation = Options.Save_Confirmation(); + if (confirmation != TXT_NONE) { + WWMessageBox().Process(confirmation, TXT_OK, TXT_NONE, TXT_NONE); + } + + if (Options.Description != NULL) { + strcpy(Options.Description, Description.c_str()); + } + + Finish(true); + break; + } + + case STYLE_DELETE: { + char buffer[256]; + sprintf(buffer, "%s\n%s", Fetch_String(TXT_DELETE_FILE_QUERY), entry->Descr); + + if (WWMessageBox()._Process(buffer, 1, TXT_YES, TXT_NO, TXT_NONE)) { + return; + } + + Options.Delete_File(entry->Filename); + + Options.Files.Delete_Index(Selected); + delete entry; + Entries.erase(Entries.begin() + Selected); + + // The list stays open for another deletion; emptying it leaves the screen. + Selected = Entries.empty() ? -1 : 0; + CanAct = !Entries.empty(); + ListChanged = true; + + if (Entries.empty()) { + Finish(true); + } + break; + } + } +} + + +void UISaveBrowserPresenterClass::Execute(UIIntent const & intent) +{ + if (intent.Action == UI_SAVEBROWSER_SELECT) { + if (intent.Value < 0 || intent.Value >= (int)Entries.size()) { + return; + } + + Selected = intent.Value; + + // Picking a row in the save list offers that game's description, so an existing + // game can be written over without typing its name out again; the empty slot offers + // the description the caller suggested. + if (Style == STYLE_SAVE) { + if (Entries[Selected].Valid) { + Description = Entries[Selected].Description; + } else if (Options.Description != NULL) { + Description = Options.Description; + } + FocusDescription = true; + } + return; + } + + if (intent.Action == UI_SAVEBROWSER_DESCRIBE) { + Description = intent.Identity; + if (Description.size() > DESCRIPTION_LIMIT) { + Description.resize(DESCRIPTION_LIMIT); + } + return; + } + + if (intent.Action == UI_SAVEBROWSER_ACCEPT) { + Accept(); + return; + } + + if (intent.Action == UI_SAVEBROWSER_CANCEL) { + Finish(false); + return; + } +} diff --git a/code/ui/uisavebrowser.h b/code/ui/uisavebrowser.h new file mode 100644 index 000000000..b5c1e6711 --- /dev/null +++ b/code/ui/uisavebrowser.h @@ -0,0 +1,123 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The save game browser's behavior, with no toolkit in it. One screen serves loading, +// saving and deleting, because the three templates differ by which controls exist and by +// what the action button does, not by how the list is built. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + +#include +#include + +class LoadOptionsClass; + + +inline constexpr char const * UI_SAVEBROWSER_SELECT = "select"; // Value: row +inline constexpr char const * UI_SAVEBROWSER_DESCRIBE = "describe"; // Identity: the text +inline constexpr char const * UI_SAVEBROWSER_ACCEPT = "accept"; +inline constexpr char const * UI_SAVEBROWSER_CANCEL = "cancel"; + + +class UISaveBrowserPresenterClass : public UIPresenterClass +{ + public: + enum StyleType { + STYLE_LOAD, + STYLE_SAVE, + STYLE_DELETE, + }; + + // A screen this one opens on top of itself. Loading takes a while and draws where + // this screen is, so the view steps aside for it, which is what the dialog's own + // ShowWindow did. + enum SubScreenType { + SUB_NONE, + SUB_LOAD, + }; + + struct EntryType + { + std::string Description; + + // The date and the time the list showed in its own two columns. + std::string Date; + std::string Time; + + // Was the game a multiplayer one? The list marked those with a star. + bool Session = false; + + // Does the row stand for a saved game? The save list opens with one row that + // does not, which is the empty slot. + bool Valid = false; + }; + + UISaveBrowserPresenterClass(LoadOptionsClass & options, StyleType style); + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + virtual void Service(void) override; + virtual bool Suspends(void) const override { return(Pending != SUB_NONE); } + + // Is there room on disk to save at all? Reports the shortage where the dialog + // reported it, before anything is shown. + bool Can_Open(void); + + // Runs the sub-screen an executed intent asked for and clears the request. Safe to + // call with nothing pending. + void Run_Pending(void); + + // Did the player go through with the operation? This is what the callers read. + bool Accepted(void) const { return(Outcome); } + + /* + ** The view-model. Plain values, and the only thing a view reads. + */ + StyleType Style; + + std::vector Entries; + int Selected = -1; + + // What the description field holds. The save screen is the only style that has one. + std::string Description; + + // The longest description the field accepts, in bytes, which is what the dialog + // capped the edit control at. + enum { DESCRIPTION_LIMIT = 79 }; + + // Is there anything for the action button to act upon? The dialog disabled it with + // an empty list. + bool CanAct = false; + + // Should the description field take the focus with its text selected? The dialog + // did that when a row was picked and when it refused an empty description. + bool FocusDescription = false; + + SubScreenType Pending = SUB_NONE; + + // Has the list itself changed since a view last drew it? Only a deletion moves it, + // so a view that rebuilds a control has one thing to test. + bool ListChanged = false; + + private: + void Accept(void); + void Finish(bool accepted); + + LoadOptionsClass & Options; + bool Outcome = false; +}; + + +// Shows the browser through its RmlUi view. FAILED_TO_OPEN leaves nothing shown and the +// caller falls through to the legacy dialog. +UIResult UI_Save_Browser_Screen(UISaveBrowserPresenterClass & presenter); From ecf459186b7260d4108edd589749d1d8b5e17df1 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 05:48:16 +0100 Subject: [PATCH 118/179] feat(ui): show the save game browser through RmlUi Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/loaddlg.cpp | 17 ++++ code/ui/uisavebrowser.cpp | 188 ++++++++++++++++++++++++++++++++++++++ ui/missiondelete.rcss | 46 ++++++++++ ui/missiondelete.rml | 27 ++++++ ui/missionload.rcss | 48 ++++++++++ ui/missionload.rml | 27 ++++++ ui/missionsave.rcss | 58 ++++++++++++ ui/missionsave.rml | 29 ++++++ ui/savebrowser.rcss | 78 ++++++++++++++++ 9 files changed, 518 insertions(+) create mode 100644 ui/missiondelete.rcss create mode 100644 ui/missiondelete.rml create mode 100644 ui/missionload.rcss create mode 100644 ui/missionload.rml create mode 100644 ui/missionsave.rcss create mode 100644 ui/missionsave.rml create mode 100644 ui/savebrowser.rcss diff --git a/code/loaddlg.cpp b/code/loaddlg.cpp index e575d1b7d..55d2872c3 100644 --- a/code/loaddlg.cpp +++ b/code/loaddlg.cpp @@ -470,6 +470,23 @@ bool LoadOptionsClass::Dialog(void) screen.Refresh(); + State = STATE_PENDING; + + if (UI_Use_Rml()) { + UIResult const result = UI_Save_Browser_Screen(screen); + + if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { + Clear_List(); + State = screen.Accepted() ? STATE_OK : STATE_CLOSE; + return(screen.Accepted()); + } + + // Preparation failed, so nothing is shown and the legacy dialog answers instead. A + // suspended screen leaves the presenter marked, and the dialog runs the same screen. + screen.Result.reset(); + screen.IsClosing = false; + } + HWND dialog = 0; HWND list = 0; diff --git a/code/ui/uisavebrowser.cpp b/code/ui/uisavebrowser.cpp index 12a81b7ed..f479ebfc7 100644 --- a/code/ui/uisavebrowser.cpp +++ b/code/ui/uisavebrowser.cpp @@ -37,8 +37,10 @@ #include "vector.h" #include +#include #include #include +#include #include #include @@ -318,3 +320,189 @@ void UISaveBrowserPresenterClass::Execute(UIIntent const & intent) return; } } + + +//--------------------------------------------------------------------------------------- +// The RmlUi view. +//--------------------------------------------------------------------------------------- + +/// +/// The RmlUi half of the save game browser. +/// +class SaveBrowserViewClass : public UIRmlViewClass +{ + public: + SaveBrowserViewClass(UISaveBrowserPresenterClass & presenter, char const * document) : + UIRmlViewClass(presenter, document), + Screen(presenter) + { + } + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + private: + void Press(Rml::String const & action); + + // The description the field is holding, which the save screen reads when the action + // button is pressed rather than tracking, as the dialog read its edit control. + std::string Field_Text(void) const; + + Rml::ElementFormControlInput * Field(void) const; + + UISaveBrowserPresenterClass & Screen; +}; + + +Rml::ElementFormControlInput * SaveBrowserViewClass::Field(void) const +{ + if (Element == nullptr) { + return(nullptr); + } + return(rmlui_dynamic_cast(Element->GetElementById("description"))); +} + + +std::string SaveBrowserViewClass::Field_Text(void) const +{ + Rml::ElementFormControlInput * const field = Field(); + if (field == nullptr) { + return(Screen.Description); + } + return(field->GetValue()); +} + + +/// +/// Queues what a button or its key stands for. +/// The save screen reads the description out of the field here rather than tracking it, +/// because that is when the dialog read its edit control, and the read is queued ahead of +/// the action it is read for so the two execute in that order. +/// +void SaveBrowserViewClass::Press(Rml::String const & action) +{ + if (Screen.Style == UISaveBrowserPresenterClass::STYLE_SAVE && action == UI_SAVEBROWSER_ACCEPT) { + std::string const text = Field_Text(); + if (text != Screen.Description) { + Screen.Queue(UIIntent{UI_SAVEBROWSER_DESCRIBE, text, 0}); + } + } + + Screen.Queue(UIIntent{action, "", 0}); +} + + +void SaveBrowserViewClass::Bind(Rml::DataModelConstructor & model) +{ + if (auto entry = model.RegisterStruct()) { + entry.RegisterMember("description", &UISaveBrowserPresenterClass::EntryType::Description); + entry.RegisterMember("date", &UISaveBrowserPresenterClass::EntryType::Date); + entry.RegisterMember("time", &UISaveBrowserPresenterClass::EntryType::Time); + } + model.RegisterArray>(); + + model.Bind("entries", &Screen.Entries); + model.Bind("selected", &Screen.Selected); + model.Bind("description", &Screen.Description); + model.Bind("canact", &Screen.CanAct); + + model.BindEventCallback("pick", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{UI_SAVEBROWSER_SELECT, "", (int)arguments[0].Get()}); + }); + + // The field is bound one way, so a value the model already holds is never queued back as + // a change the player did not type. + model.BindEventCallback("describe", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + Rml::String const value = event.GetParameter("value", Rml::String()); + if (value == Screen.Description) return; + Screen.Queue(UIIntent{UI_SAVEBROWSER_DESCRIBE, value, 0}); + }); + + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Press(arguments[0].Get()); + }); + + // Escape cancels and Enter presses the action button, which is what IsDialogMessage + // delivered to a template that names IDCANCEL and no default push button. + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key == Rml::Input::KI_ESCAPE) { + Press(UI_SAVEBROWSER_CANCEL); + } else if (key == Rml::Input::KI_RETURN || key == Rml::Input::KI_NUMPADENTER) { + Press(UI_SAVEBROWSER_ACCEPT); + } + }); +} + + +void SaveBrowserViewClass::Sync(void) +{ + if (!Model) return; + + Model.DirtyVariable("entries"); + Model.DirtyVariable("selected"); + Model.DirtyVariable("description"); + Model.DirtyVariable("canact"); + + // Picking a row, and a refused empty description, put the focus on the field with its + // text selected, which is what the dialog did with SetFocus and Edit_SetSel. + if (Screen.FocusDescription) { + Screen.FocusDescription = false; + if (Rml::ElementFormControlInput * const field = Field()) { + field->Focus(); + field->Select(); + } + } + + Screen.ListChanged = false; +} + + +/// +/// Shows the browser and waits for the player to leave it. +/// +UIResult UI_Save_Browser_Screen(UISaveBrowserPresenterClass & presenter) +{ + char const * document = "missionload.rml"; + if (presenter.Style == UISaveBrowserPresenterClass::STYLE_SAVE) { + document = "missionsave.rml"; + } else if (presenter.Style == UISaveBrowserPresenterClass::STYLE_DELETE) { + document = "missiondelete.rml"; + } + + SaveBrowserViewClass view(presenter, document); + + if (!view.Prepare(true)) { + UIResult result; + result.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + return(result); + } + + // Loading draws where this screen is, so the document steps aside for it, which is what + // the dialog's own ShowWindow did. + while (!presenter.Result.has_value()) { + UI_Run_Modal(presenter, view); + + if (presenter.Pending == UISaveBrowserPresenterClass::SUB_NONE) { + break; + } + + view.Hide(); + presenter.Run_Pending(); + if (presenter.Result.has_value()) { + break; + } + view.Show(); + view.Sync(); + } + + UIResult const result = presenter.Result.value_or(UIResult{}); + view.Close(); + return(result); +} diff --git a/ui/missiondelete.rcss b/ui/missiondelete.rcss new file mode 100644 index 000000000..6b9959fdc --- /dev/null +++ b/ui/missiondelete.rcss @@ -0,0 +1,46 @@ +/* The delete browser. Geometry from the IDD_MISSION_DELETE template, converted from dialog + units at the 8 point MS Sans Serif the template names: 1.5 pixels across and 1.625 down, + with a child's offset taken from the panel's content box and the panel's declared size + taken inside its own border. */ + +/* 302 x 198 dialog units, so 453 x 321.75 pixels, centred on the frame. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -226.5dp; + margin-top: -160.875dp; + + width: 449dp; + height: 317.75dp; +} + +/* The CTEXT title, 258 x 8 dialog units at 22, 12. */ +#title +{ + left: 31dp; + top: 17.5dp; + width: 387dp; + height: 13dp; + line-height: 13dp; + text-align: center; +} + +/* The two visible column headings; the template's "Mission" heading is not visible. */ +#descriptionhead { left: 31dp; top: 40.25dp; width: 181.5dp; height: 13dp; line-height: 13dp; } +#timehead { left: 292dp; top: 40.25dp; width: 106.5dp; height: 13dp; line-height: 13dp; text-align: center; } + +/* The list, 258 x 122 dialog units at 22, 42. */ +#games +{ + left: 31dp; + top: 66.25dp; + width: 387dp; + height: 198.25dp; +} + +/* Delete and Cancel, both 50 x 14 dialog units. */ +.button { height: 22.75dp; line-height: 22.75dp; width: 75dp; } + +#accept { left: 257.5dp; top: 277.5dp; } +#cancel { left: 343dp; top: 277.5dp; } diff --git a/ui/missiondelete.rml b/ui/missiondelete.rml new file mode 100644 index 000000000..76b1dc9f9 --- /dev/null +++ b/ui/missiondelete.rml @@ -0,0 +1,27 @@ + + + Delete mission + + + + + +
+
DELETE
+ +
Description
+
Time Stamp
+ +
+
+
{{ entry.description }}
+
{{ entry.date }}
+
{{ entry.time }}
+
+
+ +
Delete
+
[[TXT_CANCEL]]
+
+ +
diff --git a/ui/missionload.rcss b/ui/missionload.rcss new file mode 100644 index 000000000..92956f850 --- /dev/null +++ b/ui/missionload.rcss @@ -0,0 +1,48 @@ +/* The load browser. Geometry from the IDD_MISSION_LOAD template, converted from dialog + units at the 8 point MS Sans Serif the template names: 1.5 pixels across and 1.625 down, + with a child's offset taken from the panel's content box and the panel's declared size + taken inside its own border. */ + +/* 302 x 198 dialog units, so 453 x 321.75 pixels, centred on the frame: its driver does not + move it. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -226.5dp; + margin-top: -160.875dp; + + width: 449dp; + height: 317.75dp; +} + +/* The CTEXT title, 258 x 8 dialog units at 22, 12. */ +#title +{ + left: 31dp; + top: 17.5dp; + width: 387dp; + height: 13dp; + line-height: 13dp; + text-align: center; +} + +/* The two visible column headings. The template's "Mission" heading carries NOT WS_VISIBLE, + so the dialog never drew it and neither does this. */ +#descriptionhead { left: 31dp; top: 40.25dp; width: 181.5dp; height: 13dp; line-height: 13dp; } +#timehead { left: 292dp; top: 40.25dp; width: 108dp; height: 13dp; line-height: 13dp; text-align: center; } + +/* The list, 258 x 124 dialog units at 22, 40. */ +#games +{ + left: 31dp; + top: 63dp; + width: 387dp; + height: 201.5dp; +} + +/* Load and Cancel, both 50 x 14 dialog units. */ +.button { height: 22.75dp; line-height: 22.75dp; width: 75dp; } + +#accept { left: 254.5dp; top: 275.875dp; } +#cancel { left: 343dp; top: 275.875dp; } diff --git a/ui/missionload.rml b/ui/missionload.rml new file mode 100644 index 000000000..a60864cd2 --- /dev/null +++ b/ui/missionload.rml @@ -0,0 +1,27 @@ + + + Load mission + + + + + +
+
LOAD
+ +
Description
+
Time Stamp
+ +
+
+
{{ entry.description }}
+
{{ entry.date }}
+
{{ entry.time }}
+
+
+ +
Load
+
[[TXT_CANCEL]]
+
+ +
diff --git a/ui/missionsave.rcss b/ui/missionsave.rcss new file mode 100644 index 000000000..4c08a8496 --- /dev/null +++ b/ui/missionsave.rcss @@ -0,0 +1,58 @@ +/* The save browser. Geometry from the IDD_MISSION_SAVE template, converted from dialog + units at the 8 point MS Sans Serif the template names: 1.5 pixels across and 1.625 down, + with a child's offset taken from the panel's content box and the panel's declared size + taken inside its own border. */ + +/* 302 x 198 dialog units, so 453 x 321.75 pixels, centred on the frame. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -226.5dp; + margin-top: -160.875dp; + + width: 449dp; + height: 317.75dp; +} + +/* The CTEXT title, 258 x 8 dialog units at 22, 12. */ +#title +{ + left: 31dp; + top: 17.5dp; + width: 387dp; + height: 13dp; + line-height: 13dp; + text-align: center; +} + +/* The two visible column headings; the template's "Mission" heading is not visible. */ +#descriptionhead { left: 31dp; top: 40.25dp; width: 181.5dp; height: 13dp; line-height: 13dp; } +#timehead { left: 296.5dp; top: 40.25dp; width: 99dp; height: 13dp; line-height: 13dp; text-align: center; } + +/* The list, 258 x 105 dialog units at 22, 42: shorter than the other two, because the + description field sits under it. */ +#games +{ + left: 31dp; + top: 66.25dp; + width: 387dp; + height: 170.625dp; +} + +/* The EDITTEXT, 258 x 14 dialog units at 22, 154. */ +#description +{ + left: 31dp; + top: 248.25dp; + width: 387dp; + height: 22.75dp; + line-height: 18.75dp; + padding: 0dp 4dp; +} + +/* Save and Cancel, both 50 x 14 dialog units. */ +.button { height: 22.75dp; line-height: 22.75dp; width: 75dp; } + +#accept { left: 259dp; top: 277.5dp; } +#cancel { left: 343dp; top: 277.5dp; } diff --git a/ui/missionsave.rml b/ui/missionsave.rml new file mode 100644 index 000000000..cfe039687 --- /dev/null +++ b/ui/missionsave.rml @@ -0,0 +1,29 @@ + + + Save mission + + + + + +
+
SAVE
+ +
Description
+
Time Stamp
+ +
+
+
{{ entry.description }}
+
{{ entry.date }}
+
{{ entry.time }}
+
+
+ + + +
Save
+
[[TXT_CANCEL]]
+
+ +
diff --git a/ui/savebrowser.rcss b/ui/savebrowser.rcss new file mode 100644 index 000000000..531ec053a --- /dev/null +++ b/ui/savebrowser.rcss @@ -0,0 +1,78 @@ +/* What the load, save and delete browsers share. Only the look and the list's own columns + live here; each document's stylesheet carries the geometry its template gives it. + + The three templates are the same 302 x 198 dialog units and differ only by the list's + height, where the action button sits and whether a description field exists, so the + family's own sheet is the panel, the column headings and a row. */ + +/* A row's cells stand where the owner-draw list put its columns. The three dialog + procedures register columns with OD_ADDCOLUMN at x 2, 255 and 315, widths 249, 56 and + unbounded, and the item's own string is column zero. + + The multiplayer star is deliberately absent. LoadOptionsClass::Fill_List sends OD_SETCELL + at x 200 for it, no column is registered there, and OD_SETCELL answers -1 for a column it + cannot find, so the legacy list never drew the star either. Preserved rather than + repaired, and reported separately. */ +.list .row +{ + position: relative; + width: 375dp; + height: 14dp; + line-height: 14dp; + padding: 0dp; +} + +.row .description +{ + display: block; + position: absolute; + left: 2dp; + top: 0dp; + width: 249dp; + white-space: nowrap; + overflow: hidden; +} + +.row .date +{ + display: block; + position: absolute; + left: 255dp; + top: 0dp; + width: 56dp; + white-space: nowrap; + overflow: hidden; +} + +.row .time +{ + display: block; + position: absolute; + left: 315dp; + top: 0dp; + width: 60dp; + white-space: nowrap; + overflow: hidden; +} + +/* The description field, where the template puts an EDITTEXT. It states a width, because a + field with none formats no line and RmlUi's End key then moves the caret to the start of + an empty line rather than to the end of the value. */ +.field +{ + display: block; + position: absolute; + box-sizing: border-box; + white-space: nowrap; + overflow: hidden; + + color: #e4e6da; + background-color: #14160f; + border-width: 2dp; + border-top-color: #10120e; + border-left-color: #10120e; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +.field:focus { background-color: #1d2017; } From 751bc668bd2538645ff2454a0edb509bddb53fb8 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 05:48:28 +0100 Subject: [PATCH 119/179] docs: record the save game browser as migrated Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/UI_DESIGN.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index d820efbd9..ddb94f8aa 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -1,7 +1,7 @@ # UI system design -Status: in progress. Steps 1 to 8 of the migration plan have landed; nothing -from step 9 onward is implemented. Everything outside the migration plan +Status: in progress. Steps 1 to 9 of the migration plan have landed; nothing +from step 10 onward is implemented. Everything outside the migration plan remains a proposal informed by source inspection and upstream documentation. This page owns the UI architecture and migration; [Building OpenTS](BUILDING.md) owns build support and [Project @@ -890,7 +890,26 @@ text beyond an ASCII test document. document that names another's gets no bindings and no events at all. The step 7 variants had that fault and it went unseen until a click was driven through one. -9. **Load, save, delete** (M, two changes). +9. **Load, save, delete** (M, two changes). Landed: `code/ui/uisavebrowser.{h,cpp}` + holds all three templates as one screen, because they differ by which controls + exist and by what the action button does rather than by how the list is built, + with `ui/missionload.rml`, `ui/missionsave.rml` and `ui/missiondelete.rml` + sharing `ui/savebrowser.rcss` beside `ui/optionsbase.rcss` and each carrying its + own geometry, converted from the `IDD_MISSION_LOAD`, `IDD_MISSION_SAVE` and + `IDD_MISSION_DELETE` templates. + + A row's cells stand where the owner-draw list put its columns, which the three + dialog procedures register with `OD_ADDCOLUMN` at x 2, 255 and 315. The + multiplayer star is absent from the documents because it was absent from the + dialog: `Fill_List` sends `OD_SETCELL` at x 200, no column is registered there, + and `OD_SETCELL` answers -1 for a column it cannot find. + + The description field states a width. RmlUi moves the caret for the End key by + the length of the formatted line, and a field that formats no line, because it + has no usable width or no font face, reports that length as zero and sends the + caret to the start instead. Home is unaffected, because it asks for index zero + outright, and so is Ctrl+End, which takes the value's own length. Step 12's map + generator screens want the same field. 10. **Skirmish and map selection** (M, two changes). Includes the scenario picker templates and the preview surface. 11. **Network lobbies** (L, two changes). Host, guest, game list, the `WS_` From 19adfdcf78e1800f599576426f45fa63a94c0976 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 05:55:04 +0100 Subject: [PATCH 120/179] refactor(ui): put skirmish and map selection behind presenters Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/netshare.cpp | 176 ++++++++++++------- code/netshare.h | 5 + code/skirmish.cpp | 296 +++++++++++++++---------------- code/ui/uiscenariopick.cpp | 174 +++++++++++++++++++ code/ui/uiscenariopick.h | 87 ++++++++++ code/ui/uiskirmish.cpp | 346 +++++++++++++++++++++++++++++++++++++ code/ui/uiskirmish.h | 149 ++++++++++++++++ 7 files changed, 1014 insertions(+), 219 deletions(-) create mode 100644 code/ui/uiscenariopick.cpp create mode 100644 code/ui/uiscenariopick.h create mode 100644 code/ui/uiskirmish.cpp create mode 100644 code/ui/uiskirmish.h diff --git a/code/netshare.cpp b/code/netshare.cpp index 46d0cafaa..e8bddea7d 100644 --- a/code/netshare.cpp +++ b/code/netshare.cpp @@ -10,6 +10,7 @@ #include "always.h" #include "netshare.h" +#include "ui/uiscenariopick.h" #include "_rules.h" #include "conquer.h" @@ -1117,41 +1118,78 @@ int RandomMapWaypointCount(int index) static int LastPreviewedScenario; -static int OriginalScenario; static HWND ScenarioPick; +// The screen the map selection dialog is showing. The dialog's own driver is a wait +// callback with no argument, so the screen it is driving is held here the way the dialog +// held its result in DWLP_USER. +static UIScenarioPickPresenterClass * ScenarioScreen; + + +/// +/// Puts the view-model on the dialog's own controls. +/// +static void Scenario_Sync_Controls(HWND window, UIScenarioPickPresenterClass & screen) +{ + if (screen.ListChanged) { + screen.ListChanged = false; + SendDlgItemMessage(window, IDC_SELECTMAP_LIST, LB_RESETCONTENT, 0, 0); + for (int index = 0; index < Session.Scenarios.Count(); index++) { + SendDlgItemMessage(window, IDC_SELECTMAP_LIST, LB_INSERTSTRING, -1, (LPARAM)Session.Scenarios[index]); + } + SendDlgItemMessage(window, IDC_SELECTMAP_LIST, LB_SETCURSEL, screen.Selected, 0); + InvalidateRect(window, NULL, FALSE); + } +} + /// /// Handles the idle processing while the map selection dialog is up. -/// This routine keeps the preview in step with whichever map is highlighted and pumps the -/// network layer the session is using, so that a game sitting in the lobby does not stall -/// while the host browses for a scenario. +/// The screen's own maintenance keeps the preview in step with whichever map is highlighted +/// and pumps the network layer, so a game sitting in the lobby does not stall while the host +/// browses for a scenario. /// /// bool; Should the dialog be shut down? bool Scenario_Select_Callback(void) { - int index = SendDlgItemMessage(ScenarioPick, IDC_AILEVEL_SLIDER, LB_GETCURSEL, 0, 0); - if (index != LastPreviewedScenario && index != -1) { - Set_Scenario_Info_From_Index(index); - if (stricmp(Session.Scenarios[index]->Get_Filename(), "RandMap.Sed") == 0) { - delete MultiplayerMapPreview; - MultiplayerMapPreview = new MapPreviewClass; - MultiplayerMapPreview->Read_PCX_Preview("RandMap.img"); - if (MultiplayerMapPreview->Get_Preview_Surface() == NULL) { - Update_Network_Dialog_Preview(ScenarioPick); - } - InvalidateRect(ScenarioPick, NULL, FALSE); - } else { - Update_Network_Dialog_Preview(ScenarioPick); - } - LastPreviewedScenario = index; - Session.Options.ScenarioIndex = OriginalScenario; - Set_Scenario_Info_From_Index(OriginalScenario); + if (ScenarioScreen == NULL) { + Call_Back(); + return(false); } - if (Session.Type == GAME_IPX || Session.Type == GAME_INTERNET) { - return(Net2Callback()); + + int const index = SendDlgItemMessage(ScenarioPick, IDC_SELECTMAP_LIST, LB_GETCURSEL, 0, 0); + if (index != -1 && index != ScenarioScreen->Selected) { + ScenarioScreen->Queue(UIIntent{UI_SCENARIOPICK_SELECT, "", index}); + } + + // A control handler queues rather than acts, so the queue is executed here. + ScenarioScreen->Drain(); + + unsigned int const generation = ScenarioScreen->PreviewGeneration; + + // The map generator draws where this screen is, so the dialog gets out of its way. + if (ScenarioScreen->Pending != UIScenarioPickPresenterClass::SUB_NONE) { + ShowWindow(ScenarioPick, SW_HIDE); + ScenarioScreen->Run_Pending(); + ShowWindow(ScenarioPick, SW_SHOW); + } + + ScenarioScreen->Service(); + Scenario_Sync_Controls(ScenarioPick, *ScenarioScreen); + + if (ScenarioScreen->PreviewGeneration != generation) { + InvalidateRect(ScenarioPick, NULL, FALSE); + } + + if (ScenarioScreen->Result.has_value()) { + WS_Destroy_Dialog(ScenarioPick, + ScenarioScreen->Result->Outcome == UIResult::OUTCOME_ACCEPTED ? IDOK : IDCANCEL); + return(true); + } + + if (Session.Type != GAME_IPX && Session.Type != GAME_INTERNET) { + Call_Back(); } - Call_Back(); return(false); } @@ -1168,21 +1206,42 @@ INT_PTR CALLBACK Scenario_DlgProc(HWND window, UINT message, WPARAM wparam, LPAR /// IDCANCEL. int Scenario_Dialog(HWND top) { + UIScenarioPickPresenterClass screen; + screen.Refresh(); + + ScenarioScreen = &screen; + Hide_Mouse(); Draw_Menu_Background(); Show_Mouse(); ScenarioPick = WS_Create_Dialog(ProgramInstance, IDD_MPLAYER_SELECT_MAP, top, Scenario_DlgProc, FALSE); Center_Window_Within_Window(ScenarioPick); OwnerDraw::Subclass_Dialog(ScenarioPick, 0); + Scenario_Sync_Controls(ScenarioPick, screen); ShowWindow(ScenarioPick, SW_NORMAL); - return(WS_Wait_Dialog(ScenarioPick, Scenario_Select_Callback)); + int const rc = WS_Wait_Dialog(ScenarioPick, Scenario_Select_Callback); + + ScenarioScreen = NULL; + return(rc); +} + + +/// +/// Runs the map selection screen. +/// This is the entry a screen uses rather than the dialog, because a presenter names no +/// window. +/// +/// bool; Did the player settle on a map? +bool Pick_Scenario_Screen(void) +{ + return(Scenario_Dialog(MainWindow) == IDOK); } /// /// Handles the messages for the multiplayer map selection dialog. -/// This routine fills the map list, paints the preview of the highlighted map, and services -/// the random map generator button. +/// This routine paints the preview of the highlighted map and queues what its buttons stand +/// for; the wait callback executes the queue. /// /// Returns with TRUE if the message was dealt with here, FALSE to leave it to the /// dialog manager. @@ -1209,54 +1268,33 @@ INT_PTR CALLBACK Scenario_DlgProc(HWND window, UINT message, WPARAM wparam, LPAR return(TRUE); case WM_COMMAND: + if (ScenarioScreen == NULL) { + break; + } switch (LOWORD(wparam)) { - case IDC_AILEVEL_SLIDER: + case IDC_SELECTMAP_LIST: return(FALSE); - case IDOK: { - int index = SendDlgItemMessage(window, IDC_AILEVEL_SLIDER, LB_GETCURSEL, 0, 0); - Session.Options.ScenarioIndex = std::max(0, index); - WS_Destroy_Dialog(window, IDOK); - SendDlgItemMessage(GameoptWindow(), IDC_SCENARIONAME, WM_SETTEXT, 0, (LPARAM)Session.Scenarios[Session.Options.ScenarioIndex]); + case IDOK: + ScenarioScreen->Queue(UIIntent{UI_SCENARIOPICK_ACCEPT, "", 0}); break; - } case IDCANCEL: - WS_Destroy_Dialog(window, IDCANCEL); + ScenarioScreen->Queue(UIIntent{UI_SCENARIOPICK_CANCEL, "", 0}); break; - case IDC_CREATE_RANDOM_MAP: { - ShowWindow(window, SW_HIDE); - int scenario = CreateRandomMap(); - if (scenario != -1) { - SendDlgItemMessage(window, IDC_AILEVEL_SLIDER, LB_RESETCONTENT, 0, 0); - for (int i = 0; i < Session.Scenarios.Count(); i++) { - SendDlgItemMessage(window, IDC_AILEVEL_SLIDER, LB_INSERTSTRING, -1, (LPARAM)Session.Scenarios[i]); - } - SendDlgItemMessage(window, IDC_AILEVEL_SLIDER, LB_SETCURSEL, scenario, 0); - Set_Scenario_Info_From_Index(scenario); - if (!MultiplayerMapPreview->Get_Preview_Surface()) { - Update_Network_Dialog_Preview(window); - } - Session.Options.ScenarioIndex = OriginalScenario; - Set_Scenario_Info_From_Index(OriginalScenario); - } - ShowWindow(window, SW_SHOW); + case IDC_CREATE_RANDOM_MAP: + ScenarioScreen->Queue(UIIntent{UI_SCENARIOPICK_RANDOM, "", 0}); break; - } } break; - case OD_SUBCLASSED: { - SendDlgItemMessage(window, IDC_AILEVEL_SLIDER, LB_RESETCONTENT, 0, 0); - for (int i = 0; i < Session.Scenarios.Count(); i++) { - SendDlgItemMessage(window, IDC_AILEVEL_SLIDER, LB_INSERTSTRING, -1, (LPARAM)Session.Scenarios[i]); + case OD_SUBCLASSED: + if (ScenarioScreen != NULL) { + ScenarioScreen->ListChanged = true; + Scenario_Sync_Controls(window, *ScenarioScreen); } - SendDlgItemMessage(window, IDC_AILEVEL_SLIDER, LB_SETCURSEL, Session.Options.ScenarioIndex, 0); - OriginalScenario = Session.Options.ScenarioIndex; - LastPreviewedScenario = -1; break; - } } return(FALSE); } @@ -1302,6 +1340,21 @@ void PregameSetup(void) ///
/// The dialog window that displays the preview. void Update_Network_Dialog_Preview(HWND win) +{ + Rebuild_Network_Map_Preview(); + + if (MultiplayerMapPreview != NULL) { + InvalidateRect(win, NULL, FALSE); + } +} + + +/// +/// Rebuilds the map preview for the scenario the session currently names. +/// This is the half of the update that owns the preview itself, split from the half that +/// tells a window to repaint, so a presentation that is not a window can ask for it. +/// +void Rebuild_Network_Map_Preview(void) { delete MultiplayerMapPreview; MultiplayerMapPreview = NULL; @@ -1342,7 +1395,6 @@ void Update_Network_Dialog_Preview(HWND win) MultiplayerMapPreview = new MapPreviewClass; if (MultiplayerMapPreview != NULL) { MultiplayerMapPreview->Read_INI_Preview(Session.ScenarioFileName); - InvalidateRect(win, NULL, FALSE); } } diff --git a/code/netshare.h b/code/netshare.h index c71393c09..d87991c9b 100644 --- a/code/netshare.h +++ b/code/netshare.h @@ -23,6 +23,11 @@ bool Set_Scenario_Info_From_Index(int index); void Commit_Session_Specials(void); void PregameSetup(void); void Update_Network_Dialog_Preview(HWND win); +void Rebuild_Network_Map_Preview(void); + +// Runs the map selection screen and reports whether the player settled on a map. This is +// the entry a screen uses, because a presenter names no window. +bool Pick_Scenario_Screen(void); void Receive_Random_Map_Preview(void); void Send_Preview_To_Guests(void); int CountAliveTeams(HouseClass * house); diff --git a/code/skirmish.cpp b/code/skirmish.cpp index eb6651352..59e31c580 100644 --- a/code/skirmish.cpp +++ b/code/skirmish.cpp @@ -26,6 +26,8 @@ #include "newmenu.h" #include "ownrdraw.h" #include "rules.h" +#include "ui/uiskirmish.h" +#include "ui/uishell.h" #include "win.h" @@ -34,178 +36,142 @@ BOOL Skirmish_On_WM_INITDIALOG(HWND window, WPARAM wparam, LPARAM lparam); /// -/// Handles a control notification from the skirmish dialog. -/// This routine services the buttons and check boxes of the setup dialog. When the player -/// accepts the dialog, the slider and combo box settings are harvested into the session -/// options and the local player is added to the player list; when the player cancels, only -/// the handle, side, and color are remembered. +/// Reads the controls the screen takes its settings from and queues what they hold. +/// The dialog read its sliders, name field and boxes when a button was pressed rather than +/// tracking them, because a keyboard or page move changes a track bar without raising the +/// notification a tracking handler would follow. /// -/// The identifier of the control that sent the notification. -/// The notification code that came with the command. -void Skirmish_On_WM_COMMAND(HWND window, int message, WPARAM wparam, LPARAM lparam) +static void Skirmish_Read_Controls(HWND window, UISkirmishPresenterClass & screen) { - int * rc = (int *)GetWindowLongPtr(window, DWLP_USER); - char buffer[256]; - HWND handle; - - switch (message) { - case IDOK: { - if (lparam == 0) { - EnableWindow(GetDlgItem(window, 1), FALSE); - - int waypoint_count = RandomMapWaypointCount(Session.Options.ScenarioIndex); - int waypoint = 1; - - handle = GetDlgItem(window, IDC_SKIRMISH_AIPLAYERS); - if (handle) waypoint = Slider_GetPos(handle) + 1; - - if (waypoint_count < waypoint) { - sprintf(buffer, Fetch_String(TXT_SCENARIO_TOO_SMALL), waypoint_count); - WWMessageBox().Process(buffer, TXT_OK); - EnableWindow(GetDlgItem(window, 1), TRUE); - return; - } - - GetWindowText(GetDlgItem(window, IDC_SKIRMISH_NAME), Session.Handle, sizeof(Session.Handle)); - - handle = GetDlgItem(window, IDC_SKIRMISH_UNITCOUNT); - if (handle) Session.Options.UnitCount = Slider_GetPos(handle); - - handle = GetDlgItem(window, IDC_SKIRMISH_TECHLEVEL); - if (handle) BuildLevel = Slider_GetPos(handle); - - handle = GetDlgItem(window, IDC_SKIRMISH_CREDITS); - if (handle) Session.Options.Credits = Slider_GetPos(handle); - - handle = GetDlgItem(window, IDC_DIFFICULTY_SLIDER); - if (handle) Session.Options.AIDifficulty = (DiffType)Slider_GetPos(handle); - - handle = GetDlgItem(window, IDC_SKIRMISH_AIPLAYERS); - if (handle) Session.Options.AIPlayers = Slider_GetPos(handle); + static struct { int id; char const * name; } const sliders[] = { + { IDC_SKIRMISH_UNITCOUNT, UI_SKIRMISH_UNITCOUNT }, + { IDC_SKIRMISH_CREDITS, UI_SKIRMISH_CREDITS }, + { IDC_SKIRMISH_TECHLEVEL, UI_SKIRMISH_TECHLEVEL }, + { IDC_DIFFICULTY_SLIDER, UI_SKIRMISH_AILEVEL }, + { IDC_SKIRMISH_AIPLAYERS, UI_SKIRMISH_AIPLAYERS }, + { IDC_GAME_SPEED_SLIDER, UI_SKIRMISH_GAMESPEED }, + }; + + for (auto const & entry : sliders) { + HWND const handle = GetDlgItem(window, entry.id); + if (handle) { + screen.Queue(UIIntent{UI_SKIRMISH_SLIDER, entry.name, Slider_GetPos(handle)}); + } + } - handle = GetDlgItem(window, IDC_GAME_SPEED_SLIDER); - if (handle) { - Session.Options.GameSpeed = 6 - Slider_GetPos(handle); - Options.GameSpeed = Session.Options.GameSpeed; - } + char buffer[128]; + GetWindowText(GetDlgItem(window, IDC_SKIRMISH_NAME), buffer, sizeof(buffer)); + screen.Queue(UIIntent{UI_SKIRMISH_HANDLE, buffer, 0}); - handle = GetDlgItem(window, IDC_SKIRMISH_SIDE); - if (handle) Session.House = Country_From_Box(handle); + HWND handle = GetDlgItem(window, IDC_SKIRMISH_SIDE); + if (handle) { + int const country = Country_From_Box(handle); + for (int row = 0; row < (int)screen.Sides.size(); row++) { + if (screen.Sides[row].Country == country) { + screen.Queue(UIIntent{UI_SKIRMISH_SIDE, "", row}); + break; + } + } + } - handle = GetDlgItem(window, IDC_SKIRMISH_COLOR); - if (handle) { - Session.ColorIdx = ComboBox_GetCurSel(handle); - Session.PrefColor = Session.ColorIdx; - } + handle = GetDlgItem(window, IDC_SKIRMISH_COLOR); + if (handle) { + screen.Queue(UIIntent{UI_SKIRMISH_COLOR, "", (int)ComboBox_GetCurSel(handle)}); + } +} - NodeNameType * who = new NodeNameType; - if (who) { - strcpy(who->Name, Session.Handle); - who->Player.House = Session.House; - who->Player.Color = Session.ColorIdx; - who->Player.ProcessTime = -1; - Session.Players.Add(who); - } - handle = GetDlgItem(window, IDC_SKIRMISH_BASES); - if (handle) Session.Options.Bases = Button_GetCheck(handle) == BST_CHECKED; - handle = GetDlgItem(window, IDC_SKIRMISH_CRATES); - if (handle) Session.Options.Goodies = Button_GetCheck(handle) == BST_CHECKED; - handle = GetDlgItem(window, IDC_SKIRMISH_FOG); - if (handle) Session.Options.FogOfWar = Button_GetCheck(handle) == BST_CHECKED; - handle = GetDlgItem(window, IDC_SKIRMISH_BRIDGES); - if (handle) Session.Options.BridgeDestruction = Button_GetCheck(handle) == BST_CHECKED; - handle = GetDlgItem(window, IDC_REDEPLOY_MCV); - if (handle) Session.Options.MCVRedeploy = Button_GetCheck(handle) == BST_CHECKED; - handle = GetDlgItem(window, IDC_SHORT_GAME); - if (handle) Session.Options.ShortGame = Button_GetCheck(handle) == BST_CHECKED; - Session.Options.HarvTruce = false; - handle = GetDlgItem(window, IDC_MULTI_ENGINEER); - if (handle) Session.Options.CrapEngineers = Button_GetCheck(handle) == BST_CHECKED; +/// +/// Handles a control notification from the skirmish dialog. +/// The controls are read into the view-model and the command is queued as an intent; the +/// driver executes the queue after the pump returns. +/// +void Skirmish_On_WM_COMMAND(HWND window, int message, WPARAM wparam, LPARAM lparam) +{ + UISkirmishPresenterClass * const screen = + (UISkirmishPresenterClass *)GetWindowLongPtr(window, DWLP_USER); + if (screen == NULL) { + return; + } - if (MultiplayerMapPreview) { - delete MultiplayerMapPreview; - MultiplayerMapPreview = NULL; - } - *rc = IDOK; + switch (message) { + case IDOK: + if (lparam == 0) { + EnableWindow(GetDlgItem(window, 1), FALSE); + Skirmish_Read_Controls(window, *screen); + screen->Queue(UIIntent{UI_SKIRMISH_ACCEPT, "", 0}); } - } break; case IDCANCEL: if (!lparam) { - GetWindowText(GetDlgItem(window, IDC_SKIRMISH_NAME), Session.Handle, sizeof(Session.Handle)); - handle = GetDlgItem(window, IDC_SKIRMISH_SIDE); - if (handle) Session.House = Country_From_Box(handle); - handle = GetDlgItem(window, IDC_SKIRMISH_COLOR); - if (handle) { - Session.ColorIdx = ComboBox_GetCurSel(handle); - Session.PrefColor = Session.ColorIdx; - } - *rc = IDCANCEL; + Skirmish_Read_Controls(window, *screen); + screen->Queue(UIIntent{UI_SKIRMISH_CANCEL, "", 0}); } break; case IDC_SHORT_GAME: - handle = GetDlgItem(window, IDC_SHORT_GAME); - if (handle && Button_GetCheck(handle) == BST_CHECKED) { - SendDlgItemMessage(window, IDC_SKIRMISH_BASES, BM_SETCHECK, TRUE, 0); - } + screen->Queue(UIIntent{UI_SKIRMISH_TOGGLE, UI_SKIRMISH_SHORTGAME, 0}); break; - case IDC_MULTIMAP: { - int old_scen = Session.Options.ScenarioIndex; - strcpy(buffer, Session.ScenarioFileName); - strcpy(buffer, Session.Options.ScenarioDescription); - ShowWindow(window, SW_HIDE); - if (Scenario_Dialog(MainWindow) == IDCANCEL) { - Session.Options.ScenarioIndex = old_scen; - Set_Scenario_Info_From_Index(old_scen); - Update_Network_Dialog_Preview(window); - ShowWindow(window, SW_SHOW); - if (stricmp(Session.Scenarios[Session.Options.ScenarioIndex]->Get_Filename(), RANDOM_MAP_FILE_NAME) == 0) { - delete MultiplayerMapPreview; - MultiplayerMapPreview = new MapPreviewClass; - MultiplayerMapPreview->Read_PCX_Preview("RandMap.img"); - if (MultiplayerMapPreview->Get_Preview_Surface() == NULL) { - Update_Network_Dialog_Preview(window); - } - InvalidateRect(window, NULL, FALSE); - } else { - Update_Network_Dialog_Preview(window); - } - InvalidateRect(window, NULL, FALSE); - } else { - ShowWindow(window, SW_SHOW); - if (Set_Scenario_Info_From_Index(Session.Options.ScenarioIndex) == true) { - SendDlgItemMessage(window, IDC_SCENARIONAME, WM_SETTEXT, 0, (LPARAM)Session.Options.ScenarioDescription); - if (stricmp(Session.Scenarios[Session.Options.ScenarioIndex]->Get_Filename(), "RandMap.Sed") == 0) { - if (MultiplayerMapPreview != NULL) { - delete MultiplayerMapPreview; - MultiplayerMapPreview = new MapPreviewClass; - MultiplayerMapPreview->Read_PCX_Preview("RandMap.img"); - } - if (MultiplayerMapPreview->Get_Preview_Surface() == NULL) { - Update_Network_Dialog_Preview(window); - } - InvalidateRect(window, NULL, FALSE); - } else { - Update_Network_Dialog_Preview(window); - } - } else { - Session.Options.ScenarioIndex = old_scen; - } - } - } + case IDC_SKIRMISH_BASES: + screen->Queue(UIIntent{UI_SKIRMISH_TOGGLE, UI_SKIRMISH_BASES, 0}); break; - case IDC_SKIRMISH_BASES: - handle = GetDlgItem(window, IDC_SKIRMISH_BASES); - if (handle && Button_GetCheck(handle) != 1) { - SendDlgItemMessage(window, IDC_SHORT_GAME, BM_SETCHECK, 0, 0); - } + case IDC_SKIRMISH_CRATES: + screen->Queue(UIIntent{UI_SKIRMISH_TOGGLE, UI_SKIRMISH_CRATES, 0}); + break; + + case IDC_SKIRMISH_FOG: + screen->Queue(UIIntent{UI_SKIRMISH_TOGGLE, UI_SKIRMISH_FOG, 0}); + break; + + case IDC_SKIRMISH_BRIDGES: + screen->Queue(UIIntent{UI_SKIRMISH_TOGGLE, UI_SKIRMISH_BRIDGES, 0}); + break; + + case IDC_REDEPLOY_MCV: + screen->Queue(UIIntent{UI_SKIRMISH_TOGGLE, UI_SKIRMISH_MCV, 0}); break; + + case IDC_MULTI_ENGINEER: + screen->Queue(UIIntent{UI_SKIRMISH_TOGGLE, UI_SKIRMISH_ENGINEER, 0}); + break; + + case IDC_MULTIMAP: + screen->Queue(UIIntent{UI_SKIRMISH_PICK_MAP, "", 0}); + break; + } +} + + +/// +/// Puts the view-model on the dialog's own controls. +/// +static void Skirmish_Sync_Controls(HWND window, UISkirmishPresenterClass & screen) +{ + static struct { int id; bool UISkirmishPresenterClass::* field; } const boxes[] = { + { IDC_SKIRMISH_BASES, &UISkirmishPresenterClass::Bases }, + { IDC_SKIRMISH_CRATES, &UISkirmishPresenterClass::Crates }, + { IDC_SKIRMISH_FOG, &UISkirmishPresenterClass::FogOfWar }, + { IDC_SKIRMISH_BRIDGES, &UISkirmishPresenterClass::Bridges }, + { IDC_REDEPLOY_MCV, &UISkirmishPresenterClass::MCVRedeploy }, + { IDC_SHORT_GAME, &UISkirmishPresenterClass::ShortGame }, + { IDC_MULTI_ENGINEER, &UISkirmishPresenterClass::MultiEngineer }, + }; + + for (auto const & entry : boxes) { + HWND const handle = GetDlgItem(window, entry.id); + if (handle == NULL) continue; + + int const wanted = (screen.*(entry.field)) ? BST_CHECKED : BST_UNCHECKED; + if (Button_GetCheck(handle) != wanted) { + Button_SetCheck(handle, wanted); + } } + + SendDlgItemMessage(window, IDC_SCENARIONAME, WM_SETTEXT, 0, (LPARAM)screen.ScenarioName.c_str()); + EnableWindow(GetDlgItem(window, 1), screen.CanAccept ? TRUE : FALSE); } @@ -227,25 +193,41 @@ bool Skirmish_Mode_Dialog(void) Draw_Menu_Background(); Show_Mouse(); + UISkirmishPresenterClass screen; + screen.Refresh(); + HWND dialog = OwnerDraw::Begin_Dialog(IDD_SKIRMISH, Skirmish_Dialog_Proc); if (dialog) { - SetWindowLongPtr(dialog, DWLP_USER, (LONG_PTR)&rc); + SetWindowLongPtr(dialog, DWLP_USER, (LONG_PTR)&screen); + Skirmish_Sync_Controls(dialog, screen); OwnerDraw::Display_Dialog(dialog); - while (rc != IDOK && rc != IDCANCEL) { + while (!screen.Result.has_value()) { if (OwnerDraw::Dialog_Message_Handler() == IDOK) { break; } - Title_Screen_Restore(); + + // A control handler queues rather than acts, so the queue is executed here, + // after the pump has returned. + screen.Drain(); + + // The map selection screen draws where this one is, so the dialog gets out of + // its way, which is what its own ShowWindow did. + if (screen.Pending != UISkirmishPresenterClass::SUB_NONE) { + ShowWindow(dialog, SW_HIDE); + screen.Run_Pending(); + ShowWindow(dialog, SW_SHOW); + InvalidateRect(dialog, NULL, FALSE); + } + + Skirmish_Sync_Controls(dialog, screen); + screen.Service(); } OwnerDraw::End_Dialog(dialog); } - if (MultiplayerMapPreview != NULL) { - delete MultiplayerMapPreview; - MultiplayerMapPreview = NULL; - } + rc = screen.Accepted() ? IDOK : IDCANCEL; - Session.Write_MultiPlayer_Settings(); + screen.End(); if (rc == IDCANCEL) { Hide_Mouse(); diff --git a/code/ui/uiscenariopick.cpp b/code/ui/uiscenariopick.cpp new file mode 100644 index 000000000..3fb132e5d --- /dev/null +++ b/code/ui/uiscenariopick.cpp @@ -0,0 +1,174 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The multiplayer map selection screen. Behavior traced out of Scenario_DlgProc and +// Scenario_Select_Callback in netshare.cpp. +// +// What the extraction fixes in place: the preview follows the highlighted row rather than +// the session's own choice, and the session's choice is put back after every look, so +// browsing the list changes nothing until the player accepts; the random map generator draws +// where this screen is, so the screen steps aside for it; and backing out leaves the +// scenario the screen opened on. +// +// docs/UI_DESIGN.md, "Screens", owns the contracts this keeps to. + +#include "always.h" + +#include "uiscenariopick.h" + +#include "data.h" +#include "mapgen.h" +#include "netdlg2.h" +#include "netshare.h" +#include "preview.h" +#include "session.h" + +#include + + +UIScenarioPickPresenterClass::UIScenarioPickPresenterClass(void) +{ +} + + +void UIScenarioPickPresenterClass::Build_List(void) +{ + Scenarios.clear(); + for (int index = 0; index < Session.Scenarios.Count(); index++) { + Scenarios.push_back(Session.Scenarios[index]->Description()); + } + ListChanged = true; +} + + +void UIScenarioPickPresenterClass::Refresh(void) +{ + Build_List(); + + Selected = Session.Options.ScenarioIndex; + Original = Selected; + LastPreviewed = -1; +} + + +/// +/// Builds the preview for the highlighted row and puts the session's own choice back. +/// The list can be walked without committing to anything, so the scenario information the +/// preview needs is loaded, used, and then replaced by the one the screen opened on. +/// +void UIScenarioPickPresenterClass::Preview_Selection(void) +{ + if (Selected == LastPreviewed || Selected < 0 || Selected >= Session.Scenarios.Count()) { + return; + } + + Set_Scenario_Info_From_Index(Selected); + + // A generated map has a picture of its own beside it rather than one read out of the map. + if (stricmp(Session.Scenarios[Selected]->Get_Filename(), RANDOM_MAP_FILE_NAME) == 0) { + delete MultiplayerMapPreview; + MultiplayerMapPreview = new MapPreviewClass; + MultiplayerMapPreview->Read_PCX_Preview("RandMap.img"); + if (MultiplayerMapPreview->Get_Preview_Surface() == NULL) { + Rebuild_Network_Map_Preview(); + } + } else { + Rebuild_Network_Map_Preview(); + } + + LastPreviewed = Selected; + PreviewGeneration++; + + Session.Options.ScenarioIndex = Original; + Set_Scenario_Info_From_Index(Original); +} + + +/// +/// The maintenance the dialog's wait callback ran on every pass of its own loop. +/// +void UIScenarioPickPresenterClass::Service(void) +{ + Preview_Selection(); + + // A game already in the lobby keeps talking while the host browses. The runner services + // a session of its own, so only the network pump the callback added belongs here. + if (Session.Type == GAME_IPX || Session.Type == GAME_INTERNET) { + Net2Callback(); + } +} + + +/// +/// Runs the map generator with the screen already out of the way. +/// +void UIScenarioPickPresenterClass::Run_Pending(void) +{ + if (Pending != SUB_RANDOM_MAP) { + return; + } + + Pending = SUB_NONE; + + int const scenario = CreateRandomMap(); + if (scenario == -1) { + return; + } + + // A generated map joins the list and is highlighted, but the session keeps the scenario + // the screen opened on until the player accepts. + Build_List(); + Selected = scenario; + LastPreviewed = -1; + + Set_Scenario_Info_From_Index(scenario); + if (MultiplayerMapPreview == NULL || MultiplayerMapPreview->Get_Preview_Surface() == NULL) { + Rebuild_Network_Map_Preview(); + } + PreviewGeneration++; + + Session.Options.ScenarioIndex = Original; + Set_Scenario_Info_From_Index(Original); +} + + +void UIScenarioPickPresenterClass::Execute(UIIntent const & intent) +{ + if (intent.Action == UI_SCENARIOPICK_SELECT) { + if (intent.Value < 0 || intent.Value >= (int)Scenarios.size()) { + return; + } + Selected = intent.Value; + return; + } + + if (intent.Action == UI_SCENARIOPICK_RANDOM) { + Pending = SUB_RANDOM_MAP; + return; + } + + if (intent.Action == UI_SCENARIOPICK_ACCEPT) { + // The dialog clamped a list with nothing selected to the first entry. + Selected = Selected > 0 ? Selected : 0; + Session.Options.ScenarioIndex = Selected; + + UIResult result; + result.Outcome = UIResult::OUTCOME_ACCEPTED; + result.Value = Selected; + Result = result; + return; + } + + if (intent.Action == UI_SCENARIOPICK_CANCEL) { + UIResult result; + result.Outcome = UIResult::OUTCOME_CANCELLED; + Result = result; + return; + } +} diff --git a/code/ui/uiscenariopick.h b/code/ui/uiscenariopick.h new file mode 100644 index 000000000..bb03ef5bb --- /dev/null +++ b/code/ui/uiscenariopick.h @@ -0,0 +1,87 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The multiplayer map selection screen's behavior, with no toolkit in it. The skirmish +// setup screen and the network lobbies both open it. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + +#include +#include + + +inline constexpr char const * UI_SCENARIOPICK_SELECT = "select"; +inline constexpr char const * UI_SCENARIOPICK_ACCEPT = "accept"; +inline constexpr char const * UI_SCENARIOPICK_CANCEL = "cancel"; +inline constexpr char const * UI_SCENARIOPICK_RANDOM = "random"; + + +// The artwork the map preview is registered under. A presenter carries the name; the view +// owns the provider. +inline constexpr char const * UI_MAP_PREVIEW_SURFACE = "mappreview"; + + +class UIScenarioPickPresenterClass : public UIPresenterClass +{ + public: + // A screen this one opens on top of itself. The map generator draws where this + // screen is, so the view steps aside for it, which is what its ShowWindow did. + enum SubScreenType { + SUB_NONE, + SUB_RANDOM_MAP, + }; + + UIScenarioPickPresenterClass(void); + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + virtual void Service(void) override; + virtual bool Suspends(void) const override { return(Pending != SUB_NONE); } + + // Runs the sub-screen an executed intent asked for and clears the request. + void Run_Pending(void); + + // Which scenario the player settled on. Only meaningful once the screen accepted. + int Chosen(void) const { return(Selected); } + + /* + ** The view-model. + */ + std::vector Scenarios; + int Selected = 0; + + // The name of the artwork the preview draws into, never a surface. + std::string Preview = UI_MAP_PREVIEW_SURFACE; + + // Moves whenever the preview was rebuilt, so a view marks its provider dirty once + // per change rather than once per pass. + unsigned int PreviewGeneration = 0; + + SubScreenType Pending = SUB_NONE; + + // Has the list itself changed? Only the map generator moves it. + bool ListChanged = false; + + private: + void Build_List(void); + void Preview_Selection(void); + + // The scenario the screen opened on. The preview walks the list without moving the + // session's own choice, so the session is put back after every look. + int Original = 0; + int LastPreviewed = -1; +}; + + +// Shows the picker through its RmlUi view. +UIResult UI_Scenario_Pick_Screen(UIScenarioPickPresenterClass & presenter); diff --git a/code/ui/uiskirmish.cpp b/code/ui/uiskirmish.cpp new file mode 100644 index 000000000..23fc081f3 --- /dev/null +++ b/code/ui/uiskirmish.cpp @@ -0,0 +1,346 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The skirmish setup screen. Behavior traced out of Skirmish_On_WM_INITDIALOG, +// Skirmish_On_WM_COMMAND and Skirmish_Mode_Dialog in skirmish.cpp. +// +// What the extraction fixes in place: a side row carries the country it stands for rather +// than its position, because the list holds only the countries that may be played; the map +// the player asked for is checked for enough start positions when the accept button is +// pressed, and a map with too few leaves the screen standing; Short Game turns Bases on and +// turning Bases off turns Short Game off; and backing out still records the name, side and +// color, which is what the cancel arm read before it answered. +// +// docs/UI_DESIGN.md, "Screens", owns the contracts this keeps to. + +#include "always.h" + +#include "uiskirmish.h" + +#include "uiscenariopick.h" + +#include "_rules.h" +#include "data.h" +#include "houstype.h" +#include "init.h" +#include "language/language.h" +#include "msgbox.h" +#include "netdlg2.h" +#include "goptions.h" +#include "mapgen.h" +#include "mplayer.h" +#include "netshare.h" +#include "preview.h" +#include "rules.h" +#include "session.h" + +#include +#include + + +// The least money a skirmish may be started with, which is where the credits track bar +// begins. +enum { MP_MIN_MONEY = 2500 }; + + +UISkirmishPresenterClass::UISkirmishPresenterClass(void) : + Preview(UI_MAP_PREVIEW_SURFACE) +{ +} + + +void UISkirmishPresenterClass::Refresh(void) +{ + Handle = Session.Handle; + + Sides.clear(); + SelectedSide = 0; + for (int index = 0; index < HouseTypes.Count(); index++) { + HouseTypeClass const * const house = HouseTypes[index]; + if (!house->IsMultiplay) continue; + + if (index == Session.House) { + SelectedSide = (int)Sides.size(); + } + Sides.push_back(SideType{(char const *)house->GivenName, index}); + } + + Colors.clear(); + Colors.push_back(Fetch_String(TXT_GOLD)); + Colors.push_back(Fetch_String(TXT_RED)); + Colors.push_back(Fetch_String(TXT_BLUE)); + Colors.push_back(Fetch_String(TXT_GREEN)); + Colors.push_back(Fetch_String(TXT_ORANGE)); + Colors.push_back(Fetch_String(TXT_SKY_BLUE)); + Colors.push_back(Fetch_String(TXT_PURPLE)); + Colors.push_back(Fetch_String(TXT_PINK)); + SelectedColor = Session.PrefColor; + + UnitCount = SliderType{Session.Options.UnitCount, SessionClass::CountMin[1], SessionClass::CountMax[1], 1}; + Credits = SliderType{Session.Options.Credits, MP_MIN_MONEY, Rule->MPMaxMoney, 250}; + TechLevel = SliderType{BuildLevel, 1, MPLAYER_BUILD_LEVEL_MAX, 1}; + AILevel = SliderType{(int)Session.Options.AIDifficulty, 0, 2, 1}; + AIPlayers = SliderType{Session.Options.AIPlayers > 1 ? Session.Options.AIPlayers : 1, 1, 7, 1}; + + // The track bar runs the other way round from the setting: its left end is the slowest + // game, and the dialog turned one into the other at both ends. + GameSpeed = SliderType{6 - Session.Options.GameSpeed, 0, 6, 1}; + + Bases = Session.Options.Bases; + Crates = Session.Options.Goodies; + FogOfWar = Session.Options.FogOfWar; + Bridges = Session.Options.BridgeDestruction; + MCVRedeploy = Session.Options.MCVRedeploy; + ShortGame = Session.Options.ShortGame; + MultiEngineer = Session.Options.CrapEngineers; + + // The screen opens on the first scenario whatever the session was carrying. + Set_Scenario_Info_From_Index(0); + Session.Options.ScenarioIndex = 0; + ScenarioName = Session.Options.ScenarioDescription; + + Clear_Vector(&Session.Players); + Clear_Vector(&Session.Computers); + + Rebuild_Network_Map_Preview(); + PreviewGeneration++; + + CanAccept = true; + ListChanged = true; +} + + +/// +/// The maintenance the dialog driver ran on every pass of its own loop. +/// +void UISkirmishPresenterClass::Service(void) +{ + Title_Screen_Restore(); +} + + +/// +/// Records the name, side and color the player is showing. +/// Both leaving and backing out read these, because they are the player's own preferences +/// rather than the game's settings. +/// +void UISkirmishPresenterClass::Read_Identity(void) +{ + std::snprintf(Session.Handle, sizeof(Session.Handle), "%s", Handle.c_str()); + + if (SelectedSide >= 0 && SelectedSide < (int)Sides.size()) { + Session.House = (HousesType)Sides[SelectedSide].Country; + } else { + Session.House = (HousesType)HOUSE_FIRST; + } + + Session.ColorIdx = SelectedColor; + Session.PrefColor = SelectedColor; +} + + +/// +/// Writes the player's multiplayer preferences, and drops the preview. +/// +void UISkirmishPresenterClass::End(void) +{ + if (MultiplayerMapPreview != NULL) { + delete MultiplayerMapPreview; + MultiplayerMapPreview = NULL; + } + + Session.Write_MultiPlayer_Settings(); +} + + +/// +/// Runs the map selection screen with this one already out of the way. +/// Backing out of it leaves the scenario this screen was showing, which is what putting the +/// old index back did. +/// +void UISkirmishPresenterClass::Run_Pending(void) +{ + if (Pending != SUB_PICK_MAP) { + return; + } + + Pending = SUB_NONE; + + int const previous = Session.Options.ScenarioIndex; + + bool const picked = Pick_Scenario_Screen(); + + if (picked && Set_Scenario_Info_From_Index(Session.Options.ScenarioIndex) == true) { + ScenarioName = Session.Options.ScenarioDescription; + } else { + Session.Options.ScenarioIndex = previous; + Set_Scenario_Info_From_Index(previous); + } + + // A generated map has a picture of its own beside it rather than one read out of the map. + int const index = Session.Options.ScenarioIndex; + if (index >= 0 && index < Session.Scenarios.Count() + && stricmp(Session.Scenarios[index]->Get_Filename(), RANDOM_MAP_FILE_NAME) == 0) { + delete MultiplayerMapPreview; + MultiplayerMapPreview = new MapPreviewClass; + MultiplayerMapPreview->Read_PCX_Preview("RandMap.img"); + if (MultiplayerMapPreview->Get_Preview_Surface() == NULL) { + Rebuild_Network_Map_Preview(); + } + } else { + Rebuild_Network_Map_Preview(); + } + + PreviewGeneration++; +} + + +/// +/// Starts the game the player set up, if the map has room for it. +/// The start position count is checked here rather than when the slider moved, because the +/// map can change after it did. +/// +void UISkirmishPresenterClass::Accept(void) +{ + CanAccept = false; + + int const waypoints = RandomMapWaypointCount(Session.Options.ScenarioIndex); + if (waypoints < AIPlayers.Value + 1) { + char buffer[256]; + std::snprintf(buffer, sizeof(buffer), Fetch_String(TXT_SCENARIO_TOO_SMALL), waypoints); + WWMessageBox().Process(buffer, TXT_OK); + CanAccept = true; + return; + } + + Read_Identity(); + + Session.Options.UnitCount = UnitCount.Value; + BuildLevel = TechLevel.Value; + Session.Options.Credits = Credits.Value; + Session.Options.AIDifficulty = (DiffType)AILevel.Value; + Session.Options.AIPlayers = AIPlayers.Value; + Session.Options.GameSpeed = 6 - GameSpeed.Value; + Options.GameSpeed = Session.Options.GameSpeed; + + NodeNameType * const who = new NodeNameType; + if (who != NULL) { + strcpy(who->Name, Session.Handle); + who->Player.House = Session.House; + who->Player.Color = Session.ColorIdx; + who->Player.ProcessTime = -1; + Session.Players.Add(who); + } + + Session.Options.Bases = Bases; + Session.Options.Goodies = Crates; + Session.Options.FogOfWar = FogOfWar; + Session.Options.BridgeDestruction = Bridges; + Session.Options.MCVRedeploy = MCVRedeploy; + Session.Options.ShortGame = ShortGame; + Session.Options.HarvTruce = false; + Session.Options.CrapEngineers = MultiEngineer; + + if (MultiplayerMapPreview != NULL) { + delete MultiplayerMapPreview; + MultiplayerMapPreview = NULL; + } + + Outcome = true; + + UIResult result; + result.Outcome = UIResult::OUTCOME_ACCEPTED; + Result = result; +} + + +void UISkirmishPresenterClass::Execute(UIIntent const & intent) +{ + if (intent.Action == UI_SKIRMISH_HANDLE) { + Handle = intent.Identity; + if (Handle.size() > HANDLE_LIMIT) { + Handle.resize(HANDLE_LIMIT); + } + return; + } + + if (intent.Action == UI_SKIRMISH_SIDE) { + if (intent.Value >= 0 && intent.Value < (int)Sides.size()) { + SelectedSide = intent.Value; + } + return; + } + + if (intent.Action == UI_SKIRMISH_COLOR) { + if (intent.Value >= 0 && intent.Value < (int)Colors.size()) { + SelectedColor = intent.Value; + } + return; + } + + if (intent.Action == UI_SKIRMISH_SLIDER) { + SliderType * slider = NULL; + if (intent.Identity == UI_SKIRMISH_UNITCOUNT) slider = &UnitCount; + else if (intent.Identity == UI_SKIRMISH_CREDITS) slider = &Credits; + else if (intent.Identity == UI_SKIRMISH_TECHLEVEL) slider = &TechLevel; + else if (intent.Identity == UI_SKIRMISH_AILEVEL) slider = &AILevel; + else if (intent.Identity == UI_SKIRMISH_AIPLAYERS) slider = &AIPlayers; + else if (intent.Identity == UI_SKIRMISH_GAMESPEED) slider = &GameSpeed; + + if (slider != NULL) { + int value = intent.Value; + if (value < slider->Minimum) value = slider->Minimum; + if (value > slider->Maximum) value = slider->Maximum; + slider->Value = value; + } + return; + } + + if (intent.Action == UI_SKIRMISH_TOGGLE) { + if (intent.Identity == UI_SKIRMISH_BASES) { + Bases = !Bases; + // A short game is decided by what a player still holds, so it needs bases. + if (!Bases) ShortGame = false; + } else if (intent.Identity == UI_SKIRMISH_SHORTGAME) { + ShortGame = !ShortGame; + if (ShortGame) Bases = true; + } else if (intent.Identity == UI_SKIRMISH_CRATES) { + Crates = !Crates; + } else if (intent.Identity == UI_SKIRMISH_FOG) { + FogOfWar = !FogOfWar; + } else if (intent.Identity == UI_SKIRMISH_BRIDGES) { + Bridges = !Bridges; + } else if (intent.Identity == UI_SKIRMISH_MCV) { + MCVRedeploy = !MCVRedeploy; + } else if (intent.Identity == UI_SKIRMISH_ENGINEER) { + MultiEngineer = !MultiEngineer; + } + return; + } + + if (intent.Action == UI_SKIRMISH_PICK_MAP) { + Pending = SUB_PICK_MAP; + return; + } + + if (intent.Action == UI_SKIRMISH_ACCEPT) { + Accept(); + return; + } + + if (intent.Action == UI_SKIRMISH_CANCEL) { + Read_Identity(); + Outcome = false; + + UIResult result; + result.Outcome = UIResult::OUTCOME_CANCELLED; + Result = result; + return; + } +} diff --git a/code/ui/uiskirmish.h b/code/ui/uiskirmish.h new file mode 100644 index 000000000..31926c178 --- /dev/null +++ b/code/ui/uiskirmish.h @@ -0,0 +1,149 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The skirmish setup screen's behavior, with no toolkit in it. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + +#include +#include + + +inline constexpr char const * UI_SKIRMISH_HANDLE = "handle"; // Identity: the name typed +inline constexpr char const * UI_SKIRMISH_SIDE = "side"; // Value: row in Sides +inline constexpr char const * UI_SKIRMISH_COLOR = "color"; // Value: row in Colors +inline constexpr char const * UI_SKIRMISH_SLIDER = "slider"; // Identity: which, Value: position +inline constexpr char const * UI_SKIRMISH_TOGGLE = "toggle"; // Identity: which +inline constexpr char const * UI_SKIRMISH_PICK_MAP = "pickmap"; +inline constexpr char const * UI_SKIRMISH_ACCEPT = "accept"; +inline constexpr char const * UI_SKIRMISH_CANCEL = "cancel"; + +// The sliders and check boxes, named rather than numbered, because an intent carries an +// identity and never a control. +inline constexpr char const * UI_SKIRMISH_UNITCOUNT = "unitcount"; +inline constexpr char const * UI_SKIRMISH_CREDITS = "credits"; +inline constexpr char const * UI_SKIRMISH_TECHLEVEL = "techlevel"; +inline constexpr char const * UI_SKIRMISH_AILEVEL = "ailevel"; +inline constexpr char const * UI_SKIRMISH_AIPLAYERS = "aiplayers"; +inline constexpr char const * UI_SKIRMISH_GAMESPEED = "gamespeed"; + +inline constexpr char const * UI_SKIRMISH_BASES = "bases"; +inline constexpr char const * UI_SKIRMISH_CRATES = "crates"; +inline constexpr char const * UI_SKIRMISH_FOG = "fog"; +inline constexpr char const * UI_SKIRMISH_BRIDGES = "bridges"; +inline constexpr char const * UI_SKIRMISH_MCV = "mcv"; +inline constexpr char const * UI_SKIRMISH_SHORTGAME = "shortgame"; +inline constexpr char const * UI_SKIRMISH_ENGINEER = "engineer"; + + +class UISkirmishPresenterClass : public UIPresenterClass +{ + public: + // A screen this one opens on top of itself. + enum SubScreenType { + SUB_NONE, + SUB_PICK_MAP, + }; + + // A track bar, with the range the dialog gave it. + struct SliderType + { + int Value = 0; + int Minimum = 0; + int Maximum = 0; + + // The amount one move covers. Credits move in steps of 250, which is what + // OD_SETTRACKSTEP set on that control. + int Step = 1; + }; + + // A playable side, carrying the country it stands for rather than its position, + // because the list holds only the countries that may be played. + struct SideType + { + std::string Name; + int Country = 0; + }; + + UISkirmishPresenterClass(void); + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + virtual void Service(void) override; + virtual bool Suspends(void) const override { return(Pending != SUB_NONE); } + + void Run_Pending(void); + + // Did the player ask for the game to start? + bool Accepted(void) const { return(Outcome); } + + // Writes the player's multiplayer preferences, which the driver did on the way out + // whichever way the screen was left. + void End(void); + + /* + ** The view-model. + */ + std::string Handle; + + std::vector Sides; + int SelectedSide = 0; + + std::vector Colors; + int SelectedColor = 0; + + SliderType UnitCount; + SliderType Credits; + SliderType TechLevel; + SliderType AILevel; + SliderType AIPlayers; + SliderType GameSpeed; + + bool Bases = false; + bool Crates = false; + bool FogOfWar = false; + bool Bridges = false; + bool MCVRedeploy = false; + bool ShortGame = false; + bool MultiEngineer = false; + + std::string ScenarioName; + + // The name of the artwork the map preview draws into, never a surface. + std::string Preview; + + // Moves whenever the preview was rebuilt. + unsigned int PreviewGeneration = 0; + + // The longest handle the name field accepts, in bytes, which is the buffer the + // dialog read the control into. + enum { HANDLE_LIMIT = 19 }; + + // Is the accept button available? The dialog disabled it while it checked whether + // the map has room for the computer players asked for. + bool CanAccept = true; + + SubScreenType Pending = SUB_NONE; + + bool ListChanged = false; + + private: + void Accept(void); + void Read_Identity(void); + + bool Outcome = false; +}; + + +// Shows the skirmish screen through its RmlUi view. +UIResult UI_Skirmish_Screen(UISkirmishPresenterClass & presenter); From b4e16fcc8be3c709b32cdca239880d8b2cc3d262 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 06:00:06 +0100 Subject: [PATCH 121/179] feat(ui): show map selection and its preview through RmlUi Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/netshare.cpp | 11 +++ code/ui/uiscenariopick.cpp | 190 +++++++++++++++++++++++++++++++++++++ ui/selectmap.rcss | 79 +++++++++++++++ ui/selectmap.rml | 24 +++++ 4 files changed, 304 insertions(+) create mode 100644 ui/selectmap.rcss create mode 100644 ui/selectmap.rml diff --git a/code/netshare.cpp b/code/netshare.cpp index e8bddea7d..cbf5121f8 100644 --- a/code/netshare.cpp +++ b/code/netshare.cpp @@ -11,6 +11,7 @@ #include "netshare.h" #include "ui/uiscenariopick.h" +#include "ui/uishell.h" #include "_rules.h" #include "conquer.h" @@ -1234,6 +1235,16 @@ int Scenario_Dialog(HWND top) /// bool; Did the player settle on a map? bool Pick_Scenario_Screen(void) { + if (UI_Use_Rml()) { + UIScenarioPickPresenterClass screen; + screen.Refresh(); + + UIResult const result = UI_Scenario_Pick_Screen(screen); + if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { + return(result.Outcome == UIResult::OUTCOME_ACCEPTED); + } + } + return(Scenario_Dialog(MainWindow) == IDOK); } diff --git a/code/ui/uiscenariopick.cpp b/code/ui/uiscenariopick.cpp index 3fb132e5d..fb8815c32 100644 --- a/code/ui/uiscenariopick.cpp +++ b/code/ui/uiscenariopick.cpp @@ -22,14 +22,26 @@ #include "uiscenariopick.h" +#include "uirmlview.h" +#include "uisurface.h" + #include "data.h" #include "mapgen.h" #include "netdlg2.h" #include "netshare.h" +#include "dsurface.h" #include "preview.h" #include "session.h" +#include "xsurface.h" + +#include +#include +#include +#include #include +#include +#include UIScenarioPickPresenterClass::UIScenarioPickPresenterClass(void) @@ -172,3 +184,181 @@ void UIScenarioPickPresenterClass::Execute(UIIntent const & intent) return; } } + + +//--------------------------------------------------------------------------------------- +// The RmlUi view. +//--------------------------------------------------------------------------------------- + +// The picture the preview frame holds, in game logical units. The frame is the template's +// 129 x 80 dialog units, which is 193.5 by 130 at the family's 1.5 across and 1.625 down, +// and the picture sits inside its one pixel border. +enum { PREVIEW_WIDTH = 191, PREVIEW_HEIGHT = 128 }; + + +/// +/// The map preview as a surface a document can show. +/// The picture is scaled into the frame the way MapPreviewClass::Blit_Preview scales it into +/// the dialog's group box, so what the document shows is what the dialog showed. The frame is +/// filled with a color key first, so the letterbox around a picture of a different shape is +/// transparent rather than black. +/// +class MapPreviewSurfaceClass : public UISurfaceBufferClass +{ + public: + MapPreviewSurfaceClass(void) : + UISurfaceBufferClass(PREVIEW_WIDTH, PREVIEW_HEIGHT) + { + Set_Transparent_Color(DSurface::Build_Hicolor_Pixel(255, 0, 255)); + Clear(); + } + + void Redraw(void); +}; + + +void MapPreviewSurfaceClass::Redraw(void) +{ + Clear(); + + if (MultiplayerMapPreview == NULL) { + return; + } + + XSurface * const picture = MultiplayerMapPreview->Get_Preview_Surface(); + if (picture == NULL) { + return; + } + + Rect const source = picture->Get_Rect(); + if (source.Width <= 0 || source.Height <= 0) { + return; + } + + int const scale = std::min(1000 * PREVIEW_WIDTH / source.Width, 1000 * PREVIEW_HEIGHT / source.Height); + + Rect destination; + destination.Width = (scale * source.Width) / 1000; + destination.Height = (scale * source.Height) / 1000; + destination.X = PREVIEW_WIDTH / 2 - destination.Width / 2; + destination.Y = PREVIEW_HEIGHT / 2 - destination.Height / 2; + + Get_Surface().Blit_From(destination, *picture, source, false, false); + Mark_Dirty(); +} + + +/// +/// The RmlUi half of the map selection screen. +/// +class ScenarioPickViewClass : public UIRmlViewClass +{ + public: + ScenarioPickViewClass(UIScenarioPickPresenterClass & presenter) : + UIRmlViewClass(presenter, "selectmap.rml"), + Screen(presenter) + { + UI_Register_Surface(Screen.Preview.c_str(), &Picture); + } + + virtual ~ScenarioPickViewClass(void) override + { + UI_Unregister_Surface(Screen.Preview.c_str()); + } + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + private: + UIScenarioPickPresenterClass & Screen; + + // The view owns the pixels; the presenter carries only the name they answer to. + MapPreviewSurfaceClass Picture; + + unsigned int Drawn = 0; +}; + + +void ScenarioPickViewClass::Bind(Rml::DataModelConstructor & model) +{ + model.RegisterArray>(); + + model.Bind("scenarios", &Screen.Scenarios); + model.Bind("selected", &Screen.Selected); + model.Bind("preview", &Screen.Preview); + + model.BindEventCallback("pick", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{UI_SCENARIOPICK_SELECT, "", (int)arguments[0].Get()}); + }); + + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{arguments[0].Get(), "", 0}); + }); + + // Escape backs out and Enter takes the highlighted map, which is what IsDialogMessage + // delivered to a template that names IDCANCEL and no default push button. + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key == Rml::Input::KI_ESCAPE) { + Screen.Queue(UIIntent{UI_SCENARIOPICK_CANCEL, "", 0}); + } else if (key == Rml::Input::KI_RETURN || key == Rml::Input::KI_NUMPADENTER) { + Screen.Queue(UIIntent{UI_SCENARIOPICK_ACCEPT, "", 0}); + } + }); +} + + +void ScenarioPickViewClass::Sync(void) +{ + if (!Model) return; + + Model.DirtyVariable("scenarios"); + Model.DirtyVariable("selected"); + + // The picture is redrawn where it changed, not every pass, so the element uploads once + // per map rather than once per present. + if (Screen.PreviewGeneration != Drawn) { + Drawn = Screen.PreviewGeneration; + Picture.Redraw(); + } + + Screen.ListChanged = false; +} + + +/// +/// Shows the map selection screen and waits for the player to leave it. +/// +UIResult UI_Scenario_Pick_Screen(UIScenarioPickPresenterClass & presenter) +{ + ScenarioPickViewClass view(presenter); + + if (!view.Prepare(true)) { + UIResult result; + result.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + return(result); + } + + // The map generator draws where this screen is, so the document steps aside for it. + while (!presenter.Result.has_value()) { + UI_Run_Modal(presenter, view); + + if (presenter.Pending == UIScenarioPickPresenterClass::SUB_NONE) { + break; + } + + view.Hide(); + presenter.Run_Pending(); + view.Show(); + view.Sync(); + } + + UIResult const result = presenter.Result.value_or(UIResult{}); + view.Close(); + return(result); +} diff --git a/ui/selectmap.rcss b/ui/selectmap.rcss new file mode 100644 index 000000000..25b92eb54 --- /dev/null +++ b/ui/selectmap.rcss @@ -0,0 +1,79 @@ +/* The multiplayer map selection screen. Geometry from the IDD_MPLAYER_SELECT_MAP template, + converted from dialog units at the 8 point MS Sans Serif the template names: 1.5 pixels + across and 1.625 down, with a child's offset taken from the panel's content box and the + panel's declared size taken inside its own border. */ + +/* 360 x 200 dialog units, so 540 x 325 pixels, centred on the frame. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -270dp; + margin-top: -162.5dp; + + width: 536dp; + height: 321dp; +} + +/* The CTEXT title, 316 x 12 dialog units at 22, 12. */ +#title +{ + left: 31dp; + top: 17.5dp; + width: 474dp; + height: 19.5dp; + line-height: 19.5dp; + text-align: center; +} + +/* The map list, 175 x 142 dialog units at 22, 26. */ +#maps +{ + left: 31dp; + top: 40.25dp; + width: 262.5dp; + height: 230.75dp; +} + +/* A row spans the list less its scrollbar, and stands as tall as the owner-draw list's own + item, which is the 12 pixel list font plus two. */ +#maps .row +{ + width: 250.5dp; + height: 14dp; + line-height: 14dp; +} + +/* The preview frame, a GROUPBOX 129 x 80 dialog units at 209, 59. The picture inside it is + drawn by the screen and reaches the document through the element. */ +#previewframe +{ + display: block; + position: absolute; + box-sizing: border-box; + left: 311.5dp; + top: 93.875dp; + width: 193.5dp; + height: 130dp; + + background-color: #14160f; + border-width: 1dp; + border-top-color: #10120e; + border-left-color: #10120e; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +/* The picture takes its size from the provider, which is the frame's own extents in game + logical units, so nothing here states one. */ +#previewframe surface +{ + display: block; +} + +/* OK, Create Random Map and Cancel, all 14 dialog units tall at y 174. */ +.button { height: 22.75dp; line-height: 22.75dp; top: 280.75dp; } + +#accept { left: 31dp; width: 75dp; } +#random { left: 196dp; width: 159dp; } +#cancel { left: 430dp; width: 75dp; } diff --git a/ui/selectmap.rml b/ui/selectmap.rml new file mode 100644 index 000000000..f36143f2d --- /dev/null +++ b/ui/selectmap.rml @@ -0,0 +1,24 @@ + + + Select multiplayer map + + + + +
+
Select Multiplayer Map
+ +
+
{{ name }}
+
+ +
+ +
+ +
[[TXT_OK]]
+
Create Random Map
+
[[TXT_CANCEL]]
+
+ +
From 5b740d7b5a5e41d2d7080c1dce2bc600e011c51d Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 06:00:18 +0100 Subject: [PATCH 122/179] docs: record map selection as migrated Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/UI_DESIGN.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index ddb94f8aa..7f6375c4f 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -911,7 +911,26 @@ text beyond an ASCII test document. outright, and so is Ctrl+End, which takes the value's own length. Step 12's map generator screens want the same field. 10. **Skirmish and map selection** (M, two changes). Includes the scenario - picker templates and the preview surface. + picker templates and the preview surface. In progress: both screens are + extracted, `code/ui/uiskirmish.{h,cpp}` and `code/ui/uiscenariopick.{h,cpp}`, + with `skirmish.cpp` and `netshare.cpp` rewired and the legacy views still + selected; the map selection screen has its RmlUi view, `ui/selectmap.rml` + with `ui/selectmap.rcss`, converted from the `IDD_MPLAYER_SELECT_MAP` + template. The skirmish screen has no document yet. + + `Update_Network_Dialog_Preview` is split the way `Fill_List` was: a new + `Rebuild_Network_Map_Preview` owns the preview and the old name owns telling + a window to repaint, so a presentation that is not a window can ask for one. + `Pick_Scenario_Screen` is the entry a screen uses, because a presenter names + no window. + + The preview reaches the document through the `` element step 6 + built, with no change to the element. The view owns a `UISurfaceBufferClass` + the size of the template's preview frame, scales the picture into it the way + `MapPreviewClass::Blit_Preview` scales it into the group box, and fills the + letterbox with the buffer's color key; the element takes its size from that + provider and uploads when the provider's generation moves. The presenter + carries only the artwork's name. 11. **Network lobbies** (L, two changes). Host, guest, game list, the `WS_` stack, and `netshare.cpp` as one family; then disconnect, desync, and reconnect. Packets unchanged. From a77f4033705ddb4114ec9160bb36c8c1bc52ef81 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 08:28:27 +0100 Subject: [PATCH 123/179] feat(ui): show the skirmish setup screen through RmlUi Adds ui/skirmish.rml and ui/skirmish.rcss, converted from the IDD_SKIRMISH template's 426 x 240 dialog units at the options family's 1.5 pixels across and 1.625 down, and the view that binds them to UISkirmishPresenterClass. Skirmish_Mode_Dialog latches the selection with UI_Use_Rml at screen entry and falls through to the legacy dialog when the document cannot be prepared. The two screens hold previews of different sizes, so the map preview provider moves into code/ui/uimappreview.{h,cpp} with its extents as constructor arguments, and the skirmish preview registers under its own name. A name is unique among live providers and the picker opens over this screen, so sharing one would have taken the picture away when the picker closed. A track bar's range is set before its value, because the control clamps a value into the range it is holding and the default range stops well short of what the rules allow. Without that the credits bar opened at its minimum rather than at the rules' own figure. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/skirmish.cpp | 14 +- code/ui/uimappreview.cpp | 60 ++++++ code/ui/uimappreview.h | 36 ++++ code/ui/uiscenariopick.cpp | 59 +----- code/ui/uiskirmish.cpp | 364 ++++++++++++++++++++++++++++++++++++- code/ui/uiskirmish.h | 4 + ui/skirmish.rcss | 271 +++++++++++++++++++++++++++ ui/skirmish.rml | 63 +++++++ 8 files changed, 811 insertions(+), 60 deletions(-) create mode 100644 code/ui/uimappreview.cpp create mode 100644 code/ui/uimappreview.h create mode 100644 ui/skirmish.rcss create mode 100644 ui/skirmish.rml diff --git a/code/skirmish.cpp b/code/skirmish.cpp index 59e31c580..688f6903a 100644 --- a/code/skirmish.cpp +++ b/code/skirmish.cpp @@ -196,7 +196,14 @@ bool Skirmish_Mode_Dialog(void) UISkirmishPresenterClass screen; screen.Refresh(); - HWND dialog = OwnerDraw::Begin_Dialog(IDD_SKIRMISH, Skirmish_Dialog_Proc); + if (UI_Use_Rml()) { + UIResult const result = UI_Skirmish_Screen(screen); + if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { + rc = screen.Accepted() ? IDOK : IDCANCEL; + } + } + + HWND dialog = rc == -1 ? OwnerDraw::Begin_Dialog(IDD_SKIRMISH, Skirmish_Dialog_Proc) : NULL; if (dialog) { SetWindowLongPtr(dialog, DWLP_USER, (LONG_PTR)&screen); Skirmish_Sync_Controls(dialog, screen); @@ -223,9 +230,12 @@ bool Skirmish_Mode_Dialog(void) screen.Service(); } OwnerDraw::End_Dialog(dialog); + rc = screen.Accepted() ? IDOK : IDCANCEL; } - rc = screen.Accepted() ? IDOK : IDCANCEL; + if (rc == -1) { + rc = IDCANCEL; + } screen.End(); diff --git a/code/ui/uimappreview.cpp b/code/ui/uimappreview.cpp new file mode 100644 index 000000000..d3691db6f --- /dev/null +++ b/code/ui/uimappreview.cpp @@ -0,0 +1,60 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#include "always.h" + +#include "uimappreview.h" + +#include "dsurface.h" +#include "netshare.h" +#include "preview.h" +#include "xsurface.h" + +#include + + +MapPreviewSurfaceClass::MapPreviewSurfaceClass(int width, int height) : + UISurfaceBufferClass(width, height), + Width(width), + Height(height) +{ + Set_Transparent_Color(DSurface::Build_Hicolor_Pixel(255, 0, 255)); + Clear(); +} + + +void MapPreviewSurfaceClass::Redraw(void) +{ + Clear(); + + if (MultiplayerMapPreview == NULL) { + return; + } + + XSurface * const picture = MultiplayerMapPreview->Get_Preview_Surface(); + if (picture == NULL) { + return; + } + + Rect const source = picture->Get_Rect(); + if (source.Width <= 0 || source.Height <= 0) { + return; + } + + int const scale = std::min(1000 * Width / source.Width, 1000 * Height / source.Height); + + Rect destination; + destination.Width = (scale * source.Width) / 1000; + destination.Height = (scale * source.Height) / 1000; + destination.X = Width / 2 - destination.Width / 2; + destination.Y = Height / 2 - destination.Height / 2; + + Get_Surface().Blit_From(destination, *picture, source, false, false); + Mark_Dirty(); +} diff --git a/code/ui/uimappreview.h b/code/ui/uimappreview.h new file mode 100644 index 000000000..846e9aaa8 --- /dev/null +++ b/code/ui/uimappreview.h @@ -0,0 +1,36 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The multiplayer map preview as a surface a document can show. The map selection screen +// and the skirmish setup screen both hold one, at the size their own template's preview +// frame gives it. +// +// docs/UI_DESIGN.md, "Assets and strings", owns the routing. + +#pragma once + +#include "uisurface.h" + + +class MapPreviewSurfaceClass : public UISurfaceBufferClass +{ + public: + // The extents are the interior of the template's preview frame, in game logical + // units, because a provider's pixels are game logical units. + MapPreviewSurfaceClass(int width, int height); + + // Draws the session's current preview, scaled and centered the way + // MapPreviewClass::Blit_Preview scales it into a dialog's group box. The letterbox + // around a picture of a different shape is left transparent rather than black. + void Redraw(void); + + private: + int Width; + int Height; +}; diff --git a/code/ui/uiscenariopick.cpp b/code/ui/uiscenariopick.cpp index fb8815c32..95a732287 100644 --- a/code/ui/uiscenariopick.cpp +++ b/code/ui/uiscenariopick.cpp @@ -22,23 +22,20 @@ #include "uiscenariopick.h" +#include "uimappreview.h" #include "uirmlview.h" -#include "uisurface.h" #include "data.h" #include "mapgen.h" #include "netdlg2.h" #include "netshare.h" -#include "dsurface.h" #include "preview.h" #include "session.h" -#include "xsurface.h" #include #include #include -#include #include #include #include @@ -196,58 +193,6 @@ void UIScenarioPickPresenterClass::Execute(UIIntent const & intent) enum { PREVIEW_WIDTH = 191, PREVIEW_HEIGHT = 128 }; -/// -/// The map preview as a surface a document can show. -/// The picture is scaled into the frame the way MapPreviewClass::Blit_Preview scales it into -/// the dialog's group box, so what the document shows is what the dialog showed. The frame is -/// filled with a color key first, so the letterbox around a picture of a different shape is -/// transparent rather than black. -/// -class MapPreviewSurfaceClass : public UISurfaceBufferClass -{ - public: - MapPreviewSurfaceClass(void) : - UISurfaceBufferClass(PREVIEW_WIDTH, PREVIEW_HEIGHT) - { - Set_Transparent_Color(DSurface::Build_Hicolor_Pixel(255, 0, 255)); - Clear(); - } - - void Redraw(void); -}; - - -void MapPreviewSurfaceClass::Redraw(void) -{ - Clear(); - - if (MultiplayerMapPreview == NULL) { - return; - } - - XSurface * const picture = MultiplayerMapPreview->Get_Preview_Surface(); - if (picture == NULL) { - return; - } - - Rect const source = picture->Get_Rect(); - if (source.Width <= 0 || source.Height <= 0) { - return; - } - - int const scale = std::min(1000 * PREVIEW_WIDTH / source.Width, 1000 * PREVIEW_HEIGHT / source.Height); - - Rect destination; - destination.Width = (scale * source.Width) / 1000; - destination.Height = (scale * source.Height) / 1000; - destination.X = PREVIEW_WIDTH / 2 - destination.Width / 2; - destination.Y = PREVIEW_HEIGHT / 2 - destination.Height / 2; - - Get_Surface().Blit_From(destination, *picture, source, false, false); - Mark_Dirty(); -} - - /// /// The RmlUi half of the map selection screen. /// @@ -273,7 +218,7 @@ class ScenarioPickViewClass : public UIRmlViewClass UIScenarioPickPresenterClass & Screen; // The view owns the pixels; the presenter carries only the name they answer to. - MapPreviewSurfaceClass Picture; + MapPreviewSurfaceClass Picture{PREVIEW_WIDTH, PREVIEW_HEIGHT}; unsigned int Drawn = 0; }; diff --git a/code/ui/uiskirmish.cpp b/code/ui/uiskirmish.cpp index 23fc081f3..7502697f8 100644 --- a/code/ui/uiskirmish.cpp +++ b/code/ui/uiskirmish.cpp @@ -25,6 +25,9 @@ #include "uiscenariopick.h" +#include "uimappreview.h" +#include "uirmlview.h" + #include "_rules.h" #include "data.h" #include "houstype.h" @@ -40,6 +43,11 @@ #include "rules.h" #include "session.h" +#include +#include +#include +#include + #include #include @@ -50,7 +58,7 @@ enum { MP_MIN_MONEY = 2500 }; UISkirmishPresenterClass::UISkirmishPresenterClass(void) : - Preview(UI_MAP_PREVIEW_SURFACE) + Preview(UI_SKIRMISH_PREVIEW_SURFACE) { } @@ -344,3 +352,357 @@ void UISkirmishPresenterClass::Execute(UIIntent const & intent) return; } } + + +//--------------------------------------------------------------------------------------- +// The RmlUi view. +//--------------------------------------------------------------------------------------- + +// The picture the preview frame holds, in game logical units. The frame is the template's +// 215 x 106 dialog units, which is 322.5 by 172.25 at the family's 1.5 across and 1.625 +// down, and the picture sits inside its one pixel border. +enum { PREVIEW_WIDTH = 320, PREVIEW_HEIGHT = 170 }; + + +/// +/// The RmlUi half of the skirmish setup screen. +/// +class SkirmishViewClass : public UIRmlViewClass +{ + public: + // A color the player may take, with the swatch the owner-draw combo drew its row in. + struct ColorRowType + { + std::string Name; + std::string Hex; + }; + + SkirmishViewClass(UISkirmishPresenterClass & presenter) : + UIRmlViewClass(presenter, "skirmish.rml"), + Screen(presenter) + { + UI_Register_Surface(Screen.Preview.c_str(), &Picture); + } + + virtual ~SkirmishViewClass(void) override + { + UI_Unregister_Surface(Screen.Preview.c_str()); + } + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + // Puts the track bar ranges the rules give this screen on the controls, and lets + // the change handlers start reporting. A range is set before the data binding fills + // a value in, so a value outside a track bar's default range is not clamped away. + void Settle(void); + + private: + void Move(char const * which, int value); + void Press(char const * action); + void Set_Range(char const * id, UISkirmishPresenterClass::SliderType const & slider); + + std::string Field_Text(void) const; + Rml::ElementFormControlInput * Field(void) const; + + UISkirmishPresenterClass & Screen; + + // The view owns the pixels; the presenter carries only the name they answer to. + MapPreviewSurfaceClass Picture{PREVIEW_WIDTH, PREVIEW_HEIGHT}; + + std::vector ColorRows; + + unsigned int Drawn = 0; + bool Settled = false; +}; + + +Rml::ElementFormControlInput * SkirmishViewClass::Field(void) const +{ + if (Element == nullptr) { + return(nullptr); + } + return(rmlui_dynamic_cast(Element->GetElementById("name"))); +} + + +std::string SkirmishViewClass::Field_Text(void) const +{ + Rml::ElementFormControlInput * const field = Field(); + if (field == nullptr) { + return(Screen.Handle); + } + return(field->GetValue()); +} + + +void SkirmishViewClass::Set_Range(char const * id, UISkirmishPresenterClass::SliderType const & slider) +{ + if (Element == nullptr) { + return; + } + + Rml::Element * const control = Element->GetElementById(id); + if (control == nullptr) { + return; + } + + // The range is set before the value, because a track bar clamps a value into the range + // it is holding and the default range stops well short of what the rules allow. + control->SetAttribute("min", slider.Minimum); + control->SetAttribute("max", slider.Maximum); + control->SetAttribute("step", slider.Step); + control->SetAttribute("value", slider.Value); +} + + +void SkirmishViewClass::Settle(void) +{ + Set_Range("unitcount", Screen.UnitCount); + Set_Range("credits", Screen.Credits); + Set_Range("techlevel", Screen.TechLevel); + Set_Range("ailevel", Screen.AILevel); + Set_Range("aiplayers", Screen.AIPlayers); + Set_Range("gamespeed", Screen.GameSpeed); + + Settled = true; +} + + +/// +/// Queues a track bar's new position, dropping one that matches what the model already +/// holds so that setting a control from the model is not read back as a move. +/// +void SkirmishViewClass::Move(char const * which, int value) +{ + if (!Settled) return; + + UISkirmishPresenterClass::SliderType const * held = NULL; + if (which == UI_SKIRMISH_UNITCOUNT) held = &Screen.UnitCount; + else if (which == UI_SKIRMISH_CREDITS) held = &Screen.Credits; + else if (which == UI_SKIRMISH_TECHLEVEL) held = &Screen.TechLevel; + else if (which == UI_SKIRMISH_AILEVEL) held = &Screen.AILevel; + else if (which == UI_SKIRMISH_AIPLAYERS) held = &Screen.AIPlayers; + else if (which == UI_SKIRMISH_GAMESPEED) held = &Screen.GameSpeed; + + if (held == NULL || held->Value == value) { + return; + } + + Screen.Queue(UIIntent{UI_SKIRMISH_SLIDER, which, value}); +} + + +/// +/// Queues what a button or its key stands for. +/// The name is read out of the field here rather than tracked, because that is when the +/// dialog read its edit control, and the read is queued ahead of the action it is read for +/// so the two execute in that order. Backing out records the name too, which is what the +/// cancel arm did. +/// +void SkirmishViewClass::Press(char const * action) +{ + if (action == UI_SKIRMISH_ACCEPT || action == UI_SKIRMISH_CANCEL) { + std::string const text = Field_Text(); + if (text != Screen.Handle) { + Screen.Queue(UIIntent{UI_SKIRMISH_HANDLE, text, 0}); + } + } + + Screen.Queue(UIIntent{action, "", 0}); +} + + +void SkirmishViewClass::Bind(Rml::DataModelConstructor & model) +{ + ColorRows.clear(); + for (int index = 0; index < (int)Screen.Colors.size(); index++) { + char hex[8]; + if (index < MAX_PLAYERS) { + // A COLORREF holds its blue byte highest, which is the order RGB() packs. + unsigned long const color = (unsigned long)PlayerColorTable[index]; + std::snprintf(hex, sizeof(hex), "#%02x%02x%02x", + (unsigned)(color & 0xFF), (unsigned)((color >> 8) & 0xFF), (unsigned)((color >> 16) & 0xFF)); + } else { + std::snprintf(hex, sizeof(hex), "#b9bcae"); + } + ColorRows.push_back(ColorRowType{Screen.Colors[index], hex}); + } + + if (auto side = model.RegisterStruct()) { + side.RegisterMember("name", &UISkirmishPresenterClass::SideType::Name); + } + model.RegisterArray>(); + + if (auto swatch = model.RegisterStruct()) { + swatch.RegisterMember("name", &ColorRowType::Name); + swatch.RegisterMember("hex", &ColorRowType::Hex); + } + model.RegisterArray>(); + + if (auto slider = model.RegisterStruct()) { + slider.RegisterMember("value", &UISkirmishPresenterClass::SliderType::Value); + slider.RegisterMember("min", &UISkirmishPresenterClass::SliderType::Minimum); + slider.RegisterMember("max", &UISkirmishPresenterClass::SliderType::Maximum); + slider.RegisterMember("step", &UISkirmishPresenterClass::SliderType::Step); + } + + model.Bind("handle", &Screen.Handle); + model.Bind("sides", &Screen.Sides); + model.Bind("selectedside", &Screen.SelectedSide); + model.Bind("colors", &ColorRows); + model.Bind("selectedcolor", &Screen.SelectedColor); + + model.Bind("unitcount", &Screen.UnitCount); + model.Bind("credits", &Screen.Credits); + model.Bind("techlevel", &Screen.TechLevel); + model.Bind("ailevel", &Screen.AILevel); + model.Bind("aiplayers", &Screen.AIPlayers); + model.Bind("gamespeed", &Screen.GameSpeed); + + model.Bind("bases", &Screen.Bases); + model.Bind("crates", &Screen.Crates); + model.Bind("fog", &Screen.FogOfWar); + model.Bind("bridges", &Screen.Bridges); + model.Bind("mcv", &Screen.MCVRedeploy); + model.Bind("shortgame", &Screen.ShortGame); + model.Bind("engineer", &Screen.MultiEngineer); + + model.Bind("scenarioname", &Screen.ScenarioName); + model.Bind("preview", &Screen.Preview); + model.Bind("canaccept", &Screen.CanAccept); + + // The field is bound one way, so a value the model already holds is never queued back + // as a change the player did not type. + model.BindEventCallback("rename", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + Rml::String const value = event.GetParameter("value", Rml::String()); + if (value == Screen.Handle) return; + Screen.Queue(UIIntent{UI_SKIRMISH_HANDLE, value, 0}); + }); + + model.BindEventCallback("chooseside", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + if (!Settled) return; + int const row = (int)(event.GetParameter("value", 0.0f) + 0.5f); + if (row == Screen.SelectedSide) return; + Screen.Queue(UIIntent{UI_SKIRMISH_SIDE, "", row}); + }); + + model.BindEventCallback("choosecolor", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + if (!Settled) return; + int const row = (int)(event.GetParameter("value", 0.0f) + 0.5f); + if (row == Screen.SelectedColor) return; + Screen.Queue(UIIntent{UI_SKIRMISH_COLOR, "", row}); + }); + + model.BindEventCallback("move", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + + Rml::String const which = arguments[0].Get(); + int const value = (int)(event.GetParameter("value", 0.0f) + 0.5f); + + if (which == UI_SKIRMISH_UNITCOUNT) Move(UI_SKIRMISH_UNITCOUNT, value); + else if (which == UI_SKIRMISH_CREDITS) Move(UI_SKIRMISH_CREDITS, value); + else if (which == UI_SKIRMISH_TECHLEVEL) Move(UI_SKIRMISH_TECHLEVEL, value); + else if (which == UI_SKIRMISH_AILEVEL) Move(UI_SKIRMISH_AILEVEL, value); + else if (which == UI_SKIRMISH_AIPLAYERS) Move(UI_SKIRMISH_AIPLAYERS, value); + else if (which == UI_SKIRMISH_GAMESPEED) Move(UI_SKIRMISH_GAMESPEED, value); + }); + + // A check box is a class plus a click that queues a toggle, not a two-way bound control. + model.BindEventCallback("toggle", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{UI_SKIRMISH_TOGGLE, arguments[0].Get(), 0}); + }); + + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + + Rml::String const action = arguments[0].Get(); + if (action == UI_SKIRMISH_ACCEPT) Press(UI_SKIRMISH_ACCEPT); + else if (action == UI_SKIRMISH_CANCEL) Press(UI_SKIRMISH_CANCEL); + else if (action == UI_SKIRMISH_PICK_MAP) Press(UI_SKIRMISH_PICK_MAP); + }); + + // Escape backs out and Enter starts the game, which is what IsDialogMessage delivered to + // a template that names IDCANCEL and no default push button. + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key == Rml::Input::KI_ESCAPE) { + Press(UI_SKIRMISH_CANCEL); + } else if (key == Rml::Input::KI_RETURN || key == Rml::Input::KI_NUMPADENTER) { + Press(UI_SKIRMISH_ACCEPT); + } + }); +} + + +void SkirmishViewClass::Sync(void) +{ + if (!Model) return; + + // The track bar, combo box and field values are not dirtied, because each already + // carries what its own change event reported. + Model.DirtyVariable("sides"); + Model.DirtyVariable("colors"); + Model.DirtyVariable("bases"); + Model.DirtyVariable("crates"); + Model.DirtyVariable("fog"); + Model.DirtyVariable("bridges"); + Model.DirtyVariable("mcv"); + Model.DirtyVariable("shortgame"); + Model.DirtyVariable("engineer"); + Model.DirtyVariable("scenarioname"); + Model.DirtyVariable("canaccept"); + + // The picture is redrawn where it changed, not every pass, so the element uploads once + // per map rather than once per present. + if (Screen.PreviewGeneration != Drawn) { + Drawn = Screen.PreviewGeneration; + Picture.Redraw(); + } + + Screen.ListChanged = false; +} + + +/// +/// Shows the skirmish screen and waits for the player to leave it. +/// +UIResult UI_Skirmish_Screen(UISkirmishPresenterClass & presenter) +{ + SkirmishViewClass view(presenter); + + if (!view.Prepare(true)) { + UIResult result; + result.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + return(result); + } + + view.Settle(); + + // The map selection screen draws where this one is, so the document steps aside for it, + // which is what the dialog's own ShowWindow did. + while (!presenter.Result.has_value()) { + UI_Run_Modal(presenter, view); + + if (presenter.Pending == UISkirmishPresenterClass::SUB_NONE) { + break; + } + + view.Hide(); + presenter.Run_Pending(); + view.Show(); + view.Sync(); + } + + UIResult const result = presenter.Result.value_or(UIResult{}); + view.Close(); + return(result); +} diff --git a/code/ui/uiskirmish.h b/code/ui/uiskirmish.h index 31926c178..7019c36f6 100644 --- a/code/ui/uiskirmish.h +++ b/code/ui/uiskirmish.h @@ -19,6 +19,10 @@ #include +// The artwork the map preview draws into. The map selection screen this one opens holds a +// preview of its own at a different size, so the two register under different names. +inline constexpr char const * UI_SKIRMISH_PREVIEW_SURFACE = "skirmishpreview"; + inline constexpr char const * UI_SKIRMISH_HANDLE = "handle"; // Identity: the name typed inline constexpr char const * UI_SKIRMISH_SIDE = "side"; // Value: row in Sides inline constexpr char const * UI_SKIRMISH_COLOR = "color"; // Value: row in Colors diff --git a/ui/skirmish.rcss b/ui/skirmish.rcss new file mode 100644 index 000000000..08aed39cd --- /dev/null +++ b/ui/skirmish.rcss @@ -0,0 +1,271 @@ +/* The skirmish setup screen. Geometry from the IDD_SKIRMISH template, converted from dialog + units at the 8 point MS Sans Serif the template names: 1.5 pixels across and 1.625 down, + with a child's offset taken from the panel's content box and the panel's declared size + taken inside its own border. */ + +/* 426 x 240 dialog units, so 639 x 390 pixels, centred on the frame. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -319.5dp; + margin-top: -195dp; + + width: 635dp; + height: 386dp; +} + +/* The player's own settings, down the left edge. */ +#namelabel { left: 31dp; top: 17.5dp; width: 117dp; height: 13dp; line-height: 13dp; } +#sidelabel { left: 31dp; top: 66.25dp; width: 117dp; height: 13dp; line-height: 13dp; } +#colorlabel { left: 31dp; top: 113.375dp; width: 117dp; height: 13dp; line-height: 13dp; } +#maplabel { left: 31dp; top: 155.625dp; width: 54dp; height: 16.25dp; line-height: 16.25dp; } + +/* The scenario's own name, which the map selection screen writes. */ +#scenarioname +{ + left: 31dp; + top: 178.375dp; + width: 402dp; + height: 16.25dp; + line-height: 16.25dp; +} + +/* The EDITTEXT, 78 x 12 dialog units at 22, 24. It states a width, because a field with + none formats no line and RmlUi's End key then moves the caret to the start of an empty + line rather than to the end of the value. */ +#name +{ + display: block; + position: absolute; + box-sizing: border-box; + left: 31dp; + top: 37dp; + width: 117dp; + height: 19.5dp; + line-height: 15.5dp; + padding: 0dp 4dp; + white-space: nowrap; + overflow: hidden; + + font-family: LatoLatin; + color: #e4e6da; + background-color: #14160f; + border-width: 2dp; + border-top-color: #10120e; + border-left-color: #10120e; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +#name:focus { background-color: #1d2017; } + +/* The two CBS_DROPDOWNLIST combo boxes, 78 dialog units wide at 22, 52 and 22, 82. A + closed combo stands as tall as the item height ownrdraw.cpp sets, which is the 14 pixel + dialog font plus two, inside a two pixel border; the template's own height is how far the + list drops. */ +#side, +#color +{ + display: block; + position: absolute; + box-sizing: border-box; + left: 31dp; + width: 117dp; + height: 20dp; + + color: #b9bcae; + background-color: #2b2f25; + border-width: 2dp; + border-top-color: #14160f; + border-left-color: #14160f; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +#side { top: 82.5dp; } +#color { top: 131.25dp; } + +#side selectvalue, +#color selectvalue +{ + width: auto; + margin-right: 16dp; + height: 16dp; + line-height: 16dp; + padding: 0dp 4dp; + white-space: nowrap; + overflow: hidden; +} + +#side selectarrow, +#color selectarrow +{ + width: 16dp; + height: 16dp; + + background-color: #33372c; + border-width: 2dp; + border-top-color: #767c68; + border-left-color: #767c68; + border-right-color: #14160f; + border-bottom-color: #14160f; +} + +#side selectarrow:active, +#color selectarrow:active +{ + border-top-color: #14160f; + border-left-color: #14160f; + border-right-color: #767c68; + border-bottom-color: #767c68; +} + +/* The dropped lists, capped at the 74 and 73 dialog units the template gives them. */ +#side selectbox, +#color selectbox +{ + width: 113dp; + overflow-y: auto; + + background-color: #14160f; + border-width: 1dp; + border-top-color: #6e7360; + border-left-color: #6e7360; + border-right-color: #10120e; + border-bottom-color: #10120e; +} + +#side selectbox { max-height: 120.25dp; } +#color selectbox { max-height: 118.625dp; } + +#side selectbox option, +#color selectbox option +{ + width: auto; + height: 18dp; + line-height: 18dp; + padding: 0dp 4dp; + white-space: nowrap; + color: #b9bcae; +} + +#side selectbox option:hover { color: #e4e6da; } + +#side selectbox option:checked, +#color selectbox option:checked { background-color: #3f4536; } + +/* The preview frame, a GROUPBOX 215 x 106 dialog units at 22, 122. The picture inside it is + drawn by the screen and reaches the document through the element. */ +#previewframe +{ + display: block; + position: absolute; + box-sizing: border-box; + left: 31dp; + top: 196.25dp; + width: 322.5dp; + height: 172.25dp; + + background-color: #14160f; + border-width: 1dp; + border-top-color: #10120e; + border-left-color: #10120e; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +/* The template's own CTEXT inside the frame, which the picture is drawn over the way the + dialog blitted its preview over the group box. It shows through the letterbox around a + picture of a different shape, because the surface fills that with its color key. */ +#previewtext +{ + display: block; + position: absolute; + left: 0dp; + top: 68.875dp; + width: 320.5dp; + height: 21.125dp; + line-height: 21.125dp; + text-align: center; + color: #6e7360; +} + +/* The picture takes its size from the provider, which is the frame's interior in game + logical units, so nothing here states one. */ +#previewframe surface +{ + display: block; + position: absolute; + left: 0dp; + top: 0dp; +} + +/* The unnamed GROUPBOX around the check boxes, 167 x 96 dialog units at 117, 12, and the + one around the track bars, 110 x 181 at 294, 12. */ +#optionbox, +#sliderbox +{ + display: block; + position: absolute; + box-sizing: border-box; + + border-width: 1dp; + border-top-color: #10120e; + border-left-color: #10120e; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +#optionbox { left: 173.5dp; top: 17.5dp; width: 250.5dp; height: 156dp; } +#sliderbox { left: 439dp; top: 17.5dp; width: 165dp; height: 294.125dp; } + +/* The seven check boxes, all 157 dialog units wide but the last, at x 123. */ +.check { left: 182.5dp; width: 235.5dp; height: 16.25dp; line-height: 16.25dp; } + +#bases { top: 32.125dp; } +#crates { top: 51.625dp; } +#fog { top: 71.125dp; } +#bridges { top: 90.625dp; } +#mcv { top: 110.125dp; } +#shortgame { top: 129.625dp; } +#engineer { top: 149.125dp; width: 162dp; } + +/* The six track bars and their captions, all 100 dialog units wide at x 298. The captions + never change: every WM_HSCROLL case in the dialog fetched its label and did nothing with + it, so the template's own words are what the screen has always shown. */ +#unitcountlabel, +#creditslabel, +#techlevellabel, +#ailevellabel, +#aiplayerslabel, +#gamespeedlabel +{ + left: 445dp; + width: 150dp; + height: 13dp; + line-height: 13dp; +} + +#unitcountlabel { top: 27.25dp; } +#creditslabel { top: 74.375dp; } +#techlevellabel { top: 121.5dp; } +#ailevellabel { top: 168.625dp; } +#aiplayerslabel { top: 215.75dp; } +#gamespeedlabel { top: 262.875dp; } + +.slider { left: 445dp; width: 150dp; height: 22.75dp; } + +#unitcount { top: 46.75dp; } +#credits { top: 93.875dp; } +#techlevel { top: 141dp; } +#ailevel { top: 188.125dp; } +#aiplayers { top: 235.25dp; } +#gamespeed { top: 282.375dp; } + +/* Multiplay Map, OK and Cancel, all 14 dialog units tall. */ +.button { height: 22.75dp; line-height: 22.75dp; } + +#multimap { left: 439dp; top: 318.125dp; width: 165dp; } +#accept { left: 439dp; top: 345.75dp; width: 75dp; } +#cancel { left: 529dp; top: 345.75dp; width: 75dp; } diff --git a/ui/skirmish.rml b/ui/skirmish.rml new file mode 100644 index 000000000..3f3ed2cb7 --- /dev/null +++ b/ui/skirmish.rml @@ -0,0 +1,63 @@ + + + Skirmish + + + + +
+
Name:
+ + +
Side:
+ + +
Color:
+ + +
Map:
+
{{ scenarioname }}
+ +
+
Preview
+ +
+ +
+
Bases
+
Crates
+
Fog Of War
+
Bridges Destroyable
+
Re-Deployable MCV
+
Short Game
+
Multi Engineer
+ +
+
Unit Count:
+ + +
Credits:
+ + +
Tech Level:
+ + +
AI Level:
+ + +
AI Players:
+ + +
Game Speed
+ + +
Multiplay Map
+
[[TXT_OK]]
+
[[TXT_CANCEL]]
+
+ +
From 6d8fd18adeed0486942d4b93ab3205030331acc4 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 08:28:57 +0100 Subject: [PATCH 124/179] docs: record the skirmish screen as migrated Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/UI_DESIGN.md | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index 7f6375c4f..3c747b84f 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -1,7 +1,7 @@ # UI system design -Status: in progress. Steps 1 to 9 of the migration plan have landed; nothing -from step 10 onward is implemented. Everything outside the migration plan +Status: in progress. Steps 1 to 10 of the migration plan have landed; nothing +from step 11 onward is implemented. Everything outside the migration plan remains a proposal informed by source inspection and upstream documentation. This page owns the UI architecture and migration; [Building OpenTS](BUILDING.md) owns build support and [Project @@ -911,12 +911,13 @@ text beyond an ASCII test document. outright, and so is Ctrl+End, which takes the value's own length. Step 12's map generator screens want the same field. 10. **Skirmish and map selection** (M, two changes). Includes the scenario - picker templates and the preview surface. In progress: both screens are + picker templates and the preview surface. Landed: both screens are extracted, `code/ui/uiskirmish.{h,cpp}` and `code/ui/uiscenariopick.{h,cpp}`, - with `skirmish.cpp` and `netshare.cpp` rewired and the legacy views still - selected; the map selection screen has its RmlUi view, `ui/selectmap.rml` - with `ui/selectmap.rcss`, converted from the `IDD_MPLAYER_SELECT_MAP` - template. The skirmish screen has no document yet. + with `skirmish.cpp` and `netshare.cpp` rewired, and both have their RmlUi + view: `ui/skirmish.rml` and `ui/selectmap.rml` with their stylesheets beside + `ui/optionsbase.rcss`, converted from the `IDD_SKIRMISH` and + `IDD_MPLAYER_SELECT_MAP` templates. This is the step that makes a skirmish + reachable from the menu. `Update_Network_Dialog_Preview` is split the way `Fill_List` was: a new `Rebuild_Network_Map_Preview` owns the preview and the old name owns telling @@ -931,6 +932,18 @@ text beyond an ASCII test document. letterbox with the buffer's color key; the element takes its size from that provider and uploads when the provider's generation moves. The presenter carries only the artwork's name. + + The two screens hold previews of different sizes and the picker opens over + the skirmish screen, so the provider lives in `code/ui/uimappreview.{h,cpp}` + with its extents as constructor arguments and each screen registers under + its own name. A provider name is unique among live providers, so sharing one + would have taken the picture away when the picker closed. + + A track bar's range is set before its value. A range control clamps a value + into the range it is holding, and the default range stops well short of what + the rules allow, so a value written by the data binding before the range was + known opened the credits bar at its minimum instead of at the rules' figure. + The same rule the text field learned at step 9, one control further on. 11. **Network lobbies** (L, two changes). Host, guest, game list, the `WS_` stack, and `netshare.cpp` as one family; then disconnect, desync, and reconnect. Packets unchanged. From 7c3e527205d920325f0c3e15d9c254d3349c9ae2 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 08:35:15 +0100 Subject: [PATCH 125/179] refactor(ui): put the lobby's game list behind a presenter Step 11's first change, for the game list half of the family. The three lobby screens share one model, because they share the session's game, player and chat rosters and hand the driver one answer between them, so the presenter is one class: code/ui/uilobby.{h,cpp}. The host and guest command handlers are not extracted yet and still write the driver's response themselves. MPlayer_Game_List_Dialog_Proc now reads the view-model and queues intents, and Net2Remote_Connect drains the queue after its pump and maps the answer onto the control identifier its loop already tests. Net2DisplayGameList and _Net2DisplayUsers are split the way Fill_List was: the presenter reads the rosters into the model and the old names put the model on the controls, so a presentation that draws a different number of times cannot lose a change. The host's accepted status is recorded with the roster rather than while painting the row it was about to draw, because it is a fact about the player. Classified preserved, with one exception, classified fixed and offered upstream: the chat handler read up to 256 characters and strcpy'd them into GlobalPacketType's 224-byte Message.Buf, so a long line overran into the Color and NameCRC fields that follow it. The copy is now bounded. Nothing else about the packet moves: no field, no size, no order, no send or receive path. The legacy lobby dialogs cannot open on this fork, because WS_Create_Dialog answers NULL when Fetch_Resource has no RT_DIALOG, so this commit's evidence is compilation, the boundary greps and the source tracing above. The extraction commits at steps 8 and 9 stood in the same position. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/netdlg2.cpp | 290 +++++++++++++++------------------------ code/netdlg2.h | 12 ++ code/ui/uilobby.cpp | 327 ++++++++++++++++++++++++++++++++++++++++++++ code/ui/uilobby.h | 108 +++++++++++++++ 4 files changed, 559 insertions(+), 178 deletions(-) create mode 100644 code/ui/uilobby.cpp create mode 100644 code/ui/uilobby.h diff --git a/code/netdlg2.cpp b/code/netdlg2.cpp index d0df3c38c..d98ee9f0d 100644 --- a/code/netdlg2.cpp +++ b/code/netdlg2.cpp @@ -43,6 +43,7 @@ #include "stimer.h" #include "timer.h" #include "utf8.h" +#include "ui/uilobby.h" #include "windlg.h" #include "winstub.h" #include "wsproto.h" @@ -55,7 +56,6 @@ */ static int Request_To_Join(int join_index); static void Unjoin_Game(int game_index); -static void Send_Join_Queries(int gamenow, int playernow, int chatnow, int init = 0); static void Get_Join_Responses(void); INT_PTR CALLBACK MPlayer_Guest_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); @@ -78,6 +78,25 @@ int Net2_g_Col_Accept; int Net2_g_Col_Name; int Net2_g_Col_House; +// The lobby screen the driver is running. The dialog procedures read its view-model and +// queue intents against it; the driver executes the queue after its pump returns. +static UILobbyPresenterClass * _LobbyScreen = NULL; + + +/// +/// Maps a lobby answer onto the control identifier the driver loop already tests, the way +/// every migrated screen's wrapper maps its outcome onto the value its caller expects. +/// +static int Lobby_Response_Identifier(UILobbyPresenterClass::ResponseType response) +{ + switch (response) { + case UILobbyPresenterClass::RESPONSE_CANCEL: return(IDCANCEL); + case UILobbyPresenterClass::RESPONSE_JOIN: return(IDC_GAMELIST_JOIN); + case UILobbyPresenterClass::RESPONSE_NEW: return(IDC_GAMELIST_NEW); + default: return(0); + } +} + /// /// Fills a side box with the multiplayable countries, each entry carrying its country index. @@ -195,92 +214,64 @@ void Net2DisplayUsers(void) /// void _Net2DisplayUsers(void) { - int i; - int color; - char hname[128]; - char info[128]; - Surface * surf = NULL; + HWND win = WS_Top_Window(); + HWND userwin = win ? GetDlgItem(win, IDC_USERS) : NULL; - HWND win=WS_Top_Window(); - - HWND userwin=GetDlgItem(win,IDC_USERS); - - if (win==NULL || userwin==NULL) { + if (win == NULL || userwin == NULL || _LobbyScreen == NULL) { return; } + // The rows are built where the session changed rather than here, so what is drawn is + // the model the screen holds. + _LobbyScreen->Build_User_Rows(); + OwnerDraw::CellData thecell; - int topindex=SendDlgItemMessage(win, IDC_USERS, LB_GETTOPINDEX, 0, 0); + int topindex = SendDlgItemMessage(win, IDC_USERS, LB_GETTOPINDEX, 0, 0); SendDlgItemMessage(win, IDC_USERS, OD_DISABLEPAINT, 0, TRUE); - Dictionary lbdict(Wstring_Hash); + Dictionary lbdict(Wstring_Hash); LBSaveSelections(userwin, lbdict); SendDlgItemMessage(win, IDC_USERS, LB_RESETCONTENT, NULL, NULL); - if (CurGame == 0) { - SendDlgItemMessage(win, IDC_USERS, LB_INSERTSTRING, (WPARAM)0, (LPARAM)Session.Handle); + bool const inlobby = CurGame == 0; - for (i = 1; i < Session.Chat.Count(); i++) { - SendDlgItemMessage(win, IDC_USERS, LB_INSERTSTRING, (WPARAM)i, (LPARAM)Session.Chat[i]->Name); - } - - } else { - - for (i = 0; i < Session.Players.Count(); i++) { - int type = 0; - - if (!strcmp(Session.Players[i]->Name, Session.GameName)) { - Session.Players[i]->Player.Status = 1; - type = 2; - } else if (Session.Players[i]->Player.Status != 0) { - type = 1; - } + for (int i = 0; i < (int)_LobbyScreen->Users.size(); i++) { + UILobbyPresenterClass::UserRowType const & row = _LobbyScreen->Users[i]; - sprintf(info, "%s", Session.Players[i]->Name); + SendDlgItemMessage(win, IDC_USERS, LB_INSERTSTRING, (WPARAM)(inlobby ? i : -1), (LPARAM)row.Name.c_str()); - // Only two icons ship, so every side past the first borrows the second's. - int country = Session.Players[i]->Player.House; - SideType side = country >= HOUSE_FIRST && country < HouseTypes.Count() ? HouseTypes[country]->Side : SIDE_NONE; - if (side == SIDE_GDI) { - sprintf(hname, "%s", Fetch_String(TXT_GDI)); - surf = SurfaceCache.GetSurface("gdii.pcx"); - } else if (side == SIDE_NOD || side == SIDE_NONE) { - sprintf(hname, "%s", Fetch_String(TXT_NOD)); - surf = SurfaceCache.GetSurface("nodi.pcx"); - } else { - sprintf(hname, "%s", (char const *)HouseTypes[country]->GivenName); - surf = SurfaceCache.GetSurface("nodi.pcx"); - } - - SendDlgItemMessage(win, IDC_USERS, LB_INSERTSTRING, (WPARAM) -1, (LPARAM)info); + if (inlobby) { + continue; + } - color = PlayerColorTable[Session.Players[i]->Player.Color]; + // Only two icons ship, so every side past the first borrows the second's. + Surface * surf = row.Side == SIDE_GDI + ? SurfaceCache.GetSurface("gdii.pcx") + : SurfaceCache.GetSurface("nodi.pcx"); - thecell.type = OwnerDraw::CellData::PRIMARY; - thecell.color = color; - thecell.hint.set(""); - SendDlgItemMessage(win, IDC_USERS, OD_SETCELL, MAKEWPARAM(Net2_g_Col_Name,i),(LPARAM)&thecell); + thecell.type = OwnerDraw::CellData::PRIMARY; + thecell.color = PlayerColorTable[row.Color]; + thecell.hint.set(""); + SendDlgItemMessage(win, IDC_USERS, OD_SETCELL, MAKEWPARAM(Net2_g_Col_Name, i), (LPARAM)&thecell); - thecell.type = OwnerDraw::CellData::SURFACE; - thecell.hint.set(hname); - thecell.surf = surf; - SendDlgItemMessage(win, IDC_USERS, OD_SETCELL, MAKEWPARAM(Net2_g_Col_House,i),(LPARAM)&thecell); + thecell.type = OwnerDraw::CellData::SURFACE; + thecell.hint.set(row.SideName.c_str()); + thecell.surf = surf; + SendDlgItemMessage(win, IDC_USERS, OD_SETCELL, MAKEWPARAM(Net2_g_Col_House, i), (LPARAM)&thecell); - thecell.hint.set(""); - thecell.type = OwnerDraw::CellData::SURFACE; - if (type == 2) { - thecell.surf=SurfaceCache.GetSurface("wolhost.pcx"); - } else if (type != 0) { - thecell.surf=SurfaceCache.GetSurface("wolacpt.pcx"); - } else { - thecell.type = OwnerDraw::CellData::INVALID; - } - SendDlgItemMessage(win, IDC_USERS, OD_SETCELL, MAKEWPARAM(Net2_g_Col_Accept,i),(LPARAM)&thecell); + thecell.hint.set(""); + thecell.type = OwnerDraw::CellData::SURFACE; + if (row.IsHost) { + thecell.surf = SurfaceCache.GetSurface("wolhost.pcx"); + } else if (row.HasAccepted) { + thecell.surf = SurfaceCache.GetSurface("wolacpt.pcx"); + } else { + thecell.type = OwnerDraw::CellData::INVALID; } - + SendDlgItemMessage(win, IDC_USERS, OD_SETCELL, MAKEWPARAM(Net2_g_Col_Accept, i), (LPARAM)&thecell); } LBRestoreSelections(userwin, lbdict); @@ -375,38 +366,33 @@ void Net2ServiceGameList(void) ///
void Net2DisplayGameList(void) { - char buffer[80]; - HWND window = WS_Top_Window(); - int count = Session.Games.Count(); - if (CurGame >= count) { - CurGame = count - 1; - Send_Join_Queries(0, 1, 0, 0); + if (window == NULL || _LobbyScreen == NULL) { + return; } - if (CurGame < 0) { - CurGame = 0; - } + _LobbyScreen->Build_Game_Rows(); int top = SendDlgItemMessage(window, IDC_GAMELIST, LB_GETTOPINDEX, 0, 0); SendDlgItemMessage(window, IDC_GAMELIST, OD_DISABLEPAINT, 0, 1); SendDlgItemMessage(window, IDC_GAMELIST, LB_RESETCONTENT, 0, 0); - SendDlgItemMessage(window, IDC_GAMELIST, LB_INSERTSTRING, -1, (LPARAM)Fetch_String(TXT_LOBBY)); - for (int i = 1; i < Session.Games.Count(); i++) { - NodeNameType *node = Session.Games[i]; - if (node->Game.IsOpen) { - sprintf(buffer, Fetch_String(TXT_THATGUYS_GAME), node); - } else { - sprintf(buffer, Fetch_String(TXT_THATGUYS_GAME_BRACKET), node); + for (int i = 0; i < (int)_LobbyScreen->Games.size(); i++) { + UILobbyPresenterClass::GameRowType const & row = _LobbyScreen->Games[i]; + + if (i == 0) { + SendDlgItemMessage(window, IDC_GAMELIST, LB_INSERTSTRING, -1, (LPARAM)row.Label.c_str()); + continue; } + + char buffer[80]; + sprintf(buffer, Fetch_String(row.IsOpen ? TXT_THATGUYS_GAME : TXT_THATGUYS_GAME_BRACKET), row.Label.c_str()); SendDlgItemMessage(window, IDC_GAMELIST, LB_INSERTSTRING, -1, (LPARAM)buffer); } - int idx = CurGame; - SendDlgItemMessage(window, IDC_GAMELIST, LB_SETCURSEL, idx, 0); + SendDlgItemMessage(window, IDC_GAMELIST, LB_SETCURSEL, _LobbyScreen->SelectedGame, 0); SendDlgItemMessage(window, IDC_GAMELIST, LB_SETTOPINDEX, top, 0); SendDlgItemMessage(window, IDC_GAMELIST, OD_DISABLEPAINT, 0, 0); @@ -715,6 +701,9 @@ bool Net2Remote_Connect(void) OwnerDraw::Register_Control_Classes(); + UILobbyPresenterClass screen; + _LobbyScreen = &screen; + HWND game_list_dialog = WS_Create_Dialog(ProgramInstance, IDD_MPLAYER_GAME_LIST, MainWindow, MPlayer_Game_List_Dialog_Proc, FALSE); Center_Window_Within_Window(game_list_dialog); OwnerDraw::Subclass_Dialog(game_list_dialog, 0); @@ -747,6 +736,16 @@ bool Net2Remote_Connect(void) } Call_Back(); + + // A control handler queues rather than acts, so the queue is executed here, + // after the pump has returned. The lobby's own answer is one of the + // presenter's, and the other two screens still write theirs directly. + screen.Drain(); + if (screen.Response != UILobbyPresenterClass::RESPONSE_NONE) { + _netresponse = Lobby_Response_Identifier(screen.Response); + screen.Response = UILobbyPresenterClass::RESPONSE_NONE; + } + if (_netresponse != 0) { break; } @@ -757,7 +756,7 @@ bool Net2Remote_Connect(void) if (WS_Top_Window_ID() == IDD_MPLAYER_HOST) { PumpGameopts(false); } - Net2ServiceGameList(); + screen.Service(); } } @@ -777,6 +776,7 @@ bool Net2Remote_Connect(void) Clear_Vector(&Session.Chat); Session.NetOpen = false; Ipx.Service(); + _LobbyScreen = NULL; return(false); } @@ -1128,6 +1128,7 @@ bool Net2Remote_Connect(void) Session.NetOpen = false; Session.Write_MultiPlayer_Settings(); + _LobbyScreen = NULL; return(true); } /* end of Remote_Connect */ @@ -1145,139 +1146,72 @@ INT_PTR CALLBACK MPlayer_Game_List_Dialog_Proc(HWND window, UINT message, WPARAM switch (message) { case WM_INITDIALOG: { - CurGame = 0; - Net2IsGameListActive = 1; + if (_LobbyScreen == NULL) { + return(0); + } - SendDlgItemMessage(window, IDC_YOURNAME, EM_SETLIMITTEXT, 16, 0); - SetWindowText(GetDlgItem(window, IDC_YOURNAME), Session.Handle); + _LobbyScreen->Open(); - Session.Options.ScenarioDescription[0] = '\0'; - Session.ColorIdx = Session.PrefColor; - - Clear_Vector(&Session.Games); - Clear_Vector(&Session.Players); - Clear_Vector(&Session.Chat); - - NodeNameType * who = new NodeNameType; - strcpy(who->Name, Session.Handle); - who->Chat.LastTime = 0; - who->Chat.LastChance = 0; - who->Chat.Color = Session.GPacket.PlayerInfo.Color; - Session.Chat.Add(who); - - NodeNameType * game = new NodeNameType; - strcpy(game->Name, ""); - game->Game.IsOpen = 0; - game->Game.LastTime = 0; - Session.Games.Add(game); - - Send_Join_Queries(true, false, true, true); + SendDlgItemMessage(window, IDC_YOURNAME, EM_SETLIMITTEXT, UILobbyPresenterClass::HANDLE_LIMIT, 0); + SetWindowText(GetDlgItem(window, IDC_YOURNAME), _LobbyScreen->Handle.c_str()); return(0); } case WM_COMMAND: { + if (_LobbyScreen == NULL) { + return(0); + } + switch (LOWORD(wparam)) { case IDC_YOURNAME: { char name_buf[64]; SendDlgItemMessage(window, IDC_YOURNAME, WM_GETTEXT, 63, (LPARAM)name_buf); - - if (strcmp(name_buf, Session.Handle)) { - if (UTF8::Copy(Session.Handle, sizeof(Session.Handle), name_buf) < strlen(name_buf)) { - SetDlgItemText(window, IDC_YOURNAME, Session.Handle); - } - Send_Join_Queries(0, 0, 1, 0); - _Net2DisplayUsers(); - } - + _LobbyScreen->Queue(UIIntent{UI_LOBBY_RENAME, name_buf, 0}); return(0); } case IDCANCEL: { - _netresponse = IDCANCEL; + _LobbyScreen->Queue(UIIntent{UI_LOBBY_CANCEL, "", 0}); return(0); } case IDC_GAMELIST_NEW: { - _netresponse = IDC_GAMELIST_NEW; + _LobbyScreen->Queue(UIIntent{UI_LOBBY_NEW, "", 0}); return(0); } case IDC_INPUT: { - char text[260]; + if (HIWORD(wparam) != EN_MAXTEXT) { + return(0); + } + char text[260]; SendDlgItemMessage(window, IDC_INPUT, WM_GETTEXT, 256, (LPARAM)text); + SendDlgItemMessage(window, IDC_INPUT, WM_SETTEXT, 0, (LPARAM) ""); - int len = strlen(text); - - if (HIWORD(wparam) == EN_MAXTEXT) { - SendDlgItemMessage(window, IDC_INPUT, WM_SETTEXT, 0, (LPARAM) ""); - - if (len > 2) { - - PMessagePrintf(ColorMe, "[%s] %s", Session.Handle, text); - - GlobalPacketType gpacket; - memset(&gpacket, 0, sizeof(gpacket)); - - gpacket.Command = NET_MESSAGE; - strcpy(gpacket.Name, Session.Handle); - strcpy(gpacket.Message.Buf, text); - gpacket.Message.Color = Session.ColorIdx; - gpacket.Message.NameCRC = Compute_Name_CRC(Session.GameName); - - if (JoinState == JOIN_CONFIRMED) { - for (int i = 1; i < Session.Players.Count(); ++i) { - Ipx.Send_Global_Message(&gpacket, sizeof(gpacket), 1, &Session.Players[i]->Address); - Call_Back(); - } - } else { - for (int i = 1; i < Session.Chat.Count(); ++i) { - Ipx.Send_Global_Message(&gpacket, sizeof(gpacket), 1, &Session.Chat[i]->Address); - Call_Back(); - } - } - } - } - + _LobbyScreen->Queue(UIIntent{UI_LOBBY_SAY, text, 0}); return(0); } case IDC_YOURCOLOR: { if (HIWORD(wparam) == LBN_SELCHANGE) { - Session.ColorIdx = SendDlgItemMessage(window, IDC_YOURCOLOR, LB_GETCURSEL, 0, 0); + _LobbyScreen->Queue(UIIntent{UI_LOBBY_COLOR, "", + (int)SendDlgItemMessage(window, IDC_YOURCOLOR, LB_GETCURSEL, 0, 0)}); } return(0); } case IDC_GAMELIST: { - - if (JoinState > JOIN_NOTHING) { - return(0); - } - - int old_game = CurGame; - LRESULT sel = SendDlgItemMessage(window, IDC_GAMELIST, LB_GETCURSEL, 0, 0); - - if (sel >= 0 && Net2IsGameListActive) { - CurGame = sel; - strcpy(Session.GameName, Session.Games[sel]->Name); - } - if (HIWORD(wparam) == LBN_SELCHANGE) { - Clear_Vector(&Session.Players); - - if (old_game != CurGame) { - Send_Join_Queries(1, 1, 1, 0); - } - - _Net2DisplayUsers(); + _LobbyScreen->Queue(UIIntent{UI_LOBBY_PICK_GAME, "", + (int)SendDlgItemMessage(window, IDC_GAMELIST, LB_GETCURSEL, 0, 0)}); return(0); } if (HIWORD(wparam) == LBN_DBLCLK) { - _netresponse = IDC_GAMELIST_JOIN; + _LobbyScreen->Queue(UIIntent{UI_LOBBY_JOIN, "", 0}); return(0); } @@ -1285,7 +1219,7 @@ INT_PTR CALLBACK MPlayer_Game_List_Dialog_Proc(HWND window, UINT message, WPARAM } case IDC_GAMELIST_JOIN: { - _netresponse = IDC_GAMELIST_JOIN; + _LobbyScreen->Queue(UIIntent{UI_LOBBY_JOIN, "", 0}); return(0); } } @@ -2127,7 +2061,7 @@ static void Unjoin_Game(int game_index) * 02/14/1995 BR : Created. * * 04/15/1995 BRR : Created. * *=============================================================================================*/ -static void Send_Join_Queries(int gamenow, int playernow, int chatnow, int init) +void Send_Join_Queries(int gamenow, int playernow, int chatnow, int init) { GlobalPacketType packet = {}; diff --git a/code/netdlg2.h b/code/netdlg2.h index 5b5d4baf5..edb22e22a 100644 --- a/code/netdlg2.h +++ b/code/netdlg2.h @@ -15,9 +15,21 @@ #include "win.h" +#include "netdlg.h" + struct GlobalPacketType; class IPXAddressClass; +/* +** The lobby's own state, shared by the game list, host and guest screens. +*/ +extern int CurGame; +extern JoinStateType JoinState; +extern bool Net2IsGameListActive; + +void Send_Join_Queries(int gamenow, int playernow, int chatnow, int init = 0); +void Net2ServiceGameList(void); + int Net2FirstFreeColor(int reqcolor, int index); void Fill_Country_Box(HWND combo); int Country_From_Box(HWND combo); diff --git a/code/ui/uilobby.cpp b/code/ui/uilobby.cpp new file mode 100644 index 000000000..76be98b09 --- /dev/null +++ b/code/ui/uilobby.cpp @@ -0,0 +1,327 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The network lobby. Behavior traced out of MPlayer_Game_List_Dialog_Proc, +// Net2DisplayGameList, _Net2DisplayUsers and Net2Remote_Connect in netdlg2.cpp. +// +// What the extraction fixes in place: the rosters are read into the model where the +// session changes rather than where a list is drawn, so a presentation that draws a +// different number of times cannot lose a change or repeat one; the host's accepted +// status is a fact about the player, so it is recorded with the roster rather than while +// painting a row; a game row carries whether the game is open rather than the bracketed +// caption; and the selection is clamped against a list a host can shorten at any moment, +// which is what the display function did before it drew. +// +// Packets are untouched. Nothing here changes what goes on the wire, only who owns the +// state the screens show. +// +// docs/UI_DESIGN.md, "Screens", owns the contracts this keeps to. + +#include "always.h" + +#include "uilobby.h" + +#include "_timer.h" +#include "conquer.h" +#include "data.h" +#include "houstype.h" +#include "ipxmgr.h" +#include "language/language.h" +#include "mplayer.h" +#include "netdlg.h" +#include "netdlg2.h" +#include "netshare.h" +#include "session.h" +#include "utf8.h" + +#include +#include + + +void UILobbyPresenterClass::Refresh(void) +{ + Handle = Session.Handle; + Color = Session.ColorIdx; + + Build_Game_Rows(); + Build_User_Rows(); +} + + +/// +/// Puts the lobby's own rosters back the way the game list dialog created them: the +/// player's chat entry first, and an entry standing for the lobby itself at the head of +/// the game list. +/// +void UILobbyPresenterClass::Open(void) +{ + CurGame = 0; + Net2IsGameListActive = true; + + Handle = Session.Handle; + + Session.Options.ScenarioDescription[0] = '\0'; + Session.ColorIdx = Session.PrefColor; + Color = Session.ColorIdx; + + Clear_Vector(&Session.Games); + Clear_Vector(&Session.Players); + Clear_Vector(&Session.Chat); + + NodeNameType * const who = new NodeNameType; + strcpy(who->Name, Session.Handle); + who->Chat.LastTime = 0; + who->Chat.LastChance = 0; + who->Chat.Color = Session.GPacket.PlayerInfo.Color; + Session.Chat.Add(who); + + NodeNameType * const game = new NodeNameType; + strcpy(game->Name, ""); + game->Game.IsOpen = 0; + game->Game.LastTime = 0; + Session.Games.Add(game); + + Send_Join_Queries(true, false, true, true); + + Build_Game_Rows(); + Build_User_Rows(); +} + + +/// +/// Reads the advertised games into the view-model and clamps the selection. +/// A game can vanish while the list is open, so a selection past the end is pulled back +/// and the queries are asked again for the game the selection landed on. +/// +void UILobbyPresenterClass::Build_Game_Rows(void) +{ + int const count = Session.Games.Count(); + + if (CurGame >= count) { + CurGame = count - 1; + Send_Join_Queries(0, 1, 0, 0); + } + if (CurGame < 0) { + CurGame = 0; + } + + Games.clear(); + + GameRowType lobby; + lobby.Label = Fetch_String(TXT_LOBBY); + lobby.IsOpen = false; + Games.push_back(lobby); + + for (int index = 1; index < Session.Games.Count(); index++) { + NodeNameType const * const node = Session.Games[index]; + + // The name is the node's first member, which is what the caption format has always + // been handed. A row carries the name and whether the game is open; the bracketed + // caption belongs to the presentation. + GameRowType row; + row.Label = node->Name; + row.IsOpen = node->Game.IsOpen != 0; + Games.push_back(row); + } + + SelectedGame = CurGame; + GamesChanged = true; +} + + +/// +/// Reads the chat roster, or the selected game's player roster, into the view-model. +/// Out in the lobby the rows are the handles of everybody chatting. Inside a game each row +/// carries the player's color, the side its emblem stands for, and whether the player is +/// the host or has accepted the settings. +/// +void UILobbyPresenterClass::Build_User_Rows(void) +{ + Users.clear(); + + if (CurGame == 0) { + UserRowType me; + me.Name = Session.Handle; + Users.push_back(me); + + for (int index = 1; index < Session.Chat.Count(); index++) { + UserRowType row; + row.Name = Session.Chat[index]->Name; + Users.push_back(row); + } + + UsersChanged = true; + return; + } + + for (int index = 0; index < Session.Players.Count(); index++) { + NodeNameType * const node = Session.Players[index]; + + // The host counts as having accepted its own settings, which the list recorded on + // the player rather than on the row it was about to draw. + bool const host = strcmp(node->Name, Session.GameName) == 0; + if (host) { + node->Player.Status = 1; + } + + UserRowType row; + row.Name = node->Name; + row.Color = node->Player.Color; + row.IsHost = host; + row.HasAccepted = node->Player.Status != 0; + + int const country = node->Player.House; + row.Side = (country >= HOUSE_FIRST && country < HouseTypes.Count()) + ? (int)HouseTypes[country]->Side : (int)SIDE_NONE; + + if (row.Side == SIDE_GDI) { + row.SideName = Fetch_String(TXT_GDI); + } else if (row.Side == SIDE_NOD || row.Side == SIDE_NONE) { + row.SideName = Fetch_String(TXT_NOD); + } else { + row.SideName = (char const *)HouseTypes[country]->GivenName; + } + + Users.push_back(row); + } + + UsersChanged = true; +} + + +/// +/// The maintenance the driver ran on every pass of its own loop: a game or a chat partner +/// that has stopped answering is dropped, and a partner close to timing out is asked once +/// more before it goes. +/// +void UILobbyPresenterClass::Service(void) +{ + Net2ServiceGameList(); +} + + +/// +/// Records the name the player is showing and tells the lobby about it. +/// The name is truncated to what the session's buffer holds, and the model carries the +/// truncated name back so the field shows what was actually kept. +/// +void UILobbyPresenterClass::Rename(std::string const & name) +{ + if (name == Session.Handle) { + return; + } + + UTF8::Copy(Session.Handle, sizeof(Session.Handle), name.c_str()); + Handle = Session.Handle; + + Send_Join_Queries(0, 0, 1, 0); + Build_User_Rows(); +} + + +/// +/// Moves the highlight to another advertised game and asks that game who is in it. +/// A player already joined to a game cannot browse away from it, which is what the list +/// refused to do once JoinState left JOIN_NOTHING. +/// +void UILobbyPresenterClass::Pick_Game(int row) +{ + if (JoinState > JOIN_NOTHING) { + return; + } + if (row < 0 || row >= Session.Games.Count() || !Net2IsGameListActive) { + return; + } + + int const previous = CurGame; + + CurGame = row; + strcpy(Session.GameName, Session.Games[row]->Name); + SelectedGame = CurGame; + + Clear_Vector(&Session.Players); + + if (previous != CurGame) { + Send_Join_Queries(1, 1, 1, 0); + } + + Build_User_Rows(); +} + + +/// +/// Sends a line of chat to whoever is listening, which is the game's players once joined +/// and the lobby's chat roster otherwise. A line of two characters or fewer is dropped, +/// which is what the edit control's own handler did. +/// +void UILobbyPresenterClass::Say(std::string const & text) +{ + if (text.size() <= 2) { + return; + } + + PMessagePrintf(ColorMe, "[%s] %s", Session.Handle, text.c_str()); + + GlobalPacketType gpacket; + memset(&gpacket, 0, sizeof(gpacket)); + + gpacket.Command = NET_MESSAGE; + strcpy(gpacket.Name, Session.Handle); + std::snprintf(gpacket.Message.Buf, sizeof(gpacket.Message.Buf), "%s", text.c_str()); + gpacket.Message.Color = Session.ColorIdx; + gpacket.Message.NameCRC = Compute_Name_CRC(Session.GameName); + + DynamicVectorClass & who = + JoinState == JOIN_CONFIRMED ? Session.Players : Session.Chat; + + for (int index = 1; index < who.Count(); index++) { + Ipx.Send_Global_Message(&gpacket, sizeof(gpacket), 1, &who[index]->Address); + Call_Back(); + } +} + + +void UILobbyPresenterClass::Execute(UIIntent const & intent) +{ + if (intent.Action == UI_LOBBY_RENAME) { + Rename(intent.Identity); + return; + } + + if (intent.Action == UI_LOBBY_PICK_GAME) { + Pick_Game(intent.Value); + return; + } + + if (intent.Action == UI_LOBBY_SAY) { + Say(intent.Identity); + return; + } + + if (intent.Action == UI_LOBBY_COLOR) { + Session.ColorIdx = intent.Value; + Color = intent.Value; + return; + } + + if (intent.Action == UI_LOBBY_JOIN) { + Response = RESPONSE_JOIN; + return; + } + + if (intent.Action == UI_LOBBY_NEW) { + Response = RESPONSE_NEW; + return; + } + + if (intent.Action == UI_LOBBY_CANCEL) { + Response = RESPONSE_CANCEL; + return; + } +} diff --git a/code/ui/uilobby.h b/code/ui/uilobby.h new file mode 100644 index 000000000..098b510cc --- /dev/null +++ b/code/ui/uilobby.h @@ -0,0 +1,108 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The network lobby's behavior, with no toolkit in it. The game list, the host setup and +// the guest setup are one screen family sharing one model, because they share the session's +// game, player and chat rosters and hand the driver one answer between them. +// +// This holds the game list half. The host and guest commands still write the driver's +// response themselves. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + +#include +#include + + +inline constexpr char const * UI_LOBBY_RENAME = "rename"; +inline constexpr char const * UI_LOBBY_PICK_GAME = "pickgame"; +inline constexpr char const * UI_LOBBY_JOIN = "join"; +inline constexpr char const * UI_LOBBY_NEW = "new"; +inline constexpr char const * UI_LOBBY_CANCEL = "cancel"; +inline constexpr char const * UI_LOBBY_SAY = "say"; +inline constexpr char const * UI_LOBBY_COLOR = "color"; + + +class UILobbyPresenterClass : public UIPresenterClass +{ + public: + // What the driver loop is being asked to do next. These stand where the dialogs' + // own control identifiers stood, so a presenter names no control. + enum ResponseType { + RESPONSE_NONE, + RESPONSE_CANCEL, + RESPONSE_JOIN, + RESPONSE_NEW, + }; + + // A game somebody is advertising. The lobby itself heads the list. + struct GameRowType + { + std::string Label; + bool IsOpen = false; + }; + + // Somebody in the lobby, or a player in the game the list is showing. Out in the + // lobby only the name is known; inside a game the row also carries the color, the + // side its emblem stands for and whether the player is the host or has accepted. + struct UserRowType + { + std::string Name; + std::string SideName; + int Color = 0; + int Side = -1; + bool IsHost = false; + bool HasAccepted = false; + }; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + virtual void Service(void) override; + + // The rosters the lobby opens with, which the game list dialog built as it was + // created: the player's own chat entry and the lobby's own game entry. + void Open(void); + + // Reads the session's rosters into the view-model. Marking the host as accepted + // happens here rather than while drawing, because it is a fact about the player + // rather than about the row. + void Build_Game_Rows(void); + void Build_User_Rows(void); + + /* + ** The view-model. + */ + std::string Handle; + + // The longest handle the name field accepts, in bytes, which is the limit the + // dialog set on its edit control. + enum { HANDLE_LIMIT = 16 }; + + int Color = 0; + + std::vector Games; + int SelectedGame = 0; + + std::vector Users; + + ResponseType Response = RESPONSE_NONE; + + // Did the last executed intent move a roster? A view redraws only what moved. + bool GamesChanged = false; + bool UsersChanged = false; + + private: + void Rename(std::string const & name); + void Pick_Game(int row); + void Say(std::string const & text); +}; From c73a36b198a81969eb298d3d3a54312281133ef6 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 08:37:12 +0100 Subject: [PATCH 126/179] refactor(ui): put the lobby's guest screen behind the same presenter MPlayer_Guest_Dialog_Proc now reads the view-model and queues intents on UILobbyPresenterClass, and the driver drains them with the game list's. A guest arrives having accepted nothing, accepting tells the game so, and changing the side or the color sends the host one packet carrying the pair: the side is recorded by its own intent ahead of the color's, because the dialog read both of its boxes before it sent. The side intent carries the country it stands for rather than the row it sat on, which is the same rule the campaign and skirmish lists follow. A roster an executed intent moved is put on the controls after the queue, where the other rewired drivers sync their views. Taking the accept button away on a press stays with the view; what the host does to put it back is not extracted. Classified preserved. The chat handler is the presenter's, so the guest's copy of the overrun the game list's had is gone with it. Packets are untouched. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/netdlg2.cpp | 94 +++++++++++++++++---------------------------- code/ui/uilobby.cpp | 72 ++++++++++++++++++++++++++++++++++ code/ui/uilobby.h | 25 ++++++++++++ 3 files changed, 132 insertions(+), 59 deletions(-) diff --git a/code/netdlg2.cpp b/code/netdlg2.cpp index d98ee9f0d..79af6d946 100644 --- a/code/netdlg2.cpp +++ b/code/netdlg2.cpp @@ -746,6 +746,17 @@ bool Net2Remote_Connect(void) screen.Response = UILobbyPresenterClass::RESPONSE_NONE; } + // A roster an executed intent moved is put on the controls here, after the + // queue, which is where the other rewired drivers sync their views. + if (screen.UsersChanged) { + _Net2DisplayUsers(); + } + if (screen.GamesChanged) { + Net2DisplayGameList(); + } + screen.UsersChanged = false; + screen.GamesChanged = false; + if (_netresponse != 0) { break; } @@ -3279,98 +3290,63 @@ INT_PTR CALLBACK MPlayer_Guest_Dialog_Proc(HWND window, UINT message, WPARAM wpa EnableWindow(GetDlgItem(window, IDC_ACCEPT), FALSE); - int self_index = -1; - for (int i = 0; i < Session.Players.Count(); ++i) { - if (!strcmp(Session.Players[i]->Name, Session.Handle)) { - self_index = i; - } - } - - if (self_index != -1) { - Session.Players[self_index]->Player.Status = 0; + if (_LobbyScreen != NULL) { + _LobbyScreen->Open_Guest(); + EnableWindow(GetDlgItem(window, IDC_ACCEPT), _LobbyScreen->CanAccept ? TRUE : FALSE); } _Net2DisplayUsers(); - Session.Options.ScenarioDescription[0] = '\0'; - return(0); } case WM_COMMAND: { + if (_LobbyScreen == NULL) { + return(0); + } + switch (LOWORD(wparam)) { case IDC_ACCEPT: { - Session.Players[0]->Player.Status = 1; - - char dest[64]; - sprintf(dest, "A1"); - SendPublicGameopts(dest); + _LobbyScreen->Queue(UIIntent{UI_LOBBY_ACCEPT, "", 0}); + // Taking the button away is the view's, the way getting out of a browser's way + // stayed with the view at step 7. What the host does to put it back is not + // extracted yet. EnableWindow(GetDlgItem(window, IDC_ACCEPT), FALSE); InvalidateRect(GetDlgItem(window, IDC_ACCEPT), NULL, FALSE); - - _Net2DisplayUsers(); return(0); } case IDC_YOURSIDE: case IDC_YOURCOLOR: { if (HIWORD(wparam) == CBN_SELCHANGE) { - LRESULT color = SendDlgItemMessage(window, IDC_YOURCOLOR, CB_GETCURSEL, 0, 0); - - LRESULT house = Country_From_Box(GetDlgItem(window, IDC_YOURSIDE)); - - Session.PrefColor = color; - - char dest[64]; - sprintf(dest, "R%d,%d", house, color); - SendPrivateGameopts(Session.GameName, dest); + // The side is recorded ahead of the color, because the dialog read both of + // its boxes and sent one packet carrying the pair. + _LobbyScreen->Queue(UIIntent{UI_LOBBY_SIDE, "", + Country_From_Box(GetDlgItem(window, IDC_YOURSIDE))}); + _LobbyScreen->Queue(UIIntent{UI_LOBBY_IDENTITY, "", + (int)SendDlgItemMessage(window, IDC_YOURCOLOR, CB_GETCURSEL, 0, 0)}); } return(0); } case IDCANCEL: { if (!Net2GameStarted) { - _netresponse = IDCANCEL; + _LobbyScreen->Queue(UIIntent{UI_LOBBY_CANCEL, "", 0}); } return(0); } case IDC_INPUT: { - char text[260]; + if (HIWORD(wparam) != EN_MAXTEXT) { + return(0); + } + char text[260]; SendDlgItemMessage(window, IDC_INPUT, WM_GETTEXT, 256, (LPARAM)text); + SendDlgItemMessage(window, IDC_INPUT, WM_SETTEXT, 0, (LPARAM)""); - int len = strlen(text); - if (HIWORD(wparam) == EN_MAXTEXT) { - SendDlgItemMessage(window, IDC_INPUT, WM_SETTEXT, 0, (LPARAM)""); - - if (len > 2) { - PMessagePrintf(ColorMe, "[%s] %s", Session.Handle, text); - - GlobalPacketType gpacket; - memset(&gpacket, 0, sizeof(gpacket)); - - gpacket.Command = NET_MESSAGE; - strcpy(gpacket.Name, Session.Handle); - strcpy(gpacket.Message.Buf, text); - gpacket.Message.Color = Session.ColorIdx; - gpacket.Message.NameCRC = Compute_Name_CRC(Session.GameName); - - if (JoinState == JOIN_CONFIRMED) { - for (int i = 1; i < Session.Players.Count(); ++i) { - Ipx.Send_Global_Message(&gpacket, sizeof(gpacket), 1, &Session.Players[i]->Address); - Call_Back(); - } - } else { - for (int i = 1; i < Session.Chat.Count(); ++i) { - Ipx.Send_Global_Message(&gpacket, sizeof(gpacket), 1, &Session.Chat[i]->Address); - Call_Back(); - } - } - } - } - + _LobbyScreen->Queue(UIIntent{UI_LOBBY_SAY, text, 0}); return(0); } } diff --git a/code/ui/uilobby.cpp b/code/ui/uilobby.cpp index 76be98b09..53b2b2fd4 100644 --- a/code/ui/uilobby.cpp +++ b/code/ui/uilobby.cpp @@ -36,6 +36,7 @@ #include "mplayer.h" #include "netdlg.h" #include "netdlg2.h" +#include "netdlg.h" #include "netshare.h" #include "session.h" #include "utf8.h" @@ -94,6 +95,62 @@ void UILobbyPresenterClass::Open(void) } +/// +/// Records where a guest stands as its screen opens: it has accepted nothing yet, and the +/// scenario description is cleared because the host has not sent one. +/// +void UILobbyPresenterClass::Open_Guest(void) +{ + House = Session.House; + Color = Session.ColorIdx; + CanAccept = false; + + for (int index = 0; index < Session.Players.Count(); index++) { + if (strcmp(Session.Players[index]->Name, Session.Handle) == 0) { + Session.Players[index]->Player.Status = 0; + } + } + + Session.Options.ScenarioDescription[0] = '\0'; + + Build_User_Rows(); +} + + +/// +/// Tells the game that this player has accepted the host's settings. +/// +void UILobbyPresenterClass::Accept(void) +{ + if (Session.Players.Count() == 0) { + return; + } + + Session.Players[0]->Player.Status = 1; + CanAccept = false; + + SendPublicGameopts("A1"); + + Build_User_Rows(); +} + + +/// +/// Tells the host which country and color this player is showing. The country is whatever +/// the model is already holding, because the side is recorded by its own intent ahead of +/// this one, the way the dialog read both of its boxes before sending one packet. +/// +void UILobbyPresenterClass::Change_Identity(int color) +{ + Color = color; + Session.PrefColor = color; + + char options[64]; + std::snprintf(options, sizeof(options), "R%d,%d", House, color); + SendPrivateGameopts(Session.GameName, options); +} + + /// /// Reads the advertised games into the view-model and clamps the selection. /// A game can vanish while the list is open, so a selection past the end is pulled back @@ -310,6 +367,21 @@ void UILobbyPresenterClass::Execute(UIIntent const & intent) return; } + if (intent.Action == UI_LOBBY_SIDE) { + House = intent.Value; + return; + } + + if (intent.Action == UI_LOBBY_IDENTITY) { + Change_Identity(intent.Value); + return; + } + + if (intent.Action == UI_LOBBY_ACCEPT) { + Accept(); + return; + } + if (intent.Action == UI_LOBBY_JOIN) { Response = RESPONSE_JOIN; return; diff --git a/code/ui/uilobby.h b/code/ui/uilobby.h index 098b510cc..82f359071 100644 --- a/code/ui/uilobby.h +++ b/code/ui/uilobby.h @@ -31,6 +31,9 @@ inline constexpr char const * UI_LOBBY_NEW = "new"; inline constexpr char const * UI_LOBBY_CANCEL = "cancel"; inline constexpr char const * UI_LOBBY_SAY = "say"; inline constexpr char const * UI_LOBBY_COLOR = "color"; +inline constexpr char const * UI_LOBBY_SIDE = "side"; +inline constexpr char const * UI_LOBBY_IDENTITY = "identity"; +inline constexpr char const * UI_LOBBY_ACCEPT = "accept"; class UILobbyPresenterClass : public UIPresenterClass @@ -60,6 +63,14 @@ class UILobbyPresenterClass : public UIPresenterClass std::string Name; std::string SideName; int Color = 0; + + // The country the player is showing, which is a country rather than a row, because + // the side list holds only the countries that may be played. + int House = 0; + + // Is the accept button available? A guest may accept once per change the host + // makes, which is what disabling the button after a press stood for. + bool CanAccept = false; int Side = -1; bool IsHost = false; bool HasAccepted = false; @@ -73,6 +84,10 @@ class UILobbyPresenterClass : public UIPresenterClass // created: the player's own chat entry and the lobby's own game entry. void Open(void); + // The rosters and the guest's own standing when the guest screen opens: a guest + // arrives having accepted nothing. + void Open_Guest(void); + // Reads the session's rosters into the view-model. Marking the host as accepted // happens here rather than while drawing, because it is a fact about the player // rather than about the row. @@ -90,6 +105,14 @@ class UILobbyPresenterClass : public UIPresenterClass int Color = 0; + // The country the player is showing, which is a country rather than a row, because + // the side list holds only the countries that may be played. + int House = 0; + + // Is the accept button available? A guest may accept once per change the host + // makes, which is what disabling the button after a press stood for. + bool CanAccept = false; + std::vector Games; int SelectedGame = 0; @@ -105,4 +128,6 @@ class UILobbyPresenterClass : public UIPresenterClass void Rename(std::string const & name); void Pick_Game(int row); void Say(std::string const & text); + void Accept(void); + void Change_Identity(int color); }; From f15bc85e3b6e0d4d2761551182cb6f6713cfeda8 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 08:37:24 +0100 Subject: [PATCH 127/179] docs: record where the lobby family stands Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/UI_DESIGN.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index 3c747b84f..cefe3b7c8 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -946,7 +946,19 @@ text beyond an ASCII test document. The same rule the text field learned at step 9, one control further on. 11. **Network lobbies** (L, two changes). Host, guest, game list, the `WS_` stack, and `netshare.cpp` as one family; then disconnect, desync, and - reconnect. Packets unchanged. + reconnect. Packets unchanged. In progress: the family shares one presenter, + `code/ui/uilobby.{h,cpp}`, because the three screens share the session's + game, player and chat rosters and hand the driver one answer between them. + The game list and the guest screen read the view-model and queue intents, + and `Net2Remote_Connect` drains them after its pump; the host screen's + commands are not extracted and still write the driver's response themselves. + No document exists for any of the three. + + `Net2DisplayGameList` and `_Net2DisplayUsers` are split the way `Fill_List` + was: the presenter reads the rosters into the model and the old names put + the model on the controls. The host's accepted status is recorded with the + roster rather than while painting the row, because it is a fact about the + player rather than about the row. 12. **Map generator and WDT** (L). 13. **Retire OwnerDraw** (M). Delete `ownrdraw.cpp`, `windlg.cpp`, the modeless dialog list, the dialog templates, the kill switch, and the From 8295823f5cf5af38470bfa4b25b7361b53dd945a Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 08:45:30 +0100 Subject: [PATCH 128/179] fix(ui): drop two members a paste left in the lobby's user row UserRowType carried a House and a CanAccept that nothing read: they are the presenter's own view-model fields, copied into the row struct by accident and left at the struct's outer indentation. Build_User_Rows never wrote them and _Net2DisplayUsers never read them. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/ui/uilobby.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/code/ui/uilobby.h b/code/ui/uilobby.h index 82f359071..05bc022d4 100644 --- a/code/ui/uilobby.h +++ b/code/ui/uilobby.h @@ -63,14 +63,6 @@ class UILobbyPresenterClass : public UIPresenterClass std::string Name; std::string SideName; int Color = 0; - - // The country the player is showing, which is a country rather than a row, because - // the side list holds only the countries that may be played. - int House = 0; - - // Is the accept button available? A guest may accept once per change the host - // makes, which is what disabling the button after a press stood for. - bool CanAccept = false; int Side = -1; bool IsHost = false; bool HasAccepted = false; From 736b9a83311dd80b882b860fb29ee57c39d922df Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 08:51:41 +0100 Subject: [PATCH 129/179] refactor(ui): put the lobby's host screen behind the same presenter MPlayer_Host_Dialog_Proc was the last of the three lobby screens still acting in its own handlers. Its commands now read the view-model and queue intents against UILobbyPresenterClass, and Net2Remote_Connect executes them after its pump beside the two screens already rewired: the game options go back on the controls through DisplayGameopts when an intent moved them, the scenario picker runs between passes with the dialog stepped aside the way its own ShowWindow stepped it aside, and RESPONSE_GO maps onto IDC_GO the way the other answers map onto the identifiers the driver's loop already tests. The host's dispatch ran twice for five of its check boxes, once in the preliminary block that owns the Bases and Short Game coupling and again in the main switch, and read every track bar back on any one of them moving. Both shapes are preserved: one toggle intent per box with the coupling in the presenter, and six slider intents per notification. Three things the extraction fixes in place: - The host's chat handler was the third copy of the overrun found last change: 256 characters read out of the edit control and strcpy'd into GlobalPacketType's Message.Buf, which is MAX_MESSAGE_LENGTH = 224, writing over the Color and NameCRC fields that follow it. There is now one bounded copy and no strcpy into a packet field is left in this family. The wire format does not move: no field, no size, no order, no send or receive path changed. - The option track bars carry the ranges DisplayGameopts sets on its initializing pass -- credits step 100 from OD_SETTRACKSTEP, AI players 0 to 6 -- so a value is never clamped into a control's default range. - The players the host picked out to kick are resolved to names when the kick executes rather than read back off a list box whose rows the roster may have moved underneath. PMessagePrintf records its line on the model as it composes it, which is the Fill_List split again: a presentation that draws a different number of times cannot lose a line or repeat one. The model's pointer moves into code/ui/uilobby.cpp so the network code reaches it from outside netdlg2.cpp. Preserved: the host's color resolution, including the second search from the color it was wearing and the message that explains it; the WM_INITDIALOG seed and the scenario the setup opens on; the guest screen's disabled controls; and the driver's own refusals to start, which put the button back. Legacy views stay selected. No document exists for any of the three screens. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/netdlg2.cpp | 653 +++++++++++--------------------------------- code/netshare.cpp | 7 + code/ui/uilobby.cpp | 469 ++++++++++++++++++++++++++++++- code/ui/uilobby.h | 140 +++++++++- 4 files changed, 779 insertions(+), 490 deletions(-) diff --git a/code/netdlg2.cpp b/code/netdlg2.cpp index 79af6d946..a1391c320 100644 --- a/code/netdlg2.cpp +++ b/code/netdlg2.cpp @@ -80,7 +80,12 @@ int Net2_g_Col_House; // The lobby screen the driver is running. The dialog procedures read its view-model and // queue intents against it; the driver executes the queue after its pump returns. -static UILobbyPresenterClass * _LobbyScreen = NULL; +// code/ui/uilobby.cpp holds the pointer, because a message produced away from a screen has +// to reach the same model. +static UILobbyPresenterClass * Lobby_Screen(void) +{ + return(UI_Lobby_Screen()); +} /// @@ -93,6 +98,7 @@ static int Lobby_Response_Identifier(UILobbyPresenterClass::ResponseType respons case UILobbyPresenterClass::RESPONSE_CANCEL: return(IDCANCEL); case UILobbyPresenterClass::RESPONSE_JOIN: return(IDC_GAMELIST_JOIN); case UILobbyPresenterClass::RESPONSE_NEW: return(IDC_GAMELIST_NEW); + case UILobbyPresenterClass::RESPONSE_GO: return(IDC_GO); default: return(0); } } @@ -217,13 +223,13 @@ void _Net2DisplayUsers(void) HWND win = WS_Top_Window(); HWND userwin = win ? GetDlgItem(win, IDC_USERS) : NULL; - if (win == NULL || userwin == NULL || _LobbyScreen == NULL) { + if (win == NULL || userwin == NULL || Lobby_Screen() == NULL) { return; } // The rows are built where the session changed rather than here, so what is drawn is // the model the screen holds. - _LobbyScreen->Build_User_Rows(); + Lobby_Screen()->Build_User_Rows(); OwnerDraw::CellData thecell; @@ -238,8 +244,8 @@ void _Net2DisplayUsers(void) bool const inlobby = CurGame == 0; - for (int i = 0; i < (int)_LobbyScreen->Users.size(); i++) { - UILobbyPresenterClass::UserRowType const & row = _LobbyScreen->Users[i]; + for (int i = 0; i < (int)Lobby_Screen()->Users.size(); i++) { + UILobbyPresenterClass::UserRowType const & row = Lobby_Screen()->Users[i]; SendDlgItemMessage(win, IDC_USERS, LB_INSERTSTRING, (WPARAM)(inlobby ? i : -1), (LPARAM)row.Name.c_str()); @@ -368,19 +374,19 @@ void Net2DisplayGameList(void) { HWND window = WS_Top_Window(); - if (window == NULL || _LobbyScreen == NULL) { + if (window == NULL || Lobby_Screen() == NULL) { return; } - _LobbyScreen->Build_Game_Rows(); + Lobby_Screen()->Build_Game_Rows(); int top = SendDlgItemMessage(window, IDC_GAMELIST, LB_GETTOPINDEX, 0, 0); SendDlgItemMessage(window, IDC_GAMELIST, OD_DISABLEPAINT, 0, 1); SendDlgItemMessage(window, IDC_GAMELIST, LB_RESETCONTENT, 0, 0); - for (int i = 0; i < (int)_LobbyScreen->Games.size(); i++) { - UILobbyPresenterClass::GameRowType const & row = _LobbyScreen->Games[i]; + for (int i = 0; i < (int)Lobby_Screen()->Games.size(); i++) { + UILobbyPresenterClass::GameRowType const & row = Lobby_Screen()->Games[i]; if (i == 0) { SendDlgItemMessage(window, IDC_GAMELIST, LB_INSERTSTRING, -1, (LPARAM)row.Label.c_str()); @@ -392,7 +398,7 @@ void Net2DisplayGameList(void) SendDlgItemMessage(window, IDC_GAMELIST, LB_INSERTSTRING, -1, (LPARAM)buffer); } - SendDlgItemMessage(window, IDC_GAMELIST, LB_SETCURSEL, _LobbyScreen->SelectedGame, 0); + SendDlgItemMessage(window, IDC_GAMELIST, LB_SETCURSEL, Lobby_Screen()->SelectedGame, 0); SendDlgItemMessage(window, IDC_GAMELIST, LB_SETTOPINDEX, top, 0); SendDlgItemMessage(window, IDC_GAMELIST, OD_DISABLEPAINT, 0, 0); @@ -702,7 +708,7 @@ bool Net2Remote_Connect(void) OwnerDraw::Register_Control_Classes(); UILobbyPresenterClass screen; - _LobbyScreen = &screen; + UI_Set_Lobby_Screen(&screen); HWND game_list_dialog = WS_Create_Dialog(ProgramInstance, IDD_MPLAYER_GAME_LIST, MainWindow, MPlayer_Game_List_Dialog_Proc, FALSE); Center_Window_Within_Window(game_list_dialog); @@ -754,8 +760,35 @@ bool Net2Remote_Connect(void) if (screen.GamesChanged) { Net2DisplayGameList(); } + if (screen.OptionsChanged) { + HWND const setup = GameoptWindow(); + if (setup != NULL) { + DisplayGameopts(setup, 0); + SendDlgItemMessage(setup, IDC_SCENARIONAME, WM_SETTEXT, 0, (LPARAM)screen.ScenarioName.c_str()); + InvalidateRect(setup, NULL, FALSE); + } + } screen.UsersChanged = false; screen.GamesChanged = false; + screen.OptionsChanged = false; + screen.MessagesChanged = false; + + // The scenario picker draws where the host screen is, so the screen steps aside + // for it, which is what the dialog's own ShowWindow did. + if (screen.Pending != UILobbyPresenterClass::SUB_NONE) { + HWND const host = WS_Find_Dialog(IDD_MPLAYER_HOST); + if (host != NULL) { + ShowWindow(host, SW_HIDE); + } + screen.Run_Pending(); + if (host != NULL) { + ShowWindow(host, SW_SHOW); + DisplayGameopts(host, 0); + SendDlgItemMessage(host, IDC_SCENARIONAME, WM_SETTEXT, 0, (LPARAM)screen.ScenarioName.c_str()); + InvalidateRect(host, NULL, FALSE); + } + screen.OptionsChanged = false; + } if (_netresponse != 0) { break; @@ -787,7 +820,7 @@ bool Net2Remote_Connect(void) Clear_Vector(&Session.Chat); Session.NetOpen = false; Ipx.Service(); - _LobbyScreen = NULL; + UI_Set_Lobby_Screen(NULL); return(false); } @@ -962,6 +995,7 @@ bool Net2Remote_Connect(void) if (Session.Players.Count() == 1) { PMessagePrintf(-1, Fetch_String(TXT_ONLY_ONE)); _netresponse = 0; + screen.CanStart = true; EnableWindow(GetDlgItem(WS_Top_Window(), IDC_GO), TRUE); } @@ -970,6 +1004,7 @@ bool Net2Remote_Connect(void) if (Session.Players[i]->Player.Status == 0) { PMessagePrintf(-1, Fetch_String(TXT_ACCEPTFIRST)); _netresponse = 0; + screen.CanStart = true; EnableWindow(GetDlgItem(WS_Top_Window(), IDC_GO), TRUE); break; } @@ -1009,6 +1044,7 @@ bool Net2Remote_Connect(void) int waypoints = RandomMapWaypointCount(Session.Options.ScenarioIndex); if (waypoints < SendDlgItemMessage(game_list_dialog, IDC_AIPLAYERS, TBM_GETPOS, 0, 0) + Session.Players.Count()) { PMessagePrintf(-1, Fetch_String(TXT_SCENARIO_TOO_SMALL)); + screen.CanStart = true; EnableWindow(GetDlgItem(WS_Top_Window(), IDC_GO), TRUE); _netresponse = 0; } else { @@ -1139,7 +1175,7 @@ bool Net2Remote_Connect(void) Session.NetOpen = false; Session.Write_MultiPlayer_Settings(); - _LobbyScreen = NULL; + UI_Set_Lobby_Screen(NULL); return(true); } /* end of Remote_Connect */ @@ -1157,19 +1193,19 @@ INT_PTR CALLBACK MPlayer_Game_List_Dialog_Proc(HWND window, UINT message, WPARAM switch (message) { case WM_INITDIALOG: { - if (_LobbyScreen == NULL) { + if (Lobby_Screen() == NULL) { return(0); } - _LobbyScreen->Open(); + Lobby_Screen()->Open(); SendDlgItemMessage(window, IDC_YOURNAME, EM_SETLIMITTEXT, UILobbyPresenterClass::HANDLE_LIMIT, 0); - SetWindowText(GetDlgItem(window, IDC_YOURNAME), _LobbyScreen->Handle.c_str()); + SetWindowText(GetDlgItem(window, IDC_YOURNAME), Lobby_Screen()->Handle.c_str()); return(0); } case WM_COMMAND: { - if (_LobbyScreen == NULL) { + if (Lobby_Screen() == NULL) { return(0); } @@ -1179,17 +1215,17 @@ INT_PTR CALLBACK MPlayer_Game_List_Dialog_Proc(HWND window, UINT message, WPARAM char name_buf[64]; SendDlgItemMessage(window, IDC_YOURNAME, WM_GETTEXT, 63, (LPARAM)name_buf); - _LobbyScreen->Queue(UIIntent{UI_LOBBY_RENAME, name_buf, 0}); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_RENAME, name_buf, 0}); return(0); } case IDCANCEL: { - _LobbyScreen->Queue(UIIntent{UI_LOBBY_CANCEL, "", 0}); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_CANCEL, "", 0}); return(0); } case IDC_GAMELIST_NEW: { - _LobbyScreen->Queue(UIIntent{UI_LOBBY_NEW, "", 0}); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_NEW, "", 0}); return(0); } @@ -1202,13 +1238,13 @@ INT_PTR CALLBACK MPlayer_Game_List_Dialog_Proc(HWND window, UINT message, WPARAM SendDlgItemMessage(window, IDC_INPUT, WM_GETTEXT, 256, (LPARAM)text); SendDlgItemMessage(window, IDC_INPUT, WM_SETTEXT, 0, (LPARAM) ""); - _LobbyScreen->Queue(UIIntent{UI_LOBBY_SAY, text, 0}); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SAY, text, 0}); return(0); } case IDC_YOURCOLOR: { if (HIWORD(wparam) == LBN_SELCHANGE) { - _LobbyScreen->Queue(UIIntent{UI_LOBBY_COLOR, "", + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_COLOR, "", (int)SendDlgItemMessage(window, IDC_YOURCOLOR, LB_GETCURSEL, 0, 0)}); } return(0); @@ -1216,13 +1252,13 @@ INT_PTR CALLBACK MPlayer_Game_List_Dialog_Proc(HWND window, UINT message, WPARAM case IDC_GAMELIST: { if (HIWORD(wparam) == LBN_SELCHANGE) { - _LobbyScreen->Queue(UIIntent{UI_LOBBY_PICK_GAME, "", + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_PICK_GAME, "", (int)SendDlgItemMessage(window, IDC_GAMELIST, LB_GETCURSEL, 0, 0)}); return(0); } if (HIWORD(wparam) == LBN_DBLCLK) { - _LobbyScreen->Queue(UIIntent{UI_LOBBY_JOIN, "", 0}); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_JOIN, "", 0}); return(0); } @@ -1230,7 +1266,7 @@ INT_PTR CALLBACK MPlayer_Game_List_Dialog_Proc(HWND window, UINT message, WPARAM } case IDC_GAMELIST_JOIN: { - _LobbyScreen->Queue(UIIntent{UI_LOBBY_JOIN, "", 0}); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_JOIN, "", 0}); return(0); } } @@ -1271,179 +1307,6 @@ INT_PTR CALLBACK MPlayer_Game_List_Dialog_Proc(HWND window, UINT message, WPARAM /// Returns with TRUE if the message was consumed by this dialog. INT_PTR CALLBACK MPlayer_Host_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) { - /* - * ------------------------------------------------------------------------ - * When 'Net2GameStarted' is set the game is in progress; this whole - * preliminary dispatch is skipped. Option checkbox and slider changes - * are applied here first, and then the message falls through to the - * full dispatch. - * ------------------------------------------------------------------------ - */ - if (!Net2GameStarted) { - - switch (message) { - - case WM_COMMAND: - - switch (LOWORD(wparam)) { - - /* - * ................................................................ - * Bases. Turning bases off also forces the "short game" option off. - * ................................................................ - */ - case IDC_BASES: - Session.Options.Bases = false; - if (SendDlgItemMessage(window, IDC_BASES, BM_GETCHECK, 0, 0) == BST_CHECKED) { - Session.Options.Bases = true; - } else if (!Session.Options.Bases) { - Session.Options.ShortGame = false; - SendDlgItemMessage(window, IDC_SHORT_GAME, BM_SETCHECK, 0, 0); - } - break; - - /* - * ................................................................ - * Redeploy MCV. - * ................................................................ - */ - case IDC_REDEPLOY_MCV: - Session.Options.MCVRedeploy = false; - if (SendDlgItemMessage(window, IDC_REDEPLOY_MCV, BM_GETCHECK, 0, 0) == BST_CHECKED) { - Session.Options.MCVRedeploy = true; - } - break; - - /* - * ................................................................ - * Crates / goodies. - * ................................................................ - */ - case IDC_CRATES: - Session.Options.Goodies = false; - if (SendDlgItemMessage(window, IDC_CRATES, BM_GETCHECK, 0, 0) == BST_CHECKED) { - Session.Options.Goodies = true; - } - break; - - /* - * ................................................................ - * Short game. Turning the short game on also forces bases on. - * ................................................................ - */ - case IDC_SHORT_GAME: - Session.Options.ShortGame = false; - if (SendDlgItemMessage(window, IDC_SHORT_GAME, BM_GETCHECK, 0, 0) == BST_CHECKED) { - Session.Options.ShortGame = true; - if (!Session.Options.Bases) { - Session.Options.Bases = true; - SendDlgItemMessage(window, IDC_BASES, BM_SETCHECK, 1, 0); - } - } - break; - - /* - * ................................................................ - * Multiplayer engineers. - * ................................................................ - */ - case IDC_MULTI_ENGINEER: - Session.Options.CrapEngineers = false; - if (SendDlgItemMessage(window, IDC_MULTI_ENGINEER, BM_GETCHECK, 0, 0) == BST_CHECKED) { - Session.Options.CrapEngineers = true; - } - break; - - /* - * ................................................................ - * Bridge destruction. - * ................................................................ - */ - case IDC_BRIDGE_DESTROY: - Session.Options.BridgeDestruction = false; - if (SendDlgItemMessage(window, IDC_BRIDGE_DESTROY, BM_GETCHECK, 0, 0) == BST_CHECKED) { - Session.Options.BridgeDestruction = true; - } - break; - - /* - * ................................................................ - * Allies allowed. - * ................................................................ - */ - case IDC_ALLIES: - Session.Options.AlliesAllowed = false; - if (SendDlgItemMessage(window, IDC_ALLIES, BM_GETCHECK, 0, 0) == BST_CHECKED) { - Session.Options.AlliesAllowed = true; - } - break; - - /* - * ................................................................ - * Harvester truce. - * ................................................................ - */ - case IDC_HARVTRUCE: - Session.Options.HarvTruce = false; - if (SendDlgItemMessage(window, IDC_HARVTRUCE, BM_GETCHECK, 0, 0) == BST_CHECKED) { - Session.Options.HarvTruce = true; - } - break; - - /* - * ................................................................ - * Fog of war. - * ................................................................ - */ - case IDC_FOG_OF_WAR: - Session.Options.FogOfWar = false; - if (SendDlgItemMessage(window, IDC_FOG_OF_WAR, BM_GETCHECK, 0, 0) == BST_CHECKED) { - Session.Options.FogOfWar = true; - } - break; - } - break; - - /* - * .................................................................... - * The option sliders are read back per-control here; the values are - * all unconditionally re-read by the main WM_HSCROLL/WM_VSCROLL - * handler below. - * .................................................................... - */ - case WM_HSCROLL: - if (GetDlgItem(window, IDC_AILEVEL_SLIDER) == (HWND)lparam) { - Session.Options.AIDifficulty = (DiffType)SendDlgItemMessage(window, IDC_AILEVEL_SLIDER, TBM_GETPOS, 0, 0); - } - if (GetDlgItem(window, IDC_AIPLAYERS) == (HWND)lparam) { - Session.Options.AIPlayers = SendDlgItemMessage(window, IDC_AIPLAYERS, TBM_GETPOS, 0, 0); - } - if (GetDlgItem(window, IDC_UNITCOUNT) == (HWND)lparam) { - Session.Options.UnitCount = SendDlgItemMessage(window, IDC_UNITCOUNT, TBM_GETPOS, 0, 0); - } - if (GetDlgItem(window, IDC_TECHLEVEL) == (HWND)lparam) { - BuildLevel = SendDlgItemMessage(window, IDC_TECHLEVEL, TBM_GETPOS, 0, 0); - } - if (GetDlgItem(window, IDC_CREDITS) == (HWND)lparam) { - Session.Options.Credits = SendDlgItemMessage(window, IDC_CREDITS, TBM_GETPOS, 0, 0); - } - if (GetDlgItem(window, IDC_GAME_SPEED_SLIDER) == (HWND)lparam) { - Session.Options.GameSpeed = 6 - SendDlgItemMessage(window, IDC_GAME_SPEED_SLIDER, TBM_GETPOS, 0, 0); - } - break; - - /* - * .................................................................... - * Refresh the game option controls. - * .................................................................... - */ - case WM_INITDIALOG: - case OD_SUBCLASSED: - DisplayGameopts(window, 1); - break; - } - } - switch (message) { case WM_DESTROY: @@ -1464,354 +1327,166 @@ INT_PTR CALLBACK MPlayer_Host_Dialog_Proc(HWND window, UINT message, WPARAM wpar return(0); case WM_INITDIALOG: { - VerNum.Init_Clipping(); - - srand(NonCriticalRandomNumber(1, 0x7FFF)); - Seed = rand(); - - Set_Scenario_Info_From_Index(0); - Session.Options.ScenarioIndex = 0; + if (Lobby_Screen() == NULL) { + return(0); + } Center_Window_Within_Window(window); + Lobby_Screen()->Open_Host(); + Fill_Country_Box(GetDlgItem(window, IDC_YOURSIDE)); - Select_Country_In_Box(GetDlgItem(window, IDC_YOURSIDE), Session.House); + SendDlgItemMessage(window, IDC_YOURSIDE, CB_SETCURSEL, Lobby_Screen()->SelectedSide, 0); SendDlgItemMessage(window, IDC_YOURCOLOR, CB_RESETCONTENT, 0, 0); - - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_INSERTSTRING, -1, (LPARAM)Fetch_String(TXT_GOLD)); - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_INSERTSTRING, -1, (LPARAM)Fetch_String(TXT_RED)); - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_INSERTSTRING, -1, (LPARAM)Fetch_String(TXT_BLUE)); - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_INSERTSTRING, -1, (LPARAM)Fetch_String(TXT_GREEN)); - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_INSERTSTRING, -1, (LPARAM)Fetch_String(TXT_ORANGE)); - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_INSERTSTRING, -1, (LPARAM)Fetch_String(TXT_SKY_BLUE)); - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_INSERTSTRING, -1, (LPARAM)Fetch_String(TXT_PURPLE)); - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_INSERTSTRING, -1, (LPARAM)Fetch_String(TXT_PINK)); - - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_SETCURSEL, Session.ColorIdx, 0); - - SendDlgItemMessage(window, IDC_AILEVEL_SLIDER, LB_RESETCONTENT, 0, 0); - - for (int j = 0; j < Session.Scenarios.Count(); ++j) { - SendDlgItemMessage(window, IDC_AILEVEL_SLIDER, LB_INSERTSTRING, -1, (LPARAM)Session.Scenarios[j]); + for (std::string const & name : Lobby_Screen()->Colors) { + SendDlgItemMessage(window, IDC_YOURCOLOR, CB_INSERTSTRING, (WPARAM)-1, (LPARAM)name.c_str()); } + SendDlgItemMessage(window, IDC_YOURCOLOR, CB_SETCURSEL, Lobby_Screen()->Color, 0); - SendDlgItemMessage(window, IDC_SCENARIONAME, WM_SETTEXT, 0, (LPARAM)Session.Options.ScenarioDescription); - - Update_Network_Dialog_Preview(window); + SendDlgItemMessage(window, IDC_SCENARIONAME, WM_SETTEXT, 0, (LPARAM)Lobby_Screen()->ScenarioName.c_str()); - SendMessage(window, WM_COMMAND, MAKEWPARAM(IDC_YOURSIDE, CBN_SELCHANGE), (LPARAM)GetDlgItem(window, IDC_YOURSIDE)); - SendMessage(window, WM_COMMAND, MAKEWPARAM(IDC_YOURCOLOR, CBN_SELCHANGE), (LPARAM)GetDlgItem(window, IDC_YOURCOLOR)); + DisplayGameopts(window, 1); + InvalidateRect(window, NULL, FALSE); return(0); } case WM_HSCROLL: case WM_VSCROLL: { - if (Net2GameStarted) return(0); - - Session.Options.UnitCount = SendDlgItemMessage(window, IDC_UNITCOUNT, TBM_GETPOS, 0, 0); - BuildLevel = SendDlgItemMessage(window, IDC_TECHLEVEL, TBM_GETPOS, 0, 0); - Session.Options.Credits = SendDlgItemMessage(window, IDC_CREDITS, TBM_GETPOS, 0, 0); - Session.Options.AIPlayers = SendDlgItemMessage(window, IDC_AIPLAYERS, TBM_GETPOS, 0, 0); - Session.Options.AIDifficulty = (DiffType)SendDlgItemMessage(window, IDC_AILEVEL_SLIDER, TBM_GETPOS, 0, 0); - Session.Options.GameSpeed = 6 - SendDlgItemMessage(window, IDC_GAME_SPEED_SLIDER, TBM_GETPOS, 0, 0); + if (Net2GameStarted || Lobby_Screen() == NULL) return(0); + + // Every bar is read back on any one of them moving, which is what the dialog's own + // handler did rather than reading only the control that reported. + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SLIDER, UI_LOBBY_UNITCOUNT, + (int)SendDlgItemMessage(window, IDC_UNITCOUNT, TBM_GETPOS, 0, 0)}); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SLIDER, UI_LOBBY_TECHLEVEL, + (int)SendDlgItemMessage(window, IDC_TECHLEVEL, TBM_GETPOS, 0, 0)}); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SLIDER, UI_LOBBY_CREDITS, + (int)SendDlgItemMessage(window, IDC_CREDITS, TBM_GETPOS, 0, 0)}); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SLIDER, UI_LOBBY_AIPLAYERS, + (int)SendDlgItemMessage(window, IDC_AIPLAYERS, TBM_GETPOS, 0, 0)}); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SLIDER, UI_LOBBY_AILEVEL, + (int)SendDlgItemMessage(window, IDC_AILEVEL_SLIDER, TBM_GETPOS, 0, 0)}); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SLIDER, UI_LOBBY_GAMESPEED, + (int)SendDlgItemMessage(window, IDC_GAME_SPEED_SLIDER, TBM_GETPOS, 0, 0)}); return(0); } case WM_COMMAND: { + if (Lobby_Screen() == NULL) { + return(0); + } + switch (LOWORD(wparam)) { case IDC_YOURSIDE: if (HIWORD(wparam) == CBN_SELCHANGE && !Net2GameStarted) { - Session.House = Country_From_Box(GetDlgItem(window, IDC_YOURSIDE)); - Session.Players[0]->Player.House = Session.House; + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_HOST_SIDE, "", + (int)SendDlgItemMessage(window, IDC_YOURSIDE, CB_GETCURSEL, 0, 0)}); + } + return(0); - PumpGameopts(1, 0); - _Net2DisplayUsers(); + case IDC_YOURCOLOR: + if (HIWORD(wparam) == CBN_SELCHANGE && !Net2GameStarted) { + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_HOST_COLOR, "", + (int)SendDlgItemMessage(window, IDC_YOURCOLOR, CB_GETCURSEL, 0, 0)}); } return(0); - case IDC_INPUT: - { + case IDC_INPUT: { + if (HIWORD(wparam) != EN_MAXTEXT) { + return(0); + } + char text[260]; SendDlgItemMessage(window, IDC_INPUT, WM_GETTEXT, 256, (LPARAM)text); - - int len = strlen(text); - - if (HIWORD(wparam) != EN_MAXTEXT) return(0); - SendDlgItemMessage(window, IDC_INPUT, WM_SETTEXT, 0, (LPARAM)""); - if (len <= 2) return(0); - - PMessagePrintf(ColorMe, "[%s] %s", Session.Handle, text); - - GlobalPacketType gpacket; - memset(&gpacket, 0, sizeof(gpacket)); - - gpacket.Command = NET_MESSAGE; - strcpy(gpacket.Name, Session.Handle); - strcpy(gpacket.Message.Buf, text); - gpacket.Message.Color = Session.ColorIdx; - gpacket.Message.NameCRC = Compute_Name_CRC(Session.GameName); - - - if (JoinState == JOIN_CONFIRMED) { - for (int i = 1; i < Session.Players.Count(); ++i) { - Ipx.Send_Global_Message(&gpacket, sizeof(gpacket), 1, &Session.Players[i]->Address); - Call_Back(); - } - } else { - for (int i = 1; i < Session.Chat.Count(); ++i) { - Ipx.Send_Global_Message(&gpacket, sizeof(gpacket), 1, &Session.Chat[i]->Address); - Call_Back(); - } - } - + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SAY, text, 0}); return(0); } - case IDCANCEL: { - if (Net2GameStarted) return(0); - - if (MultiplayerMapPreview != NULL) { - delete MultiplayerMapPreview; - MultiplayerMapPreview = NULL; + case IDCANCEL: + if (!Net2GameStarted) { + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_CANCEL, "", 0}); } - - _netresponse = IDCANCEL; return(0); - } - - /* - * .................................................................... - * The user picked a color. Resolve it against the colors already in - * use, skipping our own slot (index 0). If the chosen color is taken, - * bump forward (mod 8) until a free color is found; if that changed - * the color from what the user wanted, warn and re-resolve starting - * from our previous color. - * .................................................................... - */ - case IDC_YOURCOLOR: - if (HIWORD(wparam) == CBN_SELCHANGE && !Net2GameStarted) { - int old_color = Session.ColorIdx; - - Session.ColorIdx = SendDlgItemMessage(window, IDC_YOURCOLOR, CB_GETCURSEL, 0, 0); - Session.PrefColor = Session.ColorIdx; - - int newcolor; - int found; - int color = Session.ColorIdx; - for (;;) { - int count = 0; - newcolor = color; - found = FALSE; - - while (count < Session.Players.Count()) { - if (count != 0 && Session.Players[count]->Player.Color == color) { - color++; - found = TRUE; - } - count++; - } - - if (!found) break; - - color %= MAX_MPLAYER_COLORS; - } - - int resolved = newcolor; - if (newcolor != Session.ColorIdx) { - PMessagePrintf(ColorSystem, Fetch_String(TXT_COLOR_IN_USE)); - - color = old_color; - for (;;) { - int count = 0; - old_color = color; - found = FALSE; - - while (count < Session.Players.Count()) { - if (count != 0 && Session.Players[count]->Player.Color == color) { - color++; - found = TRUE; - } - count++; - } - - if (!found) break; - - color %= MAX_MPLAYER_COLORS; - } + case IDC_GO: + // Taking the button away is the view's, the way it is on the guest screen; the + // driver puts it back when it refuses to start the game. + EnableWindow(GetDlgItem(window, IDC_GO), FALSE); + Net2GameStarted = true; + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_GO, "", 0}); + return(0); - resolved = old_color; + case IDC_KICK: { + HWND const userwin = GetDlgItem(window, IDC_USERS); + int const count = (int)SendMessage(userwin, LB_GETSELCOUNT, 0, 0); + if (count > 0) { + std::vector rows((std::size_t)count, 0); + SendMessage(userwin, LB_GETSELITEMS, (WPARAM)count, (LPARAM)rows.data()); + for (int const row : rows) { + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_PICK_USER, "", row}); } + } - Session.ColorIdx = resolved; - Session.Players[0]->Player.Color = resolved; - - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_SETCURSEL, Session.ColorIdx, 0); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_KICK, "", 0}); - _Net2DisplayUsers(); - PumpGameopts(1, 0); - } + SendMessage(userwin, LB_SELITEMRANGE, 0, MAKELPARAM(0, -1)); return(0); + } - case IDC_CRATES: + case IDC_MULTIMAP: if (Net2GameStarted) return(0); - Session.Options.Goodies = false; - if (SendDlgItemMessage(window, IDC_CRATES, BM_GETCHECK, 0, 0) != BST_CHECKED) return(0); - Session.Options.Goodies = true; + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_PICK_MAP, "", 0}); return(0); case IDC_BASES: if (Net2GameStarted) return(0); - Session.Options.Bases = false; - if (SendDlgItemMessage(window, IDC_BASES, BM_GETCHECK, 0, 0) != BST_CHECKED) return(0); - Session.Options.Bases = true; + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_TOGGLE, UI_LOBBY_BASES, 0}); return(0); case IDC_SHORT_GAME: if (Net2GameStarted) return(0); - Session.Options.ShortGame = false; - if (SendDlgItemMessage(window, IDC_SHORT_GAME, BM_GETCHECK, 0, 0) != BST_CHECKED) return(0); - Session.Options.ShortGame = true; + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_TOGGLE, UI_LOBBY_SHORTGAME, 0}); return(0); - /* - * .................................................................... - * Toggle the "go" (start game) flag. Disable the button and signal - * the driver loop to begin the game. - * .................................................................... - */ - case IDC_GO: - EnableWindow(GetDlgItem(window, IDC_GO), FALSE); - Net2GameStarted = true; - _netresponse = IDC_GO; + case IDC_CRATES: + if (Net2GameStarted) return(0); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_TOGGLE, UI_LOBBY_CRATES, 0}); return(0); - /* - * .................................................................... - * Kick the selected players from the game. The host list-box (on the - * host dialog) holds the player rows; capture the selected names into - * a dictionary, then for each name (other than our own) find the - * matching player and send a NET_REJECT_JOIN kick packet. - * .................................................................... - */ - case IDC_KICK: - { - HWND userwin = GetDlgItem(WS_Find_Dialog(IDD_MPLAYER_HOST), IDC_USERS); - - Wstring name; - Dictionary lbdict(Wstring_Hash); - - LBSaveSelections(userwin, lbdict); - - while (lbdict.getEntries()) { - bool value; - lbdict.removeAny(name, value); - - if (strcmp(name.get(), Session.Handle)) { - int index = -1; - for (int i = 0; i < Session.Players.Count(); ++i) { - if (!strcmp(name.get(), Session.Players[i]->Name)) { - index = i; - break; - } - } - - if (index != -1) { - memset(&Session.GPacket, 0, sizeof(Session.GPacket)); - Session.GPacket.Command = NET_REJECT_JOIN; - Session.GPacket.Reject.Why = (int)REJECT_BY_OWNER; - Ipx.Send_Global_Message(&Session.GPacket, 455, 1, &Session.Players[index]->Address); - } - } - } - - SendMessage(userwin, LB_SELITEMRANGE, 0, MAKELPARAM(0, -1)); - _Net2DisplayUsers(); + case IDC_FOG_OF_WAR: + if (Net2GameStarted) return(0); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_TOGGLE, UI_LOBBY_FOG, 0}); return(0); - } - /* - * .................................................................... - * Pick a different scenario. If "RandMap.Sed" is chosen, rebuild the - * map preview from "RandMap.img". - * .................................................................... - */ - case IDC_MULTIMAP: { + case IDC_BRIDGE_DESTROY: if (Net2GameStarted) return(0); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_TOGGLE, UI_LOBBY_BRIDGES, 0}); + return(0); - int old = Session.Options.ScenarioIndex; - - ShowWindow(window, SW_HIDE); - IsRandomMap = false; - - if (Scenario_Dialog(MainWindow) == 2) { - Session.Options.ScenarioIndex = old; - Set_Scenario_Info_From_Index(Session.Options.ScenarioIndex); - Update_Network_Dialog_Preview(window); - IsRandomMap = true; - ShowWindow(window, SW_SHOW); - - if (!stricmp((char *)Session.Scenarios[Session.Options.ScenarioIndex] + DESCRIP_MAX, "RandMap.Sed")) { - if (MultiplayerMapPreview != NULL) { - delete MultiplayerMapPreview; - } - MultiplayerMapPreview = new MapPreviewClass; - MultiplayerMapPreview->Read_PCX_Preview("RandMap.img"); - if (MultiplayerMapPreview->Get_Preview_Surface() == NULL) { - Update_Network_Dialog_Preview(window); - } - InvalidateRect(window, NULL, FALSE); - } else { - Update_Network_Dialog_Preview(window); - } - - PumpGameopts(1, 0); - InvalidateRect(window, NULL, FALSE); - } else { - if (!Set_Scenario_Info_From_Index(Session.Options.ScenarioIndex)) { - Session.Options.ScenarioIndex = old; - } - IsRandomMap = true; - ShowWindow(window, SW_SHOW); - - SendDlgItemMessage(window, IDC_SCENARIONAME, WM_SETTEXT, 0, (LPARAM)Session.Options.ScenarioDescription); - - if (!stricmp((char *)Session.Scenarios[Session.Options.ScenarioIndex] + DESCRIP_MAX, "RandMap.Sed")) { - if (MultiplayerMapPreview != NULL) { - delete MultiplayerMapPreview; - } - MultiplayerMapPreview = new MapPreviewClass; - MultiplayerMapPreview->Read_PCX_Preview("RandMap.img"); - if (MultiplayerMapPreview->Get_Preview_Surface() == NULL) { - Update_Network_Dialog_Preview(window); - } - InvalidateRect(window, NULL, FALSE); - } else { - Update_Network_Dialog_Preview(window); - } - } + case IDC_REDEPLOY_MCV: + if (Net2GameStarted) return(0); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_TOGGLE, UI_LOBBY_MCV, 0}); + return(0); + case IDC_MULTI_ENGINEER: + if (Net2GameStarted) return(0); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_TOGGLE, UI_LOBBY_ENGINEER, 0}); return(0); - } - case IDC_BRIDGE_DESTROY: + case IDC_ALLIES: if (Net2GameStarted) return(0); - Session.Options.BridgeDestruction = false; - if (SendDlgItemMessage(window, IDC_BRIDGE_DESTROY, BM_GETCHECK, 0, 0) != BST_CHECKED) return(0); - Session.Options.BridgeDestruction = true; + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_TOGGLE, UI_LOBBY_ALLIES, 0}); return(0); - case IDC_MULTI_ENGINEER: + case IDC_HARVTRUCE: if (Net2GameStarted) return(0); - Session.Options.CrapEngineers = false; - if (SendDlgItemMessage(window, IDC_MULTI_ENGINEER, BM_GETCHECK, 0, 0) != BST_CHECKED) return(0); - Session.Options.CrapEngineers = true; + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_TOGGLE, UI_LOBBY_HARVTRUCE, 0}); return(0); default: @@ -1837,6 +1512,10 @@ INT_PTR CALLBACK MPlayer_Host_Dialog_Proc(HWND window, UINT message, WPARAM wpar SendDlgItemMessage(window, IDC_KICK, OD_SETIMAGE, 0, (LPARAM)SurfaceCache.GetSurface("woukick.pcx")); SendDlgItemMessage(window, IDC_KICK, OD_SETALTIMAGE, 0, (LPARAM)SurfaceCache.GetSurface("wodkick.pcx")); + if (!Net2GameStarted) { + DisplayGameopts(window, 1); + } + _Net2DisplayUsers(); Net2DisplayGameList(); return(0); @@ -3290,9 +2969,9 @@ INT_PTR CALLBACK MPlayer_Guest_Dialog_Proc(HWND window, UINT message, WPARAM wpa EnableWindow(GetDlgItem(window, IDC_ACCEPT), FALSE); - if (_LobbyScreen != NULL) { - _LobbyScreen->Open_Guest(); - EnableWindow(GetDlgItem(window, IDC_ACCEPT), _LobbyScreen->CanAccept ? TRUE : FALSE); + if (Lobby_Screen() != NULL) { + Lobby_Screen()->Open_Guest(); + EnableWindow(GetDlgItem(window, IDC_ACCEPT), Lobby_Screen()->CanAccept ? TRUE : FALSE); } _Net2DisplayUsers(); @@ -3300,14 +2979,14 @@ INT_PTR CALLBACK MPlayer_Guest_Dialog_Proc(HWND window, UINT message, WPARAM wpa } case WM_COMMAND: { - if (_LobbyScreen == NULL) { + if (Lobby_Screen() == NULL) { return(0); } switch (LOWORD(wparam)) { case IDC_ACCEPT: { - _LobbyScreen->Queue(UIIntent{UI_LOBBY_ACCEPT, "", 0}); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_ACCEPT, "", 0}); // Taking the button away is the view's, the way getting out of a browser's way // stayed with the view at step 7. What the host does to put it back is not @@ -3322,9 +3001,9 @@ INT_PTR CALLBACK MPlayer_Guest_Dialog_Proc(HWND window, UINT message, WPARAM wpa if (HIWORD(wparam) == CBN_SELCHANGE) { // The side is recorded ahead of the color, because the dialog read both of // its boxes and sent one packet carrying the pair. - _LobbyScreen->Queue(UIIntent{UI_LOBBY_SIDE, "", + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SIDE, "", Country_From_Box(GetDlgItem(window, IDC_YOURSIDE))}); - _LobbyScreen->Queue(UIIntent{UI_LOBBY_IDENTITY, "", + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_IDENTITY, "", (int)SendDlgItemMessage(window, IDC_YOURCOLOR, CB_GETCURSEL, 0, 0)}); } return(0); @@ -3332,7 +3011,7 @@ INT_PTR CALLBACK MPlayer_Guest_Dialog_Proc(HWND window, UINT message, WPARAM wpa case IDCANCEL: { if (!Net2GameStarted) { - _LobbyScreen->Queue(UIIntent{UI_LOBBY_CANCEL, "", 0}); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_CANCEL, "", 0}); } return(0); } @@ -3346,7 +3025,7 @@ INT_PTR CALLBACK MPlayer_Guest_Dialog_Proc(HWND window, UINT message, WPARAM wpa SendDlgItemMessage(window, IDC_INPUT, WM_GETTEXT, 256, (LPARAM)text); SendDlgItemMessage(window, IDC_INPUT, WM_SETTEXT, 0, (LPARAM)""); - _LobbyScreen->Queue(UIIntent{UI_LOBBY_SAY, text, 0}); + Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SAY, text, 0}); return(0); } } diff --git a/code/netshare.cpp b/code/netshare.cpp index cbf5121f8..21a72c9a1 100644 --- a/code/netshare.cpp +++ b/code/netshare.cpp @@ -10,6 +10,7 @@ #include "always.h" #include "netshare.h" +#include "ui/uilobby.h" #include "ui/uiscenariopick.h" #include "ui/uishell.h" @@ -144,6 +145,12 @@ void __cdecl PMessagePrintf(int color, const char * fmt, ...) vsprintf(buffer, fmt, va); va_end(va); + // The line is recorded where it is composed rather than where it is drawn, so a + // presentation that draws a different number of times cannot lose one or repeat one. + if (UILobbyPresenterClass * const lobby = UI_Lobby_Screen()) { + lobby->Record_Message(color, buffer); + } + if (WS_Top_Window() != 0) { HWND top = WS_Top_Window(); HWND msg = GetDlgItem(top, IDC_PMESSAGES); diff --git a/code/ui/uilobby.cpp b/code/ui/uilobby.cpp index 53b2b2fd4..153896d2a 100644 --- a/code/ui/uilobby.cpp +++ b/code/ui/uilobby.cpp @@ -8,7 +8,8 @@ ******************************************************************************/ // The network lobby. Behavior traced out of MPlayer_Game_List_Dialog_Proc, -// Net2DisplayGameList, _Net2DisplayUsers and Net2Remote_Connect in netdlg2.cpp. +// MPlayer_Host_Dialog_Proc, MPlayer_Guest_Dialog_Proc, Net2DisplayGameList, +// _Net2DisplayUsers and Net2Remote_Connect in netdlg2.cpp. // // What the extraction fixes in place: the rosters are read into the model where the // session changes rather than where a list is drawn, so a presentation that draws a @@ -18,6 +19,12 @@ // caption; and the selection is clamped against a list a host can shorten at any moment, // which is what the display function did before it drew. // +// What the host's half fixes in place: a chat line is bounded into the packet field it is +// copied to rather than into a buffer the sender chose; the option track bars carry the +// ranges DisplayGameopts sets rather than a control's default; and the players the host has +// picked out to kick are resolved to names when the kick is executed rather than read back +// off a list box that the roster may have moved underneath. +// // Packets are untouched. Nothing here changes what goes on the wire, only who owns the // state the screens show. // @@ -27,24 +34,52 @@ #include "uilobby.h" +#include "_rand.h" +#include "_rules.h" #include "_timer.h" #include "conquer.h" #include "data.h" #include "houstype.h" #include "ipxmgr.h" #include "language/language.h" +#include "mapgen.h" #include "mplayer.h" #include "netdlg.h" #include "netdlg2.h" #include "netdlg.h" #include "netshare.h" +#include "preview.h" +#include "rules.h" #include "session.h" #include "utf8.h" +#include #include #include +// The least money a network game may be started with, which is where the credits track bar +// begins. DisplayGameopts names the same figure. +enum { MP_MIN_MONEY = 2500 }; + + +// The lobby the driver is running. A message produced away from a screen reaches the model +// through this, the way PMessagePrintf found the topmost dialog with somewhere to show one. +static UILobbyPresenterClass * _LobbyScreen = NULL; + + +UILobbyPresenterClass * UI_Lobby_Screen(void) +{ + return(_LobbyScreen); +} + + +void UI_Set_Lobby_Screen(UILobbyPresenterClass * screen) +{ + _LobbyScreen = screen; +} + + void UILobbyPresenterClass::Refresh(void) { Handle = Session.Handle; @@ -117,6 +152,387 @@ void UILobbyPresenterClass::Open_Guest(void) } +/// +/// Builds the country and color lists both setup screens show, and picks out the ones this +/// player is wearing. A side row carries the country it stands for rather than its position, +/// because the list holds only the countries that may be played. +/// +void UILobbyPresenterClass::Build_Identity_Lists(void) +{ + Sides.clear(); + SelectedSide = 0; + for (int index = 0; index < HouseTypes.Count(); index++) { + HouseTypeClass const * const house = HouseTypes[index]; + if (!house->IsMultiplay) continue; + + if (index == Session.House) { + SelectedSide = (int)Sides.size(); + } + Sides.push_back(SideType{(char const *)house->GivenName, index}); + } + + Colors.clear(); + Colors.push_back(Fetch_String(TXT_GOLD)); + Colors.push_back(Fetch_String(TXT_RED)); + Colors.push_back(Fetch_String(TXT_BLUE)); + Colors.push_back(Fetch_String(TXT_GREEN)); + Colors.push_back(Fetch_String(TXT_ORANGE)); + Colors.push_back(Fetch_String(TXT_SKY_BLUE)); + Colors.push_back(Fetch_String(TXT_PURPLE)); + Colors.push_back(Fetch_String(TXT_PINK)); + + House = Session.House; + Color = Session.ColorIdx; +} + + +/// +/// Reads the session's game options into the view-model, with the ranges the rules give +/// them. The ranges are the ones DisplayGameopts puts on the track bars on its initializing +/// pass, and they belong with the values because a range control clamps a value into the +/// range it is holding. +/// +void UILobbyPresenterClass::Read_Options(void) +{ + UnitCount = SliderType{Session.Options.UnitCount, 1, 10, 1}; + TechLevel = SliderType{BuildLevel, 1, MPLAYER_BUILD_LEVEL_MAX, 1}; + Credits = SliderType{Session.Options.Credits, MP_MIN_MONEY, Rule->MPMaxMoney, 100}; + AIPlayers = SliderType{Session.Options.AIPlayers, 0, 6, 1}; + AILevel = SliderType{(int)Session.Options.AIDifficulty, 0, 2, 1}; + + // The track bar runs the other way round from the setting: its left end is the slowest + // game, and the dialog turned one into the other at both ends. + GameSpeed = SliderType{6 - Session.Options.GameSpeed, 0, 6, 1}; + + Bases = Session.Options.Bases; + Crates = Session.Options.Goodies; + FogOfWar = Session.Options.FogOfWar; + Bridges = Session.Options.BridgeDestruction; + MCVRedeploy = Session.Options.MCVRedeploy; + ShortGame = Session.Options.ShortGame; + MultiEngineer = Session.Options.CrapEngineers; + Allies = Session.Options.AlliesAllowed; + HarvTruce = Session.Options.HarvTruce; + + ScenarioName = Session.Options.ScenarioDescription; + + OptionsChanged = true; +} + + +/// +/// The game the host has just created. The setup opens on the first scenario whatever the +/// session was carrying, seeds the match, and tells the guests what this player is wearing, +/// which is what the dialog did by sending itself its own two selection changes. +/// +void UILobbyPresenterClass::Open_Host(void) +{ + VerNum.Init_Clipping(); + + srand(NonCriticalRandomNumber(1, 0x7FFF)); + Seed = rand(); + + Set_Scenario_Info_From_Index(0); + Session.Options.ScenarioIndex = 0; + + Build_Identity_Lists(); + Read_Options(); + + Rebuild_Network_Map_Preview(); + PreviewGeneration++; + + CanStart = true; + + Host_Side(SelectedSide); + Host_Color(Color); +} + + +/// +/// Records the country the host is showing and tells the guests about it. +/// +void UILobbyPresenterClass::Host_Side(int row) +{ + if (row < 0 || row >= (int)Sides.size()) { + return; + } + + SelectedSide = row; + House = Sides[row].Country; + Session.House = (HousesType)House; + + if (Session.Players.Count() > 0) { + Session.Players[0]->Player.House = Session.House; + } + + PumpGameopts(1, 0); + Build_User_Rows(); +} + + +/// +/// Records the color the host asked for, resolved against the colors already taken. +/// A color somebody else is wearing is bumped forward until a free one is found; if that +/// moved the color away from what the host asked for, the host is told so and the search +/// runs again from the color it was wearing before. +/// +void UILobbyPresenterClass::Host_Color(int color) +{ + if (color < 0 || color >= (int)Colors.size()) { + return; + } + + int old_color = Session.ColorIdx; + + Session.ColorIdx = color; + Session.PrefColor = Session.ColorIdx; + + int newcolor; + int found; + int probe = Session.ColorIdx; + for (;;) { + int count = 0; + newcolor = probe; + found = false; + + while (count < Session.Players.Count()) { + if (count != 0 && Session.Players[count]->Player.Color == probe) { + probe++; + found = true; + } + count++; + } + + if (!found) break; + + probe %= MAX_MPLAYER_COLORS; + } + + int resolved = newcolor; + if (newcolor != Session.ColorIdx) { + Record_Message(ColorSystem, Fetch_String(TXT_COLOR_IN_USE)); + + probe = old_color; + for (;;) { + int count = 0; + old_color = probe; + found = false; + + while (count < Session.Players.Count()) { + if (count != 0 && Session.Players[count]->Player.Color == probe) { + probe++; + found = true; + } + count++; + } + + if (!found) break; + + probe %= MAX_MPLAYER_COLORS; + } + + resolved = old_color; + } + + Session.ColorIdx = resolved; + Color = resolved; + + if (Session.Players.Count() > 0) { + Session.Players[0]->Player.Color = resolved; + } + + Build_User_Rows(); + PumpGameopts(1, 0); +} + + +/// +/// Turns a game option on or off. Short Game needs bases, so turning bases off turns the +/// short game off with it and turning the short game on turns bases on. +/// +void UILobbyPresenterClass::Toggle(std::string const & which) +{ + if (which == UI_LOBBY_BASES) { + Bases = !Bases; + if (!Bases) ShortGame = false; + } else if (which == UI_LOBBY_SHORTGAME) { + ShortGame = !ShortGame; + if (ShortGame) Bases = true; + } else if (which == UI_LOBBY_CRATES) { + Crates = !Crates; + } else if (which == UI_LOBBY_FOG) { + FogOfWar = !FogOfWar; + } else if (which == UI_LOBBY_BRIDGES) { + Bridges = !Bridges; + } else if (which == UI_LOBBY_MCV) { + MCVRedeploy = !MCVRedeploy; + } else if (which == UI_LOBBY_ENGINEER) { + MultiEngineer = !MultiEngineer; + } else if (which == UI_LOBBY_ALLIES) { + Allies = !Allies; + } else if (which == UI_LOBBY_HARVTRUCE) { + HarvTruce = !HarvTruce; + } else { + return; + } + + Session.Options.Bases = Bases; + Session.Options.ShortGame = ShortGame; + Session.Options.Goodies = Crates; + Session.Options.FogOfWar = FogOfWar; + Session.Options.BridgeDestruction = Bridges; + Session.Options.MCVRedeploy = MCVRedeploy; + Session.Options.CrapEngineers = MultiEngineer; + Session.Options.AlliesAllowed = Allies; + Session.Options.HarvTruce = HarvTruce; + + OptionsChanged = true; +} + + +/// +/// Moves a game option's track bar. The dialog re-read every bar on each notification, so +/// the whole set is written through rather than the one that moved. +/// +void UILobbyPresenterClass::Slide(std::string const & which, int value) +{ + SliderType * slider = NULL; + if (which == UI_LOBBY_UNITCOUNT) slider = &UnitCount; + else if (which == UI_LOBBY_CREDITS) slider = &Credits; + else if (which == UI_LOBBY_TECHLEVEL) slider = &TechLevel; + else if (which == UI_LOBBY_AILEVEL) slider = &AILevel; + else if (which == UI_LOBBY_AIPLAYERS) slider = &AIPlayers; + else if (which == UI_LOBBY_GAMESPEED) slider = &GameSpeed; + + if (slider == NULL) { + return; + } + + if (value < slider->Minimum) value = slider->Minimum; + if (value > slider->Maximum) value = slider->Maximum; + slider->Value = value; + + Session.Options.UnitCount = UnitCount.Value; + BuildLevel = TechLevel.Value; + Session.Options.Credits = Credits.Value; + Session.Options.AIPlayers = AIPlayers.Value; + Session.Options.AIDifficulty = (DiffType)AILevel.Value; + Session.Options.GameSpeed = 6 - GameSpeed.Value; +} + + +/// +/// Throws the picked players out of the game. +/// The rows are resolved to names here rather than when they were picked, because the roster +/// can move underneath a selection at any moment, and the host cannot kick itself. +/// +void UILobbyPresenterClass::Kick(void) +{ + for (int const row : PickedUsers) { + if (row < 0 || row >= (int)Users.size()) { + continue; + } + + std::string const & name = Users[row].Name; + if (name == Session.Handle) { + continue; + } + + int index = -1; + for (int i = 0; i < Session.Players.Count(); i++) { + if (name == Session.Players[i]->Name) { + index = i; + break; + } + } + + if (index == -1) { + continue; + } + + memset(&Session.GPacket, 0, sizeof(Session.GPacket)); + Session.GPacket.Command = NET_REJECT_JOIN; + Session.GPacket.Reject.Why = (int)REJECT_BY_OWNER; + Ipx.Send_Global_Message(&Session.GPacket, 455, 1, &Session.Players[index]->Address); + } + + PickedUsers.clear(); + Build_User_Rows(); +} + + +/// +/// Runs the scenario picker with the host screen out of the way, and puts the map it chose +/// on the model. Backing out leaves the scenario the screen was showing, which is what +/// putting the old index back did. +/// +void UILobbyPresenterClass::Run_Pending(void) +{ + if (Pending != SUB_PICK_MAP) { + return; + } + + Pending = SUB_NONE; + + int const previous = Session.Options.ScenarioIndex; + + IsRandomMap = false; + bool const picked = Pick_Scenario_Screen(); + IsRandomMap = true; + + if (!picked) { + Session.Options.ScenarioIndex = previous; + Set_Scenario_Info_From_Index(previous); + + // Backing out puts the settings back on the wire, which is what the cancel arm did + // and the accept arm left to the driver's own pump. + PumpGameopts(1, 0); + } else if (Set_Scenario_Info_From_Index(Session.Options.ScenarioIndex) != true) { + Session.Options.ScenarioIndex = previous; + Set_Scenario_Info_From_Index(previous); + } + + ScenarioName = Session.Options.ScenarioDescription; + + // A generated map has a picture of its own beside it rather than one read out of the map. + int const index = Session.Options.ScenarioIndex; + if (index >= 0 && index < Session.Scenarios.Count() + && stricmp(Session.Scenarios[index]->Get_Filename(), RANDOM_MAP_FILE_NAME) == 0) { + delete MultiplayerMapPreview; + MultiplayerMapPreview = new MapPreviewClass; + MultiplayerMapPreview->Read_PCX_Preview("RandMap.img"); + if (MultiplayerMapPreview->Get_Preview_Surface() == NULL) { + Rebuild_Network_Map_Preview(); + } + } else { + Rebuild_Network_Map_Preview(); + } + + PreviewGeneration++; + OptionsChanged = true; +} + + +/// +/// Records a line of chat or system text for whatever is showing the lobby. +/// The line is kept whole; breaking it to the width it is shown at belongs to the +/// presentation, which is what _DrawMessage did with the list box it was handed. +/// +void UILobbyPresenterClass::Record_Message(int color, char const * text) +{ + if (text == NULL) { + return; + } + + Messages.push_back(ChatLineType{text, color}); + if ((int)Messages.size() > MESSAGE_LIMIT) { + Messages.erase(Messages.begin()); + } + + MessagesChanged = true; +} + + /// /// Tells the game that this player has accepted the host's settings. /// @@ -372,11 +788,62 @@ void UILobbyPresenterClass::Execute(UIIntent const & intent) return; } + if (intent.Action == UI_LOBBY_TOGGLE) { + Toggle(intent.Identity); + return; + } + + if (intent.Action == UI_LOBBY_SLIDER) { + Slide(intent.Identity, intent.Value); + return; + } + + if (intent.Action == UI_LOBBY_PICK_USER) { + // A picked row is added rather than replacing the selection, because the player list + // is a multiple-selection one and a second click takes a row back off it. + auto const found = std::find(PickedUsers.begin(), PickedUsers.end(), intent.Value); + if (found != PickedUsers.end()) { + PickedUsers.erase(found); + } else if (intent.Value >= 0 && intent.Value < (int)Users.size()) { + PickedUsers.push_back(intent.Value); + } + UsersChanged = true; + return; + } + + if (intent.Action == UI_LOBBY_KICK) { + Kick(); + return; + } + + if (intent.Action == UI_LOBBY_PICK_MAP) { + Pending = SUB_PICK_MAP; + return; + } + + if (intent.Action == UI_LOBBY_GO) { + // The button goes away until the driver has decided the game may begin, which is what + // disabling the window stood for; the driver puts it back when it refuses. + CanStart = false; + Response = RESPONSE_GO; + return; + } + if (intent.Action == UI_LOBBY_IDENTITY) { Change_Identity(intent.Value); return; } + if (intent.Action == UI_LOBBY_HOST_SIDE) { + Host_Side(intent.Value); + return; + } + + if (intent.Action == UI_LOBBY_HOST_COLOR) { + Host_Color(intent.Value); + return; + } + if (intent.Action == UI_LOBBY_ACCEPT) { Accept(); return; diff --git a/code/ui/uilobby.h b/code/ui/uilobby.h index 05bc022d4..a13abbcce 100644 --- a/code/ui/uilobby.h +++ b/code/ui/uilobby.h @@ -11,8 +11,7 @@ // the guest setup are one screen family sharing one model, because they share the session's // game, player and chat rosters and hand the driver one answer between them. // -// This holds the game list half. The host and guest commands still write the driver's -// response themselves. +// All three screens are here: the game list, the host setup and the guest setup. // // docs/UI_DESIGN.md, "Screens", owns the contract. @@ -34,6 +33,33 @@ inline constexpr char const * UI_LOBBY_COLOR = "color"; inline constexpr char const * UI_LOBBY_SIDE = "side"; inline constexpr char const * UI_LOBBY_IDENTITY = "identity"; inline constexpr char const * UI_LOBBY_ACCEPT = "accept"; +inline constexpr char const * UI_LOBBY_GO = "go"; +inline constexpr char const * UI_LOBBY_HOST_SIDE = "hostside"; +inline constexpr char const * UI_LOBBY_HOST_COLOR = "hostcolor"; +inline constexpr char const * UI_LOBBY_KICK = "kick"; +inline constexpr char const * UI_LOBBY_PICK_USER = "pickuser"; +inline constexpr char const * UI_LOBBY_PICK_MAP = "pickmap"; +inline constexpr char const * UI_LOBBY_TOGGLE = "toggle"; +inline constexpr char const * UI_LOBBY_SLIDER = "slider"; + +// The game options the host owns. A track bar and a check box name themselves, because an +// intent carries an identity rather than a control. +inline constexpr char const * UI_LOBBY_UNITCOUNT = "unitcount"; +inline constexpr char const * UI_LOBBY_CREDITS = "credits"; +inline constexpr char const * UI_LOBBY_TECHLEVEL = "techlevel"; +inline constexpr char const * UI_LOBBY_AILEVEL = "ailevel"; +inline constexpr char const * UI_LOBBY_AIPLAYERS = "aiplayers"; +inline constexpr char const * UI_LOBBY_GAMESPEED = "gamespeed"; + +inline constexpr char const * UI_LOBBY_BASES = "bases"; +inline constexpr char const * UI_LOBBY_CRATES = "crates"; +inline constexpr char const * UI_LOBBY_FOG = "fog"; +inline constexpr char const * UI_LOBBY_BRIDGES = "bridges"; +inline constexpr char const * UI_LOBBY_MCV = "mcv"; +inline constexpr char const * UI_LOBBY_SHORTGAME = "shortgame"; +inline constexpr char const * UI_LOBBY_ENGINEER = "engineer"; +inline constexpr char const * UI_LOBBY_ALLIES = "allies"; +inline constexpr char const * UI_LOBBY_HARVTRUCE = "harvtruce"; class UILobbyPresenterClass : public UIPresenterClass @@ -46,6 +72,41 @@ class UILobbyPresenterClass : public UIPresenterClass RESPONSE_CANCEL, RESPONSE_JOIN, RESPONSE_NEW, + RESPONSE_GO, + }; + + // A screen the lobby opens and comes back from. The scenario picker draws where the + // host screen is, so its owner takes the host screen off the screen and puts it + // back rather than running it underneath. + enum PendingType { + SUB_NONE, + SUB_PICK_MAP, + }; + + // A country that may be played, carrying the country itself rather than its + // position, because the list holds only the multiplayable countries. + struct SideType + { + std::string Name; + int Country = 0; + }; + + // A track bar and the range the rules give it. A range control clamps a value into + // the range it is holding, so a view sets the range before the value. + struct SliderType + { + int Value = 0; + int Minimum = 0; + int Maximum = 0; + int Step = 1; + }; + + // A line of chat or system text, as PMessagePrintf composed it. Wrapping it to the + // width it is shown at belongs to the presentation. + struct ChatLineType + { + std::string Text; + int Color = -1; }; // A game somebody is advertising. The lobby itself heads the list. @@ -80,6 +141,18 @@ class UILobbyPresenterClass : public UIPresenterClass // arrives having accepted nothing. void Open_Guest(void); + // The game the host has just created: the scenario the setup opens on, the option + // values the rules allow, and the country and color lists both setup screens show. + void Open_Host(void); + + // Runs the scenario picker with the host screen out of the way, and puts the map it + // chose on the model. Called by the owner between passes, never from an event. + void Run_Pending(void); + + // Records a line of chat or system text for whatever is showing the lobby. Called + // from the network code wherever PMessagePrintf composes one. + void Record_Message(int color, char const * text); + // Reads the session's rosters into the view-model. Marking the host as accepted // happens here rather than while drawing, because it is a fact about the player // rather than about the row. @@ -110,11 +183,60 @@ class UILobbyPresenterClass : public UIPresenterClass std::vector Users; + // Which player rows the host has picked out to kick. The list is a LBS_MULTIPLESEL + // one, so this is a set of rows rather than a single selection. + std::vector PickedUsers; + + std::vector Messages; + + // The most lines the model keeps, which is what the message list box was capped at. + enum { MESSAGE_LIMIT = 128 }; + + /* + ** The host's half of the model. The guest screen shows the same fields and cannot + ** change them, which is what WS_DISABLED on every one of its controls stood for. + */ + std::vector Sides; + int SelectedSide = 0; + + std::vector Colors; + + std::string ScenarioName; + + SliderType UnitCount; + SliderType Credits; + SliderType TechLevel; + SliderType AILevel; + SliderType AIPlayers; + SliderType GameSpeed; + + bool Bases = false; + bool Crates = false; + bool FogOfWar = false; + bool Bridges = false; + bool MCVRedeploy = false; + bool ShortGame = false; + bool MultiEngineer = false; + bool Allies = false; + bool HarvTruce = false; + + // Is the start button available? Pressing it takes it away until the driver has + // decided the game may begin, which is what disabling the window stood for. + bool CanStart = true; + + PendingType Pending = SUB_NONE; + + // Moves when the map picture changes, so a view uploads once per map rather than + // once per present. + unsigned int PreviewGeneration = 0; + ResponseType Response = RESPONSE_NONE; // Did the last executed intent move a roster? A view redraws only what moved. bool GamesChanged = false; bool UsersChanged = false; + bool MessagesChanged = false; + bool OptionsChanged = false; private: void Rename(std::string const & name); @@ -122,4 +244,18 @@ class UILobbyPresenterClass : public UIPresenterClass void Say(std::string const & text); void Accept(void); void Change_Identity(int color); + + void Build_Identity_Lists(void); + void Read_Options(void); + void Host_Side(int row); + void Host_Color(int color); + void Toggle(std::string const & which); + void Slide(std::string const & which, int value); + void Kick(void); }; + + +// The lobby screen the driver is running, or NULL when no lobby is up. The network code +// reaches the model through this wherever a change is produced away from a screen. +UILobbyPresenterClass * UI_Lobby_Screen(void); +void UI_Set_Lobby_Screen(UILobbyPresenterClass * screen); From 93574d47320de1b30cb41af27ab41578f6f56750 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 09:02:21 +0100 Subject: [PATCH 130/179] feat(ui): show the network lobby's three screens through RmlUi ui/gamelist.rml, ui/mphost.rml and ui/mpguest.rml, with ui/lobbybase.rcss beside ui/optionsbase.rcss and each carrying its own geometry, converted from the IDD_MPLAYER_GAME_LIST, IDD_MPLAYER_HOST and IDD_MPLAYER_GUEST templates. All three are 426 x 240 dialog units, the same as IDD_SKIRMISH, so the conversion is the same 1.5 pixels across and 1.625 down. Each document names its data model after its own file, because a document that names another's gets no bindings and no events at all. One view class serves all three and binds only what its own document names, since the three show overlapping halves of one model. Net2Remote_Connect keeps its shape. What was a WS_ dialog creation is now Lobby_Open_Screen, which records which of the three screens the presenter is on and either loads the document or creates the dialog; what was WS_Top_Window_ID is Net2LobbyScreenID, because a document has no window; and the inner pump loop becomes UI_Lobby_Run, which is UI_Run_Modal with the family's own reset around it. The documents outlive one pass, because the lobby moves between them and comes back, which is what its own dialogs did. One pass of the lobby's maintenance is now one function, Net2ServiceLobby, reached through the presenter's Service by both drivers. The legacy loop's join-query block therefore runs before its pump rather than after it; nothing else about a pass moved. Three splits the documents needed, each the shape Fill_List had: - PMessagePrintf records its line on the model as it composes it. Wrapping it to the width it is shown at stays with the presentation, which is what _DrawMessage did to the list box it was handed. - The host's settings are read back onto the model where they are decoded, through Options_Received, rather than only written onto controls. - Rebuild_Network_Map_Preview asks the lobby which screen is up rather than asking for the top window, so a guest with no local copy of the map still requests the picture from the host. The guest's track bars are dirtied on sync and the host's are not: every one of them is WS_DISABLED on the guest template, so their values come from the host's packets, while the host's own carry what their change events reported. A track bar's range is set before its value, and again every time a screen is shown, because a screen that is come back to opens on what the model holds now. Preserved, and reported rather than repaired: the start-position check adds in an AI player count read from the game list dialog, whose template carries no IDC_AIPLAYERS, so that term has always been zero. Packets are unchanged: no field, no size, no order, no send or receive path. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/netdlg2.cpp | 217 ++++++++++---- code/netdlg2.h | 9 + code/netshare.cpp | 14 +- code/ui/uilobby.cpp | 672 +++++++++++++++++++++++++++++++++++++++++++- code/ui/uilobby.h | 26 ++ ui/gamelist.rcss | 51 ++++ ui/gamelist.rml | 34 +++ ui/lobbybase.rcss | 214 ++++++++++++++ ui/mpguest.rcss | 105 +++++++ ui/mpguest.rml | 76 +++++ ui/mphost.rcss | 96 +++++++ ui/mphost.rml | 76 +++++ 12 files changed, 1534 insertions(+), 56 deletions(-) create mode 100644 ui/gamelist.rcss create mode 100644 ui/gamelist.rml create mode 100644 ui/lobbybase.rcss create mode 100644 ui/mpguest.rcss create mode 100644 ui/mpguest.rml create mode 100644 ui/mphost.rcss create mode 100644 ui/mphost.rml diff --git a/code/netdlg2.cpp b/code/netdlg2.cpp index a1391c320..d45911a72 100644 --- a/code/netdlg2.cpp +++ b/code/netdlg2.cpp @@ -44,6 +44,7 @@ #include "timer.h" #include "utf8.h" #include "ui/uilobby.h" +#include "ui/uishell.h" #include "windlg.h" #include "winstub.h" #include "wsproto.h" @@ -88,6 +89,111 @@ static UILobbyPresenterClass * Lobby_Screen(void) } +// Is the lobby being shown through RmlUi? Latched when the lobby opens, the way every +// migrated screen latches its selection at screen entry. +static bool _LobbyRml = false; + + +int Net2LobbyScreenID(void) +{ + UILobbyPresenterClass const * const screen = Lobby_Screen(); + + if (_LobbyRml && screen != NULL) { + switch (screen->Showing) { + case UILobbyPresenterClass::SCREEN_GAME_LIST: return(IDD_MPLAYER_GAME_LIST); + case UILobbyPresenterClass::SCREEN_HOST: return(IDD_MPLAYER_HOST); + case UILobbyPresenterClass::SCREEN_GUEST: return(IDD_MPLAYER_GUEST); + default: return(0); + } + } + + return(WS_Top_Window_ID()); +} + + +/// +/// Shows one of the lobby's three screens, as a document or as its legacy dialog. +/// The screen the lobby moved away from stays alive underneath, which is what the lobby's +/// own dialogs did. +/// +static void Lobby_Open_Screen(UILobbyPresenterClass::ScreenType kind) +{ + UILobbyPresenterClass * const screen = Lobby_Screen(); + if (screen == NULL) { + return; + } + + screen->Showing = kind; + + if (_LobbyRml) { + // What the legacy dialog's WM_INITDIALOG did before it put anything on a control. + switch (kind) { + case UILobbyPresenterClass::SCREEN_GAME_LIST: screen->Open(); break; + case UILobbyPresenterClass::SCREEN_HOST: screen->Open_Host(); break; + case UILobbyPresenterClass::SCREEN_GUEST: screen->Open_Guest(); break; + default: break; + } + return; + } + + int identifier = IDD_MPLAYER_GAME_LIST; + DLGPROC procedure = MPlayer_Game_List_Dialog_Proc; + if (kind == UILobbyPresenterClass::SCREEN_HOST) { + identifier = IDD_MPLAYER_HOST; + procedure = MPlayer_Host_Dialog_Proc; + } else if (kind == UILobbyPresenterClass::SCREEN_GUEST) { + identifier = IDD_MPLAYER_GUEST; + procedure = MPlayer_Guest_Dialog_Proc; + } + + HWND const dialog = WS_Create_Dialog(ProgramInstance, identifier, MainWindow, procedure, FALSE); + Center_Window_Within_Window(dialog); + OwnerDraw::Subclass_Dialog(dialog, 0); + if (kind == UILobbyPresenterClass::SCREEN_HOST) { + SendMessage(dialog, OD_SETTOP, 0, 1); + } + ShowWindow(dialog, SW_SHOWNORMAL); +} + + +/// +/// Takes the lobby's topmost screen away. +/// +/// bool; Was there one to take away? +static bool Lobby_Close_Screen(void) +{ + if (_LobbyRml) { + return(true); + } + + return(WS_Destroy_Dialog(NULL, 0)); +} + + +/// +/// One pass of the lobby's own maintenance, which both of its drivers run through the +/// presenter's Service. +/// +void Net2ServiceLobby(void) +{ + Ipx.Service(); + Call_Back(); + Ipx.Service(); + Title_Screen_Restore(); + + if (Net2LobbyScreenID() == 0) { + return; + } + + Send_Join_Queries(false, false, false, false); + Get_Join_Responses(); + if (Net2LobbyScreenID() == IDD_MPLAYER_HOST) { + PumpGameopts(false); + } + Net2ServiceGameList(); +} + + /// /// Maps a lobby answer onto the control identifier the driver loop already tests, the way /// every migrated screen's wrapper maps its outcome onto the value its caller expects. @@ -710,10 +816,11 @@ bool Net2Remote_Connect(void) UILobbyPresenterClass screen; UI_Set_Lobby_Screen(&screen); - HWND game_list_dialog = WS_Create_Dialog(ProgramInstance, IDD_MPLAYER_GAME_LIST, MainWindow, MPlayer_Game_List_Dialog_Proc, FALSE); - Center_Window_Within_Window(game_list_dialog); - OwnerDraw::Subclass_Dialog(game_list_dialog, 0); - ShowWindow(game_list_dialog, SW_SHOWNORMAL); + // The presentation is latched here, at screen entry, and a document that will not + // prepare drops the whole family back to the legacy dialogs. + _LobbyRml = UI_Use_Rml(); + + Lobby_Open_Screen(UILobbyPresenterClass::SCREEN_GAME_LIST); Net2DisplayUsers(); _netresponse = 0; @@ -729,11 +836,27 @@ bool Net2Remote_Connect(void) // Pop up the network Join/New dialog //..................................................................... while (_netresponse == 0) { - Ipx.Service(); + if (_LobbyRml) { + UIResult const answer = UI_Lobby_Run(screen); + if (answer.Outcome == UIResult::OUTCOME_FAILED_TO_OPEN) { + // Preparation failed, so the family opens its legacy view instead, which + // is what every migrated screen does with a resource it cannot load. + UI_Lobby_Close_Views(); + _LobbyRml = false; + Lobby_Open_Screen(screen.Showing); + continue; + } + + screen.Result.reset(); + if (screen.Response != UILobbyPresenterClass::RESPONSE_NONE) { + _netresponse = Lobby_Response_Identifier(screen.Response); + screen.Response = UILobbyPresenterClass::RESPONSE_NONE; + } + continue; + } + Sleep(0); - Call_Back(); - Ipx.Service(); - Title_Screen_Restore(); + screen.Service(); MSG msg; while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { @@ -793,15 +916,6 @@ bool Net2Remote_Connect(void) if (_netresponse != 0) { break; } - - if (WS_Top_Window()) { - Send_Join_Queries(false, false, false, false); - Get_Join_Responses(); - if (WS_Top_Window_ID() == IDD_MPLAYER_HOST) { - PumpGameopts(false); - } - screen.Service(); - } } //..................................................................... @@ -809,12 +923,13 @@ bool Net2Remote_Connect(void) //..................................................................... if (_netresponse == IDCANCEL) { Session.Write_MultiPlayer_Settings(); - if (WS_Top_Window_ID() == IDD_MPLAYER_GAME_LIST) { + if (Net2LobbyScreenID() == IDD_MPLAYER_GAME_LIST) { if (JoinState > JOIN_NOTHING) { Unjoin_Game(CurGame); Ipx.Service(); } - WS_Destroy_Dialog(NULL, 0); + Lobby_Close_Screen(); + UI_Lobby_Close_Views(); Clear_Vector(&Session.Players); Clear_Vector(&Session.Games); Clear_Vector(&Session.Chat); @@ -824,18 +939,15 @@ bool Net2Remote_Connect(void) return(false); } - if (WS_Top_Window_ID() == IDD_MPLAYER_HOST) { + if (Net2LobbyScreenID() == IDD_MPLAYER_HOST) { Unjoin_Game(CurGame); JoinState = JOIN_NOTHING; - WS_Destroy_Dialog(WS_Top_Window(), 0); - game_list_dialog = WS_Create_Dialog(ProgramInstance, IDD_MPLAYER_GAME_LIST, MainWindow, MPlayer_Game_List_Dialog_Proc, 0); - Center_Window_Within_Window(game_list_dialog); - OwnerDraw::Subclass_Dialog(game_list_dialog, 0); - ShowWindow(game_list_dialog, SW_SHOWNORMAL); + Lobby_Close_Screen(); + Lobby_Open_Screen(UILobbyPresenterClass::SCREEN_GAME_LIST); Send_Join_Queries(false, false, true, false); } - if (WS_Top_Window_ID() == IDD_MPLAYER_GUEST) { + if (Net2LobbyScreenID() == IDD_MPLAYER_GUEST) { //............................................................... // If we're joined to a game, make extra sure the other players in // that game know I'm exiting; send my SIGN_OFF as an ack-required @@ -880,14 +992,11 @@ bool Net2Remote_Connect(void) Session.GameName[0] = '\0'; JoinState = JOIN_NOTHING; - WS_Destroy_Dialog(0, 0); + Lobby_Close_Screen(); _netresponse = 0; CurGame = 0; Clear_Vector(&Session.Players); - game_list_dialog = WS_Create_Dialog(ProgramInstance, IDD_MPLAYER_GAME_LIST, MainWindow, MPlayer_Game_List_Dialog_Proc, 0); - Center_Window_Within_Window(game_list_dialog); - OwnerDraw::Subclass_Dialog(game_list_dialog, 0); - ShowWindow(game_list_dialog, SW_SHOWNORMAL); + Lobby_Open_Screen(UILobbyPresenterClass::SCREEN_GAME_LIST); } } @@ -941,7 +1050,7 @@ bool Net2Remote_Connect(void) Session.PlayingAgainstVersion = VerNum.Version_Number(); Set_Scenario_Info_From_Index(Session.Options.ScenarioIndex); - WS_Destroy_Dialog(NULL, NULL); + Lobby_Close_Screen(); _netresponse = 0; //------------------------------------------------------------------------ @@ -975,15 +1084,11 @@ bool Net2Remote_Connect(void) // Pop up the New Network Game dialog; if user selects OK, return // 'true'; otherwise, return to the Join Dialog. //.................................................................. - HWND host_dialog = WS_Create_Dialog(ProgramInstance, IDD_MPLAYER_HOST, MainWindow, MPlayer_Host_Dialog_Proc, 0); - Center_Window_Within_Window(host_dialog); - OwnerDraw::Subclass_Dialog(host_dialog, 0); - SendMessage(host_dialog, OD_SETTOP, 0, 1); - ShowWindow(host_dialog, SW_SHOWNORMAL); + Lobby_Open_Screen(UILobbyPresenterClass::SCREEN_HOST); } } - if (_netresponse != 1 || WS_Top_Window_ID() != IDD_MPLAYER_GUEST) { + if (_netresponse != 1 || Net2LobbyScreenID() != IDD_MPLAYER_GUEST) { if (_netresponse == IDC_GO) { Net2GameStarted = 0; Session.Write_MultiPlayer_Settings(); @@ -996,7 +1101,8 @@ bool Net2Remote_Connect(void) PMessagePrintf(-1, Fetch_String(TXT_ONLY_ONE)); _netresponse = 0; screen.CanStart = true; - EnableWindow(GetDlgItem(WS_Top_Window(), IDC_GO), TRUE); + Net2GameStarted = false; + EnableWindow(GetDlgItem(WS_Find_Dialog(IDD_MPLAYER_HOST), IDC_GO), TRUE); } if (_netresponse == IDC_GO) { @@ -1005,7 +1111,8 @@ bool Net2Remote_Connect(void) PMessagePrintf(-1, Fetch_String(TXT_ACCEPTFIRST)); _netresponse = 0; screen.CanStart = true; - EnableWindow(GetDlgItem(WS_Top_Window(), IDC_GO), TRUE); + Net2GameStarted = false; + EnableWindow(GetDlgItem(WS_Find_Dialog(IDD_MPLAYER_HOST), IDC_GO), TRUE); break; } } @@ -1018,7 +1125,8 @@ bool Net2Remote_Connect(void) * The guest accepted the host's "go" -- tear down the dialogs, run * the pregame setup, compute the packet timing and leave the loop. */ - while (WS_Destroy_Dialog(NULL, 0) == true) {} + while (Lobby_Close_Screen() == true) {} + UI_Lobby_Close_Views(); _netresponse = 0; PregameSetup(); @@ -1041,11 +1149,17 @@ bool Net2Remote_Connect(void) break; } + // The AI player count this check adds in has always been zero: it is read from the + // game list dialog, whose template carries no IDC_AIPLAYERS, so the track bar it asks + // is not there to answer. Preserved rather than repaired, and reported separately. + int const ai_players = 0; + int waypoints = RandomMapWaypointCount(Session.Options.ScenarioIndex); - if (waypoints < SendDlgItemMessage(game_list_dialog, IDC_AIPLAYERS, TBM_GETPOS, 0, 0) + Session.Players.Count()) { + if (waypoints < ai_players + Session.Players.Count()) { PMessagePrintf(-1, Fetch_String(TXT_SCENARIO_TOO_SMALL)); screen.CanStart = true; - EnableWindow(GetDlgItem(WS_Top_Window(), IDC_GO), TRUE); + Net2GameStarted = false; + EnableWindow(GetDlgItem(WS_Find_Dialog(IDD_MPLAYER_HOST), IDC_GO), TRUE); _netresponse = 0; } else { if (_netresponse != IDC_GO) { @@ -1167,7 +1281,8 @@ bool Net2Remote_Connect(void) Hide_Mouse(); Draw_Menu_Background(); Show_Mouse(); - WS_Destroy_Dialog(NULL, 0); + Lobby_Close_Screen(); + UI_Lobby_Close_Views(); break; } } @@ -1175,6 +1290,7 @@ bool Net2Remote_Connect(void) Session.NetOpen = false; Session.Write_MultiPlayer_Settings(); + UI_Lobby_Close_Views(); UI_Set_Lobby_Screen(NULL); return(true); @@ -2171,6 +2287,9 @@ static void Get_Join_Responses(void) NodeNameType * player = Session.Players[i]; if (strcmp(player->Name,Session.GameName) && player->Player.Status != 0) { player->Player.Status = 0; + if (Lobby_Screen() != NULL) { + Lobby_Screen()->CanAccept = true; + } EnableWindow(GetDlgItem(GameoptWindow(), IDC_ACCEPT), TRUE); } } @@ -2211,12 +2330,9 @@ static void Get_Join_Responses(void) Session.Players.Add (who); Net2IsGameListActive = false; - WS_Destroy_Dialog(0, 0); + Lobby_Close_Screen(); _netresponse = 0; - dialog = WS_Create_Dialog(ProgramInstance, IDD_MPLAYER_GUEST, MainWindow, MPlayer_Guest_Dialog_Proc, FALSE); - Center_Window_Within_Window(dialog); - OwnerDraw::Subclass_Dialog(dialog, 0); - ShowWindow(dialog, SW_SHOWNORMAL); + Lobby_Open_Screen(UILobbyPresenterClass::SCREEN_GUEST); display_users = true; Send_Join_Queries(1, 1, 1, 0); @@ -2492,6 +2608,9 @@ static void Get_Join_Responses(void) NodeNameType * player = Session.Players[i]; if (strcmp(player->Name,Session.GameName) && player->Player.Status != 0) { player->Player.Status = 0; + if (Lobby_Screen() != NULL) { + Lobby_Screen()->CanAccept = true; + } EnableWindow(GetDlgItem(GameoptWindow(), IDC_ACCEPT), TRUE); } } diff --git a/code/netdlg2.h b/code/netdlg2.h index edb22e22a..33eca0267 100644 --- a/code/netdlg2.h +++ b/code/netdlg2.h @@ -30,6 +30,15 @@ extern bool Net2IsGameListActive; void Send_Join_Queries(int gamenow, int playernow, int chatnow, int init = 0); void Net2ServiceGameList(void); +// One pass of the lobby's own maintenance: service the transport, answer the join protocol, +// broadcast the host's options and age out what has gone quiet. Both of the lobby's drivers +// run this once per pass, through the presenter's Service. +void Net2ServiceLobby(void); + +// Which of the lobby's three screens is up, as its dialog identifier, or 0 when none is. +// A document has no window, so this answers for both presentations. +int Net2LobbyScreenID(void); + int Net2FirstFreeColor(int reqcolor, int index); void Fill_Country_Box(HWND combo); int Country_From_Box(HWND combo); diff --git a/code/netshare.cpp b/code/netshare.cpp index 21a72c9a1..00b0f17ba 100644 --- a/code/netshare.cpp +++ b/code/netshare.cpp @@ -941,6 +941,12 @@ bool DecodePubGameopt(char * options, char * name) free(string); + // The settings the host sent are on the model where they arrived, not where a control is + // written, so a presentation that is not a window sees them too. + if (UILobbyPresenterClass * const lobby = UI_Lobby_Screen()) { + lobby->Options_Received(); + } + DisplayGameopts(GameoptWindow(), false); if (_last_unit_count != Session.Options.UnitCount) do_decode = true; @@ -972,9 +978,15 @@ bool DecodePubGameopt(char * options, char * name) sprintf(buffer, "A0"); SendPublicGameopts(buffer); + if (UILobbyPresenterClass * const lobby = UI_Lobby_Screen()) { + lobby->CanAccept = true; + } EnableWindow(GetDlgItem(GameoptWindow(), IDC_ACCEPT), TRUE); InvalidateRect(GetDlgItem(GameoptWindow(), IDC_ACCEPT), NULL, FALSE); } else { + if (UILobbyPresenterClass * const lobby = UI_Lobby_Screen()) { + lobby->CanAccept = true; + } if (!IsWindowEnabled(GetDlgItem(GameoptWindow(), IDC_ACCEPT))) { EnableWindow(GetDlgItem(GameoptWindow(), IDC_ACCEPT), TRUE); InvalidateRect(GetDlgItem(GameoptWindow(), IDC_ACCEPT), NULL, FALSE); @@ -1379,7 +1391,7 @@ void Rebuild_Network_Map_Preview(void) switch (Session.Type) { case GAME_IPX: - if (WS_Top_Window_ID() == IDD_MPLAYER_GUEST && !Find_Local_Scenario(Session.ScenarioFileName, Session.ScenarioFileLength, Session.ScenarioDigest, Session.ScenarioIsOfficial)) { + if (Net2LobbyScreenID() == IDD_MPLAYER_GUEST && !Find_Local_Scenario(Session.ScenarioFileName, Session.ScenarioFileLength, Session.ScenarioDigest, Session.ScenarioIsOfficial)) { GlobalPacketType packet; memset(&packet, 0, sizeof(packet)); packet.Command = NET_REQ_PREVIEW; diff --git a/code/ui/uilobby.cpp b/code/ui/uilobby.cpp index 153896d2a..c15a9ab6f 100644 --- a/code/ui/uilobby.cpp +++ b/code/ui/uilobby.cpp @@ -34,6 +34,9 @@ #include "uilobby.h" +#include "uimappreview.h" +#include "uirmlview.h" + #include "_rand.h" #include "_rules.h" #include "_timer.h" @@ -53,6 +56,11 @@ #include "session.h" #include "utf8.h" +#include +#include +#include +#include + #include #include #include @@ -136,8 +144,9 @@ void UILobbyPresenterClass::Open(void) /// void UILobbyPresenterClass::Open_Guest(void) { - House = Session.House; - Color = Session.ColorIdx; + Build_Identity_Lists(); + Read_Options(); + CanAccept = false; for (int index = 0; index < Session.Players.Count(); index++) { @@ -147,11 +156,29 @@ void UILobbyPresenterClass::Open_Guest(void) } Session.Options.ScenarioDescription[0] = '\0'; + ScenarioName.clear(); + + Rebuild_Network_Map_Preview(); + PreviewGeneration++; Build_User_Rows(); } +/// +/// The host's settings have arrived and been written to the session. +/// The model is read back where they landed rather than where a control is written, which is +/// the same split Fill_List took: a presentation that draws a different number of times +/// cannot lose a change or repeat one. +/// +void UILobbyPresenterClass::Options_Received(void) +{ + Read_Options(); + PreviewGeneration++; + Build_User_Rows(); +} + + /// /// Builds the country and color lists both setup screens show, and picks out the ones this /// player is wearing. A side row carries the country it stands for rather than its position, @@ -669,13 +696,13 @@ void UILobbyPresenterClass::Build_User_Rows(void) /// -/// The maintenance the driver ran on every pass of its own loop: a game or a chat partner -/// that has stopped answering is dropped, and a partner close to timing out is asked once -/// more before it goes. +/// One pass of the maintenance the lobby's driver ran on every turn of its own loop: the +/// transport is serviced, the join protocol is answered, the host's options are broadcast if +/// they moved, and a game or a chat partner that has stopped answering is dropped. /// void UILobbyPresenterClass::Service(void) { - Net2ServiceGameList(); + Net2ServiceLobby(); } @@ -864,3 +891,636 @@ void UILobbyPresenterClass::Execute(UIIntent const & intent) return; } } + + +//--------------------------------------------------------------------------------------- +// The RmlUi views. +//--------------------------------------------------------------------------------------- + +// The picture the preview frame holds, in game logical units. The frame is the setup +// templates' 126 x 73 dialog units, which is 189 by 118.625 at the family's 1.5 across and +// 1.625 down, and the picture sits inside its one pixel border. +enum { PREVIEW_WIDTH = 187, PREVIEW_HEIGHT = 116 }; + +inline constexpr char const * UI_LOBBY_HOST_PREVIEW = "lobbyhostpreview"; +inline constexpr char const * UI_LOBBY_GUEST_PREVIEW = "lobbyguestpreview"; + + +/// +/// The RmlUi half of one of the lobby's three screens. +/// The three documents show overlapping halves of one model, so one view serves them all +/// and binds only what its own document names. Each names its data model after its own +/// file, which is what the base class derives the name from; a document that names another +/// document's model gets no bindings and no events at all. +/// +class LobbyViewClass : public UIRmlViewClass +{ + public: + // A color a player may take, with the swatch the owner-draw combo drew its row in. + struct ColorRowType + { + std::string Name; + std::string Hex; + }; + + // A player row as its document shows it: the model's row plus the color the name is + // drawn in, the marker the list drew as a surface, and whether the host has picked + // the row out to kick. + struct UserViewType + { + std::string Name; + std::string SideName; + std::string Mark; + std::string Hex; + bool Picked = false; + }; + + struct MessageViewType + { + std::string Text; + std::string Hex; + }; + + LobbyViewClass(UILobbyPresenterClass & presenter, char const * document, + UILobbyPresenterClass::ScreenType kind, char const * preview); + virtual ~LobbyViewClass(void) override; + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + // Puts the track bar ranges the rules give this screen on the controls, and lets the + // change handlers start reporting. A range is set before the data binding fills a + // value in, so a value outside a track bar's default range is not clamped away. + void Settle(void); + + UILobbyPresenterClass::ScreenType Kind; + + private: + void Move(char const * which, int value); + void Press(char const * action); + void Set_Range(char const * id, UILobbyPresenterClass::SliderType const & slider); + void Submit_Chat(void); + void Rebuild_Rows(void); + + static std::string Swatch(int color); + + UILobbyPresenterClass & Screen; + + // The view owns the pixels; the presenter carries only the name they answer to. + std::string Preview; + MapPreviewSurfaceClass Picture{PREVIEW_WIDTH, PREVIEW_HEIGHT}; + + std::vector ColorRows; + std::vector UserRows; + std::vector GameRows; + std::vector MessageRows; + + unsigned int Drawn = 0; + bool Settled = false; +}; + + +LobbyViewClass::LobbyViewClass(UILobbyPresenterClass & presenter, char const * document, + UILobbyPresenterClass::ScreenType kind, char const * preview) : + UIRmlViewClass(presenter, document), + Kind(kind), + Screen(presenter), + Preview(preview != NULL ? preview : "") +{ + if (!Preview.empty()) { + UI_Register_Surface(Preview.c_str(), &Picture); + } +} + + +LobbyViewClass::~LobbyViewClass(void) +{ + if (!Preview.empty()) { + UI_Unregister_Surface(Preview.c_str()); + } +} + + +/// +/// Turns a player color into the CSS color the owner-draw list drew its row in, which is +/// what OD_SETCOLOR was handed out of PlayerColorTable. +/// +std::string LobbyViewClass::Swatch(int color) +{ + char hex[8]; + if (color >= 0 && color < MAX_PLAYERS) { + // A COLORREF holds its blue byte highest, which is the order RGB() packs. + unsigned long const packed = (unsigned long)PlayerColorTable[color]; + std::snprintf(hex, sizeof(hex), "#%02x%02x%02x", + (unsigned)(packed & 0xFF), (unsigned)((packed >> 8) & 0xFF), (unsigned)((packed >> 16) & 0xFF)); + } else { + std::snprintf(hex, sizeof(hex), "#b9bcae"); + } + return(hex); +} + + +void LobbyViewClass::Set_Range(char const * id, UILobbyPresenterClass::SliderType const & slider) +{ + if (Element == nullptr) { + return; + } + + Rml::Element * const control = Element->GetElementById(id); + if (control == nullptr) { + return; + } + + // The range is set before the value, because a track bar clamps a value into the range + // it is holding and the default range stops well short of what the rules allow. + control->SetAttribute("min", slider.Minimum); + control->SetAttribute("max", slider.Maximum); + control->SetAttribute("step", slider.Step); + control->SetAttribute("value", slider.Value); +} + + +void LobbyViewClass::Settle(void) +{ + if (Kind != UILobbyPresenterClass::SCREEN_GAME_LIST) { + Set_Range("unitcount", Screen.UnitCount); + Set_Range("credits", Screen.Credits); + Set_Range("techlevel", Screen.TechLevel); + Set_Range("ailevel", Screen.AILevel); + Set_Range("aiplayers", Screen.AIPlayers); + Set_Range("gamespeed", Screen.GameSpeed); + } + + Settled = true; +} + + +/// +/// Reads the chat entry and queues what was typed, then empties the field, which is what the +/// edit control's own handler did once its text had been taken. +/// +void LobbyViewClass::Submit_Chat(void) +{ + if (Element == nullptr) { + return; + } + + Rml::ElementFormControlInput * const field = + rmlui_dynamic_cast(Element->GetElementById("say")); + if (field == nullptr) { + return; + } + + Rml::String const text = field->GetValue(); + field->SetValue(""); + + if (text.empty()) { + return; + } + + Screen.Queue(UIIntent{UI_LOBBY_SAY, text, 0}); +} + + +void LobbyViewClass::Move(char const * which, int value) +{ + if (!Settled) return; + + UILobbyPresenterClass::SliderType const * held = NULL; + if (which == UI_LOBBY_UNITCOUNT) held = &Screen.UnitCount; + else if (which == UI_LOBBY_CREDITS) held = &Screen.Credits; + else if (which == UI_LOBBY_TECHLEVEL) held = &Screen.TechLevel; + else if (which == UI_LOBBY_AILEVEL) held = &Screen.AILevel; + else if (which == UI_LOBBY_AIPLAYERS) held = &Screen.AIPlayers; + else if (which == UI_LOBBY_GAMESPEED) held = &Screen.GameSpeed; + + if (held == NULL || held->Value == value) { + return; + } + + Screen.Queue(UIIntent{UI_LOBBY_SLIDER, which, value}); +} + + +/// +/// Queues what a button or its key stands for. The game list's name field is read here +/// rather than tracked, because that is when the dialog read its edit control. +/// +void LobbyViewClass::Press(char const * action) +{ + if (Kind == UILobbyPresenterClass::SCREEN_GAME_LIST && Element != nullptr) { + Rml::ElementFormControlInput * const field = + rmlui_dynamic_cast(Element->GetElementById("yourname")); + if (field != nullptr) { + Rml::String const text = field->GetValue(); + if (text != Screen.Handle) { + Screen.Queue(UIIntent{UI_LOBBY_RENAME, text, 0}); + } + } + } + + Screen.Queue(UIIntent{action, "", 0}); +} + + +void LobbyViewClass::Rebuild_Rows(void) +{ + GameRows = Screen.Games; + + UserRows.clear(); + for (int index = 0; index < (int)Screen.Users.size(); index++) { + UILobbyPresenterClass::UserRowType const & row = Screen.Users[index]; + + UserViewType view; + view.Name = row.Name; + view.SideName = row.SideName; + view.Hex = Swatch(row.Color); + + // The host and accepted markers, which the list drew as the wolhost.pcx and + // wolacpt.pcx surfaces. + if (row.IsHost) { + view.Mark = "*"; + } else if (row.HasAccepted) { + view.Mark = "+"; + } + + view.Picked = std::find(Screen.PickedUsers.begin(), Screen.PickedUsers.end(), index) + != Screen.PickedUsers.end(); + + UserRows.push_back(view); + } + + MessageRows.clear(); + for (UILobbyPresenterClass::ChatLineType const & line : Screen.Messages) { + MessageViewType view; + view.Text = line.Text; + + if (line.Color < 0) { + view.Hex = "#b9bcae"; + } else { + unsigned long const packed = (unsigned long)line.Color; + char hex[8]; + std::snprintf(hex, sizeof(hex), "#%02x%02x%02x", + (unsigned)(packed & 0xFF), (unsigned)((packed >> 8) & 0xFF), (unsigned)((packed >> 16) & 0xFF)); + view.Hex = hex; + } + + MessageRows.push_back(view); + } +} + + +void LobbyViewClass::Bind(Rml::DataModelConstructor & model) +{ + ColorRows.clear(); + for (int index = 0; index < (int)Screen.Colors.size(); index++) { + ColorRows.push_back(ColorRowType{Screen.Colors[index], Swatch(index)}); + } + + Rebuild_Rows(); + + if (auto row = model.RegisterStruct()) { + row.RegisterMember("label", &UILobbyPresenterClass::GameRowType::Label); + row.RegisterMember("isopen", &UILobbyPresenterClass::GameRowType::IsOpen); + } + model.RegisterArray>(); + + if (auto row = model.RegisterStruct()) { + row.RegisterMember("name", &UserViewType::Name); + row.RegisterMember("sidename", &UserViewType::SideName); + row.RegisterMember("mark", &UserViewType::Mark); + row.RegisterMember("hex", &UserViewType::Hex); + row.RegisterMember("picked", &UserViewType::Picked); + } + model.RegisterArray>(); + + if (auto row = model.RegisterStruct()) { + row.RegisterMember("text", &MessageViewType::Text); + row.RegisterMember("hex", &MessageViewType::Hex); + } + model.RegisterArray>(); + + if (auto swatch = model.RegisterStruct()) { + swatch.RegisterMember("name", &ColorRowType::Name); + swatch.RegisterMember("hex", &ColorRowType::Hex); + } + model.RegisterArray>(); + + if (auto side = model.RegisterStruct()) { + side.RegisterMember("name", &UILobbyPresenterClass::SideType::Name); + } + model.RegisterArray>(); + + if (auto slider = model.RegisterStruct()) { + slider.RegisterMember("value", &UILobbyPresenterClass::SliderType::Value); + slider.RegisterMember("min", &UILobbyPresenterClass::SliderType::Minimum); + slider.RegisterMember("max", &UILobbyPresenterClass::SliderType::Maximum); + slider.RegisterMember("step", &UILobbyPresenterClass::SliderType::Step); + } + + model.Bind("handle", &Screen.Handle); + model.Bind("games", &GameRows); + model.Bind("selectedgame", &Screen.SelectedGame); + model.Bind("users", &UserRows); + model.Bind("messages", &MessageRows); + + model.Bind("sides", &Screen.Sides); + model.Bind("selectedside", &Screen.SelectedSide); + model.Bind("colors", &ColorRows); + model.Bind("selectedcolor", &Screen.Color); + + model.Bind("scenarioname", &Screen.ScenarioName); + model.Bind("preview", &Preview); + + model.Bind("unitcount", &Screen.UnitCount); + model.Bind("credits", &Screen.Credits); + model.Bind("techlevel", &Screen.TechLevel); + model.Bind("ailevel", &Screen.AILevel); + model.Bind("aiplayers", &Screen.AIPlayers); + model.Bind("gamespeed", &Screen.GameSpeed); + + model.Bind("bases", &Screen.Bases); + model.Bind("crates", &Screen.Crates); + model.Bind("fog", &Screen.FogOfWar); + model.Bind("bridges", &Screen.Bridges); + model.Bind("mcv", &Screen.MCVRedeploy); + model.Bind("shortgame", &Screen.ShortGame); + model.Bind("engineer", &Screen.MultiEngineer); + model.Bind("allies", &Screen.Allies); + model.Bind("harvtruce", &Screen.HarvTruce); + + model.Bind("canaccept", &Screen.CanAccept); + model.Bind("canstart", &Screen.CanStart); + + // The field is bound one way, so a value the model already holds is never queued back as + // a change the player did not type. + model.BindEventCallback("rename", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + Rml::String const value = event.GetParameter("value", Rml::String()); + if (value == Screen.Handle) return; + Screen.Queue(UIIntent{UI_LOBBY_RENAME, value, 0}); + }); + + model.BindEventCallback("pickgame", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{UI_LOBBY_PICK_GAME, "", arguments[0].Get()}); + }); + + model.BindEventCallback("pickuser", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{UI_LOBBY_PICK_USER, "", arguments[0].Get()}); + }); + + model.BindEventCallback("chooseside", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + if (!Settled) return; + int const row = (int)(event.GetParameter("value", 0.0f) + 0.5f); + if (row == Screen.SelectedSide) return; + + if (Kind == UILobbyPresenterClass::SCREEN_HOST) { + Screen.Queue(UIIntent{UI_LOBBY_HOST_SIDE, "", row}); + return; + } + + // The guest records the side ahead of the color, because the dialog read both of + // its boxes and sent one packet carrying the pair. + Screen.SelectedSide = row; + if (row >= 0 && row < (int)Screen.Sides.size()) { + Screen.Queue(UIIntent{UI_LOBBY_SIDE, "", Screen.Sides[row].Country}); + } + Screen.Queue(UIIntent{UI_LOBBY_IDENTITY, "", Screen.Color}); + }); + + model.BindEventCallback("choosecolor", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + if (!Settled) return; + int const row = (int)(event.GetParameter("value", 0.0f) + 0.5f); + if (row == Screen.Color) return; + + if (Kind == UILobbyPresenterClass::SCREEN_HOST) { + Screen.Queue(UIIntent{UI_LOBBY_HOST_COLOR, "", row}); + return; + } + + if (Screen.SelectedSide >= 0 && Screen.SelectedSide < (int)Screen.Sides.size()) { + Screen.Queue(UIIntent{UI_LOBBY_SIDE, "", Screen.Sides[Screen.SelectedSide].Country}); + } + Screen.Queue(UIIntent{UI_LOBBY_IDENTITY, "", row}); + }); + + model.BindEventCallback("move", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + + Rml::String const which = arguments[0].Get(); + int const value = (int)(event.GetParameter("value", 0.0f) + 0.5f); + + if (which == UI_LOBBY_UNITCOUNT) Move(UI_LOBBY_UNITCOUNT, value); + else if (which == UI_LOBBY_CREDITS) Move(UI_LOBBY_CREDITS, value); + else if (which == UI_LOBBY_TECHLEVEL) Move(UI_LOBBY_TECHLEVEL, value); + else if (which == UI_LOBBY_AILEVEL) Move(UI_LOBBY_AILEVEL, value); + else if (which == UI_LOBBY_AIPLAYERS) Move(UI_LOBBY_AIPLAYERS, value); + else if (which == UI_LOBBY_GAMESPEED) Move(UI_LOBBY_GAMESPEED, value); + }); + + // A check box is a class plus a click that queues a toggle, not a two-way bound control. + model.BindEventCallback("toggle", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{UI_LOBBY_TOGGLE, arguments[0].Get(), 0}); + }); + + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + + Rml::String const action = arguments[0].Get(); + if (action == UI_LOBBY_JOIN) Press(UI_LOBBY_JOIN); + else if (action == UI_LOBBY_NEW) Press(UI_LOBBY_NEW); + else if (action == UI_LOBBY_CANCEL) Press(UI_LOBBY_CANCEL); + else if (action == UI_LOBBY_ACCEPT) Press(UI_LOBBY_ACCEPT); + else if (action == UI_LOBBY_GO) Press(UI_LOBBY_GO); + else if (action == UI_LOBBY_KICK) Press(UI_LOBBY_KICK); + else if (action == UI_LOBBY_PICK_MAP) Press(UI_LOBBY_PICK_MAP); + }); + + // Enter in the chat field sends the line, which is what EN_MAXTEXT stood for on an + // ES_WANTRETURN edit control. Escape backs out of the screen. + model.BindEventCallback("submit", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key == Rml::Input::KI_RETURN || key == Rml::Input::KI_NUMPADENTER) { + Submit_Chat(); + event.StopPropagation(); + } + }); + + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key == Rml::Input::KI_ESCAPE) { + Press(UI_LOBBY_CANCEL); + } + }); +} + + +void LobbyViewClass::Sync(void) +{ + if (!Model) return; + + Rebuild_Rows(); + + Model.DirtyVariable("games"); + Model.DirtyVariable("selectedgame"); + Model.DirtyVariable("users"); + Model.DirtyVariable("messages"); + Model.DirtyVariable("scenarioname"); + Model.DirtyVariable("canaccept"); + Model.DirtyVariable("canstart"); + + // The track bar, combo box and field values are not dirtied, because each already + // carries what its own change event reported. The options are, because the host's + // coupling and the guest's packets both move them from underneath. + Model.DirtyVariable("bases"); + Model.DirtyVariable("crates"); + Model.DirtyVariable("fog"); + Model.DirtyVariable("bridges"); + Model.DirtyVariable("mcv"); + Model.DirtyVariable("shortgame"); + Model.DirtyVariable("engineer"); + Model.DirtyVariable("allies"); + Model.DirtyVariable("harvtruce"); + + // The guest never moves a track bar -- every one of them is WS_DISABLED on its template + // -- so its values come from the host's packets and have to be dirtied. The host's own + // bars already carry what their change events reported. + if (Kind == UILobbyPresenterClass::SCREEN_GUEST) { + Model.DirtyVariable("unitcount"); + Model.DirtyVariable("credits"); + Model.DirtyVariable("techlevel"); + Model.DirtyVariable("ailevel"); + Model.DirtyVariable("aiplayers"); + Model.DirtyVariable("gamespeed"); + Model.DirtyVariable("selectedside"); + Model.DirtyVariable("selectedcolor"); + } + + // The picture is redrawn where it changed, not every pass, so the element uploads once + // per map rather than once per present. + if (!Preview.empty() && Screen.PreviewGeneration != Drawn) { + Drawn = Screen.PreviewGeneration; + Picture.Redraw(); + } +} + + +// The three documents, kept alive across the driver's passes because the lobby moves +// between them and comes back, the way it kept its host and game list dialogs alive +// together. +static LobbyViewClass * _GameListView = NULL; +static LobbyViewClass * _HostView = NULL; +static LobbyViewClass * _GuestView = NULL; +static LobbyViewClass * _ShownView = NULL; + + +static LobbyViewClass ** Lobby_View_Slot(UILobbyPresenterClass::ScreenType kind) +{ + switch (kind) { + case UILobbyPresenterClass::SCREEN_GAME_LIST: return(&_GameListView); + case UILobbyPresenterClass::SCREEN_HOST: return(&_HostView); + case UILobbyPresenterClass::SCREEN_GUEST: return(&_GuestView); + default: return(NULL); + } +} + + +void UI_Lobby_Close_Views(void) +{ + delete _GameListView; + delete _HostView; + delete _GuestView; + + _GameListView = NULL; + _HostView = NULL; + _GuestView = NULL; + _ShownView = NULL; +} + + +/// +/// Shows whichever of the three documents the screen says it is on, and runs it until the +/// player answers. +/// +UIResult UI_Lobby_Run(UILobbyPresenterClass & presenter) +{ + UIResult failed; + failed.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + + LobbyViewClass ** const slot = Lobby_View_Slot(presenter.Showing); + if (slot == NULL) { + return(failed); + } + + if (*slot == NULL) { + char const * document = "gamelist.rml"; + char const * preview = NULL; + if (presenter.Showing == UILobbyPresenterClass::SCREEN_HOST) { + document = "mphost.rml"; + preview = UI_LOBBY_HOST_PREVIEW; + } else if (presenter.Showing == UILobbyPresenterClass::SCREEN_GUEST) { + document = "mpguest.rml"; + preview = UI_LOBBY_GUEST_PREVIEW; + } + + LobbyViewClass * const view = new LobbyViewClass(presenter, document, presenter.Showing, preview); + if (!view->Prepare(true)) { + delete view; + return(failed); + } + + view->Settle(); + *slot = view; + } + + // The screen the lobby moved away from steps aside rather than being torn down, because + // it is come back to and its document is the same one. + if (_ShownView != NULL && _ShownView != *slot) { + _ShownView->Hide(); + } + if (!(*slot)->Is_Visible()) { + (*slot)->Show(); + } + _ShownView = *slot; + + // The ranges go back on the controls every time a screen is shown, because a screen that + // is come back to opens on what the model holds now rather than on what it held when the + // document was first loaded. + (*slot)->Settle(); + + // A family reopened in a loop resets the close mark and the held result, since a close + // marks the presenter closing and a marked presenter drains nothing. + presenter.Result.reset(); + presenter.IsClosing = false; + + (*slot)->Sync(); + + while (!presenter.Result.has_value()) { + UI_Run_Modal(presenter, **slot); + + if (presenter.Pending == UILobbyPresenterClass::SUB_NONE) { + break; + } + + // The scenario picker draws where the host screen is, so the document steps aside + // for it, which is what the dialog's own ShowWindow did. + (*slot)->Hide(); + presenter.Run_Pending(); + (*slot)->Show(); + (*slot)->Sync(); + } + + return(presenter.Result.value_or(UIResult{})); +} diff --git a/code/ui/uilobby.h b/code/ui/uilobby.h index a13abbcce..363de20b1 100644 --- a/code/ui/uilobby.h +++ b/code/ui/uilobby.h @@ -75,6 +75,15 @@ class UILobbyPresenterClass : public UIPresenterClass RESPONSE_GO, }; + // Which of the family's three screens is being shown. The driver moves between them + // and the presentation follows; a presenter names a screen rather than a window. + enum ScreenType { + SCREEN_NONE, + SCREEN_GAME_LIST, + SCREEN_HOST, + SCREEN_GUEST, + }; + // A screen the lobby opens and comes back from. The scenario picker draws where the // host screen is, so its owner takes the host screen off the screen and puts it // back rather than running it underneath. @@ -133,6 +142,10 @@ class UILobbyPresenterClass : public UIPresenterClass virtual void Refresh(void) override; virtual void Service(void) override; + // The scenario picker draws where the host screen is, so the host screen is stepped + // aside for it rather than run underneath. + virtual bool Suspends(void) const override { return(Pending != SUB_NONE); } + // The rosters the lobby opens with, which the game list dialog built as it was // created: the player's own chat entry and the lobby's own game entry. void Open(void); @@ -153,6 +166,10 @@ class UILobbyPresenterClass : public UIPresenterClass // from the network code wherever PMessagePrintf composes one. void Record_Message(int color, char const * text); + // The host's settings have arrived and been written to the session. Called where the + // options are decoded, so a presentation that is not a window sees them too. + void Options_Received(void); + // Reads the session's rosters into the view-model. Marking the host as accepted // happens here rather than while drawing, because it is a fact about the player // rather than about the row. @@ -162,6 +179,8 @@ class UILobbyPresenterClass : public UIPresenterClass /* ** The view-model. */ + ScreenType Showing = SCREEN_NONE; + std::string Handle; // The longest handle the name field accepts, in bytes, which is the limit the @@ -259,3 +278,10 @@ class UILobbyPresenterClass : public UIPresenterClass // reaches the model through this wherever a change is produced away from a screen. UILobbyPresenterClass * UI_Lobby_Screen(void); void UI_Set_Lobby_Screen(UILobbyPresenterClass * screen); + + +// Shows whichever of the three documents the screen says it is on, and runs it until the +// player answers. The documents outlive one call, because the lobby moves between them and +// comes back; UI_Lobby_Close_Views drops them when the lobby ends. +UIResult UI_Lobby_Run(UILobbyPresenterClass & presenter); +void UI_Lobby_Close_Views(void); diff --git a/ui/gamelist.rcss b/ui/gamelist.rcss new file mode 100644 index 000000000..539748609 --- /dev/null +++ b/ui/gamelist.rcss @@ -0,0 +1,51 @@ +/* The network game list. Geometry from the IDD_MPLAYER_GAME_LIST template, converted from + dialog units at the 8 point MS Sans Serif the template names: 1.5 pixels across and 1.625 + down, with a child's offset taken from the panel's content box and the panel's declared + size taken inside its own border. */ + +/* 426 x 240 dialog units, so 639 x 390 pixels, centred on the frame. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -319.5dp; + margin-top: -195dp; + + width: 635dp; + height: 386dp; +} + +/* "Your Name:" at 24, 9 and the EDITTEXT beside it, 68 x 12 dialog units at 89, 8. */ +#namelabel { left: 34dp; top: 12.625dp; width: 93dp; height: 16.25dp; line-height: 16.25dp; } +#yourname { left: 131.5dp; top: 11dp; width: 102dp; height: 19.5dp; line-height: 15.5dp; } + +/* "Games:" at 296, 14 and its 113 x 68 list at 294, 27. */ +#gameslabel { left: 442dp; top: 20.75dp; width: 75dp; height: 16.25dp; line-height: 16.25dp; } +#games { left: 439dp; top: 41.875dp; width: 169.5dp; height: 110.5dp; } + +/* "Players:" at 296, 99 and its 113 x 100 list at 294, 111. */ +#playerslabel { left: 442dp; top: 158.875dp; width: 75dp; height: 16.25dp; line-height: 16.25dp; } +#users { left: 439dp; top: 178.375dp; width: 169.5dp; height: 162.5dp; } + +/* A row states its own width, because a scrolling container gives its children none to be + a proportion of. The lists are 113 dialog units wide, less the scrollbar. */ +#games .row, +#users .row +{ + width: 157.5dp; + height: 16dp; + line-height: 16dp; +} + +/* The message log, 266 x 166 dialog units at 19, 27, and the chat entry below it. */ +#messages { left: 26.5dp; top: 41.875dp; width: 399dp; height: 269.75dp; } +#messages .line { width: 387dp; } + +#say { left: 26.5dp; top: 319.75dp; width: 399dp; height: 19.5dp; line-height: 15.5dp; } + +/* Cancel, Join and New, all 62 x 18 dialog units on the same row. */ +.button { height: 29.25dp; line-height: 29.25dp; width: 93dp; } + +#cancel { left: 269.5dp; top: 345.75dp; } +#join { left: 394dp; top: 345.75dp; } +#new { left: 515.5dp; top: 345.75dp; } diff --git a/ui/gamelist.rml b/ui/gamelist.rml new file mode 100644 index 000000000..5fea4c1c6 --- /dev/null +++ b/ui/gamelist.rml @@ -0,0 +1,34 @@ + + + Network games + + + + + +
+
Your Name:
+ + +
Games:
+
+
{{ entry.label }}
+
+ +
Players:
+
+
{{ entry.name }}
+
+ +
+
{{ entry.text }}
+
+ + + +
[[TXT_CANCEL]]
+
Join
+
New
+
+ +
diff --git a/ui/lobbybase.rcss b/ui/lobbybase.rcss new file mode 100644 index 000000000..12758c3f0 --- /dev/null +++ b/ui/lobbybase.rcss @@ -0,0 +1,214 @@ +/* What the three network lobby documents share. Only the look lives here; each document's + own stylesheet carries its geometry, converted from its dialog template's units. + + The palette and the raised and sunken borders are the ones ui/optionsbase.rcss + established for this family of dialogs. The three templates are all 426 x 240 dialog + units, the same as IDD_SKIRMISH, so the conversion is the same 1.5 pixels across and + 1.625 down. + + The lobby is list heavy: the game list, the player list and the message log are all + scrolling containers, and a scrolling container gives its children no width to be a + proportion of, so every row here states its own. */ + +/* The chat and system message log, which the templates give a LBS_NOSEL list box. A line + is kept whole in the model and wrapped here, which is what _DrawMessage did to the width + of the list box it was handed. */ +.log +{ + display: block; + position: absolute; + box-sizing: border-box; + + background-color: #14160f; + overflow-y: auto; + overflow-x: hidden; +} + +.log .line +{ + display: block; + box-sizing: border-box; + padding: 0dp 2dp; + line-height: 14dp; + color: #b9bcae; +} + +/* The player list's own columns. The two setup dialogs register them with OD_ADDCOLUMN at + widths 45, 25 and 5, and the item's own string is column zero. */ +.users .row +{ + position: relative; + height: 16dp; + line-height: 16dp; + padding: 0dp; +} + +.users .name +{ + display: block; + position: absolute; + left: 2dp; + top: 0dp; + width: 108dp; + white-space: nowrap; + overflow: hidden; +} + +.users .side +{ + display: block; + position: absolute; + left: 112dp; + top: 0dp; + width: 37dp; + white-space: nowrap; + overflow: hidden; + color: #949a84; +} + +/* The host and accepted markers, which the list drew as the wolhost.pcx and wolacpt.pcx + surfaces. PCX decoding is not here yet, so the marker is a character in the same column + the surface stood in. */ +.users .mark +{ + display: block; + position: absolute; + left: 152dp; + top: 0dp; + width: 20dp; + text-align: center; + color: #e4e6da; +} + +/* The chat entry, where the templates put an EDITTEXT. It states a width, because a field + with none formats no line and RmlUi's End key then moves the caret to the start of an + empty line rather than to the end of the value. */ +.field +{ + display: block; + position: absolute; + box-sizing: border-box; + white-space: nowrap; + overflow: hidden; + padding: 0dp 4dp; + + font-family: LatoLatin; + color: #e4e6da; + background-color: #14160f; + border-width: 2dp; + border-top-color: #10120e; + border-left-color: #10120e; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +.field:focus { background-color: #1d2017; } + +/* A CBS_DROPDOWNLIST combo, sized the way ownrdraw.cpp sizes one: the item height, which is + the 14 pixel dialog font plus two, inside a two pixel border. The template's own height is + how far the list drops. */ +.combo +{ + display: block; + position: absolute; + box-sizing: border-box; + height: 20dp; + + color: #b9bcae; + background-color: #2b2f25; + border-width: 2dp; + border-top-color: #14160f; + border-left-color: #14160f; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +.combo selectvalue +{ + width: auto; + margin-right: 16dp; + height: 16dp; + line-height: 16dp; + padding: 0dp 4dp; + white-space: nowrap; + overflow: hidden; +} + +.combo selectarrow +{ + width: 16dp; + height: 16dp; + + background-color: #33372c; + border-width: 2dp; + border-top-color: #767c68; + border-left-color: #767c68; + border-right-color: #14160f; + border-bottom-color: #14160f; +} + +.combo selectarrow:active +{ + border-top-color: #14160f; + border-left-color: #14160f; + border-right-color: #767c68; + border-bottom-color: #767c68; +} + +.combo selectbox +{ + width: 114dp; + overflow-y: auto; + + background-color: #14160f; + border-width: 1dp; + border-top-color: #6e7360; + border-left-color: #6e7360; + border-right-color: #10120e; + border-bottom-color: #10120e; +} + +.combo selectbox option +{ + width: auto; + height: 18dp; + line-height: 18dp; + padding: 0dp 4dp; + white-space: nowrap; + color: #b9bcae; +} + +.combo selectbox option:hover { color: #e4e6da; } +.combo selectbox option:checked { background-color: #3f4536; } + +/* The preview frame, a GROUPBOX 126 x 73 dialog units at 281, 138 on both setup templates. + The picture inside it is drawn by the screen and reaches the document through the + element, which takes its size from the provider. */ +.previewframe +{ + display: block; + position: absolute; + box-sizing: border-box; + left: 419.5dp; + top: 222.25dp; + width: 189dp; + height: 118.625dp; + + background-color: #14160f; + border-width: 1dp; + border-top-color: #10120e; + border-left-color: #10120e; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +.previewframe surface +{ + display: block; + position: absolute; + left: 0dp; + top: 0dp; +} + +/* A caption the template writes right aligned, which every track bar label is. */ +.rcaption { text-align: right; } diff --git a/ui/mpguest.rcss b/ui/mpguest.rcss new file mode 100644 index 000000000..ce6d0938b --- /dev/null +++ b/ui/mpguest.rcss @@ -0,0 +1,105 @@ +/* The guest's game setup. Geometry from the IDD_MPLAYER_GUEST template, converted from + dialog units at the 8 point MS Sans Serif the template names: 1.5 pixels across and 1.625 + down, with a child's offset taken from the panel's content box and the panel's declared + size taken inside its own border. + + The template gives every game option WS_DISABLED on this screen: the guest is shown what + the host has chosen and cannot change any of it. */ + +/* 426 x 240 dialog units, so 639 x 390 pixels, centred on the frame. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -319.5dp; + margin-top: -195dp; + + width: 635dp; + height: 386dp; +} + +/* The player's own settings, top left. The combos are 76 dialog units wide at 66, 7 and + 66, 24; these two are the only controls this screen may touch. */ +#sidelabel { left: 25dp; top: 9.375dp; width: 75dp; height: 19.5dp; line-height: 19.5dp; } +#colorlabel { left: 25dp; top: 37dp; width: 75dp; height: 19.5dp; line-height: 19.5dp; } + +#side, #color { left: 97dp; width: 114dp; } +#side { top: 9.375dp; } +#color { top: 37dp; } + +#side selectbox { max-height: 164.125dp; } +#color selectbox { max-height: 235.625dp; } + +/* "Map:" at 149, 7 and the scenario's own name at 176, 7. */ +#maplabel { left: 221.5dp; top: 9.375dp; width: 39dp; height: 16.25dp; line-height: 16.25dp; } +#scenarioname { left: 262dp; top: 9.375dp; width: 348dp; height: 16.25dp; line-height: 16.25dp; } + +/* "Players:" at 18, 42 and the 150 x 68 player list at 18, 56. */ +#playerslabel { left: 25dp; top: 66.25dp; width: 75dp; height: 19.5dp; line-height: 19.5dp; } +#users { left: 25dp; top: 89dp; width: 225dp; height: 110.5dp; } +#users .row { width: 213dp; } + +/* The message log, 256 x 65 at 18, 130, and the chat entry, 256 x 12 at 18, 199. */ +#messages { left: 25dp; top: 209.25dp; width: 384dp; height: 105.625dp; } +#messages .line { width: 372dp; } + +#say { left: 25dp; top: 321.375dp; width: 384dp; height: 19.5dp; line-height: 15.5dp; } + +/* The six track bars, 66 x 12 dialog units at x 227, and their right aligned captions, + 57 x 10 at x 162. */ +#gamespeedlabel, +#aiplayerslabel, +#ailevellabel, +#unitcountlabel, +#techlevellabel, +#creditslabel +{ + left: 241dp; + width: 85.5dp; + height: 16.25dp; + line-height: 16.25dp; +} + +#gamespeedlabel { top: 43.5dp; } +#aiplayerslabel { top: 69.5dp; } +#ailevellabel { top: 95.5dp; } +#unitcountlabel { top: 121.5dp; } +#techlevellabel { top: 147.5dp; } +#creditslabel { top: 173.5dp; } + +.slider { left: 338.5dp; width: 99dp; height: 19.5dp; } + +#gamespeed { top: 41.875dp; } +#aiplayers { top: 67.875dp; } +#ailevel { top: 93.875dp; } +#unitcount { top: 119.875dp; } +#techlevel { top: 145.875dp; } +#credits { top: 171.875dp; } + +/* The nine check boxes, 108 dialog units wide at x 300 but Allies, 105, and Short Game, 84. */ +.check { left: 448dp; width: 162dp; height: 16.25dp; line-height: 16.25dp; } + +#allies { top: 38.625dp; width: 157.5dp; } +#harvtruce { top: 58.125dp; } +#bases { top: 77.625dp; } +#mcv { top: 97.125dp; } +#fog { top: 116.625dp; } +#bridges { top: 136.125dp; } +#crates { top: 155.625dp; } +#shortgame { top: 175.125dp; width: 126dp; } +#engineer { top: 194.625dp; } + +/* What WS_DISABLED looks like: shown, dimmed and out of reach. */ +.off +{ + color: #6b6e63; + pointer-events: none; +} + +.off sliderbar { background-color: #3f4336; border-top-color: #5c6152; border-left-color: #5c6152; } + +/* Cancel and Accept, both 58 x 18 dialog units at 279, 215 and 350, 215. */ +.button { height: 29.25dp; line-height: 29.25dp; width: 87dp; } + +#cancel { left: 416.5dp; top: 347.375dp; } +#accept { left: 523dp; top: 347.375dp; } diff --git a/ui/mpguest.rml b/ui/mpguest.rml new file mode 100644 index 000000000..c7b5e1067 --- /dev/null +++ b/ui/mpguest.rml @@ -0,0 +1,76 @@ + + + Join a network game + + + + + +
+
Your Side:
+ + +
Your Color:
+ + +
Map:
+
{{ scenarioname }}
+ +
Players:
+
+
+
{{ entry.name }}
+
{{ entry.sidename }}
+
{{ entry.mark }}
+
+
+ +
+
{{ entry.text }}
+
+ + + + +
Game Speed
+ + +
AI Players
+ + +
AI Level
+ + +
Unit Count
+ + +
Tech Level
+ + +
Credits
+ + +
Allies allowed
+
Harvester Truce
+
Bases
+
Re-Deployable MCV
+
Fog Of War
+
Bridges Destroyable
+
Crates
+
Short Game
+
Multi Engineer
+ +
+ +
+ +
[[TXT_CANCEL]]
+
Accept
+
+ +
diff --git a/ui/mphost.rcss b/ui/mphost.rcss new file mode 100644 index 000000000..8e92cc5ec --- /dev/null +++ b/ui/mphost.rcss @@ -0,0 +1,96 @@ +/* The host's game setup. Geometry from the IDD_MPLAYER_HOST template, converted from dialog + units at the 8 point MS Sans Serif the template names: 1.5 pixels across and 1.625 down, + with a child's offset taken from the panel's content box and the panel's declared size + taken inside its own border. */ + +/* 426 x 240 dialog units, so 639 x 390 pixels, centred on the frame. */ +#dialog +{ + left: 50%; + top: 50%; + margin-left: -319.5dp; + margin-top: -195dp; + + width: 635dp; + height: 386dp; +} + +/* The player's own settings, top left. The combos are 76 dialog units wide at 73, 6 and + 73, 22; a closed one stands as tall as the item height rather than as its dropped extent. */ +#sidelabel { left: 25dp; top: 11dp; width: 75dp; height: 19.5dp; line-height: 19.5dp; } +#colorlabel { left: 25dp; top: 35.375dp; width: 78dp; height: 19.5dp; line-height: 19.5dp; } + +#side, #color { left: 107.5dp; width: 114dp; } +#side { top: 7.75dp; } +#color { top: 33.75dp; } + +#side selectbox { max-height: 146.25dp; } +#color selectbox { max-height: 237.25dp; } + +/* Multiplayer Map, 90 x 14 at 160, 5, and the scenario's own name at 255, 8. */ +#multimap { left: 238dp; top: 6.125dp; width: 135dp; } +#scenarioname { left: 380.5dp; top: 11dp; width: 229.5dp; height: 16.25dp; line-height: 16.25dp; } + +/* "Players:" at 18, 41, the 150 x 67 player list at 18, 56 and the kick button under it. */ +#playerslabel { left: 25dp; top: 64.625dp; width: 85.5dp; height: 19.5dp; line-height: 19.5dp; } +#users { left: 25dp; top: 89dp; width: 225dp; height: 108.875dp; } +#users .row { width: 213dp; } + +#kick { left: 25dp; top: 347.375dp; width: 30dp; height: 29.25dp; line-height: 29.25dp; } + +/* The message log, 256 x 65 at 18, 130, and the chat entry, 256 x 12 at 18, 199. */ +#messages { left: 25dp; top: 209.25dp; width: 384dp; height: 105.625dp; } +#messages .line { width: 372dp; } + +#say { left: 25dp; top: 321.375dp; width: 384dp; height: 19.5dp; line-height: 15.5dp; } + +/* The six track bars, 66 x 12 dialog units at x 227, and their right aligned captions, + 58 x 10 at x 166. The captions never change: every WM_HSCROLL case in the dialog fetched + its label and did nothing with it. */ +#gamespeedlabel, +#aiplayerslabel, +#ailevellabel, +#unitcountlabel, +#techlevellabel, +#creditslabel +{ + left: 247dp; + width: 87dp; + height: 16.25dp; + line-height: 16.25dp; +} + +#gamespeedlabel { top: 43.5dp; } +#aiplayerslabel { top: 69.5dp; } +#ailevellabel { top: 95.5dp; } +#unitcountlabel { top: 121.5dp; } +#techlevellabel { top: 147.5dp; } +#creditslabel { top: 173.5dp; } + +.slider { left: 338.5dp; width: 99dp; height: 19.5dp; } + +#gamespeed { top: 41.875dp; } +#aiplayers { top: 67.875dp; } +#ailevel { top: 93.875dp; } +#unitcount { top: 119.875dp; } +#techlevel { top: 145.875dp; } +#credits { top: 171.875dp; } + +/* The nine check boxes, 108 dialog units wide at x 300 but Allies, which is 105. */ +.check { left: 448dp; width: 162dp; height: 16.25dp; line-height: 16.25dp; } + +#allies { top: 38.625dp; width: 157.5dp; } +#harvtruce { top: 58.125dp; } +#bases { top: 77.625dp; } +#mcv { top: 97.125dp; } +#fog { top: 116.625dp; } +#bridges { top: 136.125dp; } +#crates { top: 155.625dp; } +#shortgame { top: 175.125dp; } +#engineer { top: 194.625dp; } + +/* Cancel, 53 x 14 at 291, 219, and Go!, 54 x 14 at 354, 219. */ +.button { height: 22.75dp; line-height: 22.75dp; } + +#cancel { left: 434.5dp; top: 353.875dp; width: 79.5dp; } +#go { left: 529dp; top: 353.875dp; width: 81dp; } diff --git a/ui/mphost.rml b/ui/mphost.rml new file mode 100644 index 000000000..36f34aa2b --- /dev/null +++ b/ui/mphost.rml @@ -0,0 +1,76 @@ + + + Host a network game + + + + + +
+
Your Side:
+ + +
Your Color:
+ + +
Multiplayer Map
+
{{ scenarioname }}
+ +
Players:
+
+
+
{{ entry.name }}
+
{{ entry.sidename }}
+
{{ entry.mark }}
+
+
+ +
Kick
+ +
+
{{ entry.text }}
+
+ + + +
Game Speed:
+ + +
AI Players:
+ + +
AI Level:
+ + +
Unit Count:
+ + +
Tech Level:
+ + +
Credits:
+ + +
Allies allowed
+
Harvester Truce
+
Bases
+
Re-Deployable MCV
+
Fog Of War
+
Bridges Destroyable
+
Crates
+
Short Game
+
Multi Engineer
+ +
+ +
+ +
[[TXT_CANCEL]]
+
Go!
+
+ +
From c14e06c3e1923b9ecb756375b28c00edf0011178 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 09:19:45 +0100 Subject: [PATCH 131/179] fix(ui): answer the lobby's runner and rebuild its rosters off the window Three defects, each found by running two instances against the lobby rather than by reading it. A pressed button never left the screen. The lobby's answers set Response, which the legacy driver reads after its pump, but UI_Run_Modal returns on a result, and nothing set one: New, Join, Cancel and Go were executed and the runner kept looping. Answer now records both, and the family's own loop clears both before it shows the next screen. The rosters were rebuilt behind a window check. Net2DisplayGameList and _Net2DisplayUsers asked for the top window first and returned when there was none, so on a presentation that is not a window the model was never rebuilt and a game somebody else advertised never reached the list. The rebuild moves above the check, which is where the split belonged: the model is built where the roster is known to have moved, and the rest of the function puts it on the controls. Five more places asked for the top window's identifier where they meant the lobby's screen. The one that matters is Send_Join_Queries, which suppresses its NET_QUERY_GAME while the host screen is up; with no window to ask, a host would have gone on querying for games. Receive_Random_Map_Preview, Send_Preview_To_Guests and the two join-response arms had the same shape. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/netdlg2.cpp | 36 +++++++++++++++++++++++------------- code/ui/uilobby.cpp | 26 ++++++++++++++++++++++---- code/ui/uilobby.h | 1 + 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/code/netdlg2.cpp b/code/netdlg2.cpp index d45911a72..a2143a5d0 100644 --- a/code/netdlg2.cpp +++ b/code/netdlg2.cpp @@ -326,17 +326,22 @@ void Net2DisplayUsers(void) ///
void _Net2DisplayUsers(void) { + if (Lobby_Screen() == NULL) { + return; + } + + // The rows are built before anything is drawn, and before the window is even asked for, + // because this is the point the roster is known to have moved. A presentation that is not + // a window reads the model and would otherwise never be told. + Lobby_Screen()->Build_User_Rows(); + HWND win = WS_Top_Window(); HWND userwin = win ? GetDlgItem(win, IDC_USERS) : NULL; - if (win == NULL || userwin == NULL || Lobby_Screen() == NULL) { + if (win == NULL || userwin == NULL) { return; } - // The rows are built where the session changed rather than here, so what is drawn is - // the model the screen holds. - Lobby_Screen()->Build_User_Rows(); - OwnerDraw::CellData thecell; int topindex = SendDlgItemMessage(win, IDC_USERS, LB_GETTOPINDEX, 0, 0); @@ -478,14 +483,18 @@ void Net2ServiceGameList(void) ///
void Net2DisplayGameList(void) { - HWND window = WS_Top_Window(); - - if (window == NULL || Lobby_Screen() == NULL) { + if (Lobby_Screen() == NULL) { return; } Lobby_Screen()->Build_Game_Rows(); + HWND window = WS_Top_Window(); + + if (window == NULL) { + return; + } + int top = SendDlgItemMessage(window, IDC_GAMELIST, LB_GETTOPINDEX, 0, 0); SendDlgItemMessage(window, IDC_GAMELIST, OD_DISABLEPAINT, 0, 1); @@ -870,6 +879,7 @@ bool Net2Remote_Connect(void) // after the pump has returned. The lobby's own answer is one of the // presenter's, and the other two screens still write theirs directly. screen.Drain(); + screen.Result.reset(); if (screen.Response != UILobbyPresenterClass::RESPONSE_NONE) { _netresponse = Lobby_Response_Identifier(screen.Response); screen.Response = UILobbyPresenterClass::RESPONSE_NONE; @@ -1901,7 +1911,7 @@ void Send_Join_Queries(int gamenow, int playernow, int chatnow, int init) if (!game_timer || gamenow) { game_timer = GAME_QUERY_TIME; - if ((WS_Top_Window_ID() != IDD_MPLAYER_HOST) || gamenow) { + if ((Net2LobbyScreenID() != IDD_MPLAYER_HOST) || gamenow) { memset (&packet, 0, sizeof(GlobalPacketType)); packet.Command = NET_QUERY_GAME; @@ -2109,14 +2119,14 @@ static void Get_Join_Responses(void) } if (Session.GPacket.Command==NET_PREVIEW_MODE) { - if (WS_Top_Window_ID() == IDD_MPLAYER_GUEST) { + if (Net2LobbyScreenID() == IDD_MPLAYER_GUEST) { Receive_Random_Map_Preview(); } continue; } if (Session.GPacket.Command==NET_REQ_PREVIEW) { - if (WS_Top_Window_ID() == IDD_MPLAYER_HOST) { + if (Net2LobbyScreenID() == IDD_MPLAYER_HOST) { Send_Preview_To_Guests(); } continue; @@ -2427,7 +2437,7 @@ static void Get_Join_Responses(void) if (item) { ODMessageBox(item, 0, Net2Callback, 0); } - if ( WS_Top_Window_ID() != IDD_MPLAYER_GAME_LIST ) { + if ( Net2LobbyScreenID() != IDD_MPLAYER_GAME_LIST ) { _netresponse = IDCANCEL; } Send_Join_Queries (0, 0, 1, 0); @@ -2515,7 +2525,7 @@ static void Get_Join_Responses(void) //............................................................... if (i==CurGame) { Clear_Vector (&Session.Players); - if (WS_Top_Window_ID() != IDD_MPLAYER_GAME_LIST && WS_Top_Window_ID() == IDD_MPLAYER_GUEST) { + if (Net2LobbyScreenID() != IDD_MPLAYER_GAME_LIST && Net2LobbyScreenID() == IDD_MPLAYER_GUEST) { _netresponse = 2; } } diff --git a/code/ui/uilobby.cpp b/code/ui/uilobby.cpp index c15a9ab6f..c664f1b8b 100644 --- a/code/ui/uilobby.cpp +++ b/code/ui/uilobby.cpp @@ -53,6 +53,7 @@ #include "netshare.h" #include "preview.h" #include "rules.h" +#include "dbgprint.h" #include "session.h" #include "utf8.h" @@ -787,6 +788,23 @@ void UILobbyPresenterClass::Say(std::string const & text) } +/// +/// Records what the driver loop is being asked to do next, and ends the pass. +/// The answer is the screen's result as well, because the runner returns on a result and the +/// driver reads the answer after it does; the family's own loop clears both before it shows +/// the next screen. +/// +void UILobbyPresenterClass::Answer(ResponseType response) +{ + Response = response; + + UIResult result; + result.Outcome = response == RESPONSE_CANCEL + ? UIResult::OUTCOME_CANCELLED : UIResult::OUTCOME_ACCEPTED; + Result = result; +} + + void UILobbyPresenterClass::Execute(UIIntent const & intent) { if (intent.Action == UI_LOBBY_RENAME) { @@ -852,7 +870,7 @@ void UILobbyPresenterClass::Execute(UIIntent const & intent) // The button goes away until the driver has decided the game may begin, which is what // disabling the window stood for; the driver puts it back when it refuses. CanStart = false; - Response = RESPONSE_GO; + Answer(RESPONSE_GO); return; } @@ -877,17 +895,17 @@ void UILobbyPresenterClass::Execute(UIIntent const & intent) } if (intent.Action == UI_LOBBY_JOIN) { - Response = RESPONSE_JOIN; + Answer(RESPONSE_JOIN); return; } if (intent.Action == UI_LOBBY_NEW) { - Response = RESPONSE_NEW; + Answer(RESPONSE_NEW); return; } if (intent.Action == UI_LOBBY_CANCEL) { - Response = RESPONSE_CANCEL; + Answer(RESPONSE_CANCEL); return; } } diff --git a/code/ui/uilobby.h b/code/ui/uilobby.h index 363de20b1..6340790c3 100644 --- a/code/ui/uilobby.h +++ b/code/ui/uilobby.h @@ -258,6 +258,7 @@ class UILobbyPresenterClass : public UIPresenterClass bool OptionsChanged = false; private: + void Answer(ResponseType response); void Rename(std::string const & name); void Pick_Game(int row); void Say(std::string const & text); From d5e845959dd17bcc728c69e9fcd1ceb3dfd91f2a Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 09:23:01 +0100 Subject: [PATCH 132/179] docs: record the network lobby family as migrated All three screens are extracted and all three have their RmlUi view; disconnect, desync and reconnect are what step 11 still owes. Three notes the family earned: the Fill_List split belongs above the window check rather than below it, or a presentation that is not a window holds a stale model; the driver asks the presenter which screen it is on rather than asking for the top window; and a screen answers with a result as well as a response, because the runner returns on a result. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/UI_DESIGN.md | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index cefe3b7c8..518cee56a 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -1,7 +1,8 @@ # UI system design -Status: in progress. Steps 1 to 10 of the migration plan have landed; nothing -from step 11 onward is implemented. Everything outside the migration plan +Status: in progress. Steps 1 to 10 of the migration plan have landed, and step +11's first change and most of its second; nothing from step 12 onward is +implemented. Everything outside the migration plan remains a proposal informed by source inspection and upstream documentation. This page owns the UI architecture and migration; [Building OpenTS](BUILDING.md) owns build support and [Project @@ -946,19 +947,31 @@ text beyond an ASCII test document. The same rule the text field learned at step 9, one control further on. 11. **Network lobbies** (L, two changes). Host, guest, game list, the `WS_` stack, and `netshare.cpp` as one family; then disconnect, desync, and - reconnect. Packets unchanged. In progress: the family shares one presenter, - `code/ui/uilobby.{h,cpp}`, because the three screens share the session's - game, player and chat rosters and hand the driver one answer between them. - The game list and the guest screen read the view-model and queue intents, - and `Net2Remote_Connect` drains them after its pump; the host screen's - commands are not extracted and still write the driver's response themselves. - No document exists for any of the three. + reconnect. Packets unchanged. All three screens are extracted behind one + presenter, `code/ui/uilobby.{h,cpp}`, because they share the session's game, + player and chat rosters and hand the driver one answer between them, and all + three have their RmlUi view: `ui/gamelist.rml`, `ui/mphost.rml` and + `ui/mpguest.rml`, sharing `ui/lobbybase.rcss` beside `ui/optionsbase.rcss` + and each carrying its own geometry. Disconnect, desync and reconnect are + outstanding. `Net2DisplayGameList` and `_Net2DisplayUsers` are split the way `Fill_List` was: the presenter reads the rosters into the model and the old names put the model on the controls. The host's accepted status is recorded with the roster rather than while painting the row, because it is a fact about the - player rather than about the row. + player rather than about the row. That split belongs above the window + check, not below it: a rebuild that happens only when there is a window to + draw into leaves a presentation that is not a window holding a stale model. + `PMessagePrintf` and the game-option decoder take the same split. + + A document has no window, so the driver asks the presenter which of the + three screens it is on rather than asking for the top window. Five places + in the lobby's protocol asked for a window's identifier where they meant + the screen, the load-bearing one being the query a host must stop sending. + + A screen answers its driver with a result as well as a response, because + the runner returns on a result; a family whose members are opened one after + another clears both before it shows the next screen. 12. **Map generator and WDT** (L). 13. **Retire OwnerDraw** (M). Delete `ownrdraw.cpp`, `windlg.cpp`, the modeless dialog list, the dialog templates, the kill switch, and the From 5e1d8580b41af4aff685369cf666bbea14afde2a Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 09:30:32 +0100 Subject: [PATCH 133/179] fix(net): validate a lobby packet's strings on arrival The in-game path checks a global packet's fixed wire strings for a terminator before it dispatches; the lobby path never did, so a peer could send twelve non-NUL bytes of Name and every comparison, copy and print in the dispatcher would read past the field. Validate_Lobby_Packet applies the same checks at the lobby's own receive point, with a rejection counter beside the in-game one. The command set is not policed, because an unexpected command already falls through the dispatcher's empty final arm. No packet field, size, order or path changed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/netdlg2.cpp | 20 +++++++++++++++++++ code/netglobal.cpp | 50 ++++++++++++++++++++++++++++++++++++++++++++++ code/netglobal.h | 3 +++ 3 files changed, 73 insertions(+) diff --git a/code/netdlg2.cpp b/code/netdlg2.cpp index a2143a5d0..ea7581b3a 100644 --- a/code/netdlg2.cpp +++ b/code/netdlg2.cpp @@ -33,6 +33,7 @@ #include "mplayer.h" #include "msgbox.h" #include "netdlg.h" +#include "netglobal.h" #include "netshare.h" #include "newmenu.h" #include "ownrdraw.h" @@ -2048,6 +2049,19 @@ bool Process_Global_Packet(GlobalPacketType *packet, IPXAddressClass *address) } /* end of Process_Global_Packet */ +static NetGlobal::RejectionCounters LobbyPacketRejections; + + +/// Records a rejected lobby global packet. +static void Record_Lobby_Packet_Rejection(NetGlobal::DecodeError error) +{ + NetGlobal::RejectionRecord const record = LobbyPacketRejections.Record(error); + if (record.ShouldLog) { + DebugString("Lobby global packet drop [%s]: %u\n", NetGlobal::Error_Name(error), record.Count); + } +} + + /*********************************************************************************************** * Get_Join_Responses -- sends queries for the Join Dialog * * * @@ -2110,6 +2124,12 @@ static void Get_Join_Responses(void) continue; } + NetGlobal::DecodeError const admission = NetGlobal::Validate_Lobby_Packet(Session.GPacket, static_cast(Session.GPacketlen)); + if (admission != NetGlobal::DecodeError::NONE) { + Record_Lobby_Packet_Rejection(admission); + continue; + } + //------------------------------------------------------------------------ // If we're joined in a game, handle the packet in a standard way; otherwise, // don't answer standard queries. diff --git a/code/netglobal.cpp b/code/netglobal.cpp index 1f9e4d394..1ed170b0d 100644 --- a/code/netglobal.cpp +++ b/code/netglobal.cpp @@ -193,6 +193,54 @@ namespace NetGlobal } + /// + /// Validates a lobby global packet before dispatch. + /// The lobby's handlers copy, compare and print the packet's fixed wire strings, and every + /// one of those reads runs off the end of its field when a peer omits the terminator. The + /// command set is deliberately not policed here: a lobby command this routine did not + /// expect already falls through the dispatcher's final empty arm. + /// + DecodeError Validate_Lobby_Packet(GlobalPacketType const & packet, std::size_t packet_length) + { + if (packet_length != PACKET_SIZE) { + return(DecodeError::INVALID_LENGTH); + } + + // Every lobby sender fills Name from a terminated handle, and most of the dispatcher's + // arms compare or copy it, so it is required of all of them. + if (!Has_Terminator(packet.Name, sizeof(packet.Name))) { + return(DecodeError::UNTERMINATED_NAME); + } + + switch (packet.Command) { + case NET_ANSWER_PLAYER: + case NET_QUERY_JOIN: + if (!Has_Terminator(packet.Serial, sizeof(packet.Serial))) { + return(DecodeError::UNTERMINATED_SERIAL); + } + break; + + case NET_MESSAGE: + if (!Has_Terminator(packet.Message.Buf, sizeof(packet.Message.Buf))) { + return(DecodeError::UNTERMINATED_MESSAGE); + } + break; + + case NET_PUB_GAMEOPT: + case NET_PRIV_GAMEOPT: + if (!Has_Terminator(packet.Options.Buf, sizeof(packet.Options.Buf))) { + return(DecodeError::UNTERMINATED_OPTIONS); + } + break; + + default: + break; + } + + return(DecodeError::NONE); + } + + /// Counts a rejection and selects sparse diagnostics. RejectionRecord RejectionCounters::Record(DecodeError error) noexcept { @@ -228,6 +276,8 @@ namespace NetGlobal case DecodeError::SENDER_NOT_MEMBER: return("sender is not a session member"); case DecodeError::UNTERMINATED_NAME: return("unterminated player name"); case DecodeError::UNTERMINATED_MESSAGE: return("unterminated message"); + case DecodeError::UNTERMINATED_SERIAL: return("unterminated serial number"); + case DecodeError::UNTERMINATED_OPTIONS: return("unterminated game options"); case DecodeError::INVALID_COLOR: return("invalid session-member color"); case DecodeError::INVALID_PROGRESS: return("invalid progress value"); case DecodeError::INVALID_KICK_PLAYER: return("invalid kick player"); diff --git a/code/netglobal.h b/code/netglobal.h index c9ccd29c1..ecb967ffe 100644 --- a/code/netglobal.h +++ b/code/netglobal.h @@ -27,6 +27,8 @@ namespace NetGlobal SENDER_NOT_MEMBER, UNTERMINATED_NAME, UNTERMINATED_MESSAGE, + UNTERMINATED_SERIAL, + UNTERMINATED_OPTIONS, INVALID_COLOR, INVALID_PROGRESS, INVALID_KICK_PLAYER, @@ -94,5 +96,6 @@ namespace NetGlobal void Initialize_Packet(GlobalPacketType & packet, NetCommandType command) noexcept; EndpointResolution Resolve_Sender(Endpoint const & sender, std::span roster) noexcept; DecodeError Validate_In_Game_Packet(GlobalPacketType const & packet, std::size_t packet_length, ValidationContext const & context); + DecodeError Validate_Lobby_Packet(GlobalPacketType const & packet, std::size_t packet_length); char const * Error_Name(DecodeError error) noexcept; } From 713bdeb08fc51c8b4f99ebf9b0e4692668d12e80 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 09:31:04 +0100 Subject: [PATCH 134/179] fix(net): bound the lobby's copies of a peer's name and serial Four receive-side copies took a remote peer's fixed wire string into a destination of the same size with strcpy, so a field whose terminator sat outside it wrote past the end: the game name on NET_CONFIRM_JOIN, the chat node's name on NET_CHAT_ANNOUNCE, and the player serial on NET_ANSWER_PLAYER and NET_QUERY_JOIN. They now use the bounded copy their neighbours in the same handlers already used. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/netdlg2.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/code/netdlg2.cpp b/code/netdlg2.cpp index ea7581b3a..2c40fee71 100644 --- a/code/netdlg2.cpp +++ b/code/netdlg2.cpp @@ -2296,7 +2296,7 @@ static void Get_Join_Responses(void) //.................................................................. who = new NodeNameType; UTF8::Copy(who->Name, sizeof(who->Name), Session.GPacket.Name); - strcpy(who->Player.Serial, Session.GPacket.Serial); + UTF8::Copy(who->Player.Serial, sizeof(who->Player.Serial), Session.GPacket.Serial); who->Address = Session.GAddress; who->Player.House = Session.GPacket.PlayerInfo.House; who->Player.Color = Session.GPacket.PlayerInfo.Color; @@ -2344,7 +2344,7 @@ static void Get_Join_Responses(void) if (Session.GPacket.Command==NET_CONFIRM_JOIN) { if ( JoinState != JOIN_CONFIRMED) { JoinState = JOIN_CONFIRMED; - strcpy (Session.GameName, Session.GPacket.Name); + UTF8::Copy(Session.GameName, sizeof(Session.GameName), Session.GPacket.Name); Session.House = Session.GPacket.PlayerInfo.House; Session.ColorIdx = Session.GPacket.PlayerInfo.Color; @@ -2696,7 +2696,7 @@ static void Get_Join_Responses(void) else { for (i = 0; i < Session.Chat.Count(); i++) { if (Session.Chat[i]->Address==Session.GAddress) { - strcpy (Session.Chat[i]->Name, Session.GPacket.Name); + UTF8::Copy(Session.Chat[i]->Name, sizeof(Session.Chat[i]->Name), Session.GPacket.Name); Session.Chat[i]->Chat.LastTime = TickCount; Session.Chat[i]->Chat.LastChance = 0; Session.Chat[i]->Chat.Color = Session.GPacket.Chat.Color; @@ -2930,7 +2930,7 @@ static void Get_Join_Responses(void) UTF8::Copy(who->Name, sizeof(who->Name), Session.GPacket.Name); who->Address = Session.GAddress; who->Player.House = Session.GPacket.PlayerInfo.House; - strcpy(who->Player.Serial, Session.GPacket.Serial); + UTF8::Copy(who->Player.Serial, sizeof(who->Player.Serial), Session.GPacket.Serial); //.................................................................. // Set player's color; if requested color isn't used, give it to him; From 0433612f2a6d87536e249a87419f81cb82094d1e Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 09:31:58 +0100 Subject: [PATCH 135/179] fix(net): bound what a game-options packet writes into the session The game-options string a peer sends is parsed into fixed session fields and one of the copies was unbounded: the scenario description went through strcpy into a 44-byte field from a token that may be almost the whole 544-byte packet buffer. The scenario file name and digest were bounded but could be left without a terminator, which the strcpy of the file name that follows then reads past. All three use the bounded copy now. A well-formed sender's values are shorter than every destination, so nothing legitimate truncates. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/netshare.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/netshare.cpp b/code/netshare.cpp index 00b0f17ba..72951ea41 100644 --- a/code/netshare.cpp +++ b/code/netshare.cpp @@ -862,7 +862,7 @@ bool DecodePubGameopt(char * options, char * name) if (stricmp(Session.ScenarioFileName, token) != 0) { same_scenario = false; } - strncpy(Session.ScenarioFileName, token, sizeof(Session.ScenarioFileName)); + UTF8::Copy(Session.ScenarioFileName, sizeof(Session.ScenarioFileName), token); strcpy(Scen->ScenarioName, Session.ScenarioFileName); } @@ -871,7 +871,7 @@ bool DecodePubGameopt(char * options, char * name) if (strcmp(Session.ScenarioDigest, digest) != 0) { same_scenario = false; } - strncpy(Session.ScenarioDigest, digest, sizeof(Session.ScenarioDigest)-1); + UTF8::Copy(Session.ScenarioDigest, sizeof(Session.ScenarioDigest), digest); } if (!same_scenario || strlen(Session.Options.ScenarioDescription) == 0) { @@ -886,7 +886,7 @@ bool DecodePubGameopt(char * options, char * name) } } if (!found && scenario_description != NULL) { - strcpy(Session.Options.ScenarioDescription, scenario_description); + UTF8::Copy(Session.Options.ScenarioDescription, sizeof(Session.Options.ScenarioDescription), scenario_description); } if (stricmp(Session.ScenarioFileName, RANDOM_MAP_FILE_NAME) == 0) { strcpy(Session.Options.ScenarioDescription, Fetch_String(TXT_RANDOM_MAP_DESCRIPTION)); From 31a326e905efdeba8aeac5d2f8a7d635bdf77831 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 09:32:28 +0100 Subject: [PATCH 136/179] fix(net): keep a peer's game name out of a format string The two chat lines announcing another machine's game were composed into an 80-byte stack buffer by wsprintf, whose substitute here caps at 1024 bytes like the Win32 original, and the result was then passed as the format string to PMessagePrintf. A peer's name reaching a format string is its own hazard, whatever bounds the buffers have. The lines are composed with snprintf against the buffer's own size and printed through a literal format. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/netdlg2.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/code/netdlg2.cpp b/code/netdlg2.cpp index 2c40fee71..56a6dfe0f 100644 --- a/code/netdlg2.cpp +++ b/code/netdlg2.cpp @@ -2188,16 +2188,16 @@ static void Get_Join_Responses(void) //............................................................ if (JoinState < JOIN_CONFIRMED) { if (Session.Games[i]->Game.IsOpen) { - wsprintf(txt,Fetch_String(TXT_S_FORMED_NEW_GAME), + snprintf(txt, sizeof(txt), Fetch_String(TXT_S_FORMED_NEW_GAME), Session.GPacket.Name); Sound_Effect(Rule->GameForming); } else { - wsprintf(txt,Fetch_String(TXT_GAME_NOW_IN_PROGRESS), + snprintf(txt, sizeof(txt), Fetch_String(TXT_GAME_NOW_IN_PROGRESS), Session.GPacket.Name); Sound_Effect(Rule->GameClosed); } - PMessagePrintf(ColorSystem, txt); + PMessagePrintf(ColorSystem, "%s", txt); } } break; @@ -2236,9 +2236,9 @@ static void Get_Join_Responses(void) // now available. //.................................................................. if (Session.GPacket.GameInfo.IsOpen && JoinState < JOIN_CONFIRMED) { - wsprintf(txt,Fetch_String(TXT_S_FORMED_NEW_GAME), + snprintf(txt, sizeof(txt), Fetch_String(TXT_S_FORMED_NEW_GAME), Session.GPacket.Name); - PMessagePrintf(ColorSystem, txt); + PMessagePrintf(ColorSystem, "%s", txt); Sound_Effect(Rule->GameForming); } From 62550eb953cc5277fa17e7530be9b2b54bf699e8 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 09:34:25 +0100 Subject: [PATCH 137/179] fix(net): keep a scenario download inside its reassembly buffer A file chunk names its own place in the buffer being reassembled and nothing checked it. BlockNumber indexed the arrival flags and multiplied into the buffer offset unchecked, and BlockLength was the memcpy length unchecked, so a host could write anywhere past a buffer sized from the length it had itself declared. A chunk outside the buffer is now discarded and the timeout still governs the loop. Two name copies in the same transfer go with it: the short file name a NET_FILE_INFO packet carries is documented as not necessarily terminated and was read with strcpy, so it is taken by length now; and the sending side wrote a scenario name into that same 13-byte field with strcpy. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/sendfile.cpp | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/code/sendfile.cpp b/code/sendfile.cpp index 118af61f4..4cc06348e 100644 --- a/code/sendfile.cpp +++ b/code/sendfile.cpp @@ -48,8 +48,11 @@ #include "progress.h" #include "session.h" #include "stimer.h" +#include "utf8.h" #include +#include +#include bool Receive_Remote_File ( char *file_name, unsigned int file_length, bool show_progress); bool Send_Remote_File ( char const *file_name ); @@ -120,7 +123,14 @@ bool Get_File_From_Host(char *return_name, bool show_progress) //DebugString ("RA95 - Got packet from host\n"); if (net_receive_packet.Command == NET_FILE_INFO && sender_address == Session.HostAddress) { - strcpy (return_name, net_receive_packet.ScenarioInfo.ShortFileName); + // The field is documented as not necessarily terminated, so the name is taken + // from it by length rather than by a terminator the host may not have sent. + char const * const short_name = net_receive_packet.ScenarioInfo.ShortFileName; + std::size_t const short_length = static_cast( + std::find(short_name, short_name + sizeof(net_receive_packet.ScenarioInfo.ShortFileName), '\0') + - short_name); + std::memcpy(return_name, short_name, short_length); + return_name[short_length] = '\0'; file_length = net_receive_packet.ScenarioInfo.FileLength; DebugString("Host responded with file info\n"); DebugString("File name is %s\n", return_name); @@ -247,14 +257,23 @@ bool Receive_Remote_File ( char *file_name, unsigned int file_length, bool show_ if (receive_packet->Command == NET_FILE_CHUNK && sender_address == Session.HostAddress){ - char *flag = &block_received[receive_packet->BlockNumber]; - if (!block_received[receive_packet->BlockNumber]) { + // The block index and length name where in the reassembly buffer this chunk + // lands, and the host is not trusted to keep either inside it. + std::size_t const block_offset = static_cast(MAX_SEND_FILE_PACKET_SIZE) * receive_packet->BlockNumber; + bool const block_fits = receive_packet->BlockNumber < total_blocks + && receive_packet->BlockLength <= sizeof(receive_packet->RawData) + && block_offset <= file_length + && receive_packet->BlockLength <= file_length - block_offset; + if (!block_fits) { + DebugString("Discarding file chunk %u of length %u\n", (unsigned)receive_packet->BlockNumber, (unsigned)receive_packet->BlockLength); + } else if (!block_received[receive_packet->BlockNumber]) { + char *flag = &block_received[receive_packet->BlockNumber]; *flag = true; received_count++; progress += 100; response_timer = RESPONSE_TIMEOUT/2; DebugString("Received file chunk %d\n", receive_packet->BlockNumber); - memcpy (file_buffer + (MAX_SEND_FILE_PACKET_SIZE) * receive_packet->BlockNumber, + memcpy (file_buffer + block_offset, receive_packet->RawData, receive_packet->BlockLength); if (show_progress){ @@ -358,7 +377,7 @@ bool Send_Remote_File ( char const *file_name, bool send_to_all, bool show_progr ** Send the file info to the remote machine(s) */ net_file_info.Command = NET_FILE_INFO; - strcpy (net_file_info.ScenarioInfo.ShortFileName, file_name); + UTF8::Copy(net_file_info.ScenarioInfo.ShortFileName, sizeof(net_file_info.ScenarioInfo.ShortFileName), file_name); // DebugString( "Uploading '%s'\n", file_name ); // DebugString( "ShortFileName is '%s'\n", net_file_info.ScenarioInfo.ShortFileName ); net_file_info.ScenarioInfo.FileLength = file_length; From 962393b91a9001826d3bb1ae7af96df894e849d1 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 09:37:18 +0100 Subject: [PATCH 138/179] fix(net): check a downloaded map preview against its own extents A paletted preview block arrives from the host and declared its width, height and color count with nothing checking any of them. Create_Preview_Surface allocated a surface from those numbers and then walked width by height index bytes off the end of the decompressed buffer, and the palette lookup indexed past the palette as well. The block is now measured against the bytes that came with it and refused if it does not fit, and the receive path checks the declared decompressed length before allocating from it. The lobby's name field also offered sixteen characters where Session.Handle holds eleven, so four of them were typed and thrown away. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/netshare.cpp | 31 +++++++++++++++++++++++++++---- code/preview.cpp | 29 ++++++++++++++++++++++++++--- code/preview.h | 2 +- code/ui/uilobby.h | 7 ++++--- ui/gamelist.rml | 2 +- 5 files changed, 59 insertions(+), 12 deletions(-) diff --git a/code/netshare.cpp b/code/netshare.cpp index 72951ea41..83e423a11 100644 --- a/code/netshare.cpp +++ b/code/netshare.cpp @@ -1435,6 +1435,11 @@ void Rebuild_Network_Map_Preview(void) /// ready, the compressed preview file is downloaded, and the decompressed image becomes the /// preview shown in the multiplayer dialog. ///
+// The largest paletted preview block a host may declare, which is far above the picture the +// game itself makes and well below a length that could not be allocated. +static int const MAX_PREVIEW_BLOCK_SIZE = 4 * 1024 * 1024; + + void Receive_Random_Map_Preview(void) { Ipx.Set_Timing(50, -1, 5000); @@ -1468,24 +1473,42 @@ void Receive_Random_Map_Preview(void) DebugString("Loading the compressed preview image\n"); CDFileClass file(preview_name); int size = file.Size(); + + // The file came from the host, so its declared decompressed length is checked before it + // is used to size anything. + if (size <= (int)sizeof(int)) { + DebugString("Preview file is too short to carry a length\n"); + Ipx.Set_Timing(TIMER_SECOND / 2, -1, 10 * TIMER_SECOND); + return; + } + char * buffer = new char[size]; file.Read(buffer, size); int preview_size = ((int *)buffer)[0]; + if (preview_size <= 0 || preview_size > MAX_PREVIEW_BLOCK_SIZE) { + DebugString("Preview file declares an unusable length of %d bytes\n", preview_size); + delete [] buffer; + Ipx.Set_Timing(TIMER_SECOND / 2, -1, 10 * TIMER_SECOND); + return; + } + DebugString("Decompressing the preview image\n"); - BufferStraw bstraw(&((int *)buffer)[1], size); + BufferStraw bstraw(&((int *)buffer)[1], size - (int)sizeof(int)); LZOStraw lzostraw(LZOStraw::DECOMPRESS); lzostraw.Get_From(&bstraw); - char * preview = new char[2 * preview_size]; - lzostraw.Get(preview, preview_size); + char * preview = new char[preview_size]; + int const decompressed = lzostraw.Get(preview, preview_size); DebugString("Creating the new preview surface\n"); if (MultiplayerMapPreview) { delete MultiplayerMapPreview; } MultiplayerMapPreview = new MapPreviewClass; - MultiplayerMapPreview->Create_Preview_Surface(preview); + if (!MultiplayerMapPreview->Create_Preview_Surface(preview, decompressed)) { + DebugString("Preview block does not describe a usable picture\n"); + } InvalidateRect(WS_Top_Window(), NULL, FALSE); DebugString("Cleaning up the temporary decompression buffers\n"); diff --git a/code/preview.cpp b/code/preview.cpp index a7ba40107..c5070b013 100644 --- a/code/preview.cpp +++ b/code/preview.cpp @@ -544,8 +544,19 @@ unsigned * MapPreviewClass::Create_Paletted_Preview(int colorcount, int & size) /// arrives already packed, rather than being rendered from the map that is loaded. /// /// Pointer to the paletted preview block to expand. -void MapPreviewClass::Create_Preview_Surface(char * buffer) +// The palette a paletted preview carries is indexed by one byte per pixel, so a count above +// this one could never be reached and is taken as a malformed block. +static int const MAX_PREVIEW_COLORS = 256; + + +bool MapPreviewClass::Create_Preview_Surface(char * buffer, int length) { + // A block that arrived from another machine declares its own extents, so they are + // checked against the bytes that came with it before anything is read through them. + if (buffer == NULL || length < (int)(sizeof(Header) + sizeof(int))) { + return(false); + } + int * header = (int *)buffer; int width = *header++; @@ -553,20 +564,32 @@ void MapPreviewClass::Create_Preview_Surface(char * buffer) int colorcount = *header; unsigned short *palette = (unsigned short *)header; + if (width <= 0 || height <= 0 || colorcount <= 0 || colorcount > MAX_PREVIEW_COLORS) { + return(false); + } + + long long const block_offset = (long long)colorcount * (long long)sizeof(unsigned short) + (long long)sizeof(Header) + (long long)sizeof(int); + if (block_offset + (long long)width * (long long)height > (long long)length) { + return(false); + } + if (SurfacePtr != NULL) { delete SurfacePtr; } SurfacePtr = new DSurface(width, height); SurfacePtr->Fill(TBLACK); - int offset = (colorcount * sizeof(unsigned short)) + sizeof(Header) + sizeof(int); + int offset = (int)block_offset; unsigned char * indexptr = (unsigned char *)buffer + offset; for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { - unsigned short entry = palette[*indexptr++ + 2]; + int const index = *indexptr++; + unsigned short entry = palette[(index < colorcount ? index : colorcount - 1) + 2]; int color = DSurface::Build_Hicolor_Pixel((entry >> 4) & 0x00F0, entry & 0x00F0, 16 * (entry & 0x000F)); SurfacePtr->Put_Pixel_Clip(Point2D(x, y), color, SurfacePtr->Get_Rect()); } } + + return(true); } diff --git a/code/preview.h b/code/preview.h index f63ba77fb..2b30c3f1c 100644 --- a/code/preview.h +++ b/code/preview.h @@ -34,7 +34,7 @@ class MapPreviewClass void Blit_Preview(HWND window); unsigned * Create_Paletted_Preview(int, int & size); - void Create_Preview_Surface(char * buffer); + bool Create_Preview_Surface(char * buffer, int length); XSurface * Get_Preview_Surface(void) { return(SurfacePtr); } private: diff --git a/code/ui/uilobby.h b/code/ui/uilobby.h index 6340790c3..26d6a5d42 100644 --- a/code/ui/uilobby.h +++ b/code/ui/uilobby.h @@ -183,9 +183,10 @@ class UILobbyPresenterClass : public UIPresenterClass std::string Handle; - // The longest handle the name field accepts, in bytes, which is the limit the - // dialog set on its edit control. - enum { HANDLE_LIMIT = 16 }; + // The longest handle the name field accepts, in bytes. Session.Handle is + // MPLAYER_NAME_MAX bytes and travels in a packet field of that size, so anything + // longer is thrown away rather than sent. + enum { HANDLE_LIMIT = 11 }; int Color = 0; diff --git a/ui/gamelist.rml b/ui/gamelist.rml index 5fea4c1c6..feb3fd671 100644 --- a/ui/gamelist.rml +++ b/ui/gamelist.rml @@ -8,7 +8,7 @@
Your Name:
- +
Games:
From 65b45598bc91b9bfc034b2f8043ca98edc40aafd Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 09:40:11 +0100 Subject: [PATCH 139/179] fix(net): refuse a lobby seat outside the house and color tables A peer names the house and color it wants to play and nothing bounded either. Both are carried into the roster and, when the scenario starts, index the house type list and the color-used table directly, so a join request could write and dereference outside both. The lobby refuses a join, an answer or a confirmation whose seat is out of range, the encoded game options ignore a house they cannot seat, and the free-color search no longer answers with a color the table does not hold. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/netdlg2.cpp | 34 ++++++++++++++++++++++++++++++++++ code/netglobal.cpp | 1 + code/netglobal.h | 1 + 3 files changed, 36 insertions(+) diff --git a/code/netdlg2.cpp b/code/netdlg2.cpp index 56a6dfe0f..17de1938c 100644 --- a/code/netdlg2.cpp +++ b/code/netdlg2.cpp @@ -59,6 +59,7 @@ static int Request_To_Join(int join_index); static void Unjoin_Game(int game_index); static void Get_Join_Responses(void); +static bool Lobby_Seat_Is_Valid(int house, int color); INT_PTR CALLBACK MPlayer_Guest_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); INT_PTR CALLBACK MPlayer_Game_List_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); @@ -267,9 +268,27 @@ void Select_Country_In_Box(HWND combo, int country) /// The player doing the asking, so that its own color is not counted /// against it. /// Returns with the color the player should use. +/// +/// Is a seat a peer asked for one the game can actually give it? +/// The house indexes the house type list and the color indexes the color table when the +/// scenario starts, and both arrive from another machine. +/// +static bool Lobby_Seat_Is_Valid(int house, int color) +{ + return(house >= 0 && house < HouseTypes.Count() && color >= 0 && color < MAX_MPLAYER_COLORS); +} + + int Net2FirstFreeColor(int reqcolor, int index) { int color; + + // The requested color may have come off the network, and every later step of this + // routine keeps a color in range only because the first one is. + if (reqcolor < 0 || reqcolor >= MAX_MPLAYER_COLORS) { + reqcolor = 0; + } + while (1) { int taken = 0; color = reqcolor; @@ -647,6 +666,10 @@ int Net2SetHouseAndColor(char *who, int house, int color) int offset = -1; int retval = 0; + if (!Lobby_Seat_Is_Valid(house, color)) { + return(0); + } + for (int i = 0; i < Session.Players.Count(); i++) { if (strcmp(Session.Players[i]->Name, who) == 0) { offset = i; @@ -2130,6 +2153,14 @@ static void Get_Join_Responses(void) continue; } + if (Session.GPacket.Command == NET_QUERY_JOIN || Session.GPacket.Command == NET_ANSWER_PLAYER + || Session.GPacket.Command == NET_CONFIRM_JOIN) { + if (!Lobby_Seat_Is_Valid(Session.GPacket.PlayerInfo.House, Session.GPacket.PlayerInfo.Color)) { + Record_Lobby_Packet_Rejection(NetGlobal::DecodeError::INVALID_HOUSE); + continue; + } + } + //------------------------------------------------------------------------ // If we're joined in a game, handle the packet in a standard way; otherwise, // don't answer standard queries. @@ -2493,6 +2524,9 @@ static void Get_Join_Responses(void) tok = strtok(opts, ","); if (tok) { newhouse = atol(tok); + if (newhouse < 0 || newhouse >= HouseTypes.Count()) { + newhouse = oldhouse; + } Session.Players[i]->Player.House = newhouse; } diff --git a/code/netglobal.cpp b/code/netglobal.cpp index 1ed170b0d..cb309336c 100644 --- a/code/netglobal.cpp +++ b/code/netglobal.cpp @@ -279,6 +279,7 @@ namespace NetGlobal case DecodeError::UNTERMINATED_SERIAL: return("unterminated serial number"); case DecodeError::UNTERMINATED_OPTIONS: return("unterminated game options"); case DecodeError::INVALID_COLOR: return("invalid session-member color"); + case DecodeError::INVALID_HOUSE: return("invalid session-member house"); case DecodeError::INVALID_PROGRESS: return("invalid progress value"); case DecodeError::INVALID_KICK_PLAYER: return("invalid kick player"); case DecodeError::SELF_KICK: return("self kick proposal"); diff --git a/code/netglobal.h b/code/netglobal.h index ecb967ffe..1dc5766fc 100644 --- a/code/netglobal.h +++ b/code/netglobal.h @@ -30,6 +30,7 @@ namespace NetGlobal UNTERMINATED_SERIAL, UNTERMINATED_OPTIONS, INVALID_COLOR, + INVALID_HOUSE, INVALID_PROGRESS, INVALID_KICK_PLAYER, SELF_KICK, From 6d17dae4c7c8d300a033c724da452c89bf911f3b Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 09:59:52 +0100 Subject: [PATCH 140/179] fix(net): admit a lobby peer once when it answers from two addresses The chat and player rosters recognised a machine by the address its packet came from, so a peer whose packets reach the lobby from more than one address was admitted once per address. Measured with two instances on one machine: the same PlayerB, carrying the same announcement ID, arrived from 192.168.50.98 and from 127.0.0.1 and appeared twice in the game list. The second entry then held the colour the player had asked for, so the host gave him another one and the guest was told his colour was taken. A chat announcement names its sender, and the handler already reads that ID to recognise its own packets; the node now keeps it and matches on it. The player roster matches the name as well, which the join path already treats as unique because it refuses a duplicate one. The ID fits the union's existing slack, so the node's size does not move. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/netdlg2.cpp | 12 ++++++++++-- code/session.h | 7 +++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/code/netdlg2.cpp b/code/netdlg2.cpp index 17de1938c..5b5f95116 100644 --- a/code/netdlg2.cpp +++ b/code/netdlg2.cpp @@ -2295,7 +2295,11 @@ static void Get_Join_Responses(void) // house into the existing entry, in case they've changed it without // our knowledge; set the 'found' flag so we won't create a new entry. //.................................................................. - if (Session.Players[i]->Address==Session.GAddress) { + // The name settles it as well as the address, because the join path + // keys on the name and refuses a duplicate one, so a player already + // on the roster is this player however many addresses he answers from. + if (Session.Players[i]->Address==Session.GAddress + || !strcmp(Session.Players[i]->Name, Session.GPacket.Name)) { found = 1; break; } @@ -2729,7 +2733,10 @@ static void Get_Join_Responses(void) //..................................................................... else { for (i = 0; i < Session.Chat.Count(); i++) { - if (Session.Chat[i]->Address==Session.GAddress) { + // The announcement names its sender, and that identifies the machine + // however many addresses its packets reach us from. + if (Session.Chat[i]->Chat.ID == Session.GPacket.Chat.ID + || Session.Chat[i]->Address==Session.GAddress) { UTF8::Copy(Session.Chat[i]->Name, sizeof(Session.Chat[i]->Name), Session.GPacket.Name); Session.Chat[i]->Chat.LastTime = TickCount; Session.Chat[i]->Chat.LastChance = 0; @@ -2749,6 +2756,7 @@ static void Get_Join_Responses(void) who->Chat.LastTime = TickCount; who->Chat.LastChance = 0; who->Chat.Color = Session.GPacket.Chat.Color; + who->Chat.ID = Session.GPacket.Chat.ID; Session.Chat.Add (who); } diff --git a/code/session.h b/code/session.h index 40aa14376..86391286b 100644 --- a/code/session.h +++ b/code/session.h @@ -234,6 +234,13 @@ struct NodeNameType { unsigned int LastTime; // last time we heard from this guy unsigned char LastChance; // we're about to remove him from the list int Color; // chat player's color + + /* + * This is the sender's own UniqueID out of the announcement that created this + * node. It identifies the machine whatever address its packets arrive from, and + * it fits in the union's existing slack, so the node's size does not move. + */ + unsigned int ID; } Chat; }; From dec555d1cafdab581020cf9d2b98a4750a2d8db6 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 09:59:52 +0100 Subject: [PATCH 141/179] fix(ui): show the lobby screen the join protocol moved to The runner picks its document from the screen the presenter names when it is entered and then loops until the presenter has a result. A confirmed join moves the family to the guest screen from inside the presenter's service and produces no result, so the runner kept the game list up and mpguest.rml was never shown. The family now suspends when the screen it is running moves, which is the hook step 8 added for a screen that steps aside. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/ui/uilobby.cpp | 2 ++ code/ui/uilobby.h | 13 +++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/code/ui/uilobby.cpp b/code/ui/uilobby.cpp index c664f1b8b..6bfea4aed 100644 --- a/code/ui/uilobby.cpp +++ b/code/ui/uilobby.cpp @@ -1522,6 +1522,7 @@ UIResult UI_Lobby_Run(UILobbyPresenterClass & presenter) // marks the presenter closing and a marked presenter drains nothing. presenter.Result.reset(); presenter.IsClosing = false; + presenter.Running = presenter.Showing; (*slot)->Sync(); @@ -1540,5 +1541,6 @@ UIResult UI_Lobby_Run(UILobbyPresenterClass & presenter) (*slot)->Sync(); } + presenter.Running = UILobbyPresenterClass::SCREEN_NONE; return(presenter.Result.value_or(UIResult{})); } diff --git a/code/ui/uilobby.h b/code/ui/uilobby.h index 26d6a5d42..06e3c3bee 100644 --- a/code/ui/uilobby.h +++ b/code/ui/uilobby.h @@ -143,8 +143,17 @@ class UILobbyPresenterClass : public UIPresenterClass virtual void Service(void) override; // The scenario picker draws where the host screen is, so the host screen is stepped - // aside for it rather than run underneath. - virtual bool Suspends(void) const override { return(Pending != SUB_NONE); } + // aside for it rather than run underneath. The family also steps aside when the + // protocol moves it to another of its three screens, because the runner picks the + // document once and a join is confirmed from inside the service. + virtual bool Suspends(void) const override + { + return(Pending != SUB_NONE || (Running != SCREEN_NONE && Running != Showing)); + } + + // The screen the runner is holding a document open for, which the runner sets and + // clears around itself. + ScreenType Running = SCREEN_NONE; // The rosters the lobby opens with, which the game list dialog built as it was // created: the player's own chat entry and the lobby's own game entry. From 0b97b97d86916f76b4da6385d40bbf188e61f8e9 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 10:03:20 +0100 Subject: [PATCH 142/179] docs: record the lobby's packet checks and its two join faults Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/UI_DESIGN.md | 15 +++++++++++++++ manual/changes/lobby-packet-validation.md | 12 ++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 manual/changes/lobby-packet-validation.md diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index 518cee56a..3dcca416a 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -955,6 +955,21 @@ text beyond an ASCII test document. and each carrying its own geometry. Disconnect, desync and reconnect are outstanding. + A lobby screen changes without a result, so the runner has to be told. The + join protocol moves the family from the game list to the guest screen from + inside the presenter's service, and `UI_Run_Modal` returns only on a result + or a suspension, so the runner held the game list open and the guest + document was never reached. The family suspends when the screen it is + running moves, which is the hook step 8 added for a screen that steps aside. + + A peer is recognised by what it says it is, not by where its packet came + from. Both lobby rosters keyed on the source address, so a machine whose + packets arrive from more than one address was admitted twice, and the second + entry held the color the player had asked for, so the host gave him another + one. A chat announcement carries its sender's identifier and the node keeps + it; a player is matched on the name the join path already refuses to + duplicate. + `Net2DisplayGameList` and `_Net2DisplayUsers` are split the way `Fill_List` was: the presenter reads the rosters into the model and the old names put the model on the controls. The host's accepted status is recorded with the diff --git a/manual/changes/lobby-packet-validation.md b/manual/changes/lobby-packet-validation.md new file mode 100644 index 000000000..b5c1997dc --- /dev/null +++ b/manual/changes/lobby-packet-validation.md @@ -0,0 +1,12 @@ +--- +title: Check a lobby packet before acting on it +category: fix +release: 0.2.0 +targets: +- type: system + id: network-packet-validation + effect: changed +credit: [OpenTS contributors] +--- + +A global packet that arrives while the network lobby is up is checked for a whole packet and for a terminator on every fixed wire string its handlers read, which is what an in-game packet already got. A packet that fails is counted and dropped. The lobby also refuses a join whose house or color falls outside the tables they index, ignores a game-options string longer than the field it is written into, and keeps a scenario download inside the buffer it was given. Before, a peer could make the lobby read and write past those fields. From 1c648abb74fcc844aba7d2285fd0ff8d11a238cd Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 10:24:02 +0100 Subject: [PATCH 143/179] fix(ui): let the lobby's guest leave the screen the network answered Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/netdlg2.cpp | 22 ++++++++++++++++++---- code/ui/uilobby.cpp | 1 + code/ui/uilobby.h | 7 ++++++- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/code/netdlg2.cpp b/code/netdlg2.cpp index 5b5f95116..dc795a744 100644 --- a/code/netdlg2.cpp +++ b/code/netdlg2.cpp @@ -212,6 +212,20 @@ static int Lobby_Response_Identifier(UILobbyPresenterClass::ResponseType respons } +/// +/// Answers the lobby driver from the network rather than from a button. +/// The runner returns on a result or a suspension, so a screen answered from inside its own +/// service steps aside; otherwise the driver never gets its pass back to act on the answer. +/// +static void Net2AnswerLobby(int response) +{ + _netresponse = response; + if (Lobby_Screen() != NULL) { + Lobby_Screen()->Answered = true; + } +} + + /// /// Fills a side box with the multiplayable countries, each entry carrying its country index. /// @@ -2493,7 +2507,7 @@ static void Get_Join_Responses(void) ODMessageBox(item, 0, Net2Callback, 0); } if ( Net2LobbyScreenID() != IDD_MPLAYER_GAME_LIST ) { - _netresponse = IDCANCEL; + Net2AnswerLobby(IDCANCEL); } Send_Join_Queries (0, 0, 1, 0); } @@ -2584,7 +2598,7 @@ static void Get_Join_Responses(void) if (i==CurGame) { Clear_Vector (&Session.Players); if (Net2LobbyScreenID() != IDD_MPLAYER_GAME_LIST && Net2LobbyScreenID() == IDD_MPLAYER_GUEST) { - _netresponse = 2; + Net2AnswerLobby(2); } } @@ -2696,11 +2710,11 @@ static void Get_Join_Responses(void) Session.MaxAhead = Session.GPacket.ResponseTime.OneWay; Session.HostAddress = Session.GAddress; Session.NumPlayers = Session.Players.Count(); - _netresponse = IDOK; + Net2AnswerLobby(IDOK); if (Session.GPacket.Command==NET_GO) { JoinState = JOIN_GAME_START; if (!Net2ReadyToGo(0)) { - _netresponse = 2; + Net2AnswerLobby(2); Net2GameStarted = false; } else { Net2GameStarted = true; diff --git a/code/ui/uilobby.cpp b/code/ui/uilobby.cpp index 6bfea4aed..0839bc82f 100644 --- a/code/ui/uilobby.cpp +++ b/code/ui/uilobby.cpp @@ -1522,6 +1522,7 @@ UIResult UI_Lobby_Run(UILobbyPresenterClass & presenter) // marks the presenter closing and a marked presenter drains nothing. presenter.Result.reset(); presenter.IsClosing = false; + presenter.Answered = false; presenter.Running = presenter.Showing; (*slot)->Sync(); diff --git a/code/ui/uilobby.h b/code/ui/uilobby.h index 06e3c3bee..7187d35c7 100644 --- a/code/ui/uilobby.h +++ b/code/ui/uilobby.h @@ -148,9 +148,14 @@ class UILobbyPresenterClass : public UIPresenterClass // document once and a join is confirmed from inside the service. virtual bool Suspends(void) const override { - return(Pending != SUB_NONE || (Running != SCREEN_NONE && Running != Showing)); + return(Pending != SUB_NONE || Answered || (Running != SCREEN_NONE && Running != Showing)); } + // Has the network answered the driver on the player's behalf? A confirmed start, a + // rejected join and a host signing off all answer from inside the service, where + // there is no result to return, so the screen steps aside to let the driver act. + bool Answered = false; + // The screen the runner is holding a document open for, which the runner sets and // clears around itself. ScreenType Running = SCREEN_NONE; From effba5d50dab57976291619fce73b53cffb23918 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 10:28:32 +0100 Subject: [PATCH 144/179] docs(manual): classify the UI shell's key sites as the source now names them Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- manual/data/command-adapters.yaml | 34 +++++++++++-------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/manual/data/command-adapters.yaml b/manual/data/command-adapters.yaml index 016dfe413..9fb84cd49 100644 --- a/manual/data/command-adapters.yaml +++ b/manual/data/command-adapters.yaml @@ -424,29 +424,19 @@ fixed_exclusions: reason: Tactical-tab hit-testing handled as pointer input. - sites: - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_F1 } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_F12 } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_BACK } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_TAB } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_RETURN } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_ESCAPE } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_SPACE } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_PRIOR } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_NEXT } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_END } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_HOME } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_LEFT } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_UP } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_RIGHT } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_DOWN } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_INSERT } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_DELETE } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_SHIFT } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_CONTROL } - - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_MENU } + - { file: code/ui/uishell.cpp, function: Key_Identifier, expression: VK_F24 } reason: >- - Translates a Windows virtual key into the identifier the UI toolkit names it by. The - shell hands the key to whichever document has the input scope; the key's meaning is the - document's, so no site here is a game command. + Bounds of the function-key run in the translation from a Windows virtual key to the + identifier the UI toolkit names it by. The shell hands the key to whichever document + has the input scope; the key's meaning is the document's, so no site here is a game + command. + - sites: + - { file: code/ui/uikeyboard.cpp, function: Is_Modifier_Key, expression: VK_SHIFT } + - { file: code/ui/uikeyboard.cpp, function: Is_Modifier_Key, expression: VK_CONTROL } + - { file: code/ui/uikeyboard.cpp, function: Is_Modifier_Key, expression: VK_MENU } + reason: >- + Recognizes a modifier held on its own so the hotkey capture control waits for the key + it qualifies. It classifies a keypress and dispatches nothing. - sites: - { file: code/ui/uishell.cpp, function: Key_Modifiers, expression: VK_CONTROL } - { file: code/ui/uishell.cpp, function: Key_Modifiers, expression: VK_SHIFT } From 842a46d3b469692b7f0b3867acafe792092b1c9d Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 10:41:46 +0100 Subject: [PATCH 145/179] feat(ui): give the out-of-sync screen a presenter and both documents Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/desyncdlg.cpp | 352 +++++++++--------------- code/desyncdlg.h | 27 +- code/ui/uidesync.cpp | 623 +++++++++++++++++++++++++++++++++++++++++++ code/ui/uidesync.h | 165 ++++++++++++ ui/desyncbase.rcss | 140 ++++++++++ ui/desynchost.rcss | 5 + ui/desynchost.rml | 41 +++ ui/desyncwait.rcss | 4 + ui/desyncwait.rml | 39 +++ 9 files changed, 1150 insertions(+), 246 deletions(-) create mode 100644 code/ui/uidesync.cpp create mode 100644 code/ui/uidesync.h create mode 100644 ui/desyncbase.rcss create mode 100644 ui/desynchost.rcss create mode 100644 ui/desynchost.rml create mode 100644 ui/desyncwait.rcss create mode 100644 ui/desyncwait.rml diff --git a/code/desyncdlg.cpp b/code/desyncdlg.cpp index a2719ca44..566206a21 100644 --- a/code/desyncdlg.cpp +++ b/code/desyncdlg.cpp @@ -34,6 +34,7 @@ #include "savemgr.h" #include "session.h" #include "srfcache.h" +#include "ui/uishell.h" #include "syncreport.h" #include "win.h" #include "windlg.h" @@ -67,7 +68,7 @@ namespace { /// -/// Shows the dialog and pumps it until the master has decided, or this player has quit. Game +/// Shows the screen and runs it until the master has decided, or this player has quit. Game /// logic is halted for the duration; chat, sign-offs, heartbeats and the master's decision /// still come through, since the network is serviced the whole time. /// @@ -79,66 +80,33 @@ DesyncDialogClass::OutcomeType DesyncDialogClass::Run(void) TacticalActive = false; Session.Suspended++; - Decision = 0; - ContinueReceived = false; - CountdownActive = false; - QuitEnabled = false; - LastCountdownSecond = -1; - ChatBacklog.clear(); - OpenedAt = Monotonic_Milliseconds(); - State.Begin(OpenedAt); - - Create_Dialog(); + IsRunning = true; + Screen.Open(); + UI_Set_Desync_Screen(&Screen); OutcomeType outcome = OutcomeType::Continue; - if (Window == NULL) { - DebugString("The out-of-sync dialog could not be created; continuing\n"); - } else { - while (true) { - Call_Back(); - - if (Decision == IDC_DESYNC_QUIT) { - outcome = OutcomeType::Quit; - break; - } - - std::int64_t now = Monotonic_Milliseconds(); - if (!IsHostDialog && !QuitEnabled && now - OpenedAt >= DesyncClass::QUIT_DELAY_MS) { - QuitEnabled = true; - EnableWindow(GetDlgItem(Window, IDC_DESYNC_QUIT), TRUE); - } - - if (!CountdownActive && SaveManager.MultiplayerLoad.Is_Pending()) { - Start_Countdown(); - } + // The presentation is latched here, at screen entry, and a document that will not + // prepare drops the screen back to the legacy dialog. + bool answered = false; + if (UI_Use_Rml()) { + UIResult const answer = UI_Desync_Run(Screen); + answered = answer.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN; + } + UI_Desync_Close_View(); - if (CountdownActive) { - Update_Countdown_Text(); - InvalidateRect(Window, NULL, FALSE); - if (SaveManager.MultiplayerLoad.Is_Due(now)) { - outcome = OutcomeType::Load; - break; - } - } else if (ContinueReceived || Decision == IDC_DESYNC_CONTINUE) { - if (Decision == IDC_DESYNC_CONTINUE) { - Send_Continue(); - } - outcome = OutcomeType::Continue; - break; - } else if (Decision == IDC_DESYNC_LOAD) { - EnableWindow(Window, FALSE); - SaveManager.Multiplayer_Load_Prompt(); - EnableWindow(Window, TRUE); - SetFocus(GetDlgItem(Window, IDC_DESYNC_PLAYER_LIST)); - } + if (!answered) { + Run_Legacy(); + } - Decision = 0; - Host_Sleep(10); - } + switch (Screen.Outcome) { + case UIDesyncPresenterClass::OUTCOME_LOAD: outcome = OutcomeType::Load; break; + case UIDesyncPresenterClass::OUTCOME_QUIT: outcome = OutcomeType::Quit; break; + default: outcome = OutcomeType::Continue; break; } - Destroy_Dialog(); + UI_Set_Desync_Screen(NULL); + IsRunning = false; Session.Suspended--; TacticalActive = true; @@ -149,18 +117,72 @@ DesyncDialogClass::OutcomeType DesyncDialogClass::Run(void) } +/// +/// Runs the OwnerDraw dialog against the same screen, for a build whose documents will not +/// prepare. It puts the model on its controls and queues an intent from a control. +/// +DesyncDialogClass::OutcomeType DesyncDialogClass::Run_Legacy(void) +{ + CountdownShown = false; + DrawnMessages = 0; + + Create_Dialog(); + if (Window == NULL) { + DebugString("The out-of-sync dialog could not be created; continuing\n"); + return(OutcomeType::Continue); + } + + while (!Screen.Result.has_value()) { + Call_Back(); + + Screen.Service(); + Screen.Drain(); + + Become_Host_If_Promoted(); + + if (Screen.PromptPending) { + EnableWindow(Window, FALSE); + Screen.Run_Pending(); + EnableWindow(Window, TRUE); + SetFocus(GetDlgItem(Window, IDC_DESYNC_PLAYER_LIST)); + } + + if (Screen.PlayersChanged) { + Update_Player_List(); + Screen.PlayersChanged = false; + } + if (Screen.MessagesChanged) { + Refill_Chat_List(); + Screen.MessagesChanged = false; + } + + EnableWindow(GetDlgItem(Window, IDC_DESYNC_QUIT), Screen.CanQuit ? TRUE : FALSE); + if (IsHostDialog) { + EnableWindow(GetDlgItem(Window, IDC_DESYNC_LOAD), Screen.CanLoad ? TRUE : FALSE); + EnableWindow(GetDlgItem(Window, IDC_DESYNC_CONTINUE), Screen.CanContinue ? TRUE : FALSE); + } + + Update_Countdown(); + + Host_Sleep(10); + } + + Destroy_Dialog(); + return(OutcomeType::Continue); +} + + void DesyncDialogClass::Service(void) { if (!Is_Active()) { return; } - std::int64_t now = Monotonic_Milliseconds(); - if (State.Heartbeat_Is_Due(now)) { - Send_Heartbeat(); - State.Heartbeat_Sent(now); + // The RmlUi runner services the screen itself; this is the path the network maintenance + // takes while a nested dialog owns the pump. + if (Window != NULL) { + Screen.Service(); } - Check_Timeouts(); } @@ -170,9 +192,7 @@ void DesyncDialogClass::Notify_Chat(char const * name, char const * text) return; } - char buffer[MAX_MESSAGE_LENGTH + MAX_MESSAGE_PREFIX]; - std::snprintf(buffer, sizeof(buffer), "%s: %s", name, text); - Append_Chat_Line(buffer); + Screen.Record_Chat(name, text); } @@ -182,16 +202,7 @@ void DesyncDialogClass::Notify_Player_Left(int house, char const * name) return; } - State.Mark_Left(house, name); - - if (name != NULL && name[0] != '\0') { - char buffer[128]; - std::snprintf(buffer, sizeof(buffer), Fetch_String(TXT_LEFT_GAME), name); - Append_Chat_Line(buffer); - } - - Update_Player_List(); - Become_Host_If_Promoted(); + Screen.Player_Left(house, name); } @@ -201,15 +212,14 @@ void DesyncDialogClass::Notify_Continue(void) return; } - DebugString("The master chose to continue without the players out of sync\n"); - ContinueReceived = true; + Screen.Master_Decided_To_Continue(); } void DesyncDialogClass::Notify_Heartbeat(int house) { if (Is_Active()) { - State.Heard(house, Monotonic_Milliseconds()); + Screen.Heartbeat_Heard(house); } } @@ -220,8 +230,7 @@ void DesyncDialogClass::Notify_Master_Changed(void) return; } - Update_Player_List(); - Become_Host_If_Promoted(); + Screen.Master_Changed(); } @@ -231,7 +240,7 @@ void DesyncDialogClass::Notify_Master_Changed(void) /// void DesyncDialogClass::Create_Dialog(void) { - IsHostDialog = Session.Am_I_Master(); + IsHostDialog = Screen.IsMaster; int const id = IsHostDialog ? IDD_DESYNC_HOST : IDD_DESYNC_WAIT; Window = WS_Create_Dialog(ProgramInstance, id, MainWindow, Dialog_Proc, FALSE); @@ -259,12 +268,10 @@ void DesyncDialogClass::Create_Dialog(void) Update_Player_List(); if (IsHostDialog) { - bool const can_load = SaveManager.Multiplayer_Load_Is_Allowed() && MultiplayerLoadOptionsClass().Files_Present(); - EnableWindow(GetDlgItem(Window, IDC_DESYNC_LOAD), can_load && !CountdownActive); - EnableWindow(GetDlgItem(Window, IDC_DESYNC_CONTINUE), !CountdownActive); - } else { - EnableWindow(GetDlgItem(Window, IDC_DESYNC_QUIT), QuitEnabled); + EnableWindow(GetDlgItem(Window, IDC_DESYNC_LOAD), Screen.CanLoad); + EnableWindow(GetDlgItem(Window, IDC_DESYNC_CONTINUE), Screen.CanContinue); } + EnableWindow(GetDlgItem(Window, IDC_DESYNC_QUIT), Screen.CanQuit); Refill_Chat_List(); @@ -274,11 +281,8 @@ void DesyncDialogClass::Create_Dialog(void) ChatPlaceholderActive = true; } - if (CountdownActive) { - ShowWindow(GetDlgItem(Window, IDC_DESYNC_COUNTDOWN_TEXT), SW_SHOW); - ShowWindow(GetDlgItem(Window, IDC_DESYNC_COUNTDOWN_BAR), SW_SHOW); - Update_Countdown_Text(); - } + CountdownShown = false; + Update_Countdown(); MouseCursor->Hide_Mouse(); ShowWindow(Window, SW_SHOWNORMAL); @@ -348,7 +352,7 @@ void DesyncDialogClass::Fit_To_Screen(void) /// void DesyncDialogClass::Become_Host_If_Promoted(void) { - if (!Is_Active() || IsHostDialog || CountdownActive || !Session.Am_I_Master()) { + if (Window == NULL || IsHostDialog == Screen.IsMaster) { return; } @@ -360,7 +364,7 @@ void DesyncDialogClass::Become_Host_If_Promoted(void) void DesyncDialogClass::Update_Player_List(void) { - if (!Is_Active()) { + if (Window == NULL) { return; } @@ -372,25 +376,14 @@ void DesyncDialogClass::Update_Player_List(void) ListBox_ResetContent(list); int const status_x = Status_Column_X(list); - int const master = Session.Master_Player_ID(); - - for (int house = 0; house < MAX_PLAYERS && house < Houses.Count(); house++) { - HouseClass const * housep = Houses[house]; - bool const left = State.Has_Left(house); - // A player who left stays listed, though their seat is no longer human. - if (housep == NULL || (!housep->IsHuman && !left)) { - continue; - } - - // The roster entry is gone by now, so the kept name is the only copy while the list rebuilds. - char const * name = left && State.Left_Name(house)[0] != '\0' ? State.Left_Name(house) : housep->IniName.c_str(); - int const row = ListBox_AddString(list, name); + for (UIDesyncPresenterClass::PlayerRowType const & player : Screen.Players) { + int const row = ListBox_AddString(list, player.Name.c_str()); if (row < 0) { continue; } - if (house == master) { + if (player.IsHost) { OwnerDraw::CellData host; host.type = OwnerDraw::CellData::SURFACE; host.surf = SurfaceCache.GetSurface("wolhost.pcx"); @@ -400,10 +393,10 @@ void DesyncDialogClass::Update_Player_List(void) int text = TXT_OK; COLORREF color = RGB(0, 200, 0); - if (left) { + if (player.Status == UIDesyncPresenterClass::STATUS_LEFT) { text = TXT_SYNC_STATUS_LEFT; color = RGB(200, 0, 0); - } else if (Sync_Is_Out_Of_Sync(house)) { + } else if (player.Status == UIDesyncPresenterClass::STATUS_OUT_OF_SYNC) { text = TXT_SYNC_STATUS_OUT; color = RGB(200, 200, 0); } @@ -422,7 +415,7 @@ void DesyncDialogClass::Update_Player_List(void) void DesyncDialogClass::Refill_Chat_List(void) { - if (!Is_Active()) { + if (Window == NULL) { return; } @@ -432,40 +425,16 @@ void DesyncDialogClass::Refill_Chat_List(void) } ListBox_ResetContent(list); - for (std::string const & line : ChatBacklog) { + for (std::string const & line : Screen.Messages) { ListBox_AddString(list, line.c_str()); } ListBox_SetTopIndex(list, ListBox_GetCount(list) - 1); } -void DesyncDialogClass::Append_Chat_Line(char const * line) -{ - ChatBacklog.emplace_back(line); - if (ChatBacklog.size() > CHAT_BACKLOG_MAX) { - ChatBacklog.erase(ChatBacklog.begin()); - } - - if (!Is_Active()) { - return; - } - - HWND list = GetDlgItem(Window, IDC_DESYNC_CHAT_LIST); - if (list == NULL) { - return; - } - - ListBox_AddString(list, line); - while (ListBox_GetCount(list) > CHAT_BACKLOG_MAX) { - ListBox_DeleteString(list, 0); - } - ListBox_SetTopIndex(list, ListBox_GetCount(list) - 1); -} - - void DesyncDialogClass::Send_Chat(void) { - if (!Is_Active() || ChatPlaceholderActive) { + if (Window == NULL || ChatPlaceholderActive) { return; } @@ -483,15 +452,13 @@ void DesyncDialogClass::Send_Chat(void) SetWindowText(edit, ""); SetFocus(edit); - Session.MessageScope = ChatScopeType::Everyone; - Session.MessageAddress = IPXAddressClass(); - Chat_Send(buffer); + Screen.Queue(UIIntent{UI_DESYNC_SAY, buffer, 0}); } void DesyncDialogClass::On_Chat_Edit_Focus(bool gained) { - if (!Is_Active()) { + if (Window == NULL) { return; } @@ -510,114 +477,33 @@ void DesyncDialogClass::On_Chat_Edit_Focus(bool gained) } -void DesyncDialogClass::Send_Heartbeat(void) -{ - if (PlayerPtr == NULL || Session.Players.Count() == 0) { - return; - } - - GlobalPacketType packet; - NetGlobal::Initialize_Packet(packet, NET_DESYNC_HEARTBEAT); - std::snprintf(packet.Name, sizeof(packet.Name), "%s", Session.Players[0]->Name); - - for (int index = 1; index < Session.Players.Count(); index++) { - Ipx.Send_Global_Message(&packet, sizeof(packet), 0, &Session.Players[index]->Address); - } - Ipx.Service(); -} - - -void DesyncDialogClass::Send_Continue(void) -{ - DebugString("Telling every seat to continue without the players out of sync\n"); - - GlobalPacketType packet; - NetGlobal::Initialize_Packet(packet, NET_DESYNC_CONTINUE); - std::snprintf(packet.Name, sizeof(packet.Name), "%s", Session.Players[0]->Name); - - for (int index = 1; index < Session.Players.Count(); index++) { - Ipx.Send_Global_Message(&packet, sizeof(packet), 1, &Session.Players[index]->Address); - Ipx.Service(); - } -} - - /// -/// Drops the seats that have fallen silent, so a machine that died without a sign-off neither -/// holds up the decision nor lingers in the seats a later load reconciles. +/// Shows the countdown once a load is scheduled and keeps its text and bar current. /// -void DesyncDialogClass::Check_Timeouts(void) +void DesyncDialogClass::Update_Countdown(void) { - std::int64_t const now = Monotonic_Milliseconds(); - - for (int index = Session.Players.Count() - 1; index >= 1; index--) { - int const house = Session.Players[index]->Player.ID; - if (!State.Is_Silent(house, now)) { - continue; - } - - DebugString("No heartbeat from %s (house %d) for %d seconds; dropping the seat\n", - Session.Players[index]->Name, house, (int)(DesyncClass::HEARTBEAT_TIMEOUT_MS / 1000)); - - std::string const name = Session.Players[index]->Name; - Destroy_Connection(house, 1); - Notify_Player_Left(house, name.c_str()); - } -} - - -void DesyncDialogClass::Start_Countdown(void) -{ - DebugString("Counting down to the multiplayer load\n"); - - CountdownActive = true; - LastCountdownSecond = -1; - - if (!Is_Active()) { + if (Window == NULL || !Screen.CountdownActive) { return; } - Append_Chat_Line(Fetch_String(TXT_LOADING_SAVED_GAME)); - - ShowWindow(GetDlgItem(Window, IDC_DESYNC_COUNTDOWN_TEXT), SW_SHOW); - ShowWindow(GetDlgItem(Window, IDC_DESYNC_COUNTDOWN_BAR), SW_SHOW); - Update_Countdown_Text(); - - if (IsHostDialog) { - EnableWindow(GetDlgItem(Window, IDC_DESYNC_LOAD), FALSE); - EnableWindow(GetDlgItem(Window, IDC_DESYNC_CONTINUE), FALSE); + if (!CountdownShown) { + CountdownShown = true; + ShowWindow(GetDlgItem(Window, IDC_DESYNC_COUNTDOWN_TEXT), SW_SHOW); + ShowWindow(GetDlgItem(Window, IDC_DESYNC_COUNTDOWN_BAR), SW_SHOW); } + SetDlgItemText(Window, IDC_DESYNC_COUNTDOWN_TEXT, Screen.CountdownText.c_str()); InvalidateRect(Window, NULL, FALSE); } -void DesyncDialogClass::Update_Countdown_Text(void) -{ - if (!Is_Active() || !CountdownActive || !SaveManager.MultiplayerLoad.Is_Pending()) { - return; - } - - int const seconds = SaveManager.MultiplayerLoad.Seconds_Left(Monotonic_Milliseconds()); - if (seconds == LastCountdownSecond) { - return; - } - LastCountdownSecond = seconds; - - char buffer[128]; - std::snprintf(buffer, sizeof(buffer), - Fetch_String(seconds == 1 ? TXT_LOADING_IN_SECOND : TXT_LOADING_IN_SECONDS), seconds); - SetDlgItemText(Window, IDC_DESYNC_COUNTDOWN_TEXT, buffer); -} - - /// /// Draws the countdown bar over its placeholder the way the reconnect dialog draws its sync /// bars: shrinking, and green to yellow to red as the load nears. /// void DesyncDialogClass::Draw_Countdown_Bar(HWND window) { - if (!CountdownActive || !SaveManager.MultiplayerLoad.Is_Pending()) { + if (!CountdownShown || DesyncDialog.Screen.CountdownTotal <= 0) { return; } @@ -635,8 +521,8 @@ void DesyncDialogClass::Draw_Countdown_Bar(HWND window) bar_rect.Width = winrect.right - winrect.left; bar_rect.Height = winrect.bottom - winrect.top; - int const total = (int)MultiplayerLoadClass::COUNTDOWN_MS; - int const remaining = std::clamp((int)SaveManager.MultiplayerLoad.Milliseconds_Left(Monotonic_Milliseconds()), 0, total); + int const total = DesyncDialog.Screen.CountdownTotal; + int const remaining = std::clamp(DesyncDialog.Screen.CountdownRemaining, 0, total); int const elapsed = total - remaining; unsigned short color = DSurface::Build_Hicolor_Pixel(0, 200, 0); @@ -688,9 +574,15 @@ INT_PTR CALLBACK DesyncDialogClass::Dialog_Proc(HWND window, UINT message, WPARA case WM_COMMAND: switch (LOWORD(wparam)) { case IDC_DESYNC_LOAD: + DesyncDialog.Screen.Queue(UIIntent{UI_DESYNC_LOAD, "", 0}); + break; + case IDC_DESYNC_CONTINUE: + DesyncDialog.Screen.Queue(UIIntent{UI_DESYNC_CONTINUE, "", 0}); + break; + case IDC_DESYNC_QUIT: - DesyncDialog.Decision = LOWORD(wparam); + DesyncDialog.Screen.Queue(UIIntent{UI_DESYNC_QUIT, "", 0}); break; // Enter in the chat box arrives as IDOK, since the dialog has no default button. diff --git a/code/desyncdlg.h b/code/desyncdlg.h index ac03ec61b..cb761282c 100644 --- a/code/desyncdlg.h +++ b/code/desyncdlg.h @@ -9,7 +9,7 @@ #pragma once -#include "desync.h" +#include "ui/uidesync.h" #include "win.h" #include @@ -34,7 +34,7 @@ class DesyncDialogClass // Blocks until a decision has been made; the network is serviced throughout. OutcomeType Run(void); - bool Is_Active(void) const {return(Window != NULL);} + bool Is_Active(void) const {return(IsRunning);} // Sends the heartbeat and drops silent players; called from the network maintenance // so that both outlive a nested dialog's message loop. @@ -48,34 +48,29 @@ class DesyncDialogClass void Notify_Master_Changed(void); private: + OutcomeType Run_Legacy(void); void Create_Dialog(void); void Destroy_Dialog(void); void Fit_To_Screen(void); void Become_Host_If_Promoted(void); void Update_Player_List(void); void Refill_Chat_List(void); - void Append_Chat_Line(char const * line); void Send_Chat(void); void On_Chat_Edit_Focus(bool gained); - void Send_Heartbeat(void); - void Send_Continue(void); - void Check_Timeouts(void); - void Start_Countdown(void); - void Update_Countdown_Text(void); + void Update_Countdown(void); void Draw_Countdown_Bar(HWND window); static INT_PTR CALLBACK Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); + // The screen's whole behavior. The dialog reads this model onto its controls and + // queues an intent from a control; it decides nothing itself. + UIDesyncPresenterClass Screen; + HWND Window = NULL; + bool IsRunning = false; bool IsHostDialog = false; - int Decision = 0; - bool ContinueReceived = false; bool ChatPlaceholderActive = false; - bool CountdownActive = false; - bool QuitEnabled = false; - std::int64_t OpenedAt = 0; - int LastCountdownSecond = -1; - DesyncClass State; - std::vector ChatBacklog; + bool CountdownShown = false; + std::size_t DrawnMessages = 0; }; extern DesyncDialogClass DesyncDialog; diff --git a/code/ui/uidesync.cpp b/code/ui/uidesync.cpp new file mode 100644 index 000000000..5362c0cdf --- /dev/null +++ b/code/ui/uidesync.cpp @@ -0,0 +1,623 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#include "hostclock.h" +#include "always.h" + +#include "uidesync.h" + +#include "uirmlview.h" +#include "uishell.h" + +#include "chat.h" +#include "dbgprint.h" +#include "house.h" +#include "ipxmgr.h" +#include "conquer.h" +#include "data.h" +#include "language/language.h" +#include "loaddlg.h" +#include "mpload.h" +#include "msglist.h" +#include "netdlg.h" +#include "netglobal.h" +#include "savemgr.h" +#include "session.h" +#include "syncreport.h" + +#include +#include + +#include +#include + + +static UIDesyncPresenterClass * _DesyncScreen = NULL; + + +UIDesyncPresenterClass * UI_Desync_Screen(void) +{ + return(_DesyncScreen); +} + + +void UI_Set_Desync_Screen(UIDesyncPresenterClass * screen) +{ + _DesyncScreen = screen; +} + + +/// +/// Records the seats, the timers and the standing the screen opens with. +/// +void UIDesyncPresenterClass::Open(void) +{ + IsMaster = Session.Am_I_Master(); + OpenedAt = Monotonic_Milliseconds(); + State.Begin(OpenedAt); + + ContinueReceived = false; + CountdownActive = false; + LastCountdownSecond = -1; + PromptPending = false; + Outcome = OUTCOME_CONTINUE; + Messages.clear(); + + // The master decides and everyone else waits, so only the master's screen carries the + // two decisions; the wait screen's quit comes back after the delay. + CanContinue = IsMaster; + CanLoad = IsMaster && SaveManager.Multiplayer_Load_Is_Allowed() && MultiplayerLoadOptionsClass().Files_Present(); + CanQuit = IsMaster; + + Build_Player_Rows(); +} + + +void UIDesyncPresenterClass::Execute(UIIntent const & intent) +{ + if (intent.Action == UI_DESYNC_LOAD) { + if (!CanLoad || CountdownActive) return; + PromptPending = true; + return; + } + + if (intent.Action == UI_DESYNC_CONTINUE) { + if (!CanContinue || CountdownActive) return; + Send_Continue(); + Answer(OUTCOME_CONTINUE); + return; + } + + if (intent.Action == UI_DESYNC_QUIT) { + if (!CanQuit) return; + Answer(OUTCOME_QUIT); + return; + } + + if (intent.Action == UI_DESYNC_SAY) { + Say(intent.Identity); + return; + } +} + + +void UIDesyncPresenterClass::Refresh(void) +{ + Build_Player_Rows(); +} + + +/// +/// The maintenance the dialog's own loop ran on every pass: heartbeats out, silent seats +/// dropped, the master's decision taken, and the countdown moved. +/// +void UIDesyncPresenterClass::Service(void) +{ + std::int64_t const now = Monotonic_Milliseconds(); + + if (State.Heartbeat_Is_Due(now)) { + Send_Heartbeat(); + State.Heartbeat_Sent(now); + } + Check_Timeouts(); + + // A waiting player's quit comes back once the stall has lasted long enough to be worth + // abandoning, which is what the disabled button stood for. + if (!IsMaster && !CanQuit && now - OpenedAt >= DesyncClass::QUIT_DELAY_MS) { + CanQuit = true; + } + + if (!CountdownActive && SaveManager.MultiplayerLoad.Is_Pending()) { + Start_Countdown(); + } + + if (CountdownActive) { + Update_Countdown(); + if (SaveManager.MultiplayerLoad.Is_Due(now)) { + Answer(OUTCOME_LOAD); + } + return; + } + + if (ContinueReceived) { + Answer(OUTCOME_CONTINUE); + } +} + + +/// +/// Runs the multiplayer save browser with this screen out of the way, the way the dialog +/// disabled itself around the same prompt. +/// +void UIDesyncPresenterClass::Run_Pending(void) +{ + if (!PromptPending) { + return; + } + + PromptPending = false; + SaveManager.Multiplayer_Load_Prompt(); +} + + +void UIDesyncPresenterClass::Record_Chat(char const * name, char const * text) +{ + char buffer[MAX_MESSAGE_LENGTH + MAX_MESSAGE_PREFIX]; + std::snprintf(buffer, sizeof(buffer), "%s: %s", name, text); + Append_Chat_Line(buffer); +} + + +void UIDesyncPresenterClass::Player_Left(int house, char const * name) +{ + State.Mark_Left(house, name); + + if (name != NULL && name[0] != '\0') { + char buffer[128]; + std::snprintf(buffer, sizeof(buffer), Fetch_String(TXT_LEFT_GAME), name); + Append_Chat_Line(buffer); + } + + Build_Player_Rows(); + Master_Changed(); +} + + +void UIDesyncPresenterClass::Master_Decided_To_Continue(void) +{ + DebugString("The master chose to continue without the players out of sync\n"); + ContinueReceived = true; +} + + +void UIDesyncPresenterClass::Heartbeat_Heard(int house) +{ + State.Heard(house, Monotonic_Milliseconds()); +} + + +/// +/// Takes up the master's decisions once this machine has become master, unless a load is +/// already counting down, when there is nothing left to decide. +/// +void UIDesyncPresenterClass::Master_Changed(void) +{ + Build_Player_Rows(); + + if (IsMaster || CountdownActive || !Session.Am_I_Master()) { + return; + } + + DebugString("This machine is the new master; it makes the decision now\n"); + IsMaster = true; + CanContinue = true; + CanQuit = true; + CanLoad = SaveManager.Multiplayer_Load_Is_Allowed() && MultiplayerLoadOptionsClass().Files_Present(); +} + + +void UIDesyncPresenterClass::Build_Player_Rows(void) +{ + Players.clear(); + + int const master = Session.Master_Player_ID(); + + for (int house = 0; house < MAX_PLAYERS && house < Houses.Count(); house++) { + HouseClass const * housep = Houses[house]; + bool const left = State.Has_Left(house); + + // A player who left stays listed, though their seat is no longer human. + if (housep == NULL || (!housep->IsHuman && !left)) { + continue; + } + + PlayerRowType row; + + // The roster entry is gone by now, so the kept name is the only copy while the list + // rebuilds. + row.Name = left && State.Left_Name(house)[0] != '\0' ? State.Left_Name(house) : housep->IniName.c_str(); + row.IsHost = house == master; + + if (left) { + row.Status = STATUS_LEFT; + } else if (Sync_Is_Out_Of_Sync(house)) { + row.Status = STATUS_OUT_OF_SYNC; + } + + Players.push_back(row); + } + + PlayersChanged = true; +} + + +void UIDesyncPresenterClass::Answer(OutcomeType outcome) +{ + Outcome = outcome; + + // A screen answers its driver with a result as well as an outcome, because the runner + // returns on a result. + UIResult result; + result.Outcome = outcome == OUTCOME_QUIT ? UIResult::OUTCOME_CANCELLED : UIResult::OUTCOME_ACCEPTED; + result.Value = (int)outcome; + Result = result; +} + + +void UIDesyncPresenterClass::Say(std::string const & text) +{ + if (text.empty()) { + return; + } + + char buffer[MAX_MESSAGE_LENGTH]; + std::snprintf(buffer, sizeof(buffer), "%s", text.c_str()); + + Session.MessageScope = ChatScopeType::Everyone; + Session.MessageAddress = IPXAddressClass(); + Chat_Send(buffer); +} + + +void UIDesyncPresenterClass::Append_Chat_Line(char const * line) +{ + Messages.emplace_back(line); + if ((int)Messages.size() > MESSAGE_LIMIT) { + Messages.erase(Messages.begin()); + } + MessagesChanged = true; +} + + +void UIDesyncPresenterClass::Send_Heartbeat(void) +{ + if (PlayerPtr == NULL || Session.Players.Count() == 0) { + return; + } + + GlobalPacketType packet; + NetGlobal::Initialize_Packet(packet, NET_DESYNC_HEARTBEAT); + std::snprintf(packet.Name, sizeof(packet.Name), "%s", Session.Players[0]->Name); + + for (int index = 1; index < Session.Players.Count(); index++) { + Ipx.Send_Global_Message(&packet, sizeof(packet), 0, &Session.Players[index]->Address); + } + Ipx.Service(); +} + + +void UIDesyncPresenterClass::Send_Continue(void) +{ + DebugString("Telling every seat to continue without the players out of sync\n"); + + GlobalPacketType packet; + NetGlobal::Initialize_Packet(packet, NET_DESYNC_CONTINUE); + std::snprintf(packet.Name, sizeof(packet.Name), "%s", Session.Players[0]->Name); + + for (int index = 1; index < Session.Players.Count(); index++) { + Ipx.Send_Global_Message(&packet, sizeof(packet), 1, &Session.Players[index]->Address); + Ipx.Service(); + } +} + + +/// +/// Drops the seats that have fallen silent, so a machine that died without a sign-off +/// neither holds up the decision nor lingers in the seats a later load reconciles. +/// +void UIDesyncPresenterClass::Check_Timeouts(void) +{ + std::int64_t const now = Monotonic_Milliseconds(); + + for (int index = Session.Players.Count() - 1; index >= 1; index--) { + int const house = Session.Players[index]->Player.ID; + if (!State.Is_Silent(house, now)) { + continue; + } + + DebugString("No heartbeat from %s (house %d) for %d seconds; dropping the seat\n", + Session.Players[index]->Name, house, (int)(DesyncClass::HEARTBEAT_TIMEOUT_MS / 1000)); + + std::string const name = Session.Players[index]->Name; + Destroy_Connection(house, 1); + Player_Left(house, name.c_str()); + } +} + + +void UIDesyncPresenterClass::Start_Countdown(void) +{ + DebugString("Counting down to the multiplayer load\n"); + + CountdownActive = true; + LastCountdownSecond = -1; + CountdownTotal = (int)MultiplayerLoadClass::COUNTDOWN_MS; + + Append_Chat_Line(Fetch_String(TXT_LOADING_SAVED_GAME)); + + // Nothing is left to decide once the load is scheduled, which is what disabling both + // buttons stood for. + CanLoad = false; + CanContinue = false; + + Update_Countdown(); +} + + +void UIDesyncPresenterClass::Update_Countdown(void) +{ + if (!CountdownActive || !SaveManager.MultiplayerLoad.Is_Pending()) { + return; + } + + std::int64_t const now = Monotonic_Milliseconds(); + CountdownRemaining = std::clamp((int)SaveManager.MultiplayerLoad.Milliseconds_Left(now), 0, CountdownTotal); + + int const seconds = SaveManager.MultiplayerLoad.Seconds_Left(now); + if (seconds == LastCountdownSecond) { + return; + } + LastCountdownSecond = seconds; + + char buffer[128]; + std::snprintf(buffer, sizeof(buffer), + Fetch_String(seconds == 1 ? TXT_LOADING_IN_SECOND : TXT_LOADING_IN_SECONDS), seconds); + CountdownText = buffer; +} + + +/* +** The RmlUi view. The master's screen and the wait screen are the same document family: +** they differ by which of the three buttons exist and by the block of prose beside the +** list, so each carries its own document and its own model name. +*/ +namespace { + + // A seat as the document shows it, with the status text and its color resolved here + // rather than in the presenter. + struct PlayerViewType + { + std::string Name; + std::string Status; + std::string Hex; + std::string Mark; + }; + + + class DesyncViewClass : public UIRmlViewClass + { + public: + DesyncViewClass(UIDesyncPresenterClass & presenter, char const * document) + : UIRmlViewClass(presenter, document), Screen(presenter) {} + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + private: + void Rebuild_Rows(void); + + UIDesyncPresenterClass & Screen; + + std::vector PlayerRows; + + // How much of the countdown bar is left, as a percentage, because a document + // states a width rather than drawing a rectangle, and the color the elapsed + // time gives it. + std::string BarWidth = "0%"; + std::string BarHex = "#00c800"; + }; + + + void DesyncViewClass::Rebuild_Rows(void) + { + PlayerRows.clear(); + + for (UIDesyncPresenterClass::PlayerRowType const & row : Screen.Players) { + PlayerViewType view; + view.Name = row.Name; + + switch (row.Status) { + case UIDesyncPresenterClass::STATUS_LEFT: + view.Status = Fetch_String(TXT_SYNC_STATUS_LEFT); + view.Hex = "#c80000"; + break; + + case UIDesyncPresenterClass::STATUS_OUT_OF_SYNC: + view.Status = Fetch_String(TXT_SYNC_STATUS_OUT); + view.Hex = "#c8c800"; + break; + + default: + view.Status = Fetch_String(TXT_OK); + view.Hex = "#00c800"; + break; + } + + // The master marker, which the list drew as the wolhost.pcx surface. PCX + // decoding is not here yet, so the marker is a character in the same column. + view.Mark = row.IsHost ? "*" : ""; + + PlayerRows.push_back(view); + } + + int const total = Screen.CountdownTotal > 0 ? Screen.CountdownTotal : 1; + int const remaining = std::clamp(Screen.CountdownRemaining, 0, total); + char percent[16]; + std::snprintf(percent, sizeof(percent), "%d%%", remaining * 100 / total); + BarWidth = percent; + + // Green to yellow to red as the load nears, which is what Draw_Countdown_Bar chose + // from the elapsed fraction. + int const elapsed = total - remaining; + BarHex = "#00c800"; + if (elapsed > total * 2 / 5) { + BarHex = elapsed > total * 4 / 5 ? "#c80000" : "#c8c800"; + } + } + + + void DesyncViewClass::Bind(Rml::DataModelConstructor & model) + { + Rebuild_Rows(); + + if (auto row = model.RegisterStruct()) { + row.RegisterMember("name", &PlayerViewType::Name); + row.RegisterMember("status", &PlayerViewType::Status); + row.RegisterMember("hex", &PlayerViewType::Hex); + row.RegisterMember("mark", &PlayerViewType::Mark); + } + model.RegisterArray>(); + + model.Bind("players", &PlayerRows); + model.Bind("messages", &Screen.Messages); + + model.Bind("canload", &Screen.CanLoad); + model.Bind("cancontinue", &Screen.CanContinue); + model.Bind("canquit", &Screen.CanQuit); + + model.Bind("countdown", &Screen.CountdownActive); + model.Bind("countdowntext", &Screen.CountdownText); + model.Bind("barwidth", &BarWidth); + model.Bind("barhex", &BarHex); + + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + + Rml::String const action = arguments[0].Get(); + if (action == UI_DESYNC_LOAD) Screen.Queue(UIIntent{UI_DESYNC_LOAD, "", 0}); + else if (action == UI_DESYNC_CONTINUE) Screen.Queue(UIIntent{UI_DESYNC_CONTINUE, "", 0}); + else if (action == UI_DESYNC_QUIT) Screen.Queue(UIIntent{UI_DESYNC_QUIT, "", 0}); + }); + + // Enter in the chat field sends the line, which is what the dialog's IDOK arm did, + // since it had no default button. + model.BindEventCallback("submit", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key != Rml::Input::KI_RETURN && key != Rml::Input::KI_NUMPADENTER) { + return; + } + + Rml::Element * const field = Element != nullptr ? Element->GetElementById("say") : nullptr; + if (field == nullptr) return; + + Rml::String const text = field->GetAttribute("value", Rml::String()); + field->SetAttribute("value", Rml::String()); + Screen.Queue(UIIntent{UI_DESYNC_SAY, text, 0}); + event.StopPropagation(); + }); + } + + + void DesyncViewClass::Sync(void) + { + if (!Model) return; + + Rebuild_Rows(); + + Model.DirtyVariable("players"); + Model.DirtyVariable("messages"); + Model.DirtyVariable("canload"); + Model.DirtyVariable("cancontinue"); + Model.DirtyVariable("canquit"); + Model.DirtyVariable("countdown"); + Model.DirtyVariable("countdowntext"); + Model.DirtyVariable("barwidth"); + Model.DirtyVariable("barhex"); + + Screen.PlayersChanged = false; + Screen.MessagesChanged = false; + } + + + DesyncViewClass * _View = NULL; + +} // namespace + + +void UI_Desync_Close_View(void) +{ + delete _View; + _View = NULL; +} + + +/// +/// Shows the variant this machine gets and runs it until the decision is made. +/// +UIResult UI_Desync_Run(UIDesyncPresenterClass & presenter) +{ + UIResult failed; + failed.Outcome = UIResult::OUTCOME_FAILED_TO_OPEN; + + // The master's screen replaces the wait screen when this machine is promoted, so the + // document is released rather than kept when the variant moves. + static bool shown_as_master = false; + if (_View != NULL && shown_as_master != presenter.IsMaster) { + UI_Desync_Close_View(); + } + + if (_View == NULL) { + shown_as_master = presenter.IsMaster; + + DesyncViewClass * const view = new DesyncViewClass(presenter, + presenter.IsMaster ? "desynchost.rml" : "desyncwait.rml"); + if (!view->Prepare(true)) { + delete view; + return(failed); + } + + _View = view; + } + + // A family reopened in a loop resets the close mark and the held result, since a close + // marks the presenter closing and a marked presenter drains nothing. + presenter.Result.reset(); + presenter.IsClosing = false; + presenter.Running = presenter.IsMaster ? 1 : 0; + _View->Sync(); + + while (!presenter.Result.has_value()) { + UI_Run_Modal(presenter, *_View); + + if (!presenter.PromptPending) { + break; + } + + // The save browser draws where this screen is, so the document steps aside for it. + _View->Hide(); + presenter.Run_Pending(); + _View->Show(); + _View->Sync(); + } + + presenter.Running = -1; + return(presenter.Result.value_or(UIResult{})); +} diff --git a/code/ui/uidesync.h b/code/ui/uidesync.h new file mode 100644 index 000000000..26ca4db8a --- /dev/null +++ b/code/ui/uidesync.h @@ -0,0 +1,165 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The out-of-sync screen's behavior, with no toolkit in it. The master's decision screen and +// the wait screen everyone else gets are one screen family sharing one model, because they +// differ by which controls exist rather than by what the screen does. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + +#include "desync.h" + +#include +#include +#include + + +inline constexpr char const * UI_DESYNC_LOAD = "load"; +inline constexpr char const * UI_DESYNC_CONTINUE = "continue"; +inline constexpr char const * UI_DESYNC_QUIT = "quit"; +inline constexpr char const * UI_DESYNC_SAY = "say"; + + +class UIDesyncPresenterClass : public UIPresenterClass +{ + public: + // What the screen answers its driver with. These stand where the dialog's own control + // identifiers stood, so a presenter names no control. + enum OutcomeType { + OUTCOME_CONTINUE, + OUTCOME_LOAD, + OUTCOME_QUIT, + }; + + // A seat's standing, which the list drew as colored text in its own column. + enum StatusType { + STATUS_OK, + STATUS_OUT_OF_SYNC, + STATUS_LEFT, + }; + + // A seat and what is known about it. The name is carried rather than the house, + // because a house the computer takes over is renamed and the list would lose it. + struct PlayerRowType + { + std::string Name; + StatusType Status = STATUS_OK; + bool IsHost = false; + }; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + virtual void Service(void) override; + + // The multiplayer save browser draws where this screen is, so this screen is stepped + // aside for it rather than run underneath. The family also steps aside when this + // machine is promoted to master, because the runner picks its document once and the + // promotion arrives from inside the service. + virtual bool Suspends(void) const override + { + return(PromptPending || (Running >= 0 && (Running != 0) != IsMaster)); + } + + // Which variant the runner is holding a document open for, which the runner sets and + // clears around itself: -1 for none, 0 for the wait screen, 1 for the master's. + int Running = -1; + + // The rosters and the timers the screen opens with. Called once, before a view is + // prepared, because the wait screen's quit delay is measured from here. + void Open(void); + + // Runs the multiplayer save browser with this screen out of the way. Called by the + // owner between passes, never from an event. + void Run_Pending(void); + + // Records a line of chat for whatever is showing the screen, in the form the dialog + // composed it. + void Record_Chat(char const * name, char const * text); + + // A seat has gone. The name is kept because the roster entry is dropped with it. + void Player_Left(int house, char const * name); + + void Master_Decided_To_Continue(void); + void Heartbeat_Heard(int house); + void Master_Changed(void); + + // Reads the session's seats into the view-model. The master marker is recorded with + // the row rather than while painting it, because it is a fact about the seat. + void Build_Player_Rows(void); + + /* + ** The view-model. + */ + + // Is this machine the master? The master decides and everyone else waits, which is + // what the two templates stood for. + bool IsMaster = false; + + std::vector Players; + + // The chat backlog, kept whole here and wrapped by whatever shows it. + std::vector Messages; + + // The most lines the model keeps, which is what the chat list box was capped at. + enum { MESSAGE_LIMIT = 50 }; + + bool CanLoad = false; + bool CanContinue = false; + + // A waiting player may not quit at once: the dialog left its quit button disabled + // for the first ten seconds so a brief stall is not abandoned by reflex. + bool CanQuit = false; + + bool CountdownActive = false; + std::string CountdownText; + + // How much of the countdown is left, out of MultiplayerLoadClass::COUNTDOWN_MS, which + // is what the shrinking bar was drawn from. + int CountdownRemaining = 0; + int CountdownTotal = 0; + + bool PlayersChanged = false; + bool MessagesChanged = false; + + // Has the save browser been asked for? The owner runs it between passes. + bool PromptPending = false; + + OutcomeType Outcome = OUTCOME_CONTINUE; + + private: + void Answer(OutcomeType outcome); + void Say(std::string const & text); + void Append_Chat_Line(char const * line); + void Send_Heartbeat(void); + void Send_Continue(void); + void Check_Timeouts(void); + void Start_Countdown(void); + void Update_Countdown(void); + + DesyncClass State; + std::int64_t OpenedAt = 0; + bool ContinueReceived = false; + int LastCountdownSecond = -1; +}; + + +// The out-of-sync screen the driver is running, or NULL when none is up. The network code +// reaches the model through this wherever a change is produced away from a screen. +UIDesyncPresenterClass * UI_Desync_Screen(void); +void UI_Set_Desync_Screen(UIDesyncPresenterClass * screen); + + +// Shows the variant the screen says it is, and runs it until the decision is made. The +// document is released when the screen closes, because the screen is not come back to. +UIResult UI_Desync_Run(UIDesyncPresenterClass & presenter); +void UI_Desync_Close_View(void); diff --git a/ui/desyncbase.rcss b/ui/desyncbase.rcss new file mode 100644 index 000000000..5f1ef2f5d --- /dev/null +++ b/ui/desyncbase.rcss @@ -0,0 +1,140 @@ +/* What the two out-of-sync documents share. Only the look and the geometry both templates + agree on live here; each document's own stylesheet carries what its variant changes. + + Geometry from the IDD_DESYNC_HOST and IDD_DESYNC_WAIT templates, converted from dialog + units at the 8 point MS Sans Serif they name: 1.5 pixels across and 1.625 down, with a + child's offset taken from the panel's content box and the panel's declared size taken + inside its own border. The two templates are both 360 x 264 dialog units, so 540 x 429 + pixels, and every control they share stands in the same place. */ + +#dialog +{ + left: 50%; + top: 50%; + margin-left: -270dp; + margin-top: -214.5dp; + + width: 536dp; + height: 425dp; +} + +/* The heading, a CTEXT at 30, 10 across 280 units. */ +#header +{ + left: 45dp; + top: 16.25dp; + width: 420dp; + height: 16.25dp; + line-height: 16.25dp; + text-align: center; + color: #e4e6da; +} + +/* "Players:" at 30, 23 and the 120 x 105 seat list at 30, 35. */ +#playerslabel { left: 45dp; top: 37.375dp; width: 150dp; height: 16.25dp; line-height: 16.25dp; } +#players { left: 45dp; top: 56.875dp; width: 180dp; height: 170.625dp; } + +/* A scrolling container gives its children no width to be a proportion of, so the row + states its own, and the columns stand where OD_ADDCOLUMN put them: the marker at 2, the + name at 20, and the status against the list's right edge less 56. */ +#players .row { width: 168dp; } + +#players .mark +{ + display: block; + position: absolute; + left: 2dp; + top: 0dp; + width: 16dp; + text-align: center; + color: #e4e6da; +} + +#players .name +{ + display: block; + position: absolute; + left: 20dp; + top: 0dp; + width: 98dp; + white-space: nowrap; + overflow: hidden; +} + +#players .status +{ + display: block; + position: absolute; + left: 124dp; + top: 0dp; + width: 44dp; + white-space: nowrap; + overflow: hidden; +} + +/* The two blocks of prose beside the list, LTEXT at 160, 25 across 180 x 82 units and at + 160, 127 across 180 x 22. Each template writes its own words. */ +.prose +{ + display: block; + position: absolute; + box-sizing: border-box; + left: 240dp; + width: 270dp; + color: #b9bcae; + line-height: 16dp; +} + +#prose { top: 40.625dp; height: 133.25dp; } +#footprose { top: 206.375dp; height: 35.75dp; } + +/* The chat list, 300 x 60 at 30, 145, and the chat entry, 299 x 12 at 30, 207. */ +#messages { left: 45dp; top: 235.625dp; width: 450dp; height: 97.5dp; } +#messages .line { width: 438dp; } + +#say { left: 45dp; top: 336.375dp; width: 448.5dp; height: 19.5dp; line-height: 15.5dp; } + +/* The countdown, hidden until a load is scheduled: the text at 30, 223 and the bar's + placeholder group box at 180, 222. */ +#countdowntext +{ + left: 45dp; + top: 362.375dp; + width: 210dp; + height: 16.25dp; + line-height: 16.25dp; +} + +#countdownframe +{ + display: block; + position: absolute; + box-sizing: border-box; + left: 270dp; + top: 360.75dp; + width: 223.5dp; + height: 19.5dp; + + background-color: #14160f; + border-width: 1dp; + border-top-color: #10120e; + border-left-color: #10120e; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +/* The bar shrinks as the load nears, which is what Draw_Countdown_Bar drew straight into + the surface. Its color comes from the model, because the dialog changed it with the time + left rather than with a state a stylesheet can name. */ +#countdownbar +{ + display: block; + position: absolute; + left: 0dp; + top: 0dp; + height: 100%; + min-width: 6dp; +} + +/* The three buttons, 70 x 12 dialog units at y 239. */ +.button { top: 388.375dp; width: 105dp; height: 19.5dp; line-height: 15.5dp; } diff --git a/ui/desynchost.rcss b/ui/desynchost.rcss new file mode 100644 index 000000000..408378030 --- /dev/null +++ b/ui/desynchost.rcss @@ -0,0 +1,5 @@ +/* The master's out-of-sync screen, IDD_DESYNC_HOST. It carries all three buttons, at 30, + 145 and 260 dialog units across the bottom row the base stylesheet places. */ +#load { left: 45dp; } +#continue { left: 217.5dp; } +#quit { left: 390dp; } diff --git a/ui/desynchost.rml b/ui/desynchost.rml new file mode 100644 index 000000000..6a7c15ff6 --- /dev/null +++ b/ui/desynchost.rml @@ -0,0 +1,41 @@ + + + Synchronization error + + + + + + +
+ + +
Players:
+
+
+
{{ entry.mark }}
+
{{ entry.name }}
+
{{ entry.status }}
+
+
+ +
The game has gone out of sync.

Press "Load Game" to load a saved game from this session, re-syncing the game for all players.

Press "Continue" to continue playing without the desynced players. They will continue in a separate game session.
+
Press "Quit" to exit the game.
+ +
+
{{ line }}
+
+ + + +
{{ countdowntext }}
+
+
+
+ +
Load Game
+
Continue
+
Quit
+
+ +
diff --git a/ui/desyncwait.rcss b/ui/desyncwait.rcss new file mode 100644 index 000000000..3ca80e91f --- /dev/null +++ b/ui/desyncwait.rcss @@ -0,0 +1,4 @@ +/* The waiting player's out-of-sync screen, IDD_DESYNC_WAIT. The template gives it the quit + button alone, in the middle position at 145 dialog units, and disables it: the quit comes + back once the stall has lasted long enough to be worth abandoning. */ +#quit { left: 217.5dp; } diff --git a/ui/desyncwait.rml b/ui/desyncwait.rml new file mode 100644 index 000000000..bcf858366 --- /dev/null +++ b/ui/desyncwait.rml @@ -0,0 +1,39 @@ + + + Synchronization error + + + + + + +
+ + +
Players:
+
+
+
{{ entry.mark }}
+
{{ entry.name }}
+
{{ entry.status }}
+
+
+ +
The game has gone out of sync.

If there are saves available from this session, the game host can attempt to load a save to re-sync the game.

Alternatively, the host can choose for the desynced players to continue playing in separate game sessions.
+
Please wait while the host is making a decision.
+ +
+
{{ line }}
+
+ + + +
{{ countdowntext }}
+
+
+
+ +
Quit
+
+ +
From 40bfdd0be2868c594af729b3297ffb05be5d3473 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 10:50:05 +0100 Subject: [PATCH 146/179] docs(manual): say what ScreenWidth enlarges and what it does not Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- manual/content/keys/screenwidth.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/manual/content/keys/screenwidth.md b/manual/content/keys/screenwidth.md index 2c26fb653..2dace920b 100644 --- a/manual/content/keys/screenwidth.md +++ b/manual/content/keys/screenwidth.md @@ -2,3 +2,7 @@ key: ScreenWidth summary: The width in pixels of the game screen. --- + +This is the resolution the game renders at, not the size the picture is shown at. Raising it does not enlarge anything: it gives the tactical view more cells and the sidebar more room, while the front-end artwork keeps its own pixel size and sits in the middle of a larger, mostly empty screen. The menus, the score screens and the dialog panels are all drawn at 640 by 400 and none of them is scaled up to a larger screen. + +To fill a larger window with the game as it was drawn, leave this and [`ScreenHeight`](/keys/screenheight/) at 640 by 400 and set [`WindowWidth`](/keys/windowwidth/) and [`WindowHeight`](/keys/windowheight/) to the size wanted; the picture is then scaled to fit. A full-screen game already does this, because it covers the desktop and scales the picture into it. From 082b24278786cb2f3e71d082ad19ca0b45406d24 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 10:50:33 +0100 Subject: [PATCH 147/179] docs: record the out-of-sync screen and the lobby's network answer Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/UI_DESIGN.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index 3dcca416a..0f5e1116d 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -1,9 +1,9 @@ # UI system design Status: in progress. Steps 1 to 10 of the migration plan have landed, and step -11's first change and most of its second; nothing from step 12 onward is -implemented. Everything outside the migration plan -remains a proposal informed by source inspection and upstream documentation. +11 except its reconnect dialog; nothing from step 12 onward is implemented. +Everything outside the migration plan remains a proposal informed by source +inspection and upstream documentation. This page owns the UI architecture and migration; [Building OpenTS](BUILDING.md) owns build support and [Project direction](DIRECTION.md) the wider architecture. @@ -952,8 +952,19 @@ text beyond an ASCII test document. player and chat rosters and hand the driver one answer between them, and all three have their RmlUi view: `ui/gamelist.rml`, `ui/mphost.rml` and `ui/mpguest.rml`, sharing `ui/lobbybase.rcss` beside `ui/optionsbase.rcss` - and each carrying its own geometry. Disconnect, desync and reconnect are - outstanding. + and each carrying its own geometry. The out-of-sync screen follows in + `code/ui/uidesync.{h,cpp}` with `ui/desynchost.rml` and `ui/desyncwait.rml` + sharing `ui/desyncbase.rcss`, converted from the `IDD_DESYNC_HOST` and + `IDD_DESYNC_WAIT` templates. The reconnect and kick-vote dialog, + `IDD_MPLAYER_DISCONNECT` in `queue.cpp`, is outstanding. + + A screen answered by the network rather than by a button has to be told to + step aside too. `Get_Join_Responses` writes the driver's answer straight + into `_netresponse` for a confirmed start, a rejected join and a host + signing off, and none of those changes the screen the family is on, so the + runner held the guest's document open and the guest never entered the match + the host had started. The lobby records that it has been answered and + suspends on it, which is the same hook one cause further on. A lobby screen changes without a result, so the runner has to be told. The join protocol moves the family from the game list to the guest screen from From ba340cce0799c578c50259309d2d1a0b480d4195 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 11:06:58 +0100 Subject: [PATCH 148/179] feat(ui): give the reconnect screen a presenter and its document Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/queue.cpp | 295 ++++++++++---------------- code/queue.h | 5 + code/ui/uireconnect.cpp | 451 ++++++++++++++++++++++++++++++++++++++++ code/ui/uireconnect.h | 103 +++++++++ ui/reconnect.rcss | 96 +++++++++ ui/reconnect.rml | 26 +++ 6 files changed, 792 insertions(+), 184 deletions(-) create mode 100644 code/ui/uireconnect.cpp create mode 100644 code/ui/uireconnect.h create mode 100644 ui/reconnect.rcss create mode 100644 ui/reconnect.rml diff --git a/code/queue.cpp b/code/queue.cpp index 83b9bc2ad..ee653419d 100644 --- a/code/queue.cpp +++ b/code/queue.cpp @@ -162,6 +162,7 @@ #include "trigger.h" #include "trigtype.h" #include "tube.h" +#include "ui/uireconnect.h" #include "unit.h" #include "unittype.h" #include "vanim.h" @@ -324,6 +325,7 @@ static int Process_Reconnect_Dialog(CDTimerClass *timeout_time static int Handle_Timeout(ConnManClass *net, FrameSyncStruct *their); static void Stop_Game(bool=false); INT_PTR CALLBACK Reconnect_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); +static void Refill_Message_List(HWND window, UIReconnectPresenterClass const & screen); static void Close_Reconnect_Dialog(void); void Kick_Player_Now(ConnManClass *net, int kickee, FrameSyncStruct * their, bool error); bool Cast_Kick_Vote(int kicker, int kickee); @@ -2297,13 +2299,10 @@ static int Process_Reconnect_Dialog(CDTimerClass *timeout_time { static int displayed_time = 0; // time value currently displayed - static HWND disconnect_dialog; /// the disconnect/kick dialog - static int disconnect_return; /// set to IDCANCEL by Reconnect_Dialog_Proc + static HWND disconnect_dialog; /// the disconnect/kick dialog, when no document was shown int new_time; - int oldest_index; // index of person requiring a reconnect - int i,j; - char buf[256]; // for dialog text + int i; //------------------------------------------------------------------------ /// Update the frame-sync progress info for Draw_Sync_Bars. @@ -2314,106 +2313,92 @@ static int Process_Reconnect_Dialog(CDTimerClass *timeout_time } //------------------------------------------------------------------------ - /// The first time through, create the disconnect/kick dialog. + /// The first time through, open the screen. A build whose document will not + /// prepare gets the dialog instead, running against the same presenter. //------------------------------------------------------------------------ if (fresh) { TacticalActive = false; - disconnect_return = -1; - disconnect_dialog = WS_Create_Dialog(ProgramInstance, IDD_MPLAYER_DISCONNECT, MainWindow, Reconnect_Dialog_Proc, true); - Center_Window_Within_Window(disconnect_dialog); - if (disconnect_dialog) { - SetWindowLongPtr(disconnect_dialog, DWLP_USER, (LONG_PTR)&disconnect_return); - MouseCursor->Hide_Mouse(); - ShowWindow(disconnect_dialog, SW_SHOWNORMAL); - UpdateWindow(disconnect_dialog); - MouseCursor->Show_Mouse(); + disconnect_dialog = NULL; + + int frames[ARRAY_SIZE(SyncBarFrameSync)]; + int reported = 0; + for (i = 0; i < num_conn && i < (int)ARRAY_SIZE(frames); i++) { + frames[reported++] = their[i].frame; + } + + if (!UI_Reconnect_Open(reconn != 0, frames, reported)) { + disconnect_dialog = WS_Create_Dialog(ProgramInstance, IDD_MPLAYER_DISCONNECT, MainWindow, Reconnect_Dialog_Proc, true); + Center_Window_Within_Window(disconnect_dialog); + if (disconnect_dialog) { + MouseCursor->Hide_Mouse(); + ShowWindow(disconnect_dialog, SW_SHOWNORMAL); + UpdateWindow(disconnect_dialog); + MouseCursor->Show_Mouse(); + } } } - //------------------------------------------------------------------------ - /// If the user hit Cancel, bail out of the game. - //------------------------------------------------------------------------ - if (disconnect_return == IDCANCEL) { - WS_Destroy_Dialog(disconnect_dialog, false); - TacticalActive = true; - Map.Flag_To_Redraw(GS_REDRAW_ALL); - return(1); + UIReconnectPresenterClass * const screen = UI_Reconnect_Screen(); + if (screen == NULL) { + return(0); } + unsigned timings[ARRAY_SIZE(SyncBarFrameSync)]; + for (i = 0; i < (int)ARRAY_SIZE(timings); i++) { + timings[i] = SyncBarFrameSync[i].timing; + } + screen->Update_Bars((unsigned)SyncWaitElapsed, timings, (int)ARRAY_SIZE(timings)); + //------------------------------------------------------------------------ // Convert the timer to seconds //------------------------------------------------------------------------ new_time = *timeout_timer / TIMER_SECOND; //------------------------------------------------------------------------ - // If the timer has changed, or 'fresh' is set, redraw the dialog + // If the timer has changed, or 'fresh' is set, tell the screen //------------------------------------------------------------------------ if (fresh || new_time != displayed_time) { displayed_time = new_time; + screen->Set_Time_Remaining(displayed_time); + } - HWND item = GetDlgItem(disconnect_dialog, IDC_DISCONNECT_TIME_REMAINING); - if (item) { - sprintf(buf, Fetch_String(TXT_TIME_ALLOWED), displayed_time); - SendMessage(item, WM_SETTEXT, 0, (LPARAM)buf); - } - if (!(displayed_time & 1)) { - PostMessage(disconnect_dialog, WM_PAINT, 0, 0); - } + UI_Reconnect_Service(); - /* - * On creation, discard any stale kick proposals, clear the vote - * tallies, and fill the message list box. - */ - if (fresh) { - while (Session.KickProposals.Count()) { - delete Session.KickProposals[0]; - Session.KickProposals.Delete_Index(0); + //------------------------------------------------------------------------ + /// Put the model on the dialog's controls, for the build that has one. + //------------------------------------------------------------------------ + if (disconnect_dialog) { + if (screen->TimeChanged) { + HWND item = GetDlgItem(disconnect_dialog, IDC_DISCONNECT_TIME_REMAINING); + if (item) { + SendMessage(item, WM_SETTEXT, 0, (LPARAM)screen->TimeText.c_str()); } - memset(Session.KickVoteCount, 0, sizeof(Session.KickVoteCount)); - memset(Session.KickVoteWho, 0xFF, sizeof(Session.KickVoteWho)); - - HWND listbox = GetDlgItem(disconnect_dialog, IDC_DISCONNECT_MESSAGES); - if (listbox) { - if (reconn) { - //............................................................... - // Find the index of the person we're trying to reconnect to - //............................................................... - j = 0x7fffffff; - oldest_index = 0; - for (i = 0; i < num_conn; i++) { - if (their[i].frame < j) { - j = their[i].frame; - oldest_index = i; - } - } - if (Session.Type == GAME_IPX || Session.Type == GAME_INTERNET) { - sprintf(buf, Fetch_String(TXT_RECONNECTING_TO), Ipx.Connection_Name(Ipx.Connection_ID(oldest_index))); - } else { - sprintf(buf, Fetch_String(TXT_RECONNECTING_TO), Session.Players[1]->Name); - } - ListBox_AddString(listbox, buf); - ListBox_AddString(listbox, ""); - if (Session.Type == GAME_INTERNET) { - ListBox_AddString(listbox, Fetch_String(TXT_RECONNECT_HELP3)); - ListBox_AddString(listbox, Fetch_String(TXT_RECONNECT_HELP3B)); - ListBox_AddString(listbox, Fetch_String(TXT_RECONNECT_HELP3C)); - } - ListBox_AddString(listbox, Fetch_String(TXT_RECONNECT_HELP2)); - if (Session.Type == GAME_INTERNET) { - ListBox_AddString(listbox, Fetch_String(TXT_RECONNECT_HELP2B)); - } else if (Session.Type == GAME_IPX) { - ListBox_AddString(listbox, Fetch_String(TXT_RECONNECT_HELP4)); - } - ListBox_AddString(listbox, ""); - ListBox_AddString(listbox, Fetch_String(TXT_RECONNECT_HELP5)); - ListBox_AddString(listbox, Fetch_String(TXT_RECONNECT_HELP1)); - ListBox_AddString(listbox, ""); - } else { - sprintf(buf, Fetch_String(TXT_WAITING_FOR_CONNECTIONS)); - ListBox_AddString(listbox, buf); - } + if (!(displayed_time & 1)) { + PostMessage(disconnect_dialog, WM_PAINT, 0, 0); } + screen->TimeChanged = false; + } + + if (screen->MessagesChanged) { + Refill_Message_List(disconnect_dialog, *screen); + screen->MessagesChanged = false; + } + + screen->Drain(); + } + + //------------------------------------------------------------------------ + /// If the user gave up, bail out of the game. + //------------------------------------------------------------------------ + if (screen->Cancelled) { + if (disconnect_dialog) { + WS_Destroy_Dialog(disconnect_dialog, false); + disconnect_dialog = NULL; } + UI_Reconnect_Close(); + TacticalActive = true; + Map.Flag_To_Redraw(GS_REDRAW_ALL); + return(1); } return(0); @@ -2558,6 +2543,13 @@ static bool Kick_Proposal_Already_Pending(int kicker, int kickee) } +bool Kick_Vote_Is_Possible(int kicker, int kickee) +{ + return(Current_Player_From_ID(kicker) != NULL && Current_Player_From_ID(kickee) != NULL + && !Kick_Vote_Already_Cast(kicker, kickee)); +} + + /// /// Removes a departing player as both a kick target and a voter, including pending proposals. /// @@ -2600,72 +2592,24 @@ void Forget_Kick_Player(int player) } /// -/// Trims the message list box and scrolls it to the end. -/// Use this routine after adding a line to the reconnect dialog's message list, so that -/// the list stays a manageable length and the newest message stays in view. +/// Puts the screen's message list on the dialog's list box and scrolls it to the end. +/// The model holds the lines and the control shows them, so a presentation that is not a +/// window keeps the same backlog. /// -/// The list box to trim. -void ListBox_Trim(HWND listbox) +/// The reconnect dialog holding the list box. +/// The screen whose messages are shown. +static void Refill_Message_List(HWND window, UIReconnectPresenterClass const & screen) { - int string_count = ListBox_GetCount(listbox); - if (string_count > 50) { - ListBox_DeleteString(listbox, 0); - string_count--; - } - ListBox_SetTopIndex(listbox, string_count - 1); -} - - -/// -/// Proposes that a player be kicked out of the game. -/// This routine is called when one of the kick buttons on the reconnect dialog is pressed. -/// The proposal is sent to every other player and the local vote is cast right away. -/// Proposing to kick yourself, or to kick anybody at all during a tournament game, earns -/// nothing but a message in the dialog. -/// -/// The reconnect dialog to report the outcome in. -/// Index into the session player list of the one to be kicked. -void Propose_Kick_Player(HWND window, int id) -{ - if (id < 0 || id >= Session.Players.Count()) { - return; - } - - DebugString("Propose_Kick_Player %d - %s. Local id is %d\n", id, Session.Players[id]->Name, Session.Players[0]->Player.ID); HWND listbox = GetDlgItem(window, IDC_DISCONNECT_MESSAGES); - - if (id == 0) { - ListBox_AddString(listbox, Fetch_String(TXT_RECONNECT_KICK_SELF)); - ListBox_Trim(listbox); - return; - } - - if (Session.Type == GAME_INTERNET && WestwoodOnline_Tournament) { - ListBox_AddString(listbox, Fetch_String(TXT_CANT_KICK)); - ListBox_Trim(listbox); - return; - } - - int const kicker = Session.Players[0]->Player.ID; - int const kickee = Session.Players[id]->Player.ID; - if (Current_Player_From_ID(kicker) == NULL || Current_Player_From_ID(kickee) == NULL - || Kick_Vote_Already_Cast(kicker, kickee)) { + if (listbox == NULL) { return; } - GlobalPacketType gpacket; - NetGlobal::Initialize_Packet(gpacket, NET_PROPOSE_KICK); - strncpy(gpacket.Name, Session.Players[0]->Name, ARRAY_SIZE(gpacket.Name) - 1); - gpacket.Name[ARRAY_SIZE(gpacket.Name) - 1] = '\0'; - gpacket.Kick.KickerID = static_cast(kicker); - gpacket.Kick.KickeeID = static_cast(kickee); - - for (int i = 1; i < Session.Players.Count(); i++) { - DebugString("Sending kick proposal to %s\n", Session.Players[i]->Name); - Ipx.Send_Global_Message(&gpacket, sizeof(gpacket), 1, &Session.Players[i]->Address); + ListBox_ResetContent(listbox); + for (std::string const & line : screen.Messages) { + ListBox_AddString(listbox, line.c_str()); } - - Cast_Kick_Vote(kicker, kickee); + ListBox_SetTopIndex(listbox, ListBox_GetCount(listbox) - 1); } @@ -2745,10 +2689,10 @@ bool Cast_Kick_Vote(int kicker, int kickee) snprintf(buffer, sizeof(buffer), Fetch_String(TXT_RECONNECT_KICK_RECEIVED), kicker_player->Name, kickee_player->Name); - HWND topwindow = WS_Top_Window(); - HWND listbox = GetDlgItem(topwindow, IDC_DISCONNECT_MESSAGES); - ListBox_AddString(listbox, buffer); - ListBox_Trim(listbox); + UIReconnectPresenterClass * const screen = UI_Reconnect_Screen(); + if (screen != NULL) { + screen->Record_Message(buffer); + } } return(true); @@ -2762,7 +2706,7 @@ bool Cast_Kick_Vote(int kicker, int kickee) /// INT_PTR CALLBACK Reconnect_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) { - int * rc = (int *)GetWindowLongPtr(window, DWLP_USER); + UIReconnectPresenterClass * const screen = UI_Reconnect_Screen(); switch (message) { case IDCANCEL: @@ -2823,42 +2767,18 @@ INT_PTR CALLBACK Reconnect_Dialog_Proc(HWND window, UINT message, WPARAM wparam, return(TRUE); case WM_COMMAND: - switch (LOWORD(wparam)) { - case IDC_DISCONNECT_PLAYER1: - Propose_Kick_Player(window, 0); - break; - - case IDC_DISCONNECT_PLAYER2: - Propose_Kick_Player(window, 1); - break; - - case IDC_DISCONNECT_PLAYER3: - Propose_Kick_Player(window, 2); - break; - - case IDC_DISCONNECT_PLAYER4: - Propose_Kick_Player(window, 3); - break; - - case IDC_DISCONNECT_PLAYER5: - Propose_Kick_Player(window, 4); - break; - - case IDC_DISCONNECT_PLAYER6: - Propose_Kick_Player(window, 5); - break; - - case IDC_DISCONNECT_PLAYER7: - Propose_Kick_Player(window, 6); - break; - - case IDC_DISCONNECT_PLAYER8: - Propose_Kick_Player(window, 7); - break; - - case IDCANCEL: - *rc = IDCANCEL; - break; + if (screen == NULL) { + break; + } + + for (int seat = 0; seat < MAX_PLAYERS; seat++) { + if (LOWORD(wparam) == (WPARAM)SyncNameButtonControlsIDs[seat]) { + screen->Queue(UIIntent{UI_RECONNECT_KICK, "", seat}); + } + } + + if (LOWORD(wparam) == IDCANCEL) { + screen->Queue(UIIntent{UI_RECONNECT_CANCEL, "", 0}); } break; } @@ -2877,11 +2797,18 @@ INT_PTR CALLBACK Reconnect_Dialog_Proc(HWND window, UINT message, WPARAM wparam, static void Close_Reconnect_Dialog(void) { //------------------------------------------------------------------------ - // If the reconnect dialog was shown, force the map to redraw. + // If the reconnect screen was shown, force the map to redraw. //------------------------------------------------------------------------ + bool shown = UI_Reconnect_Has_View(); + UI_Reconnect_Close(); + HWND dialog = WS_Find_Dialog(IDD_MPLAYER_DISCONNECT); if (dialog) { WS_Destroy_Dialog(dialog, false); + shown = true; + } + + if (shown) { TacticalActive = true; Map.Flag_To_Redraw(GS_REDRAW_ALL); Map.Render(); diff --git a/code/queue.h b/code/queue.h index 539252dd0..cf145e9cc 100644 --- a/code/queue.h +++ b/code/queue.h @@ -56,5 +56,10 @@ NetGlobal::DecodeError Kick_Packet_Received(int kicker, int kickee); void Forget_Kick_Player(int player); +// Is a vote to kick worth putting to the other players? False when either side is no longer +// in the session or the voter has already cast this vote, which is what stopped a repeated +// press from sending the proposal again. +bool Kick_Vote_Is_Possible(int kicker, int kickee); + extern BasicTimerClass SentFrameSyncTimer; extern int SentFrameSyncCount; diff --git a/code/ui/uireconnect.cpp b/code/ui/uireconnect.cpp new file mode 100644 index 000000000..e34c7c3c5 --- /dev/null +++ b/code/ui/uireconnect.cpp @@ -0,0 +1,451 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The reconnect and kick-vote screen. What is preserved from IDD_MPLAYER_DISCONNECT, and +// where each came from: a seat gets a button carrying its name and a bar beside it that +// shrinks and turns yellow and then red as the wait on that seat drags on, which is what +// Draw_Sync_Bars painted straight into the surface; pressing a seat's button proposes that +// the seat be kicked, and proposing to kick yourself, or anybody at all in a tournament +// game, earns a line in the message list and nothing else; the message list carries the +// stall's own explanation, which differs between a reconnect and a first-time wait and +// between a LAN game and an internet one; and Cancel gives up on the game. +// +// The screen has no loop of its own. Wait_For_Players keeps servicing the network while the +// game is stalled, and it opens the screen, services it once a pass and closes it, the way +// it created and destroyed a modeless dialog. +// +// docs/UI_DESIGN.md, "Screens", owns the contracts this keeps to. + +#include "always.h" + +#include "uireconnect.h" + +#include "uiinternal.h" +#include "uirmlview.h" +#include "uishell.h" + +#include "data.h" +#include "dbgprint.h" +#include "ipxmgr.h" +#include "language/language.h" +#include "netglobal.h" +#include "queue.h" +#include "session.h" +#include "stats.h" + +#include +#include + +#include +#include + + +bool Cast_Kick_Vote(int kicker, int kickee); + + +/// +/// Records the seats, the stall's explanation and the cleared vote tallies the screen opens +/// with. +/// +/// True when the game is trying to reconnect to somebody, false +/// when it is still waiting for a connection that has never been made. +/// Each connection's reported frame number, used to name the seat the +/// game is furthest behind. +/// How many entries frames carries. +void UIReconnectPresenterClass::Open(bool reconnect, int const * frames, int connections) +{ + Cancelled = false; + Messages.clear(); + TimeText.clear(); + Result.reset(); + IsClosing = false; + + // A stale proposal from an earlier stall is not a vote in this one. + while (Session.KickProposals.Count()) { + delete Session.KickProposals[0]; + Session.KickProposals.Delete_Index(0); + } + memset(Session.KickVoteCount, 0, sizeof(Session.KickVoteCount)); + memset(Session.KickVoteWho, 0xFF, sizeof(Session.KickVoteWho)); + + Refresh(); + + char buffer[256]; + + if (!reconnect) { + Record_Message(Fetch_String(TXT_WAITING_FOR_CONNECTIONS)); + return; + } + + // The seat the game is furthest behind is the one it is trying to reconnect to. + int oldest = 0; + int lowest = 0x7fffffff; + for (int index = 0; index < connections && frames != NULL; index++) { + if (frames[index] < lowest) { + lowest = frames[index]; + oldest = index; + } + } + + char const * name = ""; + if (Session.Type == GAME_IPX || Session.Type == GAME_INTERNET) { + name = Ipx.Connection_Name(Ipx.Connection_ID(oldest)); + } else if (Session.Players.Count() > 1) { + name = Session.Players[1]->Name; + } + + std::snprintf(buffer, sizeof(buffer), Fetch_String(TXT_RECONNECTING_TO), name); + Record_Message(buffer); + Record_Message(""); + + if (Session.Type == GAME_INTERNET) { + Record_Message(Fetch_String(TXT_RECONNECT_HELP3)); + Record_Message(Fetch_String(TXT_RECONNECT_HELP3B)); + Record_Message(Fetch_String(TXT_RECONNECT_HELP3C)); + } + + Record_Message(Fetch_String(TXT_RECONNECT_HELP2)); + if (Session.Type == GAME_INTERNET) { + Record_Message(Fetch_String(TXT_RECONNECT_HELP2B)); + } else if (Session.Type == GAME_IPX) { + Record_Message(Fetch_String(TXT_RECONNECT_HELP4)); + } + + Record_Message(""); + Record_Message(Fetch_String(TXT_RECONNECT_HELP5)); + Record_Message(Fetch_String(TXT_RECONNECT_HELP1)); + Record_Message(""); +} + + +void UIReconnectPresenterClass::Execute(UIIntent const & intent) +{ + if (intent.Action == UI_RECONNECT_KICK) { + Propose_Kick(intent.Value); + return; + } + + if (intent.Action == UI_RECONNECT_CANCEL) { + Cancelled = true; + + UIResult result; + result.Outcome = UIResult::OUTCOME_CANCELLED; + Result = result; + return; + } +} + + +/// +/// Reads the session's seats into the view-model. Only the seats the game holds get a row, +/// which is what destroying the spare buttons stood for. +/// +void UIReconnectPresenterClass::Refresh(void) +{ + Players.clear(); + + for (int index = 0; index < Session.Players.Count() && index < MAX_PLAYERS; index++) { + PlayerRowType row; + row.Name = Session.Players[index]->Name; + Players.push_back(row); + } + + PlayersChanged = true; +} + + +/// +/// Moves every seat's bar. The local seat is never behind, so its bar stays full; every +/// other seat's is the wait since that seat last reported in. +/// +void UIReconnectPresenterClass::Update_Bars(unsigned elapsed, unsigned const * timings, int count) +{ + for (int index = 0; index < (int)Players.size(); index++) { + unsigned progress = 0; + + if (index != 0 && index < Session.Players.Count()) { + // A seat whose connection has already gone away has no timing to read. The + // dialog indexed the array with the -1 it got back for one. + int const connection = Ipx.Connection_Index(Session.Players[index]->Player.ID); + if (timings != NULL && connection >= 0 && connection < count) { + progress = elapsed - timings[connection]; + } + } + + Players[index].Lateness = progress > 480 ? 2 : (progress > 240 ? 1 : 0); + Players[index].Remaining = std::max(100 - (int)(100 * progress / 1200), 0); + } + + PlayersChanged = true; +} + + +void UIReconnectPresenterClass::Set_Time_Remaining(int seconds) +{ + char buffer[256]; + std::snprintf(buffer, sizeof(buffer), Fetch_String(TXT_TIME_ALLOWED), seconds); + + TimeText = buffer; + TimeChanged = true; +} + + +void UIReconnectPresenterClass::Record_Message(char const * line) +{ + Messages.emplace_back(line != NULL ? line : ""); + if ((int)Messages.size() > MESSAGE_LIMIT) { + Messages.erase(Messages.begin()); + } + + MessagesChanged = true; +} + + +/// +/// Proposes that a seat be kicked out of the game, telling every other player and casting +/// this machine's own vote. +/// +/// Index into the session's player list of the seat to be kicked. +void UIReconnectPresenterClass::Propose_Kick(int index) +{ + if (index < 0 || index >= Session.Players.Count()) { + return; + } + + DebugString("Propose_Kick_Player %d - %s. Local id is %d\n", index, Session.Players[index]->Name, + Session.Players[0]->Player.ID); + + if (index == 0) { + Record_Message(Fetch_String(TXT_RECONNECT_KICK_SELF)); + return; + } + + if (Session.Type == GAME_INTERNET && WestwoodOnline_Tournament) { + Record_Message(Fetch_String(TXT_CANT_KICK)); + return; + } + + int const kicker = Session.Players[0]->Player.ID; + int const kickee = Session.Players[index]->Player.ID; + if (!Kick_Vote_Is_Possible(kicker, kickee)) { + return; + } + + GlobalPacketType gpacket; + NetGlobal::Initialize_Packet(gpacket, NET_PROPOSE_KICK); + std::snprintf(gpacket.Name, sizeof(gpacket.Name), "%s", Session.Players[0]->Name); + gpacket.Kick.KickerID = static_cast(kicker); + gpacket.Kick.KickeeID = static_cast(kickee); + + for (int other = 1; other < Session.Players.Count(); other++) { + DebugString("Sending kick proposal to %s\n", Session.Players[other]->Name); + Ipx.Send_Global_Message(&gpacket, sizeof(gpacket), 1, &Session.Players[other]->Address); + } + + Cast_Kick_Vote(kicker, kickee); +} + + +/* +** The RmlUi view. One document: the template has no variants, and the seats it shows are +** the ones the game holds rather than a fixed eight. +*/ +namespace { + + // A seat as the document lays it out. The row's own position is carried here because the + // template puts the eight seats in two columns of four rather than in a list. + struct SeatViewType + { + std::string Name; + std::string Left; + std::string Top; + std::string Width; + std::string Hex; + }; + + + class ReconnectViewClass : public UIRmlViewClass + { + public: + ReconnectViewClass(UIReconnectPresenterClass & presenter) + : UIRmlViewClass(presenter, "reconnect.rml"), Screen(presenter) {} + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + private: + void Rebuild_Rows(void); + + UIReconnectPresenterClass & Screen; + + std::vector Seats; + }; + + + void ReconnectViewClass::Rebuild_Rows(void) + { + Seats.clear(); + + for (int index = 0; index < (int)Screen.Players.size(); index++) { + UIReconnectPresenterClass::PlayerRowType const & row = Screen.Players[index]; + + SeatViewType seat; + seat.Name = row.Name; + + // The template's two columns of four, at 22 and 175 dialog units across and + // every 18 units down from 12. + char position[16]; + std::snprintf(position, sizeof(position), "%gdp", index < 4 ? 33.0 : 262.5); + seat.Left = position; + std::snprintf(position, sizeof(position), "%gdp", 19.5 + (index % 4) * 29.25); + seat.Top = position; + + // The bar keeps a floor of six pixels of the group box it is drawn in, which is + // ten percent of the box's sixty. + std::snprintf(position, sizeof(position), "%d%%", std::max(row.Remaining, 10)); + seat.Width = position; + + seat.Hex = row.Lateness >= 2 ? "#c80000" : (row.Lateness == 1 ? "#c8c800" : "#00c800"); + + Seats.push_back(seat); + } + } + + + void ReconnectViewClass::Bind(Rml::DataModelConstructor & model) + { + Rebuild_Rows(); + + if (auto seat = model.RegisterStruct()) { + seat.RegisterMember("name", &SeatViewType::Name); + seat.RegisterMember("left", &SeatViewType::Left); + seat.RegisterMember("top", &SeatViewType::Top); + seat.RegisterMember("width", &SeatViewType::Width); + seat.RegisterMember("hex", &SeatViewType::Hex); + } + model.RegisterArray>(); + + model.Bind("seats", &Seats); + model.Bind("messages", &Screen.Messages); + model.Bind("timetext", &Screen.TimeText); + + model.BindEventCallback("kick", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{UI_RECONNECT_KICK, "", arguments[0].Get()}); + }); + + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const &) { + Screen.Queue(UIIntent{UI_RECONNECT_CANCEL, "", 0}); + }); + + // Escape gives up on the stalled game, which is the IDCANCEL IsDialogMessage sent + // the dialog whether or not the key reached its cancel button. + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key == Rml::Input::KI_ESCAPE) { + Screen.Queue(UIIntent{UI_RECONNECT_CANCEL, "", 0}); + } + }); + } + + + void ReconnectViewClass::Sync(void) + { + if (!Model) return; + + Rebuild_Rows(); + + Model.DirtyVariable("seats"); + Model.DirtyVariable("messages"); + Model.DirtyVariable("timetext"); + + Screen.PlayersChanged = false; + Screen.MessagesChanged = false; + Screen.TimeChanged = false; + } + + + // The one reconnect screen. Wait_For_Players opens one when the game stalls and closes + // it when the stall ends, and no caller opens a second while one is up. + UIReconnectPresenterClass * _Presenter = NULL; + ReconnectViewClass * _View = NULL; + +} // namespace + + +UIReconnectPresenterClass * UI_Reconnect_Screen(void) +{ + return(_Presenter); +} + + +bool UI_Reconnect_Open(bool reconnect, int const * frames, int connections) +{ + UI_Reconnect_Close(); + + _Presenter = new UIReconnectPresenterClass; + _Presenter->Open(reconnect, frames, connections); + + // The presentation is latched here, at screen entry. A document that will not prepare + // drops the screen back to the legacy dialog, which runs against the same presenter. + if (!UI_Use_Rml()) { + return(false); + } + + ReconnectViewClass * const view = new ReconnectViewClass(*_Presenter); + + // The wait loop stops servicing the map's input while this screen is up, so the screen + // owns the input scope the way the dialog did. + if (!view->Prepare(true)) { + delete view; + return(false); + } + + _View = view; + UI_Paint_Now(true); + return(true); +} + + +/// +/// The pass the wait loop gives the screen: what its events queued is executed, the document +/// is brought up to the model, and the result is put on screen. +/// +void UI_Reconnect_Service(void) +{ + if (_View == NULL || _Presenter == NULL) { + return; + } + + _Presenter->Drain(); + _View->Sync(); + UI_Paint_Now(false); +} + + +void UI_Reconnect_Close(void) +{ + if (_View != NULL) { + _View->Close(); + delete _View; + _View = NULL; + } + + delete _Presenter; + _Presenter = NULL; +} + + +bool UI_Reconnect_Has_View(void) +{ + return(_View != NULL); +} diff --git a/code/ui/uireconnect.h b/code/ui/uireconnect.h new file mode 100644 index 000000000..64ec485c3 --- /dev/null +++ b/code/ui/uireconnect.h @@ -0,0 +1,103 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The reconnect and kick-vote screen, IDD_MPLAYER_DISCONNECT. It stands over a stalled +// multiplayer game while Wait_For_Players keeps servicing the network, so it has no loop of +// its own: the wait loop opens it, services it once a pass and closes it. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + +#include +#include + + +inline constexpr char const * UI_RECONNECT_KICK = "kick"; +inline constexpr char const * UI_RECONNECT_CANCEL = "cancel"; + + +class UIReconnectPresenterClass : public UIPresenterClass +{ + public: + // A seat and how far behind it is. The bar's remaining part and its color are + // figures rather than pixels, because a presenter draws nothing. + struct PlayerRowType + { + std::string Name; + + // How much of the seat's bar is left, 0 to 100, which is what Draw_Sync_Bars + // scaled the group box's width by. + int Remaining = 100; + + // 0 while the seat is keeping up, 1 once it is late, 2 once it is very late. + int Lateness = 0; + }; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + + // The state the screen opens with: the seats, the prose for the stall it stands + // over, and the discarded kick proposals and vote tallies the dialog cleared on + // creation. + void Open(bool reconnect, int const * frames, int connections); + + // Moves every seat's bar. The timings are indexed by connection, as the wait loop + // holds them, because that is how the dialog read them. + void Update_Bars(unsigned elapsed, unsigned const * timings, int count); + + void Set_Time_Remaining(int seconds); + + // Records a line for whatever is showing the screen. The vote announcements arrive + // here from the wait loop as well as from a button. + void Record_Message(char const * line); + + // Puts a kick to the other players and casts this machine's own vote. The index is + // into the session's player list, as the button that raised it was. + void Propose_Kick(int index); + + /* + ** The view-model. + */ + + std::vector Players; + std::vector Messages; + + // The most lines the model keeps, which is what ListBox_Trim capped the message + // list box at. + enum { MESSAGE_LIMIT = 50 }; + + std::string TimeText; + + // Has the player given up on the stalled game? The wait loop reads this where it + // read IDCANCEL out of the dialog's own result. + bool Cancelled = false; + + bool PlayersChanged = false; + bool MessagesChanged = false; + bool TimeChanged = false; +}; + + +// The screen the wait loop is running, or NULL when none is up. The vote tally reaches the +// model through this, because a vote is counted where no screen is in hand. +UIReconnectPresenterClass * UI_Reconnect_Screen(void); + +// Opens the screen and shows its document. A false return means no document was shown and +// the caller opens the legacy dialog against the same presenter. +bool UI_Reconnect_Open(bool reconnect, int const * frames, int connections); + +// The pass the wait loop gives the screen: the queued intents are executed and the document +// is brought up to the model and put on screen. Does nothing without a document. +void UI_Reconnect_Service(void); + +void UI_Reconnect_Close(void); +bool UI_Reconnect_Has_View(void); diff --git a/ui/reconnect.rcss b/ui/reconnect.rcss new file mode 100644 index 000000000..1a6f34477 --- /dev/null +++ b/ui/reconnect.rcss @@ -0,0 +1,96 @@ +/* The reconnect and kick-vote screen, IDD_MPLAYER_DISCONNECT. + + Geometry converted from the template's dialog units at the 8 point MS Sans Serif it + names: 1.5 pixels across and 1.625 down, with a child's offset taken from the panel's + content box and the panel's declared size taken inside its own border. The template is + 339 x 220 dialog units, so 508.5 x 357.5 pixels, and its driver centers it. */ + +#dialog +{ + left: 50%; + top: 50%; + margin-left: -254.25dp; + margin-top: -178.75dp; + + width: 504.5dp; + height: 353.5dp; +} + +/* A seat: the button carrying the player's name and the sync bar beside it. The template + puts eight of these in two columns of four, the button 90 x 14 units and the bar's group + box 40 x 10 six units to its right, so a seat spans 136 units and each carries its own + position because the game shows only the seats it holds. */ +.seat +{ + display: block; + position: absolute; + width: 204dp; + height: 22.75dp; +} + +.seat .kick +{ + left: 0dp; + top: 0dp; + width: 135dp; + height: 22.75dp; + line-height: 15.5dp; +} + +.seat .barframe +{ + display: block; + position: absolute; + box-sizing: border-box; + left: 144dp; + top: 0dp; + width: 60dp; + height: 16.25dp; + + background-color: #14160f; + border-width: 1dp; + border-top-color: #10120e; + border-left-color: #10120e; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +/* The bar shrinks and changes color as the wait on its seat drags on, which is what + Draw_Sync_Bars filled into the surface. Both come from the model, because the dialog + chose them from elapsed time rather than from a state a stylesheet can name. The floor is + the six pixels the dialog kept of the box's sixty. */ +.seat .bar +{ + display: block; + position: absolute; + left: 0dp; + top: 0dp; + height: 100%; + min-width: 6dp; +} + +/* The time remaining, an LTEXT at 29, 88 across 279 x 8 units. */ +#timeremaining +{ + left: 43.5dp; + top: 143dp; + width: 418.5dp; + height: 13dp; + line-height: 13dp; + color: #e4e6da; +} + +/* The message list, 295 x 84 units at 22, 103. It is a LBS_NOSEL list box, which is what + the shared log style stands for. */ +#messages { left: 33dp; top: 167.375dp; width: 442.5dp; height: 136.5dp; } +#messages .line { width: 430.5dp; } + +/* The cancel button, 295 x 14 units at 22, 194. */ +#cancel +{ + left: 33dp; + top: 315.25dp; + width: 442.5dp; + height: 22.75dp; + line-height: 15.5dp; +} diff --git a/ui/reconnect.rml b/ui/reconnect.rml new file mode 100644 index 000000000..5a815257b --- /dev/null +++ b/ui/reconnect.rml @@ -0,0 +1,26 @@ + + + Waiting for players + + + + + +
+
+
{{ entry.name }}
+
+
+
+
+ +
{{ timetext }}
+ +
+
{{ line }}
+
+ +
[[TXT_CANCEL]]
+
+ +
From b185d2cd75fbd13a65d90e080bbe1618f3f27175 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 11:18:58 +0100 Subject: [PATCH 149/179] fix(ui): give the out-of-sync seat row a positioning context The seat cells are positioned absolutely, so without one they resolved against the list and every seat painted over the first. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- ui/desyncbase.rcss | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ui/desyncbase.rcss b/ui/desyncbase.rcss index 5f1ef2f5d..45ddad102 100644 --- a/ui/desyncbase.rcss +++ b/ui/desyncbase.rcss @@ -37,7 +37,14 @@ /* A scrolling container gives its children no width to be a proportion of, so the row states its own, and the columns stand where OD_ADDCOLUMN put them: the marker at 2, the name at 20, and the status against the list's right edge less 56. */ -#players .row { width: 168dp; } +#players .row +{ + position: relative; + width: 168dp; + height: 16dp; + line-height: 16dp; + padding: 0dp; +} #players .mark { From 3d9dadcb09b1404ffe8bf203d66e8b9b9c287df2 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 11:18:58 +0100 Subject: [PATCH 150/179] fix(ui): register the message list's type so the binding takes Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/ui/uidesync.cpp | 1 + code/ui/uireconnect.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/code/ui/uidesync.cpp b/code/ui/uidesync.cpp index 5362c0cdf..59d1e2e53 100644 --- a/code/ui/uidesync.cpp +++ b/code/ui/uidesync.cpp @@ -493,6 +493,7 @@ namespace { row.RegisterMember("mark", &PlayerViewType::Mark); } model.RegisterArray>(); + model.RegisterArray>(); model.Bind("players", &PlayerRows); model.Bind("messages", &Screen.Messages); diff --git a/code/ui/uireconnect.cpp b/code/ui/uireconnect.cpp index e34c7c3c5..6edda8c68 100644 --- a/code/ui/uireconnect.cpp +++ b/code/ui/uireconnect.cpp @@ -330,6 +330,7 @@ namespace { seat.RegisterMember("hex", &SeatViewType::Hex); } model.RegisterArray>(); + model.RegisterArray>(); model.Bind("seats", &Seats); model.Bind("messages", &Screen.Messages); From 5d8436117d8f0356f2d50b3f3d90ffef0d5102a2 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 11:43:47 +0100 Subject: [PATCH 151/179] fix(ui): resample the map preview instead of blitting it Bit_Blit copies the smaller of the two rectangles row for row, so an engine surface blit between rectangles of different sizes cropped the picture rather than scaling it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/ui/uimappreview.cpp | 24 +++++++++++++++++++----- code/ui/uimappreview.h | 10 ++++++++-- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/code/ui/uimappreview.cpp b/code/ui/uimappreview.cpp index d3691db6f..5a3774738 100644 --- a/code/ui/uimappreview.cpp +++ b/code/ui/uimappreview.cpp @@ -19,10 +19,11 @@ #include -MapPreviewSurfaceClass::MapPreviewSurfaceClass(int width, int height) : +MapPreviewSurfaceClass::MapPreviewSurfaceClass(int width, int height, MapPreviewClass * const * source) : UISurfaceBufferClass(width, height), Width(width), - Height(height) + Height(height), + Source(source != NULL ? source : &MultiplayerMapPreview) { Set_Transparent_Color(DSurface::Build_Hicolor_Pixel(255, 0, 255)); Clear(); @@ -33,11 +34,11 @@ void MapPreviewSurfaceClass::Redraw(void) { Clear(); - if (MultiplayerMapPreview == NULL) { + if (*Source == NULL) { return; } - XSurface * const picture = MultiplayerMapPreview->Get_Preview_Surface(); + XSurface * const picture = (*Source)->Get_Preview_Surface(); if (picture == NULL) { return; } @@ -55,6 +56,19 @@ void MapPreviewSurfaceClass::Redraw(void) destination.X = Width / 2 - destination.Width / 2; destination.Y = Height / 2 - destination.Height / 2; - Get_Surface().Blit_From(destination, *picture, source, false, false); + // The picture is resampled here rather than blitted. Bit_Blit copies the smaller of the + // two rectangles row for row, so an engine surface blit between rectangles of different + // sizes crops the picture instead of scaling it; only DSurface's own blitter stretches, + // and this buffer is not one. + Surface & buffer = Get_Surface(); + for (int y = 0; y < destination.Height; y++) { + int const sy = source.Y + (y * source.Height) / destination.Height; + for (int x = 0; x < destination.Width; x++) { + int const sx = source.X + (x * source.Width) / destination.Width; + buffer.Put_Pixel(Point2D(destination.X + x, destination.Y + y), + picture->Get_Pixel(Point2D(sx, sy))); + } + } + Mark_Dirty(); } diff --git a/code/ui/uimappreview.h b/code/ui/uimappreview.h index 846e9aaa8..b12775c10 100644 --- a/code/ui/uimappreview.h +++ b/code/ui/uimappreview.h @@ -18,12 +18,17 @@ #include "uisurface.h" +class MapPreviewClass; + + class MapPreviewSurfaceClass : public UISurfaceBufferClass { public: // The extents are the interior of the template's preview frame, in game logical - // units, because a provider's pixels are game logical units. - MapPreviewSurfaceClass(int width, int height); + // units, because a provider's pixels are game logical units. The source is the + // variable holding the picture, not the picture, because every owner replaces its + // preview object rather than redrawing one; NULL means the session's own. + MapPreviewSurfaceClass(int width, int height, MapPreviewClass * const * source = NULL); // Draws the session's current preview, scaled and centered the way // MapPreviewClass::Blit_Preview scales it into a dialog's group box. The letterbox @@ -33,4 +38,5 @@ class MapPreviewSurfaceClass : public UISurfaceBufferClass private: int Width; int Height; + MapPreviewClass * const * Source; }; From d6d69617b691c28caf92b440565351e65d2cb84a Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 11:43:47 +0100 Subject: [PATCH 152/179] feat(ui): give the map generator a presenter and its three documents Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/mapgen.cpp | 57 ++- code/ui/uimapgen.cpp | 839 +++++++++++++++++++++++++++++++++++++++++++ code/ui/uimapgen.h | 191 ++++++++++ ui/mapgen.rcss | 60 ++++ ui/mapgen.rml | 62 ++++ ui/mapgenbase.rcss | 167 +++++++++ ui/mapgenfs.rcss | 65 ++++ ui/mapgenfs.rml | 68 ++++ ui/mapgenwdt.rcss | 65 ++++ ui/mapgenwdt.rml | 68 ++++ 10 files changed, 1629 insertions(+), 13 deletions(-) create mode 100644 code/ui/uimapgen.cpp create mode 100644 code/ui/uimapgen.h create mode 100644 ui/mapgen.rcss create mode 100644 ui/mapgen.rml create mode 100644 ui/mapgenbase.rcss create mode 100644 ui/mapgenfs.rcss create mode 100644 ui/mapgenfs.rml create mode 100644 ui/mapgenwdt.rcss create mode 100644 ui/mapgenwdt.rml diff --git a/code/mapgen.cpp b/code/mapgen.cpp index f03f7e2fc..59114f543 100644 --- a/code/mapgen.cpp +++ b/code/mapgen.cpp @@ -54,6 +54,8 @@ #include "terrtype.h" #include "tiberium.h" #include "trigtype.h" +#include "ui/uimapgen.h" +#include "ui/uishell.h" #include "unit.h" #include "unittype.h" #include "vector.h" @@ -3266,11 +3268,24 @@ int Do_Random_Map_Dialog(bool (*callback)()) wdt = WDT_Get_Territory(Session.WDTTerritory); } - HWND dialog; - if (Addon_Enabled(ADDON_FIRESTORM)) { - dialog = OwnerDraw::Begin_Dialog(wdt != NULL ? IDD_MAPGEN_WDT : IDD_MAPGEN_FS, Map_Seed_Dialog_Proc); - } else { - dialog = OwnerDraw::Begin_Dialog(IDD_MAPGEN, Map_Seed_Dialog_Proc); + // The presentation is latched here, at screen entry. A document that will not prepare + // drops the screen back to the dialog, which works from the same settings. + if (UI_Use_Rml()) { + UIMapGenPresenterClass screen; + screen.Open(callback); + + RMGCallback = callback; + RandomMapGen.SeedData.Callback = callback; + res = UI_MapGen_Run(screen); + } + + HWND dialog = NULL; + if (res == 0) { + if (Addon_Enabled(ADDON_FIRESTORM)) { + dialog = OwnerDraw::Begin_Dialog(wdt != NULL ? IDD_MAPGEN_WDT : IDD_MAPGEN_FS, Map_Seed_Dialog_Proc); + } else { + dialog = OwnerDraw::Begin_Dialog(IDD_MAPGEN, Map_Seed_Dialog_Proc); + } } if (dialog) { @@ -4704,6 +4719,22 @@ double Sample_Truncated_Normal(double mean, double scale, double lower_bound, do /// Should the scenario be rebuilt from scratch and the preview redrawn /// between phases? /// The map generator dialog to repaint as the preview is refreshed. +/// +/// Puts the freshly drawn preview on screen while a map is being built. +/// +/// The dialog to repaint, or NULL when a document is showing the +/// picture instead. +static void Repaint_Map_Preview(HWND dialog) +{ + if (dialog != NULL) { + Repaint_Map_Preview(dialog); + return; + } + + UI_MapGen_Preview_Changed(); +} + + void MapGeneratorClass::Generate_Random_Map(bool full_init, HWND dialog) { if (RMGCallback != NULL) RMGCallback(); @@ -4731,7 +4762,7 @@ void MapGeneratorClass::Generate_Random_Map(bool full_init, HWND dialog) if (full_init) { RandomMapGen.MapPreview->Create_Preview(); - SendMessage(dialog, WM_PAINT, 0, 0); + Repaint_Map_Preview(dialog); } if (RMGCallback != NULL) RMGCallback(); @@ -4748,7 +4779,7 @@ void MapGeneratorClass::Generate_Random_Map(bool full_init, HWND dialog) if (full_init) { RandomMapGen.MapPreview->Create_Preview(); - SendMessage(dialog, WM_PAINT, 0, 0); + Repaint_Map_Preview(dialog); } if (RMGCallback != NULL) RMGCallback(); @@ -4764,7 +4795,7 @@ void MapGeneratorClass::Generate_Random_Map(bool full_init, HWND dialog) if (full_init) { RandomMapGen.MapPreview->Create_Preview(); - SendMessage(dialog, WM_PAINT, 0, 0); + Repaint_Map_Preview(dialog); } if (RMGCallback != NULL) RMGCallback(); @@ -4791,7 +4822,7 @@ void MapGeneratorClass::Generate_Random_Map(bool full_init, HWND dialog) if (full_init) { RandomMapGen.MapPreview->Create_Preview(); - SendMessage(dialog, WM_PAINT, 0, 0); + Repaint_Map_Preview(dialog); } if (RMGCallback != NULL) RMGCallback(); @@ -4852,7 +4883,7 @@ void MapGeneratorClass::Generate_Random_Map(bool full_init, HWND dialog) if (full_init) { RandomMapGen.MapPreview->Create_Preview(); - SendMessage(dialog, WM_PAINT, 0, 0); + Repaint_Map_Preview(dialog); } if (RMGCallback != NULL) RMGCallback(); @@ -4871,7 +4902,7 @@ void MapGeneratorClass::Generate_Random_Map(bool full_init, HWND dialog) if (full_init) { RandomMapGen.MapPreview->Create_Preview(); - SendMessage(dialog, WM_PAINT, 0, 0); + Repaint_Map_Preview(dialog); } if (RMGCallback != NULL) RMGCallback(); @@ -4895,7 +4926,7 @@ void MapGeneratorClass::Generate_Random_Map(bool full_init, HWND dialog) if (full_init) { RandomMapGen.MapPreview->Create_Preview(); - SendMessage(dialog, WM_PAINT, 0, 0); + Repaint_Map_Preview(dialog); } if (RMGCallback != NULL) RMGCallback(); @@ -4918,7 +4949,7 @@ void MapGeneratorClass::Generate_Random_Map(bool full_init, HWND dialog) if (full_init) { RandomMapGen.MapPreview->Create_Preview(); - SendMessage(dialog, WM_PAINT, 0, 0); + Repaint_Map_Preview(dialog); } ScenarioInit--; diff --git a/code/ui/uimapgen.cpp b/code/ui/uimapgen.cpp new file mode 100644 index 000000000..7e185d0f1 --- /dev/null +++ b/code/ui/uimapgen.cpp @@ -0,0 +1,839 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The random map generator screen. What is preserved from IDD_MAPGEN, IDD_MAPGEN_FS and +// IDD_MAPGEN_WDT, and where each came from: the variant is chosen by whether Firestorm is +// enabled and whether the session names a tournament territory, not by the caller; the +// environment and time of day lists are sorted by name and the two size lists are not, +// because only the first two templates carry CBS_SORT; every setting is read off the screen +// when a button is pressed rather than tracked, which is what Get_Settings did; a tournament +// territory narrows each track bar's span and may lock it, and a span with nothing left to +// choose between is shown disabled rather than hidden; the tournament variant fixes the +// teams at two on two and offers no player count; and the seed field, which every template +// declares hidden and disabled, becomes visible only where a territory says the player may +// change the seed. +// +// docs/UI_DESIGN.md, "Screens", owns the contracts this keeps to. + +#include "always.h" + +#include "uimapgen.h" + +#include "uiinternal.h" +#include "uimappreview.h" +#include "uirmlview.h" +#include "uishell.h" +#include "uisurface.h" + +#include "addon.h" +#include "ccrand.h" +#include "data.h" +#include "dbgprint.h" +#include "init.h" +#include "language/language.h" +#include "mapgen.h" +#include "preview.h" +#include "scenario.h" +#include "session.h" +#include "wdtnet.h" +#include "worlddom.h" + +#include +#include + +#include +#include +#include + + +namespace { + + // The strings the three lists are built from, in the order the settings are numbered. + int const _BiomeNames[BIOME_COUNT] = { + TXT_BIOME_TUNDRA, + TXT_BIOME_TAIGA, + TXT_BIOME_TEMPERATE, + TXT_BIOME_DESERT, + TXT_BIOME_MUTATED, + }; + + int const _TimeNames[TIME_OF_DAY_COUNT] = { + TXT_TIME_MORNING, + TXT_TIME_AFTERNOON, + TXT_TIME_DUSK, + TXT_TIME_NIGHT, + }; + + int const _SizeNames[MAPSIZE_COUNT] = { + TXT_MAPSIZE_SMALL, + TXT_MAPSIZE_MEDIUM, + TXT_MAPSIZE_LARGE, + TXT_MAPSIZE_VERY_LARGE, + }; + + + // The territory the session is being fought over, or NULL outside a tournament game. + WDTTerritory * Tournament_Territory(void) + { + if (Session.Type != GAME_INTERNET || !Session.IsWDT) { + return(NULL); + } + return(WDT_Get_Territory(Session.WDTTerritory)); + } + + + // A span with nothing to choose between is shown disabled and left on the full scale, + // which is what Set_Scroll_Bar did with a max no greater than its min. + void Set_Range(UIMapGenPresenterClass::RangeType & range, int min, int max, int value, bool enable) + { + if (max <= min) { + range.Min = 0; + range.Max = 100; + range.Enabled = false; + } else { + range.Min = min; + range.Max = max; + range.Enabled = enable; + } + range.Value = value; + } + +} // namespace + + +static UIMapGenPresenterClass * _MapGenScreen = NULL; + + +UIMapGenPresenterClass * UI_MapGen_Screen(void) +{ + return(_MapGenScreen); +} + + +/// +/// Picks the variant and reads the generator's settings into the view-model. +/// +/// The progress callback the driver ran on every pass of its loop. +void UIMapGenPresenterClass::Open(bool (*callback)()) +{ + Callback = callback; + Pending = PENDING_NONE; + Result.reset(); + IsClosing = false; + + WDTTerritory const * const wdt = Tournament_Territory(); + if (wdt != NULL) { + Variant = VARIANT_WDT; + } else if (Addon_Enabled(ADDON_FIRESTORM)) { + Variant = VARIANT_FIRESTORM; + } else { + Variant = VARIANT_BASE; + } + + // A screen with no seed of its own rolls one, which is what the dialog did as it opened. + if (RandomMapGen.SeedData.Seed == -1) { + RandomMapGen.SeedData.Seed = Sim_Random_Pick(0U, 65535U); + } + + // The preview button is the one control the map debugger takes away, because that build + // generates the map outright rather than previewing it. + CanPreview = !Debug_Map; + + Build_Choices(); + Refresh(); +} + + +void UIMapGenPresenterClass::Execute(UIIntent const & intent) +{ + if (intent.Action == UI_MAPGEN_SET) { + Set_Value(intent.Identity, intent.Value); + return; + } + + if (intent.Action == UI_MAPGEN_TOGGLE) { + if (intent.Identity == "lifeforms" && Lifeforms.Enabled) Lifeforms.State = !Lifeforms.State; + else if (intent.Identity == "ionstorms" && IonStorms.Enabled) IonStorms.State = !IonStorms.State; + else if (intent.Identity == "transitions" && Transitions.Enabled) Transitions.State = !Transitions.State; + return; + } + + if (intent.Action == UI_MAPGEN_SEED) { + SeedText = intent.Identity; + return; + } + + if (intent.Action == UI_MAPGEN_CANCEL) { + Answer(ANSWER_CANCELLED); + return; + } + + if (intent.Action == UI_MAPGEN_OK) { + Apply(); + + if (Debug_Map) { + RandomMapGen.Generate_Random_Map(false, NULL); + Scen->Set_Scenario_Name(Fetch_String(TXT_RANDOM_MAP_DESCRIPTION)); + Write_Scenario_INI("RandMap.Map", true); + } else if (RandomMapGen.MapPreview == NULL || RandomMapGen.MapPreview->Get_Preview_Surface() == NULL) { + // A map the player never previewed has to be built before it can be accepted. + RandomMapGen.Generate_Random_Map(true, NULL); + } + + Answer(ANSWER_ACCEPTED); + return; + } + + if (intent.Action == UI_MAPGEN_PREVIEW) { + if (!CanPreview) return; + Apply(); + Generate_Preview(); + return; + } + + if (intent.Action == UI_MAPGEN_SURPRISE) { + if (!CanSurprise) return; + Apply(); + RandomMapGen.SeedData.Randomize(); + Refresh(); + return; + } + + if (intent.Action == UI_MAPGEN_LOAD) { + if (!CanLoad) return; + Apply(); + Pending = PENDING_LOAD; + return; + } + + if (intent.Action == UI_MAPGEN_SAVE) { + Apply(); + Pending = PENDING_SAVE; + return; + } + + if (intent.Action == UI_MAPGEN_DELETE) { + if (!CanDelete) return; + Apply(); + Pending = PENDING_DELETE; + return; + } +} + + +/// +/// Reads the generator's settings and the territory's permissions into the view-model. +/// +void UIMapGenPresenterClass::Refresh(void) +{ + MapSeedClass & seed = RandomMapGen.SeedData; + WDTTerritory const * const wdt = Tournament_Territory(); + + seed.Fixup_Settings(); + + Biome = seed.Biome; + Time = seed.Time; + Width = seed.Width; + Height = seed.Height; + + char text[32]; + std::snprintf(text, sizeof(text), "%d", seed.Seed); + SeedText = text; + + BiomeEnabled = wdt == NULL || wdt->UserModBiome; + TimeEnabled = wdt == NULL || wdt->UserModTime; + WidthEnabled = wdt == NULL || wdt->UserModWidth; + HeightEnabled = wdt == NULL || wdt->UserModHeight; + SeedEnabled = wdt == NULL || wdt->UserModSeed; + + if (wdt != NULL) { + Set_Range(Tiberium, wdt->TiberiumAmountMin, wdt->TiberiumAmountMax, seed.Tiberium, wdt->UserModTiberiumAmount); + Set_Range(Hills, wdt->HillsMin, wdt->HillsMax, seed.Hills, wdt->UserModHills); + Set_Range(Water, wdt->WaterMin, wdt->WaterMax, seed.WaterAmount, wdt->UserModWater); + Set_Range(Cliffs, wdt->CliffsMin, wdt->CliffsMax, seed.Cliffs, wdt->UserModCliffs); + Set_Range(Vegetation, wdt->VegetationMin, wdt->VegetationMax, seed.Vegetation, wdt->UserModVegetation); + Set_Range(Cities, wdt->CitiesMin, wdt->CitiesMax, seed.Cities, wdt->UserModCities); + Set_Range(TiberiumFields, wdt->TiberiumFieldsMin, wdt->TiberiumFieldsMax, seed.TiberiumLayout, wdt->UserModTiberiumFields); + Set_Range(Accessibility, wdt->AccessibilityMin, wdt->AccessibilityMax, seed.Accessibility, wdt->UserModAccessability); + Set_Range(Veinholes, 0, 5, seed.VeinholeMonsters, wdt->UserModVeinholeMonsters); + + // The territory fixes the teams, so the boxes say what they are rather than + // offering a choice. + OneOnOne = false; + TwoOnTwo = true; + + Lifeforms.State = seed.TiberiumWildlife > 0; + Lifeforms.Enabled = wdt->UserModTiberiumCreatures; + Transitions.State = seed.UseTransitions; + Transitions.Enabled = wdt->UserModTimeTransitions; + IonStorms.State = seed.UseIonStorms; + IonStorms.Enabled = true; + + // Nothing left to roll leaves the randomize button dead. + CanSurprise = wdt->UserModBiome || wdt->UserModTime || wdt->UserModCliffs + || wdt->UserModAccessability || wdt->UserModHills || wdt->UserModTiberiumAmount + || wdt->UserModTiberiumFields || wdt->UserModWater || wdt->UserModVegetation + || wdt->UserModCities || wdt->UserModWidth || wdt->UserModHeight + || wdt->UserModVeinholeMonsters; + } else { + Set_Range(Tiberium, 1, 100, seed.Tiberium, true); + Set_Range(Players, 2, MAX_PLAYERS, seed.NumPlayers, true); + Set_Range(Hills, 0, 100, seed.Hills, true); + Set_Range(Water, 0, 100, seed.WaterAmount, true); + Set_Range(Cliffs, 0, 100, seed.Cliffs, true); + Set_Range(Vegetation, 0, 100, seed.Vegetation, true); + Set_Range(Cities, 0, 100, seed.Cities, true); + Set_Range(TiberiumFields, 0, 100, seed.TiberiumLayout, true); + Set_Range(Accessibility, 0, 100, seed.Accessibility, true); + Set_Range(Veinholes, 0, 5, seed.VeinholeMonsters, true); + + Lifeforms.State = seed.TiberiumWildlife > 0; + Lifeforms.Enabled = true; + Transitions.State = seed.UseTransitions; + Transitions.Enabled = true; + IonStorms.State = seed.UseIonStorms; + IonStorms.Enabled = true; + + CanSurprise = true; + } + + Refresh_File_Buttons(); + SettingsChanged = true; +} + + +/// +/// The maintenance the driver ran on every pass of its own loop: the caller's progress +/// callback and the title screen behind the screen. +/// +void UIMapGenPresenterClass::Service(void) +{ + if (Callback != NULL) { + Callback(); + } + Title_Screen_Restore(false); +} + + +/// +/// Runs the browser the player asked for with this screen out of the way. +/// +void UIMapGenPresenterClass::Run_Pending(void) +{ + PendingType const pending = Pending; + Pending = PENDING_NONE; + + MapSeedClass & seed = RandomMapGen.SeedData; + + switch (pending) { + case PENDING_LOAD: + if (seed.LoadOptionsClass::Load()) { + Refresh(); + + // A loaded seed is previewed at once, which the dialog did by posting its + // own preview command to itself after it had put the settings back. + Queue(UIIntent{UI_MAPGEN_PREVIEW, "", 0}); + return; + } + Refresh(); + return; + + case PENDING_SAVE: + seed.MapDescription[0] = '\0'; + seed.LoadOptionsClass::Save(seed.MapDescription); + Refresh_File_Buttons(); + return; + + case PENDING_DELETE: + seed.LoadOptionsClass::Delete(); + Refresh_File_Buttons(); + return; + + default: + return; + } +} + + +/// +/// Writes the view-model back into the generator's settings, so that whatever the player +/// has dialed in becomes the seed the generator works from. +/// +void UIMapGenPresenterClass::Apply(void) +{ + MapSeedClass & seed = RandomMapGen.SeedData; + + seed.Biome = Biome; + seed.Time = Time; + seed.Width = Width; + seed.Height = Height; + seed.Seed = std::atoi(SeedText.c_str()); + + seed.Tiberium = Tiberium.Value; + seed.Hills = Hills.Value; + seed.WaterAmount = Water.Value; + seed.Cliffs = Cliffs.Value; + seed.Vegetation = Vegetation.Value; + seed.Cities = Cities.Value; + seed.TiberiumLayout = TiberiumFields.Value; + seed.Accessibility = Accessibility.Value; + + // A tournament territory fixes the player count at four; the variant that shows it has + // no player bar at all. + seed.NumPlayers = Variant == VARIANT_WDT ? 4 : Players.Value; + + seed.TiberiumWildlife = 0; + seed.VeinholeMonsters = 0; + seed.UseIonStorms = false; + seed.UseTransitions = false; + seed.UseBlueTiberium = false; + + if (Addon_Enabled(ADDON_FIRESTORM)) { + seed.TiberiumWildlife = Lifeforms.State ? 30 : 0; + seed.VeinholeMonsters = Veinholes.Value; + seed.UseIonStorms = IonStorms.State; + seed.UseTransitions = Transitions.State; + seed.UseBlueTiberium = (double)seed.Tiberium > 0.75; + } + + seed.Fixup_Settings(); +} + + +/// +/// Reports what the view-model holds for a control, so a view can drop a change that only +/// puts back the value it was given. +/// +int UIMapGenPresenterClass::Value_Of(std::string const & field) const +{ + if (field == "biome") return(Biome); + if (field == "time") return(Time); + if (field == "width") return(Width); + if (field == "height") return(Height); + + if (field == "players") return(Players.Value); + if (field == "accessibility") return(Accessibility.Value); + if (field == "cliffs") return(Cliffs.Value); + if (field == "hills") return(Hills.Value); + if (field == "tiberium") return(Tiberium.Value); + if (field == "tiberiumfields") return(TiberiumFields.Value); + if (field == "water") return(Water.Value); + if (field == "vegetation") return(Vegetation.Value); + if (field == "cities") return(Cities.Value); + if (field == "veinholes") return(Veinholes.Value); + + return(-1); +} + + +void UIMapGenPresenterClass::Set_Value(std::string const & field, int value) +{ + if (field == "biome") { Biome = value; return; } + if (field == "time") { Time = value; return; } + if (field == "width") { Width = value; return; } + if (field == "height") { Height = value; return; } + + if (field == "players") { Players.Value = value; return; } + if (field == "accessibility") { Accessibility.Value = value; return; } + if (field == "cliffs") { Cliffs.Value = value; return; } + if (field == "hills") { Hills.Value = value; return; } + if (field == "tiberium") { Tiberium.Value = value; return; } + if (field == "tiberiumfields") { TiberiumFields.Value = value; return; } + if (field == "water") { Water.Value = value; return; } + if (field == "vegetation") { Vegetation.Value = value; return; } + if (field == "cities") { Cities.Value = value; return; } + if (field == "veinholes") { Veinholes.Value = value; return; } + +} + + +/// +/// Builds the three lists the combo boxes offer. The mutated environment belongs to +/// Firestorm and is left out without it. +/// +void UIMapGenPresenterClass::Build_Choices(void) +{ + Biomes.clear(); + for (int index = BIOME_FIRST; index < BIOME_COUNT; index++) { + if (index != BIOME_MUTATED || Addon_Enabled(ADDON_FIRESTORM)) { + Biomes.push_back(ChoiceType{Fetch_String(_BiomeNames[index]), index}); + } + } + + Times.clear(); + for (int index = TIME_OF_DAY_FIRST; index < TIME_OF_DAY_COUNT; index++) { + Times.push_back(ChoiceType{Fetch_String(_TimeNames[index]), index}); + } + + Sizes.clear(); + for (int index = 0; index < MAPSIZE_COUNT; index++) { + Sizes.push_back(ChoiceType{Fetch_String(_SizeNames[index]), index}); + } + + // Only the environment and time of day combos carry CBS_SORT, so only those two are + // listed by name; the two size lists keep the order their settings are numbered in. + auto by_name = [](ChoiceType const & left, ChoiceType const & right) { return(left.Name < right.Name); }; + std::sort(Biomes.begin(), Biomes.end(), by_name); + std::sort(Times.begin(), Times.end(), by_name); +} + + +void UIMapGenPresenterClass::Answer(int answer) +{ + UIResult result; + result.Outcome = answer == ANSWER_ACCEPTED ? UIResult::OUTCOME_ACCEPTED : UIResult::OUTCOME_CANCELLED; + result.Value = answer; + Result = result; +} + + +/// +/// Builds the map the current settings describe and keeps it as the seed a later accept +/// works from. +/// +void UIMapGenPresenterClass::Generate_Preview(void) +{ + RandomMapGen.Generate_Random_Map(true, NULL); + RandomMapGen.MapPreview->Create_Preview(); + + delete RandomMapGen.MapSeeder; + RandomMapGen.MapSeeder = new MapSeedClass; + memcpy(RandomMapGen.MapSeeder, &RandomMapGen.SeedData, sizeof(MapSeedClass)); + + PreviewChanged = true; +} + + +void UIMapGenPresenterClass::Refresh_File_Buttons(void) +{ + bool const present = RandomMapGen.SeedData.Files_Present(); + CanLoad = present; + CanDelete = present; +} + + +/* +** The RmlUi view. The three templates are one document family: they differ by which +** controls exist and where they stand, so each carries its own document and its own model +** name. +*/ +namespace { + + // A combo row as the document shows it. + struct ChoiceViewType + { + std::string Name; + int Value = 0; + }; + + + class MapGenViewClass : public UIRmlViewClass + { + public: + MapGenViewClass(UIMapGenPresenterClass & presenter, char const * document) + : UIRmlViewClass(presenter, document), Screen(presenter) {} + + virtual void Bind(Rml::DataModelConstructor & model) override; + virtual void Sync(void) override; + + // Puts the ranges on the track bars before their values, because a range control + // clamps a value into the range it is holding. + void Arm_Ranges(void); + + void Attach_Preview(void); + void Release_Preview(void); + + private: + void Rebuild_Rows(void); + void Bind_Range(Rml::DataModelConstructor & model, char const * name, + UIMapGenPresenterClass::RangeType & range); + + UIMapGenPresenterClass & Screen; + + std::vector BiomeRows; + std::vector TimeRows; + std::vector SizeRows; + + std::unique_ptr Preview; + + // Have the controls been given their spans yet? A change raised while they are + // being armed is the model settling, not the player moving anything. + bool Settled = false; + }; + + + // The name the document gives the preview's pixels, and the interior of the template's + // preview frame in game logical units. + char const * const PREVIEW_SURFACE = "mapgenpreview"; + int const PREVIEW_WIDTH = 292; + int const PREVIEW_HEIGHT = 214; + + + void MapGenViewClass::Rebuild_Rows(void) + { + BiomeRows.clear(); + for (UIMapGenPresenterClass::ChoiceType const & row : Screen.Biomes) { + BiomeRows.push_back(ChoiceViewType{row.Name, row.Value}); + } + + TimeRows.clear(); + for (UIMapGenPresenterClass::ChoiceType const & row : Screen.Times) { + TimeRows.push_back(ChoiceViewType{row.Name, row.Value}); + } + + SizeRows.clear(); + for (UIMapGenPresenterClass::ChoiceType const & row : Screen.Sizes) { + SizeRows.push_back(ChoiceViewType{row.Name, row.Value}); + } + } + + + void MapGenViewClass::Bind_Range(Rml::DataModelConstructor & model, char const * name, + UIMapGenPresenterClass::RangeType & range) + { + model.Bind(name, &range.Value); + + Rml::String enabled = name; + enabled += "on"; + model.Bind(enabled, &range.Enabled); + } + + + void MapGenViewClass::Bind(Rml::DataModelConstructor & model) + { + Rebuild_Rows(); + + if (auto choice = model.RegisterStruct()) { + choice.RegisterMember("name", &ChoiceViewType::Name); + choice.RegisterMember("value", &ChoiceViewType::Value); + } + model.RegisterArray>(); + + model.Bind("biomes", &BiomeRows); + model.Bind("times", &TimeRows); + model.Bind("sizes", &SizeRows); + + model.Bind("biome", &Screen.Biome); + model.Bind("time", &Screen.Time); + model.Bind("width", &Screen.Width); + model.Bind("height", &Screen.Height); + + model.Bind("biomeon", &Screen.BiomeEnabled); + model.Bind("timeon", &Screen.TimeEnabled); + model.Bind("widthon", &Screen.WidthEnabled); + model.Bind("heighton", &Screen.HeightEnabled); + + model.Bind("seed", &Screen.SeedText); + model.Bind("seedon", &Screen.SeedEnabled); + + Bind_Range(model, "players", Screen.Players); + Bind_Range(model, "accessibility", Screen.Accessibility); + Bind_Range(model, "cliffs", Screen.Cliffs); + Bind_Range(model, "hills", Screen.Hills); + Bind_Range(model, "tiberium", Screen.Tiberium); + Bind_Range(model, "tiberiumfields", Screen.TiberiumFields); + Bind_Range(model, "water", Screen.Water); + Bind_Range(model, "vegetation", Screen.Vegetation); + Bind_Range(model, "cities", Screen.Cities); + Bind_Range(model, "veinholes", Screen.Veinholes); + + model.Bind("lifeforms", &Screen.Lifeforms.State); + model.Bind("lifeformson", &Screen.Lifeforms.Enabled); + model.Bind("ionstorms", &Screen.IonStorms.State); + model.Bind("ionstormson", &Screen.IonStorms.Enabled); + model.Bind("transitions", &Screen.Transitions.State); + model.Bind("transitionson", &Screen.Transitions.Enabled); + + model.Bind("oneonone", &Screen.OneOnOne); + model.Bind("twoontwo", &Screen.TwoOnTwo); + + model.Bind("cansurprise", &Screen.CanSurprise); + model.Bind("canpreview", &Screen.CanPreview); + model.Bind("canload", &Screen.CanLoad); + model.Bind("candelete", &Screen.CanDelete); + + model.BindEventCallback("press", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{arguments[0].Get(), "", 0}); + }); + + // A form control is bound one way and a change matching the value the model holds is + // dropped, so putting the model on a control cannot look like the player moving it. + model.BindEventCallback("change", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const & arguments) { + if (!Settled || arguments.empty()) return; + + Rml::String const field = arguments[0].Get(); + int const value = (int)(event.GetParameter("value", 0.0f) + 0.5f); + if (value == Screen.Value_Of(field)) return; + + Screen.Queue(UIIntent{UI_MAPGEN_SET, field, value}); + }); + + model.BindEventCallback("toggle", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { + if (arguments.empty()) return; + Screen.Queue(UIIntent{UI_MAPGEN_TOGGLE, arguments[0].Get(), 0}); + }); + + model.BindEventCallback("typeseed", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + Rml::String const value = event.GetParameter("value", Rml::String()); + if (value == Screen.SeedText) return; + Screen.Queue(UIIntent{UI_MAPGEN_SEED, value, 0}); + }); + + model.BindEventCallback("key", + [this](Rml::DataModelHandle, Rml::Event & event, Rml::VariantList const &) { + int const key = event.GetParameter("key_identifier", Rml::Input::KI_UNKNOWN); + if (key == Rml::Input::KI_ESCAPE) { + Screen.Queue(UIIntent{UI_MAPGEN_CANCEL, "", 0}); + } + }); + } + + + /// + /// Puts each track bar's span on the control before its value, because a range control + /// clamps a value into the range it is holding. + /// + void MapGenViewClass::Arm_Ranges(void) + { + if (Element == nullptr) return; + + Settled = false; + + struct { char const * Id; UIMapGenPresenterClass::RangeType const * Range; } const bars[] = { + { "players", &Screen.Players }, + { "accessibility", &Screen.Accessibility }, + { "cliffs", &Screen.Cliffs }, + { "hills", &Screen.Hills }, + { "tiberium", &Screen.Tiberium }, + { "tiberiumfields", &Screen.TiberiumFields }, + { "water", &Screen.Water }, + { "vegetation", &Screen.Vegetation }, + { "cities", &Screen.Cities }, + { "veinholes", &Screen.Veinholes }, + }; + + for (auto const & bar : bars) { + Rml::Element * const element = Element->GetElementById(bar.Id); + if (element == nullptr) continue; + + element->SetAttribute("min", bar.Range->Min); + element->SetAttribute("max", bar.Range->Max); + element->SetAttribute("step", 1); + element->SetAttribute("value", bar.Range->Value); + } + + Settled = true; + } + + + void MapGenViewClass::Attach_Preview(void) + { + Preview = std::make_unique(PREVIEW_WIDTH, PREVIEW_HEIGHT, &RandomMapGen.MapPreview); + UI_Register_Surface(PREVIEW_SURFACE, Preview.get()); + } + + + void MapGenViewClass::Release_Preview(void) + { + UI_Unregister_Surface(PREVIEW_SURFACE); + Preview.reset(); + } + + + void MapGenViewClass::Sync(void) + { + if (!Model) return; + + if (Screen.PreviewChanged && Preview != nullptr) { + Preview->Redraw(); + Screen.PreviewChanged = false; + } + + if (Screen.SettingsChanged) { + Arm_Ranges(); + Screen.SettingsChanged = false; + } + + Model.DirtyAllVariables(); + } + + + MapGenViewClass * _View = NULL; + +} // namespace + + +void UI_MapGen_Preview_Changed(void) +{ + if (_MapGenScreen == NULL || _View == NULL) { + return; + } + + // The generator does not pump between phases, so the picture is put on screen here, the + // way the dialog got a synchronous repaint out of SendMessage. + _MapGenScreen->PreviewChanged = true; + _View->Sync(); + UI_Paint_Now(false); +} + + +/// +/// Shows the variant this session gets and runs it until the player accepts or cancels. +/// +/// What Do_Random_Map_Dialog reports: 1 accepted, 2 cancelled, 0 not shown. +int UI_MapGen_Run(UIMapGenPresenterClass & presenter) +{ + char const * document = "mapgen.rml"; + if (presenter.Variant == UIMapGenPresenterClass::VARIANT_WDT) { + document = "mapgenwdt.rml"; + } else if (presenter.Variant == UIMapGenPresenterClass::VARIANT_FIRESTORM) { + document = "mapgenfs.rml"; + } + + MapGenViewClass * const view = new MapGenViewClass(presenter, document); + if (!view->Prepare(true)) { + delete view; + return(0); + } + + _View = view; + _MapGenScreen = &presenter; + + view->Attach_Preview(); + view->Arm_Ranges(); + view->Sync(); + + while (!presenter.Result.has_value()) { + UI_Run_Modal(presenter, *view); + + if (!presenter.Suspends()) { + break; + } + + // A browser draws where this screen is, so the document steps aside for it. + view->Hide(); + presenter.Run_Pending(); + view->Show(); + view->Sync(); + } + + view->Release_Preview(); + view->Close(); + + _MapGenScreen = NULL; + _View = NULL; + delete view; + + return(presenter.Result.has_value() ? presenter.Result->Value : UIMapGenPresenterClass::ANSWER_CANCELLED); +} diff --git a/code/ui/uimapgen.h b/code/ui/uimapgen.h new file mode 100644 index 000000000..6f3e089c1 --- /dev/null +++ b/code/ui/uimapgen.h @@ -0,0 +1,191 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// The random map generator screen. One screen with three variants -- the base game's, the +// Firestorm one and the tournament one -- chosen by what the session is rather than by the +// caller, which is the sound screen's shape. +// +// docs/UI_DESIGN.md, "Screens", owns the contract. + +#pragma once + +#include "uiscreen.h" + +#include +#include + + +inline constexpr char const * UI_MAPGEN_OK = "ok"; +inline constexpr char const * UI_MAPGEN_CANCEL = "cancel"; +inline constexpr char const * UI_MAPGEN_LOAD = "load"; +inline constexpr char const * UI_MAPGEN_SAVE = "save"; +inline constexpr char const * UI_MAPGEN_DELETE = "delete"; +inline constexpr char const * UI_MAPGEN_PREVIEW = "preview"; +inline constexpr char const * UI_MAPGEN_SURPRISE = "surprise"; + +// Carries a control's new value: the identity names the setting and the value carries it. +inline constexpr char const * UI_MAPGEN_SET = "set"; + +// Flips a check box. The identity names it. +inline constexpr char const * UI_MAPGEN_TOGGLE = "toggle"; + +// Carries the seed the player typed, in the identity, because it arrives as text. +inline constexpr char const * UI_MAPGEN_SEED = "seed"; + + +class UIMapGenPresenterClass : public UIPresenterClass +{ + public: + // Which of the three templates the screen is. The addon and the session decide it, + // not the menu that opened the screen. + enum VariantType { + VARIANT_BASE, + VARIANT_FIRESTORM, + VARIANT_WDT, + }; + + // What the screen answers its driver with. Do_Random_Map_Dialog reports 1 for an + // accepted map and 2 for a cancelled screen. + enum { + ANSWER_ACCEPTED = 1, + ANSWER_CANCELLED = 2, + }; + + // A choice a combo box offers. The value is the setting, not the row, because two + // of the lists are sorted by name and a row is not a setting. + struct ChoiceType + { + std::string Name; + int Value = 0; + }; + + // A track bar and the span it is allowed. A tournament territory narrows the span + // and may lock the bar, and a span with nothing to choose between is shown disabled + // rather than hidden, which is what Set_Scroll_Bar did. + struct RangeType + { + int Min = 0; + int Max = 100; + int Value = 0; + bool Enabled = true; + }; + + // A check box and whether the player may change it. + struct SwitchType + { + bool State = false; + bool Enabled = true; + }; + + virtual void Execute(UIIntent const & intent) override; + virtual void Refresh(void) override; + virtual void Service(void) override; + + // The load, save and delete browsers draw where this screen is, so this screen is + // stepped aside for them rather than run underneath. + virtual bool Suspends(void) const override { return(Pending != PENDING_NONE); } + + // Picks the variant and reads the generator's settings into the view-model. Called + // once, before a view is prepared. + void Open(bool (*callback)()); + + // What the view-model holds for a control, so a view can drop a change that only puts + // back the value it was given. + int Value_Of(std::string const & field) const; + + // Runs the browser the player asked for with this screen out of the way. Called by + // the owner between passes, never from an event. + void Run_Pending(void); + + /* + ** The view-model. + */ + + VariantType Variant = VARIANT_BASE; + + std::vector Biomes; + std::vector Times; + std::vector Sizes; + + int Biome = 0; + int Time = 0; + int Width = 0; + int Height = 0; + + bool BiomeEnabled = true; + bool TimeEnabled = true; + bool WidthEnabled = true; + bool HeightEnabled = true; + + std::string SeedText; + bool SeedEnabled = true; + + RangeType Players; + RangeType Accessibility; + RangeType Cliffs; + RangeType Hills; + RangeType Tiberium; + RangeType TiberiumFields; + RangeType Water; + RangeType Vegetation; + RangeType Cities; + RangeType Veinholes; + + SwitchType Lifeforms; + SwitchType IonStorms; + SwitchType Transitions; + + // The tournament variant's two team boxes. The dialog set them and left them + // disabled, so they say what the territory is rather than offering a choice. + bool OneOnOne = false; + bool TwoOnTwo = true; + + bool CanSurprise = true; + bool CanPreview = true; + bool CanLoad = false; + bool CanDelete = false; + + // Has the preview picture moved? The view reads it and clears it, because the + // picture is the view's to own. + bool PreviewChanged = false; + + bool SettingsChanged = false; + + private: + enum PendingType { + PENDING_NONE, + PENDING_LOAD, + PENDING_SAVE, + PENDING_DELETE, + }; + + void Apply(void); + void Set_Value(std::string const & field, int value); + void Build_Choices(void); + void Answer(int answer); + void Generate_Preview(void); + void Refresh_File_Buttons(void); + + PendingType Pending = PENDING_NONE; + + // The maintenance the driver ran on every pass of its own loop. + bool (*Callback)(void) = NULL; +}; + + +// The generator screen the driver is running, or NULL when none is up. The generator reaches +// the picture through this while it is building a map. +UIMapGenPresenterClass * UI_MapGen_Screen(void); + +// The map preview has been redrawn. Called where the generator told the dialog to repaint. +void UI_MapGen_Preview_Changed(void); + +// Shows the variant the screen says it is and runs it until the player accepts or cancels. +// The answer is what Do_Random_Map_Dialog returns; zero means no document was shown. +int UI_MapGen_Run(UIMapGenPresenterClass & presenter); diff --git a/ui/mapgen.rcss b/ui/mapgen.rcss new file mode 100644 index 000000000..9d88b7f48 --- /dev/null +++ b/ui/mapgen.rcss @@ -0,0 +1,60 @@ +/* The base game's map generator, IDD_MAPGEN: 424 x 242 dialog units, so 636 x 393.25 pixels. + It has no Firestorm settings, its two combos start further across than the other two + templates put theirs, and its seed field sits beside the map size lists. */ + +#dialog +{ + left: 50%; + top: 50%; + margin-left: -318dp; + margin-top: -196.625dp; + + width: 632dp; + height: 389.25dp; +} + +#environmentlabel { left: 33dp; top: 13dp; width: 99dp; height: 22.75dp; line-height: 22.75dp; } +#timelabel { left: 33dp; top: 43.875dp; width: 99dp; height: 22.75dp; line-height: 22.75dp; } +#playerslabel { left: 33dp; top: 76.375dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#cliffslabel { left: 33dp; top: 107.25dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#accessibilitylabel { left: 33dp; top: 138.125dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#hillslabel { left: 33dp; top: 169dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#tiberiumlabel { left: 33dp; top: 199.875dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#tiberiumfieldslabel { left: 33dp; top: 230.75dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#waterlabel { left: 33dp; top: 261.625dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#vegetationlabel { left: 33dp; top: 292.5dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#citieslabel { left: 33dp; top: 323.375dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#widthlabel { left: 309dp; top: 13dp; width: 97.5dp; height: 19.5dp; line-height: 19.5dp; } +#heightlabel { left: 307.5dp; top: 40.625dp; width: 97.5dp; height: 19.5dp; line-height: 19.5dp; } + +/* The nine track bars, all at 95 dialog units across. */ +#players { left: 142.5dp; top: 76.375dp; } +#cliffs { left: 142.5dp; top: 107.25dp; } +#accessibility { left: 142.5dp; top: 138.125dp; } +#hills { left: 142.5dp; top: 169dp; } +#tiberium { left: 142.5dp; top: 199.875dp; } +#tiberiumfields { left: 142.5dp; top: 230.75dp; } +#water { left: 142.5dp; top: 261.625dp; } +#vegetation { left: 142.5dp; top: 292.5dp; } +#cities { left: 142.5dp; top: 323.375dp; } + +/* The two sorted combos and the two size combos. */ +#biome { left: 142.5dp; top: 13dp; width: 150dp; } +#time { left: 142.5dp; top: 40.625dp; width: 150dp; } +#width { left: 415.5dp; top: 13dp; width: 94.5dp; } +#height { left: 415.5dp; top: 40.625dp; width: 94.5dp; } + +#seed { left: 517.5dp; top: 40.625dp; width: 85.5dp; height: 19.5dp; line-height: 19.5dp; } + +#previewframe { left: 307.5dp; top: 76.375dp; } +#previewword { top: 87.75dp; height: 26dp; line-height: 26dp; } + +#surprise { left: 307.5dp; top: 313.625dp; } +#previewmap { left: 471dp; top: 313.625dp; } + +/* The bottom row: save, load, delete, then OK and cancel. */ +#save { left: 33dp; } +#load { left: 150dp; } +#delete { left: 267dp; } +#ok { left: 397.5dp; } +#cancel { left: 505.5dp; } diff --git a/ui/mapgen.rml b/ui/mapgen.rml new file mode 100644 index 000000000..08c56128c --- /dev/null +++ b/ui/mapgen.rml @@ -0,0 +1,62 @@ + + + Random map + + + + + +
+
Environment:
+ +
Time of Day:
+ +
Map Width:
+ +
Map Height:
+ + + +
Players:
+ +
Cliffs:
+ +
Accessability:
+ +
Hills:
+ +
Tiberium Amount:
+ +
Tiberium Fields:
+ +
Water:
+ +
Vegetation:
+ +
Cities:
+ + +
+
Preview
+ +
+ +
Surprise Me
+
Preview Map
+ +
Save Map
+
Load Map
+
Delete Map
+
[[TXT_OK]]
+
[[TXT_CANCEL]]
+
+ +
diff --git a/ui/mapgenbase.rcss b/ui/mapgenbase.rcss new file mode 100644 index 000000000..ef808da8a --- /dev/null +++ b/ui/mapgenbase.rcss @@ -0,0 +1,167 @@ +/* What the three map generator documents share. Only the look lives here; each document's + own stylesheet carries its geometry, converted from its dialog template's units at the + 8 point MS Sans Serif they name: 1.5 pixels across and 1.625 down, with a child's offset + taken from the panel's content box and the panel's declared size taken inside its own + border. + + The palette and the raised and sunken borders are the ones ui/optionsbase.rcss + established for this family of dialogs. */ + +/* A caption the templates give SS_CENTERIMAGE, so its text sits in the middle of the box + the template declares rather than at the top of it. */ +.caption { color: #b9bcae; } + +/* The preview frame, a GROUPBOX 197 x 134 dialog units on all three templates. The picture + inside it is drawn by the screen and reaches the document through the element, + which takes its size from the provider. */ +#previewframe +{ + display: block; + position: absolute; + box-sizing: border-box; + width: 295.5dp; + height: 217.75dp; + + background-color: #14160f; + border-width: 1dp; + border-top-color: #10120e; + border-left-color: #10120e; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +#previewframe surface +{ + display: block; + position: absolute; + left: 0dp; + top: 0dp; +} + +/* The word the template writes across the middle of an empty frame. */ +#previewword +{ + display: block; + position: absolute; + left: 0dp; + width: 100%; + text-align: center; + color: #6e7360; +} + +/* The seed field, an ES_NUMBER edit all three templates declare NOT WS_VISIBLE and + WS_DISABLED. Nothing ever shows it: Set_Settings writes the seed into it and Get_Settings + reads the seed back out, so it is where the number lives between the two rather than + something the player types in. It keeps its place and its width here -- a field with no + width formats no line, and RmlUi's End key then moves the caret to the start of an empty + line rather than to the end of the value -- and the document hides it as the template + does. */ +#seed +{ + display: none; + position: absolute; + box-sizing: border-box; + white-space: nowrap; + overflow: hidden; + padding: 0dp 4dp; + + font-family: LatoLatin; + color: #e4e6da; + background-color: #14160f; + border-width: 2dp; + border-top-color: #10120e; + border-left-color: #10120e; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +#seed:focus { background-color: #1d2017; } + +/* A control the tournament territory has fixed. It is shown rather than hidden, so the + screen keeps its shape whatever the territory allows. */ +.locked +{ + color: #6b6e63; + pointer-events: none; +} + +/* The ten track bars, all 100 x 14 dialog units. */ +.slider { width: 150dp; height: 22.75dp; } + +/* The five combo boxes. A CBS_DROPDOWNLIST is sized the way ownrdraw.cpp sizes one: the + item height, which is the 14 pixel dialog font plus two, inside a two pixel border. The + template's own height is how far the list drops. */ +.combo +{ + display: block; + position: absolute; + box-sizing: border-box; + height: 20dp; + + color: #b9bcae; + background-color: #2b2f25; + border-width: 2dp; + border-top-color: #14160f; + border-left-color: #14160f; + border-right-color: #6e7360; + border-bottom-color: #6e7360; +} + +.combo selectvalue +{ + width: auto; + margin-right: 16dp; + height: 16dp; + line-height: 16dp; + padding: 0dp 4dp; + white-space: nowrap; + overflow: hidden; +} + +.combo selectarrow +{ + width: 16dp; + height: 16dp; + + background-color: #33372c; + border-width: 2dp; + border-top-color: #767c68; + border-left-color: #767c68; + border-right-color: #14160f; + border-bottom-color: #14160f; +} + +.combo selectbox +{ + width: 100%; + overflow-y: auto; + + background-color: #14160f; + border-width: 1dp; + border-top-color: #6e7360; + border-left-color: #6e7360; + border-right-color: #10120e; + border-bottom-color: #10120e; +} + +.combo selectbox option +{ + width: auto; + height: 18dp; + line-height: 18dp; + padding: 0dp 4dp; + white-space: nowrap; + color: #b9bcae; +} + +.combo selectbox option:hover { color: #e4e6da; } +.combo selectbox option:checked { background-color: #3f4536; } + +/* The five buttons across the bottom row, 65 x 14 dialog units at y 220, and the two above + the preview, 88 x 14. */ +.footbutton { top: 357.5dp; width: 97.5dp; height: 22.75dp; line-height: 15.5dp; } +.previewbutton { width: 132dp; height: 22.75dp; line-height: 15.5dp; } + +/* A BS_AUTOCHECKBOX, which the Firestorm and tournament templates put down the right hand + side, 84 x 10 dialog units. */ +.check { width: 126dp; height: 16.25dp; line-height: 12.25dp; } diff --git a/ui/mapgenfs.rcss b/ui/mapgenfs.rcss new file mode 100644 index 000000000..b472d4252 --- /dev/null +++ b/ui/mapgenfs.rcss @@ -0,0 +1,65 @@ +/* The Firestorm map generator, IDD_MAPGEN_FS: 426 x 242 dialog units, so 639 x 393.25 + pixels. It adds the veinhole bar and the three Firestorm check boxes down the right hand + side, moves the two sorted combos left, and puts the seed field under the bars. */ + +#dialog +{ + left: 50%; + top: 50%; + margin-left: -319.5dp; + margin-top: -196.625dp; + + width: 635dp; + height: 389.25dp; +} + +#environmentlabel { left: 33dp; top: 11.375dp; width: 79.5dp; height: 22.75dp; line-height: 22.75dp; } +#timelabel { left: 33dp; top: 39dp; width: 79.5dp; height: 22.75dp; line-height: 22.75dp; } +#playerslabel { left: 33dp; top: 66.625dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#cliffslabel { left: 33dp; top: 94.25dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#accessibilitylabel { left: 33dp; top: 121.875dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#hillslabel { left: 33dp; top: 149.5dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#tiberiumlabel { left: 33dp; top: 177.125dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#tiberiumfieldslabel { left: 33dp; top: 204.75dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#waterlabel { left: 33dp; top: 232.375dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#vegetationlabel { left: 33dp; top: 260dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#citieslabel { left: 33dp; top: 287.625dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#veinholeslabel { left: 33dp; top: 315.25dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#widthlabel { left: 280.5dp; top: 13dp; width: 79.5dp; height: 19.5dp; line-height: 19.5dp; } +#heightlabel { left: 279dp; top: 40.625dp; width: 79.5dp; height: 19.5dp; line-height: 19.5dp; } +#ionstorms { left: 480dp; top: 13dp; width: 126dp; height: 16.25dp; line-height: 16.25dp; } +#transitions { left: 480dp; top: 42.25dp; width: 126dp; height: 16.25dp; line-height: 16.25dp; } +#lifeforms { left: 480dp; top: 71.5dp; width: 126dp; height: 16.25dp; line-height: 16.25dp; } + +/* The track bars. */ +#players { left: 142.5dp; top: 66.625dp; } +#cliffs { left: 142.5dp; top: 94.25dp; } +#accessibility { left: 142.5dp; top: 121.875dp; } +#hills { left: 142.5dp; top: 149.5dp; } +#tiberium { left: 142.5dp; top: 177.125dp; } +#tiberiumfields { left: 142.5dp; top: 204.75dp; } +#water { left: 142.5dp; top: 232.375dp; } +#vegetation { left: 142.5dp; top: 260dp; } +#cities { left: 142.5dp; top: 287.625dp; } +#veinholes { left: 142.5dp; top: 315.25dp; } + +/* The combo boxes. */ +#biome { left: 118.5dp; top: 13dp; width: 150dp; } +#time { left: 118.5dp; top: 40.625dp; width: 150dp; } +#width { left: 372dp; top: 13dp; width: 94.5dp; } +#height { left: 372dp; top: 42.25dp; width: 94.5dp; } + +#seed { left: 33dp; top: 334.75dp; width: 85.5dp; height: 19.5dp; line-height: 19.5dp; } + +#previewframe { left: 310.5dp; top: 92.625dp; } +#previewword { top: 107.25dp; height: 26dp; line-height: 26dp; } + +#surprise { left: 307.5dp; top: 325dp; } +#previewmap { left: 474dp; top: 325dp; } + +/* The bottom row: save, load, delete, then OK and cancel. */ +#save { left: 33dp; } +#load { left: 150dp; } +#delete { left: 267dp; } +#ok { left: 397.5dp; } +#cancel { left: 505.5dp; } diff --git a/ui/mapgenfs.rml b/ui/mapgenfs.rml new file mode 100644 index 000000000..09f77ffc0 --- /dev/null +++ b/ui/mapgenfs.rml @@ -0,0 +1,68 @@ + + + Random map + + + + + +
+
Environment:
+ +
Time of Day:
+ +
Map Width:
+ +
Map Height:
+ + + +
Players:
+ +
Cliffs:
+ +
Accessability:
+ +
Hills:
+ +
Tiberium Amount:
+ +
Tiberium Fields:
+ +
Water:
+ +
Vegetation:
+ +
Cities:
+ +
Veinholes:
+ + +
Ion Storms
+
Transitions
+
Lifeforms
+ +
+
Preview
+ +
+ +
Surprise Me
+
Preview Map
+ +
Save Map
+
Load Map
+
Delete Map
+
[[TXT_OK]]
+
[[TXT_CANCEL]]
+
+ +
diff --git a/ui/mapgenwdt.rcss b/ui/mapgenwdt.rcss new file mode 100644 index 000000000..8466f5783 --- /dev/null +++ b/ui/mapgenwdt.rcss @@ -0,0 +1,65 @@ +/* The tournament map generator, IDD_MAPGEN_WDT: 426 x 242 dialog units, so 639 x 393.25 + pixels. It drops the player count bar the territory fixes and puts the two team boxes in + its place; everything else stands where the Firestorm template puts it, a row or two up. */ + +#dialog +{ + left: 50%; + top: 50%; + margin-left: -319.5dp; + margin-top: -196.625dp; + + width: 635dp; + height: 389.25dp; +} + +#environmentlabel { left: 33dp; top: 11.375dp; width: 79.5dp; height: 22.75dp; line-height: 22.75dp; } +#timelabel { left: 33dp; top: 39dp; width: 79.5dp; height: 22.75dp; line-height: 22.75dp; } +#cliffslabel { left: 33dp; top: 94.25dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#accessibilitylabel { left: 33dp; top: 121.875dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#hillslabel { left: 33dp; top: 149.5dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#tiberiumlabel { left: 33dp; top: 177.125dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#tiberiumfieldslabel { left: 33dp; top: 204.75dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#waterlabel { left: 33dp; top: 232.375dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#vegetationlabel { left: 33dp; top: 260dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#citieslabel { left: 33dp; top: 287.625dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#veinholeslabel { left: 33dp; top: 315.25dp; width: 111dp; height: 22.75dp; line-height: 22.75dp; } +#widthlabel { left: 280.5dp; top: 13dp; width: 79.5dp; height: 19.5dp; line-height: 19.5dp; } +#heightlabel { left: 279dp; top: 40.625dp; width: 79.5dp; height: 19.5dp; line-height: 19.5dp; } +#ionstorms { left: 480dp; top: 13dp; width: 126dp; height: 16.25dp; line-height: 16.25dp; } +#transitions { left: 480dp; top: 40.625dp; width: 126dp; height: 16.25dp; line-height: 16.25dp; } +#lifeforms { left: 480dp; top: 68.25dp; width: 126dp; height: 16.25dp; line-height: 16.25dp; } +#oneonone { left: 33dp; top: 71.5dp; width: 109.5dp; height: 16.25dp; line-height: 16.25dp; } +#twoontwo { left: 153dp; top: 71.5dp; width: 109.5dp; height: 16.25dp; line-height: 16.25dp; } + +/* The track bars. */ +#cliffs { left: 142.5dp; top: 94.25dp; } +#accessibility { left: 142.5dp; top: 121.875dp; } +#hills { left: 142.5dp; top: 149.5dp; } +#tiberium { left: 142.5dp; top: 177.125dp; } +#tiberiumfields { left: 142.5dp; top: 204.75dp; } +#water { left: 142.5dp; top: 232.375dp; } +#vegetation { left: 142.5dp; top: 260dp; } +#cities { left: 142.5dp; top: 287.625dp; } +#veinholes { left: 144dp; top: 315.25dp; } + +/* The combo boxes. */ +#biome { left: 118.5dp; top: 13dp; width: 150dp; } +#time { left: 118.5dp; top: 40.625dp; width: 150dp; } +#width { left: 372dp; top: 13dp; width: 94.5dp; } +#height { left: 372dp; top: 42.25dp; width: 94.5dp; } + +#seed { left: 33dp; top: 334.75dp; width: 85.5dp; height: 19.5dp; line-height: 19.5dp; } + +#previewframe { left: 310.5dp; top: 91dp; } +#previewword { top: 107.25dp; height: 26dp; line-height: 26dp; } + +#surprise { left: 307.5dp; top: 321.75dp; } +#previewmap { left: 474dp; top: 321.75dp; } + +/* The bottom row: save, load, delete, then OK and cancel. */ +#save { left: 33dp; } +#load { left: 150dp; } +#delete { left: 267dp; } +#ok { left: 397.5dp; } +#cancel { left: 505.5dp; } diff --git a/ui/mapgenwdt.rml b/ui/mapgenwdt.rml new file mode 100644 index 000000000..c42cbbdca --- /dev/null +++ b/ui/mapgenwdt.rml @@ -0,0 +1,68 @@ + + + Random map + + + + + +
+
Environment:
+ +
Time of Day:
+ +
Map Width:
+ +
Map Height:
+ + + +
1 on 1
+
2 on 2
+
Cliffs:
+ +
Accessability:
+ +
Hills:
+ +
Tiberium Amount:
+ +
Tiberium Fields:
+ +
Water:
+ +
Vegetation:
+ +
Cities:
+ +
Veinholes:
+ + +
Ion Storms
+
Transitions
+
Lifeforms
+ +
+
Preview
+ +
+ +
Surprise Me
+
Preview Map
+ +
Save Map
+
Load Map
+
Delete Map
+
[[TXT_OK]]
+
[[TXT_CANCEL]]
+
+ +
From 414e97359df1ad9513ecae705362a3d4d0517f3f Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 11:44:38 +0100 Subject: [PATCH 153/179] docs: record the reconnect and map generator screens Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/UI_DESIGN.md | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index 0f5e1116d..36a794617 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -1,7 +1,7 @@ # UI system design -Status: in progress. Steps 1 to 10 of the migration plan have landed, and step -11 except its reconnect dialog; nothing from step 12 onward is implemented. +Status: in progress. Steps 1 to 12 of the migration plan have landed; nothing +from step 13 onward is implemented. Everything outside the migration plan remains a proposal informed by source inspection and upstream documentation. This page owns the UI architecture and migration; [Building @@ -955,8 +955,20 @@ text beyond an ASCII test document. and each carrying its own geometry. The out-of-sync screen follows in `code/ui/uidesync.{h,cpp}` with `ui/desynchost.rml` and `ui/desyncwait.rml` sharing `ui/desyncbase.rcss`, converted from the `IDD_DESYNC_HOST` and - `IDD_DESYNC_WAIT` templates. The reconnect and kick-vote dialog, - `IDD_MPLAYER_DISCONNECT` in `queue.cpp`, is outstanding. + `IDD_DESYNC_WAIT` templates. The reconnect and kick-vote dialog follows in + `code/ui/uireconnect.{h,cpp}` with `ui/reconnect.rml`, converted from + `IDD_MPLAYER_DISCONNECT`. That screen has no loop of its own: + `Wait_For_Players` keeps servicing the network while the game is stalled, so + it opens the screen, services it once a pass and closes it, the way it created + and destroyed a modeless dialog. + + A list row states its own positioning context as well as its width. The + out-of-sync seat list gives each row three absolutely positioned cells, and + without `position: relative` on the row those cells resolved against the list + instead, so every seat painted over the first and a two-player game listed one + seat. A `std::vector` bound to a data model needs its array type + registered like any other; without that the binding is refused and the list + stays empty. A screen answered by the network rather than by a button has to be told to step aside too. `Get_Join_Responses` writes the driver's answer straight @@ -998,7 +1010,30 @@ text beyond an ASCII test document. A screen answers its driver with a result as well as a response, because the runner returns on a result; a family whose members are opened one after another clears both before it shows the next screen. -12. **Map generator and WDT** (L). +12. **Map generator and WDT** (L). Landed: `code/ui/uimapgen.{h,cpp}` holds all + three templates as one screen, with `ui/mapgen.rml`, `ui/mapgenfs.rml` and + `ui/mapgenwdt.rml` sharing `ui/mapgenbase.rcss` beside `ui/optionsbase.rcss` + and each carrying its own geometry, converted from `IDD_MAPGEN`, + `IDD_MAPGEN_FS` and `IDD_MAPGEN_WDT`. The variant is chosen by whether + Firestorm is enabled and whether the session names a tournament territory, + not by the caller, which is step 5's shape. `IDD_WDT_PICK_CLAN` is a template + no code opens, so the WDT half has no OwnerDraw dialog of its own and the rest + of WDT stays with MSEngine. + + The load, save and delete browsers step 9 built draw where this screen is, so + the screen steps aside for them on the hook step 8 added. The seed field keeps + its place and its width but is hidden, because all three templates declare it + `NOT WS_VISIBLE` and nothing ever shows it: it is where the number lives + between `Set_Settings` and `Get_Settings` rather than something a player types + in. The environment and time of day lists are sorted by name and the two size + lists are not, because only the first two combo boxes carry `CBS_SORT`. + + The preview is resampled rather than blitted. `Bit_Blit` copies the smaller of + the two rectangles row for row, so an engine surface blit between rectangles + of different sizes crops the picture instead of scaling it; only `DSurface`'s + own blitter stretches, and a `UISurfaceBufferClass` is not one. + `MapPreviewSurfaceClass` had relied on that blit since step 10, where the two + sizes were close enough to hide it. 13. **Retire OwnerDraw** (M). Delete `ownrdraw.cpp`, `windlg.cpp`, the modeless dialog list, the dialog templates, the kill switch, and the coexistence assertions. String tables stay. From cfaed03eb974bdf5e32031c65f0865ce9d1b28f6 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 11:56:07 +0100 Subject: [PATCH 154/179] feat(ui): give the lobby's message box the migrated screen ODMessageBox was the one OwnerDraw screen still opened with no RmlUi view behind it, reached from three sites inside the migrated lobby. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/netshare.cpp | 25 +++++++++++++++++++++++++ code/ui/uimessagebox.cpp | 15 ++++++++++++++- code/ui/uimessagebox.h | 9 ++++++--- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/code/netshare.cpp b/code/netshare.cpp index 83e423a11..b78431222 100644 --- a/code/netshare.cpp +++ b/code/netshare.cpp @@ -11,6 +11,7 @@ #include "netshare.h" #include "ui/uilobby.h" +#include "ui/uimessagebox.h" #include "ui/uiscenariopick.h" #include "ui/uishell.h" @@ -378,6 +379,30 @@ int CountAliveTeams(HouseClass * house) int ODMessageBox(const char * text, int type, bool (*callback)(void), bool large) { if (text != NULL && strlen(text) > 0) { + + // The presentation is latched here, at screen entry. The box carries the captions the + // three templates hold and the caller's poll goes to the screen's service, which is + // what WS_Wait_Dialog did with it. + if (UI_Use_Rml()) { + char const * const ok = Fetch_String(TXT_OK); + char const * first = ok; + char const * second = NULL; + if (type == MB_OKCANCEL) { + second = Fetch_String(TXT_CANCEL); + } else if (type == MB_YESNO) { + first = Fetch_String(TXT_YES); + second = Fetch_String(TXT_NO); + } + + UIResult const result = UI_Message_Box_Screen(text, 0, first, second, NULL, callback); + if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { + if (type == MB_YESNO) { + return(result.Value == 0 ? IDYES : IDNO); + } + return(result.Value == 0 ? IDOK : IDCANCEL); + } + } + HWND dialog; if (type == MB_OKCANCEL) { dialog = WS_Create_Dialog(ProgramInstance, IDD_MSGBOX_2, MainWindow, ODMessageBox_Proc, false); diff --git a/code/ui/uimessagebox.cpp b/code/ui/uimessagebox.cpp index 725da970d..8200314e0 100644 --- a/code/ui/uimessagebox.cpp +++ b/code/ui/uimessagebox.cpp @@ -70,6 +70,10 @@ class MessageBoxPresenterClass : public UIPresenterClass int DefaultResponse = 0; + // The caller's own per-pass work, polled while the box is up. WS_Wait_Dialog took + // one for the same reason, so a lobby keeps answering the network under a warning. + bool (*ServiceRoutine)(void) = nullptr; + virtual void Execute(UIIntent const & intent) override; virtual void Refresh(void) override {} virtual void Service(void) override; @@ -112,6 +116,13 @@ void MessageBoxPresenterClass::Service(void) if (!GameActive) { Title_Screen_Restore(); } + + if (ServiceRoutine != nullptr && ServiceRoutine() && !Result.has_value()) { + UIResult result; + result.Outcome = UIResult::OUTCOME_CANCELLED; + result.Value = ESCAPE_RESPONSE; + Result = result; + } } @@ -193,7 +204,8 @@ void MessageBoxViewClass::Sync(void) /// Shows a message and waits for the player to answer it. /// UIResult UI_Message_Box_Screen(char const * message, int defresponse, - char const * b1txt, char const * b2txt, char const * b3txt) + char const * b1txt, char const * b2txt, char const * b3txt, + bool (*service)(void)) { // The presenter is declared first so that it is destroyed last: the data model the view // binds reads the presenter's view-model, and must not outlive it. @@ -224,6 +236,7 @@ UIResult UI_Message_Box_Screen(char const * message, int defresponse, presenter.FirstIsCentred = (count == 1); presenter.DefaultResponse = defresponse; + presenter.ServiceRoutine = service; if (message != nullptr) { presenter.Message = message; diff --git a/code/ui/uimessagebox.h b/code/ui/uimessagebox.h index c24734fad..51cd31fab 100644 --- a/code/ui/uimessagebox.h +++ b/code/ui/uimessagebox.h @@ -20,10 +20,13 @@ // Shows a message with up to three buttons and does not return until one is answered. The // result's Value is the index of the button the player picked, counted the way // WWMessageBox::Process counts them; GameEnded says the session ended underneath the box. -// OUTCOME_FAILED_TO_OPEN means nothing was shown, which is the caller's cue to open the -// legacy dialog instead. +// +// The service routine, where one is given, is run once a pass while the box is up. A caller +// whose own work has to keep going underneath the box supplies it, the way the lobby handed +// its network poll to WS_Wait_Dialog; a true return answers the box the way its cancel does. UIResult UI_Message_Box_Screen(char const * message, int defresponse, - char const * b1txt, char const * b2txt, char const * b3txt); + char const * b1txt, char const * b2txt, char const * b3txt, + bool (*service)(void) = nullptr); // Opens the box that stands over a long operation. It is not modal: the caller keeps From 651b1a36e47386785bd81d689bbdc8b547304543 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 12:41:15 +0100 Subject: [PATCH 155/179] fix(ui): give the pointer back to the host while a document is shown A legacy dialog released the game's mouse, so Windows drew an arrow over it. A front end has no game pointer of its own, so a migrated screen showed none. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/ui/uishell.cpp | 49 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/code/ui/uishell.cpp b/code/ui/uishell.cpp index 3d2f07722..4fbd90721 100644 --- a/code/ui/uishell.cpp +++ b/code/ui/uishell.cpp @@ -22,6 +22,7 @@ #include "uirmlview.h" #include "_keyboar.h" +#include "_xmouse.h" #include "dbgprint.h" #include "hostclock.h" #include "conquer.h" @@ -57,6 +58,11 @@ static bool _Changing = false; // than flags. static int _ModalDepth = 0; +// How many shown documents have handed the mouse pointer to the host. A legacy dialog gave +// the pointer back to Windows for as long as it was up, which is what drew an arrow over it; +// the game's own pointer is a shape it only has while a scenario is running. +static int _PointerDepth = 0; + // How many modal runners are on the stack. A runner owns the context between its own // passes, so the tick that Main_Loop and Call_Back make from inside one is dropped rather // than updating the context a second time in the same pass. @@ -603,6 +609,37 @@ static bool Handle_Developer_Key(WPARAM key) #endif +/// +/// Hands the mouse pointer to the host while a document is shown. +/// OwnerDraw::Capture_Mouse did this for every legacy dialog: with the game's mouse +/// released, WM_SETCURSOR falls through to the window class and Windows draws an arrow. +/// A front end has no game pointer of its own, so without this a document shows none. +/// +static void Release_Pointer_To_Host(void) +{ + if (MouseCursor != nullptr && MouseCursor->Is_Captured()) { + MouseCursor->Release_Mouse(); + } + + _PointerDepth++; +} + + +/// +/// Takes the pointer back once the last document has gone. +/// +static void Recapture_Pointer(void) +{ + if (_PointerDepth > 0) { + _PointerDepth--; + } + + if (_PointerDepth == 0 && MouseCursor != nullptr && !MouseCursor->Is_Captured()) { + MouseCursor->Capture_Mouse(); + } +} + + /// /// Opens an exclusive input scope for a modal document. /// The keyboard queue is cleared so a key pressed before the screen opened cannot be read @@ -849,6 +886,8 @@ bool UIRmlViewClass::Prepare(bool modal) Element->Show(modal ? Rml::ModalFlag::Modal : Rml::ModalFlag::None); + Release_Pointer_To_Host(); + if (modal) { IsModal = true; Enter_Modal_Scope(); @@ -873,6 +912,8 @@ void UIRmlViewClass::Hide(void) Element->Hide(); + Recapture_Pointer(); + if (IsModal) { Leave_Modal_Scope(); } @@ -892,6 +933,8 @@ void UIRmlViewClass::Show(void) Element->Show(IsModal ? Rml::ModalFlag::Modal : Rml::ModalFlag::None); + Release_Pointer_To_Host(); + if (IsModal) { Enter_Modal_Scope(); } @@ -912,9 +955,15 @@ void UIRmlViewClass::Close(void) Presenter.IsClosing = true; Presenter.Discard(); + bool const wasvisible = Element->IsVisible(); + Element->Close(); Element = nullptr; + if (wasvisible) { + Recapture_Pointer(); + } + _Context->RemoveDataModel(ModelName); Model = Rml::DataModelHandle(); From 151e25d666317dddaa2f3619178bf56ba0c2ebd8 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 12:46:31 +0100 Subject: [PATCH 156/179] refactor(ui): delete the legacy view behind every migrated screen The dialog procedures, their drivers and the kill-switch branch go from the sound, message box, game type, multiplayer select, skirmish, in-game options, abort, game controls, main options, display, keyboard and save browser screens. The save and load boxes take the wait box directly. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/addon.cpp | 91 +--------- code/gamedlg.cpp | 283 +---------------------------- code/gamedlg.h | 8 - code/goptions.cpp | 397 +--------------------------------------- code/loaddlg.cpp | 452 ++++------------------------------------------ code/loaddlg.h | 8 - code/mainopt.cpp | 319 +------------------------------- code/mplayer.cpp | 105 +---------- code/msgbox.cpp | 166 +---------------- code/options.cpp | 203 +-------------------- code/savemgr.cpp | 53 +++--- code/skirmish.cpp | 357 +----------------------------------- code/sounddlg.cpp | 261 +------------------------- code/sounddlg.h | 2 - 14 files changed, 102 insertions(+), 2603 deletions(-) diff --git a/code/addon.cpp b/code/addon.cpp index 3e410f8a5..c795d0ad5 100644 --- a/code/addon.cpp +++ b/code/addon.cpp @@ -15,13 +15,7 @@ #include "data.h" #include "init.h" #include "language/language.h" -#include "ownrdraw.h" #include "ui/uigametype.h" -#include "ui/uishell.h" - -INT_PTR CALLBACK Select_Game_Type_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); - -static UIGameTypePresenterClass * _GameTypeScreen = NULL; int AvailableAddOns = 1 << ADDON_BASE_GAME; int ActiveAddOns = 1 << ADDON_BASE_GAME; @@ -65,94 +59,21 @@ bool Select_Game_Type_Dialog(AddonType &type) UIGameTypePresenterClass screen; screen.Refresh(); - // The selection is latched here, at screen entry, and the legacy dialog opens only - // when the document could not be prepared. - if (UI_Use_Rml()) { - if (UI_Game_Type_Screen(screen).Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { - int addon = ADDON_BASE_GAME; - bool const carry_on = screen.Apply(addon); - type = (AddonType)addon; - return(carry_on); - } - - screen.IsClosing = false; - screen.Result.reset(); - } - - _GameTypeScreen = &screen; - - HWND dialog = OwnerDraw::Begin_Dialog(IDD_SELECT_GAME_TYPE, Select_Game_Type_Dialog_Proc); - if (dialog != 0) { - - OwnerDraw::Display_Dialog(dialog); - - while (!screen.Result.has_value()) { - if (OwnerDraw::Dialog_Message_Handler() == true) { - break; - } - - screen.Drain(); - screen.Service(); - } - - ShowWindow(dialog, SW_HIDE); - UpdateWindow(MainWindow); - OwnerDraw::End_Dialog(dialog); - - int addon = ADDON_BASE_GAME; - bool const carry_on = screen.Apply(addon); - type = (AddonType)addon; - - _GameTypeScreen = NULL; - - if (!carry_on) { - return(false); - } - + if (UI_Game_Type_Screen(screen).Outcome == UIResult::OUTCOME_FAILED_TO_OPEN) { + Set_Required_Addon(type); return(true); } - _GameTypeScreen = NULL; - - Set_Required_Addon(type); - return(true); + int addon = ADDON_BASE_GAME; + bool const carry_on = screen.Apply(addon); + type = (AddonType)addon; + return(carry_on); } return(true); } -/// -/// Handles the messages for the game type selection dialog. -/// This routine stashes the control that the player pressed into the caller's result -/// variable, which is what lets the dialog loop know it can stop. -/// -INT_PTR CALLBACK Select_Game_Type_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - - if (rc == 0 && _GameTypeScreen != NULL && message == WM_COMMAND) { - switch (LOWORD(wparam)) { - case IDC_GAMETYPE_FIRESTORM: - _GameTypeScreen->Queue(UIIntent{UI_GAMETYPE_FIRESTORM, "", 0}); - break; - - case IDCANCEL: - _GameTypeScreen->Queue(UIIntent{UI_GAMETYPE_BACK, "", 0}); - break; - - default: - // The dialog's own default arm: any identifier that was not Firestorm and - // not a cancel is the base game. - _GameTypeScreen->Queue(UIIntent{UI_GAMETYPE_ORIGINAL, "", 0}); - break; - } - } - - return(rc); -} - - /// /// Rebuilds the installed and active addon sets from the expansion rules files present. /// diff --git a/code/gamedlg.cpp b/code/gamedlg.cpp index d85b4fd49..833864969 100644 --- a/code/gamedlg.cpp +++ b/code/gamedlg.cpp @@ -44,12 +44,10 @@ #include "globals.h" #include "init.h" #include "language/language.h" -#include "ownrdraw.h" #include "queue.h" #include "session.h" #include "techno.h" #include "ui/uigamecontrols.h" -#include "ui/uishell.h" #include "special.hh" @@ -89,65 +87,6 @@ int GameDifficultyNames[OptionsClass::MAX_DIFFICULTY_SETTING] = { }; -INT_PTR CALLBACK Game_Controls_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -void Game_Controls_Dialog_On_COMMAND(HWND window, UINT message, WPARAM wparam, LPARAM lparam); - - -// The screen the dialog procedure reads and writes. The driver owns it for the whole life -// of the dialog, which is the lifetime DWLP_USER gave the result pointer it replaces. -static UIGameControlsPresenterClass * _Screen = NULL; - - -static void Game_Controls_Queue(UIGameControlsPresenterClass & screen, char const * action, int value = 0) -{ - UIIntent intent; - intent.Action = action; - intent.Value = value; - screen.Queue(intent); -} - - -// Reads every control back into the view-model. The dialog read them at IDOK rather than -// tracking them, because a keyboard or page move changes a track bar without raising the -// thumb notification the label follows. -static void Game_Controls_Read_Back(HWND window, UIGameControlsPresenterClass & screen) -{ - static struct { - int Control; - char const * Action; - } const _sliders[] = { - { IDC_GAME_SPEED_SLIDER, UI_GAMECTRL_SPEED }, - { IDC_SCROLL_SPEED_SLIDER, UI_GAMECTRL_SCROLL }, - { IDC_DETAIL_LEVEL_SLIDER, UI_GAMECTRL_DETAIL }, - { IDC_DIFFICULTY_SLIDER, UI_GAMECTRL_DIFFICULTY }, - }; - - static struct { - int Control; - char const * Action; - } const _checks[] = { - { IDC_SIDEBAR_TEXT, UI_GAMECTRL_CAMEO_TEXT }, - { IDC_TARGET_LINES, UI_GAMECTRL_ACTION_LINES }, - { IDC_TOOLTIPS, UI_GAMECTRL_TOOLTIPS }, - { IDC_SCROLL_COASTING, UI_GAMECTRL_COASTING }, - { IDC_EDGE_SCROLL, UI_GAMECTRL_EDGE_SCROLL }, - }; - - for (auto const & slider : _sliders) { - HWND handle = GetDlgItem(window, slider.Control); - if (handle) { - Game_Controls_Queue(screen, slider.Action, Slider_GetPos(handle)); - } - } - - for (auto const & check : _checks) { - HWND handle = GetDlgItem(window, check.Control); - if (handle) { - Game_Controls_Queue(screen, check.Action, Button_GetCheck(handle) == TRUE ? 1 : 0); - } - } -} - /*********************************************************************************************** * OptionsClass::Process -- Handles all the options graphic interface. * * * @@ -167,226 +106,10 @@ void GameControlsClass::Dialog(void) UIGameControlsPresenterClass screen; screen.Refresh(); - // The selection is latched here, at screen entry, and the legacy dialog opens only when - // the document could not be prepared. - if (UI_Use_Rml()) { - UIResult const result = UI_Game_Controls_Screen(screen); - if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { - if (screen.Commits()) { - screen.Apply(); - Options.Save_Settings(); - } - DebugString("GameControls: GameSpeed = %d, ScrollRate = %d, Detail = %d\n", Options.GameSpeed, Options.ScrollRate, Options.DetailLevel); - return; - } - screen.IsClosing = false; - screen.Result.reset(); - } - - _Screen = &screen; - - if (GameActive == true) { - if (Session.Type == GAME_INTERNET) { - _Dialog = OwnerDraw::Begin_Dialog(IDD_OPT_CTRL_GAME_WOL, Game_Controls_Dialog_Proc); - } else { - _Dialog = OwnerDraw::Begin_Dialog(IDD_OPT_CTRL_GAME_MP, Game_Controls_Dialog_Proc); - } - } else { - _Dialog = OwnerDraw::Begin_Dialog(IDD_OPT_CTRL_GAME_SP, Game_Controls_Dialog_Proc); - } - - if (_Dialog) { - - OwnerDraw::Display_Dialog(_Dialog); - - while (!screen.Result.has_value()) { - if (OwnerDraw::Dialog_Message_Handler() == true) { - // A session that ended underneath the screen leaves the settings alone, - // which is what the driver's own result of two did. - UIResult ended; - ended.Outcome = UIResult::OUTCOME_SESSION_ENDED; - ended.GameEnded = true; - screen.Result = ended; - break; - } - - screen.Drain(); - screen.Service(); - } - - if (screen.Commits()) { - screen.Apply(); - Options.Save_Settings(); - } - - OwnerDraw::End_Dialog(_Dialog); + if (UI_Game_Controls_Screen(screen).Outcome != UIResult::OUTCOME_FAILED_TO_OPEN && screen.Commits()) { + screen.Apply(); + Options.Save_Settings(); } - _Screen = NULL; - DebugString("GameControls: GameSpeed = %d, ScrollRate = %d, Detail = %d\n", Options.GameSpeed, Options.ScrollRate, Options.DetailLevel); } - - -/// -/// Handles the messages sent to the game controls dialog. -/// The procedure primes its controls from the view-model, tracks the label alongside a -/// slider the player is dragging, and queues what the player pressed for the driver to -/// execute after the pump. -/// -/// Returns with a non-zero value if the message was consumed by the ownerdraw -/// layer. -INT_PTR CALLBACK Game_Controls_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - HWND handle; - int index; - - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - if (rc == 0) { - if (_Screen == NULL) { - return(0); - } - - UIGameControlsPresenterClass & screen = *_Screen; - - switch (message) { - case WM_INITDIALOG: - handle = GetDlgItem(window, IDC_GAME_SPEED_SLIDER); - if (handle) { - SendMessage(handle, OD_TRACKNUMBERS, 0, 0); - Slider_SetRange(handle, 0, (OptionsClass::MAX_SPEED_SETTING-1)); - Slider_SetPos(handle, screen.SpeedStep); - } - - handle = GetDlgItem(window, IDC_SCROLL_SPEED_SLIDER); - if (handle) { - SendMessage(handle, OD_TRACKNUMBERS, 0, 0); - Slider_SetRange(handle, 0, (OptionsClass::MAX_SCROLL_SETTING-1)); - Slider_SetPos(handle, screen.ScrollStep); - } - - handle = GetDlgItem(window, IDC_DETAIL_LEVEL_SLIDER); - if (handle) { - SendMessage(handle, OD_TRACKNUMBERS, 0, 0); - Slider_SetRange(handle, 0, (OptionsClass::MAX_DETAIL_SETTING-1)); - Slider_SetPos(handle, screen.DetailStep); - } - - handle = GetDlgItem(window, IDC_SIDEBAR_TEXT); - if (handle) { - Button_SetCheck(handle, screen.CameoText); - } - - handle = GetDlgItem(window, IDC_TARGET_LINES); - if (handle) { - Button_SetCheck(handle, screen.ActionLines); - } - - handle = GetDlgItem(window, IDC_TOOLTIPS); - if (handle) { - Button_SetCheck(handle, screen.ShowToolTips); - } - - handle = GetDlgItem(window, IDC_SCROLL_COASTING); - if (handle) { - Button_SetCheck(handle, screen.Coasting); - } - - handle = GetDlgItem(window, IDC_EDGE_SCROLL); - if (handle) { - Button_SetCheck(handle, screen.EdgeScroll); - } - - if (screen.Has_Sub_Screens()) { - handle = GetDlgItem(window, IDC_OPT_SOUND_BTN); - if (handle) { - EnableWindow(handle, screen.SoundAvailable); - } - } else { - handle = GetDlgItem(window, IDC_DIFFICULTY_SLIDER); - if (handle) { - SendMessage(handle, OD_TRACKNUMBERS, 0, 0); - Slider_SetRange(handle, 0, (OptionsClass::MAX_DIFFICULTY_SETTING-1)); - Slider_SetPos(handle, screen.DifficultyStep); - } - } - break; - - case WM_COMMAND: - Game_Controls_Dialog_On_COMMAND(window, LOWORD(wparam), 0, HIWORD(wparam)); - break; - - case WM_HSCROLL: - if (LOWORD(wparam) == SB_THUMBTRACK) { - index = HIWORD(wparam); - std::vector const * labels = NULL; - - handle = 0; - if ((HWND)lparam == GetDlgItem(window, IDC_GAME_SPEED_SLIDER)) { - labels = &screen.SpeedLabels; - handle = GetDlgItem(window, IDC_GAME_SPEED_LABEL); - } else if ((HWND)lparam == GetDlgItem(window, IDC_SCROLL_SPEED_SLIDER)) { - labels = &screen.ScrollLabels; - handle = GetDlgItem(window, IDC_SCROLL_SPEED_LABEL); - } else if ((HWND)lparam == GetDlgItem(window, IDC_DETAIL_LEVEL_SLIDER)) { - labels = &screen.DetailLabels; - handle = GetDlgItem(window, IDC_DETAIL_LEVEL_LABEL); - } else if (!screen.Has_Sub_Screens() && (HWND)lparam == GetDlgItem(window, IDC_DIFFICULTY_SLIDER)) { - labels = &screen.DifficultyLabels; - handle = GetDlgItem(window, IDC_DIFFICULTY_LABEL); - } - if (handle && labels != NULL && index >= 0 && index < (int)labels->size()) { - SetWindowText(handle, (*labels)[index].c_str()); - } - } - break; - } - rc = 0; - } - return(rc); -} - - -/// -/// Queues what the player pressed in the game controls dialog. -/// Leaving through the sound or the keyboard button reads the controls back as the accept -/// button does, because the dialog answered with the same IDOK for all three. -/// -/// The game controls dialog window. -/// The identifier of the control that was activated. -/// The notification code the control sent. -void Game_Controls_Dialog_On_COMMAND(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - if (_Screen == NULL) { - return; - } - - UIGameControlsPresenterClass & screen = *_Screen; - - switch ((INT)message) { - case IDC_OPT_KEYBOARD_BTN: - if (lparam == 0 && screen.Has_Sub_Screens()) { - Game_Controls_Read_Back(window, screen); - Game_Controls_Queue(screen, UI_GAMECTRL_KEYBOARD); - } - break; - - case IDC_OPT_SOUND_BTN: - if (lparam == 0 && screen.Has_Sub_Screens()) { - Game_Controls_Read_Back(window, screen); - Game_Controls_Queue(screen, UI_GAMECTRL_SOUND); - } - break; - - case IDOK: - if (lparam == 0) { - Game_Controls_Read_Back(window, screen); - Game_Controls_Queue(screen, UI_GAMECTRL_ACCEPT); - } - break; - - case IDCANCEL: - Game_Controls_Queue(screen, UI_GAMECTRL_CANCEL); - break; - } -} diff --git a/code/gamedlg.h b/code/gamedlg.h index 6afe494e9..b37149fac 100644 --- a/code/gamedlg.h +++ b/code/gamedlg.h @@ -50,12 +50,4 @@ class GameControlsClass { return(GameDifficultyNames[difficulty]); } - - private: - /* - * This is the window handle of the game controls dialog while it is displayed. The - * dialog is primed from it, so the handle is only meaningful between the dialog - * being created and destroyed. - */ - HWND _Dialog; }; diff --git a/code/goptions.cpp b/code/goptions.cpp index 80bdc738d..ceb3bba5c 100644 --- a/code/goptions.cpp +++ b/code/goptions.cpp @@ -42,7 +42,6 @@ #include "gamedlg.h" #include "language/language.h" #include "loaddlg.h" -#include "ownrdraw.h" #include "queue.h" #include "restate.h" #include "savemgr.h" @@ -50,74 +49,13 @@ #include "stats.h" #include "ui/uiabort.h" #include "ui/uigameoptions.h" -#include "ui/uishell.h" #include "special.hh" -void Game_Options_On_INITDIALOG(HWND window, UIGameOptionsPresenterClass const & screen); -INT_PTR CALLBACK Game_Options_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -INT_PTR CALLBACK Abort_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -void Abort_Dialog_On_COMMAND(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -// The screen the dialog procedure reads and writes. The driver owns it for the whole life -// of the dialog, which is the same lifetime DWLP_USER gave the result pointer it replaces. -static UIGameOptionsPresenterClass * _Screen = NULL; - -// The abort screen, owned by Abort_Dialog for the life of its dialog. -static UIAbortPresenterClass * _Abort = NULL; - - -static void Game_Options_Queue(UIGameOptionsPresenterClass & screen, char const * action, int value = 0) -{ - UIIntent intent; - intent.Action = action; - intent.Value = value; - screen.Queue(intent); -} - - -/// -/// Puts the view-model's enabled states into the controls the dialog enabled by hand. -/// A control the template left alone for a session type is left alone here too, so styling -/// adds no restriction the dialog did not have. -/// -static void Game_Options_Sync_Controls(HWND window, UIGameOptionsPresenterClass const & screen) -{ - HWND handle; - - if (!screen.IsMultiplayer) { - handle = GetDlgItem(window, IDC_LOAD_GAME); - if (handle) { - EnableWindow(handle, screen.CanLoad); - } - - handle = GetDlgItem(window, IDC_DELETE_GAME); - if (handle) { - EnableWindow(handle, screen.CanDelete); - } - } else { - handle = GetDlgItem(window, IDC_SAVE_GAME); - if (handle) { - EnableWindow(handle, screen.CanSave); - } - - handle = GetDlgItem(window, IDC_LOAD_GAME); - if (handle) { - EnableWindow(handle, screen.CanLoad); - } - } - - if (!screen.CanBrief) { - handle = GetDlgItem(window, IDC_BRIEFING); - if (handle) { - EnableWindow(handle, FALSE); - } - } -} - -// What the driver does on the way out, whichever view was shown. The briefing is restated -// after the screen has gone, which is where the dialog driver restated it. +// What the driver does on the way out. The briefing is restated after the screen has gone, +// which is where the dialog driver restated it. static void Game_Options_Finish(UIGameOptionsPresenterClass const & screen) { Keyboard->Clear(); @@ -143,8 +81,8 @@ static void Game_Options_Finish(UIGameOptionsPresenterClass const & screen) /// /// Displays the in game options dialog. /// This routine is used by the special dialog handler when the player calls up the options -/// screen. Which dialog appears depends on the kind of game in progress. Game input stays -/// locked out for as long as the dialog is up, and if the player asked for the mission +/// screen. Which layout appears depends on the kind of game in progress. Game input stays +/// locked out for as long as the screen is up, and if the player asked for the mission /// briefing it is restated on the way out. /// void Game_Options_Dialog(void) @@ -155,221 +93,12 @@ void Game_Options_Dialog(void) IgnoreInput = true; Keyboard->Clear(); - // The selection is latched here, at screen entry, and the legacy dialog opens only when - // the document could not be prepared. - if (UI_Use_Rml()) { - UIResult const result = UI_Game_Options_Screen(screen); - if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { - Game_Options_Finish(screen); - return; - } - screen.IsClosing = false; - screen.Result.reset(); - } - - _Screen = &screen; - - HWND dialog; - if (Session.Type == GAME_NORMAL || Session.Type == GAME_SKIRMISH) { - dialog = OwnerDraw::Begin_Dialog(IDD_OPT_CTRL_SP, Game_Options_Dialog_Proc); - } else if (Session.Type == GAME_INTERNET) { - dialog = OwnerDraw::Begin_Dialog(IDD_OPT_CTRL_WOL, Game_Options_Dialog_Proc); - } else { - dialog = OwnerDraw::Begin_Dialog(IDD_OPT_CTRL_MP, Game_Options_Dialog_Proc); - } - - if (dialog) { - - OwnerDraw::Display_Dialog(dialog); - - while (!screen.Result.has_value()) { - if (OwnerDraw::Dialog_Message_Handler() == true) { - // A session that ended underneath the screen leaves it as though the player - // had resumed, which is the IDOK the driver used to write. - UIResult ended; - ended.Outcome = UIResult::OUTCOME_SESSION_ENDED; - ended.GameEnded = true; - screen.Choice = UIGameOptionsPresenterClass::CHOICE_RESUME; - screen.Result = ended; - break; - } - - // A control handler queues rather than acts, so the queue is executed here, - // after the pump has returned. - screen.Drain(); - - // Getting out of the way of a screen this one opens is the view's work; what - // running it means is the presenter's. - if (screen.Pending != UIGameOptionsPresenterClass::SUB_NONE) { - ShowWindow(dialog, SW_HIDE); - UpdateWindow(MainWindow); - screen.Run_Pending(); - if (!screen.Result.has_value()) { - ShowWindow(dialog, SW_SHOW); - UpdateWindow(dialog); - } - } - - Game_Options_Sync_Controls(dialog, screen); - } - - OwnerDraw::End_Dialog(dialog); - } - - _Screen = nullptr; + UI_Game_Options_Screen(screen); Game_Options_Finish(screen); } -/// -/// Handles messages for the in game options dialog. -/// The procedure reads the view-model and queues what the player asked for; the driver -/// executes the queue after the pump returns, as docs/UI_DESIGN.md requires of every -/// screen. Dragging the game speed or connection quality slider updates the label beside it, -/// which is the view's own business. -/// -/// Returns with TRUE if the owner draw system consumed the message. -INT_PTR CALLBACK Game_Options_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - - if (rc) { - return(rc); - } - - // The driver owns the screen for the whole life of the dialog, so a message that arrives - // without one has nothing to act on. - if (_Screen == nullptr) { - return(FALSE); - } - - UIGameOptionsPresenterClass & screen = *_Screen; - HWND handle; - - switch (message) { - - case WM_INITDIALOG: - Game_Options_On_INITDIALOG(window, screen); - break; - - case WM_COMMAND: { - int code = HIWORD(wparam); - - switch (LOWORD(wparam)) { - - case IDC_SAVE_GAME: - if (!code) Game_Options_Queue(screen, UI_GAMEOPT_SAVE); - break; - - case IDC_LOAD_GAME: - if (!code) Game_Options_Queue(screen, UI_GAMEOPT_LOAD); - break; - - case IDC_BRIEFING: - if (!code) Game_Options_Queue(screen, UI_GAMEOPT_BRIEFING); - break; - - case IDC_DELETE_GAME: - if (!code) Game_Options_Queue(screen, UI_GAMEOPT_DELETE); - break; - - case IDC_RESUME_MISSION: - if (!code) { - // The sliders are read here rather than tracked, because a keyboard - // or page move changes a track bar without raising WM_HSCROLL's - // thumb notification, and resume is where the dialog read them. - handle = GetDlgItem(window, IDC_CTRLWOL_CONNECTION); - if (handle) { - Game_Options_Queue(screen, UI_GAMEOPT_CONNECTION, SendMessage(handle, TBM_GETPOS, 0, 0)); - } - handle = GetDlgItem(window, IDC_GAME_SPEED_SLIDER); - if (handle) { - Game_Options_Queue(screen, UI_GAMEOPT_SPEED, SendMessage(handle, TBM_GETPOS, 0, 0)); - } - Game_Options_Queue(screen, UI_GAMEOPT_RESUME); - } - break; - - case IDC_ABORT_MISSION: - if (!code) Game_Options_Queue(screen, UI_GAMEOPT_ABORT); - break; - - case IDC_GAME_CONTROLS: - if (!code) Game_Options_Queue(screen, UI_GAMEOPT_SETTINGS); - break; - - default: - break; - } - break; - } - - case WM_HSCROLL: { - if (LOWORD(wparam) == SB_THUMBTRACK) { - int pos = HIWORD(wparam); - char const * label = NULL; - - if ((HWND)lparam == GetDlgItem(window, IDC_GAME_SPEED_SLIDER)) { - if (pos >= 0 && pos < (int)screen.SpeedLabels.size()) { - label = screen.SpeedLabels[pos].c_str(); - } - handle = GetDlgItem(window, IDC_GAME_SPEED_LABEL); - Game_Options_Queue(screen, UI_GAMEOPT_SPEED, pos); - } else if ((HWND)lparam == GetDlgItem(window, IDC_CTRLWOL_CONNECTION)) { - if (pos >= 0 && pos < (int)screen.ConnectionLabels.size()) { - label = screen.ConnectionLabels[pos].c_str(); - } - // The connection label shares the scroll speed label's identifier, which - // is what the IDD_OPT_CTRL_WOL template names it. - handle = GetDlgItem(window, IDC_SCROLL_SPEED_LABEL); - Game_Options_Queue(screen, UI_GAMEOPT_CONNECTION, pos); - } else { - break; - } - - if (handle && label != NULL) { - Static_SetText(handle, label); - } - } - break; - } - - default: - break; - } - - return(FALSE); -} - - -/// -/// Prepares the controls of the game options dialog. -/// Everything here comes out of the view-model, which the presenter refreshed before the -/// dialog was created and again whenever a save or a delete changed what is on disk. -/// -void Game_Options_On_INITDIALOG(HWND window, UIGameOptionsPresenterClass const & screen) -{ - HWND handle; - - Game_Options_Sync_Controls(window, screen); - - if (screen.HasSliders) { - - handle = GetDlgItem(window, IDC_CTRLWOL_CONNECTION); - if (handle) { - SetSliderRangeAndPos(handle, 0, 3, screen.ConnectionStep); - } - - handle = GetDlgItem(window, IDC_GAME_SPEED_SLIDER); - if (handle) { - Slider_SetRange(handle, 0, OptionsClass::MAX_SPEED_SETTING-1); - Slider_SetPos(handle, screen.SpeedStep); - } - } -} - - // Maps the screen's choice onto the value the special dialog handler expects. A screen that // never opened answers zero, which is what the driver's own result was left at. static int Abort_Choice_Result(UIAbortPresenterClass const & screen) @@ -402,121 +131,7 @@ int Abort_Dialog(void) { UIAbortPresenterClass screen; screen.Refresh(); - - // The selection is latched here, at screen entry, and the legacy dialog opens only when - // the document could not be prepared. - if (UI_Use_Rml()) { - UIResult const result = UI_Abort_Screen(screen); - if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { - return(Abort_Choice_Result(screen)); - } - screen.IsClosing = false; - screen.Result.reset(); - } - - _Abort = &screen; - - HWND dialog = OwnerDraw::Begin_Dialog(IDD_MISSION_ABORT, Abort_Dialog_Proc); - - if (dialog) { - - OwnerDraw::Display_Dialog(dialog); - - while (!screen.Result.has_value()) { - if (OwnerDraw::Dialog_Message_Handler() == true) { - // A session that ended underneath the box answers as though the player chose - // to quit, which is the IDOK the driver used to write. - UIResult ended; - ended.Outcome = UIResult::OUTCOME_SESSION_ENDED; - ended.GameEnded = true; - screen.Choice = UIAbortPresenterClass::CHOICE_QUIT; - screen.Result = ended; - break; - } - - screen.Drain(); - } - - OwnerDraw::End_Dialog(dialog); - } - - _Abort = NULL; + UI_Abort_Screen(screen); return(Abort_Choice_Result(screen)); } - - -/// -/// Handles messages for the abort mission dialog. -/// The procedure relabels and disables the middle button from the view-model, and queues -/// what the player pressed for the driver to execute after the pump. -/// -/// Returns with the result of the owner draw default dialog handler. -INT_PTR CALLBACK Abort_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - HWND handle; - - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - if (rc == 0) { - if (_Abort == NULL) { - return(0); - } - - UIAbortPresenterClass & screen = *_Abort; - - switch (message) { - case WM_INITDIALOG: - handle = GetDlgItem(window, IDC_RESTART_MISSION); - if (handle) { - if (!screen.RestartCaption.empty()) { - SetWindowText(handle, screen.RestartCaption.c_str()); - } - if (!screen.CanRestart) { - EnableWindow(handle, FALSE); - } - } - break; - - case WM_COMMAND: - Abort_Dialog_On_COMMAND(window, LOWORD(wparam), 0, HIWORD(wparam)); - break; - } - rc = 0; - } - return(rc); -} - - -/// -/// Queues what the player pressed in the abort mission dialog. -/// -/// The control identifier of the button that was pressed. -/// The notification code that came with the button press. -void Abort_Dialog_On_COMMAND(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - if (_Abort == NULL || lparam != 0) { - return; - } - - UIIntent intent; - - switch ((int)message) { - case IDC_ABORT_MISSION: - intent.Action = UI_ABORT_QUIT; - break; - - case IDC_RESTART_MISSION: - intent.Action = UI_ABORT_RESTART; - break; - - case IDOK: - case IDCANCEL: - intent.Action = UI_ABORT_CANCEL; - break; - - default: - return; - } - - _Abort->Queue(intent); -} diff --git a/code/loaddlg.cpp b/code/loaddlg.cpp index 55d2872c3..c0272021b 100644 --- a/code/loaddlg.cpp +++ b/code/loaddlg.cpp @@ -38,6 +38,9 @@ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "always.h" +#include "_keyboar.h" +#include "keyboard.h" +#include "ui/uimessagebox.h" #include "autosave.h" @@ -52,14 +55,12 @@ #include "init.h" #include "language/language.h" #include "msgbox.h" -#include "ownrdraw.h" #include "saveload.h" #include "savemgr.h" #include "savever.h" #include "scenario.h" #include "session.h" #include "ui/uisavebrowser.h" -#include "ui/uishell.h" #include "win.h" #include @@ -165,225 +166,6 @@ bool LoadOptionsClass::Delete(void) } -/// -/// Handles a control notification from the load game dialog. -/// This routine records how the player left the dialog, so that the processing loop -/// knows whether a game was chosen or the player backed out. -/// -/// The identifier of the control that was activated. -/// Window handle of the control that was activated. -/// The notification code that accompanied the control. -void LoadOptionsClass::Load_Dialog_On_WM_COMMAND(HWND window, WPARAM wparam, LPARAM lparam, int id) -{ - UISaveBrowserPresenterClass * screen = (UISaveBrowserPresenterClass *)GetWindowLongPtr(window, DWLP_USER); - if (screen == NULL) { - return; - } - - switch ((int)wparam) { - case IDC_MISSION_LOAD_LIST: - if (id == 2 && ListBox_GetCount((HWND)lparam) > 0) { - screen->Queue(UIIntent{UI_SAVEBROWSER_SELECT, "", ListBox_GetCurSel((HWND)lparam)}); - screen->Queue(UIIntent{UI_SAVEBROWSER_ACCEPT, "", 0}); - } - break; - - case IDOK: - if (id == 0) { - screen->Queue(UIIntent{UI_SAVEBROWSER_SELECT, "", ListBox_GetCurSel(GetDlgItem(window, IDC_MISSION_LOAD_LIST))}); - screen->Queue(UIIntent{UI_SAVEBROWSER_ACCEPT, "", 0}); - } - break; - - case IDCANCEL: - if (id == 0) { - screen->Queue(UIIntent{UI_SAVEBROWSER_CANCEL, "", 0}); - } - break; - } -} - - -/// -/// Handles a control notification from the save game dialog. -/// Picking a game in the list copies its description into the edit field, so that the -/// player can save over an existing game without typing the name out again. The buttons -/// record how the player left the dialog. -/// -/// The identifier of the control that was activated. -/// Window handle of the control that was activated. -/// The notification code that accompanied the control. -void LoadOptionsClass::Save_Dialog_On_WM_COMMAND(HWND window, WPARAM wparam, LPARAM lparam, int id) -{ - UISaveBrowserPresenterClass * screen = (UISaveBrowserPresenterClass *)GetWindowLongPtr(window, DWLP_USER); - if (screen == NULL) { - return; - } - - switch ((int)wparam) { - case IDC_MISSION_SAVE_LIST: - if (id == 1 && ListBox_GetCount((HWND)lparam) > 0) { - int const row = ListBox_GetCurSel((HWND)lparam); - if (row != LB_ERR) { - screen->Queue(UIIntent{UI_SAVEBROWSER_SELECT, "", row}); - } - } - break; - - case IDOK: - if (id == 0) { - // The field is read here rather than tracked, because the description the - // player typed is only ever wanted at the moment the button is pressed. - char buffer[256]; - GetWindowText(GetDlgItem(window, IDC_MISSION_SAVE_DESC), buffer, DESCRIP_MAX+36); - screen->Queue(UIIntent{UI_SAVEBROWSER_DESCRIBE, buffer, 0}); - screen->Queue(UIIntent{UI_SAVEBROWSER_ACCEPT, "", 0}); - } - break; - - case IDCANCEL: - if (id == 0) { - screen->Queue(UIIntent{UI_SAVEBROWSER_CANCEL, "", 0}); - } - break; - } -} - - -/// -/// Handles a control notification from the delete game dialog. -/// This routine records how the player left the dialog, so that the processing loop -/// knows whether to go ahead with the deletion. -/// -/// The identifier of the control that was activated. -/// The notification code that accompanied the control. -void LoadOptionsClass::Delete_Dialog_On_WM_COMMAND(HWND window, WPARAM wparam, LPARAM lparam, int id) -{ - UISaveBrowserPresenterClass * screen = (UISaveBrowserPresenterClass *)GetWindowLongPtr(window, DWLP_USER); - if (screen == NULL) { - return; - } - - switch ((int)wparam) { - case IDOK: - if (id == 0) { - screen->Queue(UIIntent{UI_SAVEBROWSER_SELECT, "", ListBox_GetCurSel(GetDlgItem(window, IDC_MISSION_DELETE_LIST))}); - screen->Queue(UIIntent{UI_SAVEBROWSER_ACCEPT, "", 0}); - } - break; - - case IDCANCEL: - if (id == 0) { - screen->Queue(UIIntent{UI_SAVEBROWSER_CANCEL, "", 0}); - } - break; - } -} - - -/// -/// Handles messages for the load game dialog. -/// The owner draw system is given first refusal on every message. What is left over is -/// used to set up the file list columns and to pass control activity along to the -/// command handler. -/// -/// Returns with the message result, or FALSE if nothing here dealt with it. -INT_PTR CALLBACK LoadOptionsClass::Load_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - - if (rc == 0) { - - switch (message) { - case WM_MOVING: - return(On_WM_MOVING(window, wparam, lparam)); - - case WM_COMMAND: - Load_Dialog_On_WM_COMMAND(window, LOWORD(wparam), lparam, HIWORD(wparam)); - break; - - case OD_SUBCLASSED: - SendDlgItemMessage(window, IDC_MISSION_LOAD_LIST, OD_ADDCOLUMN, 0xF9, 2); - SendDlgItemMessage(window, IDC_MISSION_LOAD_LIST, OD_ADDCOLUMN, 0x38, 255); - SendDlgItemMessage(window, IDC_MISSION_LOAD_LIST, OD_ADDCOLUMN, 0, 315); - break; - } - return(FALSE); - } - return(rc); -} - - -/// -/// Handles messages for the save game dialog. -/// The owner draw system is given first refusal on every message. What is left over is -/// used to set up the file list columns, cap the length of the description the player -/// may type, and pass control activity along to the command handler. -/// -/// Returns with the message result, or FALSE if nothing here dealt with it. -INT_PTR CALLBACK LoadOptionsClass::Save_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - - if (rc == 0) { - - switch (message) { - case WM_MOVING: - return(On_WM_MOVING(window, wparam, lparam)); - - case WM_COMMAND: - Save_Dialog_On_WM_COMMAND(window, LOWORD(wparam), lparam, HIWORD(wparam)); - break; - - case WM_INITDIALOG: - SendMessage(GetDlgItem(window, IDC_MISSION_SAVE_DESC), EM_SETLIMITTEXT, 79, 0); - break; - - case OD_SUBCLASSED: - SendDlgItemMessage(window, IDC_MISSION_SAVE_LIST, OD_ADDCOLUMN, 0xF9, 2); - SendDlgItemMessage(window, IDC_MISSION_SAVE_LIST, OD_ADDCOLUMN, 0x38, 255); - SendDlgItemMessage(window, IDC_MISSION_SAVE_LIST, OD_ADDCOLUMN, 0, 315); - break; - } - return(FALSE); - } - return(rc); -} - - -/// -/// Handles messages for the delete game dialog. -/// The owner draw system is given first refusal on every message. What is left over is -/// used to set up the file list columns and to pass control activity along to the -/// command handler. -/// -/// Returns with the message result, or FALSE if nothing here dealt with it. -INT_PTR CALLBACK LoadOptionsClass::Delete_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - - if (rc == 0) { - - switch (message) { - case WM_COMMAND: - Delete_Dialog_On_WM_COMMAND(window, LOWORD(wparam), lparam, HIWORD(wparam)); - break; - - case WM_MOVING: - return(On_WM_MOVING(window, wparam, lparam)); - - case OD_SUBCLASSED: - SendDlgItemMessage(window, IDC_MISSION_DELETE_LIST, OD_ADDCOLUMN, 0xF9, 2); - SendDlgItemMessage(window, IDC_MISSION_DELETE_LIST, OD_ADDCOLUMN, 0x38, 255); - SendDlgItemMessage(window, IDC_MISSION_DELETE_LIST, OD_ADDCOLUMN, 0, 315); - break; - } - return(FALSE); - } - return(rc); -} - - /// /// Is a saved game of this name already there? Asked before one is written, since a name the /// folder holds is written over rather than added to. @@ -409,50 +191,21 @@ static bool Saved_Game_Exists(char const * name) * HISTORY: * * 02/14/1995 BR : Created. * *=============================================================================================*/ -/// -/// Puts the view-model on the dialog's own controls. -/// -static void Save_Browser_Sync_Controls(HWND window, UISaveBrowserPresenterClass & screen) -{ - if (screen.Style != UISaveBrowserPresenterClass::STYLE_SAVE) { - return; - } - - HWND const field = GetDlgItem(window, IDC_MISSION_SAVE_DESC); - if (field == NULL) { - return; - } - - char current[256]; - GetWindowText(field, current, sizeof(current)); - - if (strcmp(current, screen.Description.c_str()) != 0) { - SetWindowText(field, screen.Description.c_str()); - } - - if (screen.FocusDescription) { - screen.FocusDescription = false; - SetFocus(field); - Edit_SetSel(field, 0, -1); - } -} - - -/// -/// Rebuilds the list control when the view-model's list has moved. -/// -void LoadOptionsClass::Sync_List(HWND list, HWND dialog, UISaveBrowserPresenterClass & screen) -{ - if (list == 0 || !screen.ListChanged) { - return; - } - - screen.ListChanged = false; - Fill_List(list, screen.Selected); - EnableWindow(GetDlgItem(dialog, 1), screen.CanAct ? TRUE : FALSE); -} - - +/*********************************************************************************************** + * LoadOptionsClass::Process -- main processing routine * + * * + * INPUT: * + * none. * + * * + * OUTPUT: * + * false = User cancelled, true = operation completed * + * * + * WARNINGS: * + * none. * + * * + * HISTORY: * + * 02/14/1995 BR : Created. * + *=============================================================================================*/ bool LoadOptionsClass::Dialog(void) { UISaveBrowserPresenterClass::StyleType style = UISaveBrowserPresenterClass::STYLE_LOAD; @@ -472,85 +225,8 @@ bool LoadOptionsClass::Dialog(void) State = STATE_PENDING; - if (UI_Use_Rml()) { - UIResult const result = UI_Save_Browser_Screen(screen); - - if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { - Clear_List(); - State = screen.Accepted() ? STATE_OK : STATE_CLOSE; - return(screen.Accepted()); - } - - // Preparation failed, so nothing is shown and the legacy dialog answers instead. A - // suspended screen leaves the presenter marked, and the dialog runs the same screen. - screen.Result.reset(); - screen.IsClosing = false; - } - - HWND dialog = 0; - HWND list = 0; - - switch (Style) { - case LOAD: - dialog = OwnerDraw::Begin_Dialog(IDD_MISSION_LOAD, Load_Dialog_Proc); - list = GetDlgItem(dialog, IDC_MISSION_LOAD_LIST); - break; - - case SAVE: - dialog = OwnerDraw::Begin_Dialog(IDD_MISSION_SAVE, Save_Dialog_Proc); - list = GetDlgItem(dialog, IDC_MISSION_SAVE_LIST); - break; - - case WWDELETE: - dialog = OwnerDraw::Begin_Dialog(IDD_MISSION_DELETE, Delete_Dialog_Proc); - list = GetDlgItem(dialog, IDC_MISSION_DELETE_LIST); - break; - - default: - break; - } - - State = STATE_PENDING; - - if (dialog) { - - SetWindowLongPtr(dialog, DWLP_USER, (LONG_PTR)&screen); - - Sync_List(list, dialog, screen); - Save_Browser_Sync_Controls(dialog, screen); - - OwnerDraw::Display_Dialog(dialog); - - while (!screen.Result.has_value()) { - if (OwnerDraw::Dialog_Message_Handler() == true) { - screen.Queue(UIIntent{UI_SAVEBROWSER_CANCEL, "", 0}); - } - - // A control handler queues rather than acts, so the queue is executed here, - // after the pump has returned. - screen.Drain(); - - // A load draws where this screen is, so the dialog gets out of its way, which - // is what its own ShowWindow did. - if (screen.Pending != UISaveBrowserPresenterClass::SUB_NONE) { - ShowWindow(dialog, SW_HIDE); - UpdateWindow(MainWindow); - screen.Run_Pending(); - if (!screen.Result.has_value()) { - ShowWindow(dialog, SW_SHOW); - UpdateWindow(dialog); - } - } - - Sync_List(list, dialog, screen); - Save_Browser_Sync_Controls(dialog, screen); - - screen.Service(); - } - + if (UI_Save_Browser_Screen(screen).Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { Clear_List(); - - OwnerDraw::End_Dialog(dialog); } State = screen.Accepted() ? STATE_OK : STATE_CLOSE; @@ -712,70 +388,6 @@ void LoadOptionsClass::Build_List(void) } -/*********************************************************************************************** - * LoadOptionsClass::Fill_List -- fills the list box & GameNum arrays * - * * - * INPUT: * - * none. * - * * - * OUTPUT: * - * none. * - * * - * WARNINGS: * - * none. * - * * - * HISTORY: * - * 02/14/1995 BR : Created. * - * 06/25/1995 JLB : Shows which saved games are "(old)". * - *=============================================================================================*/ -void LoadOptionsClass::Fill_List(HWND window, int selected) -{ - OwnerDraw::CellData thecell; - FileEntryClass * fdata = NULL; - char buffer[128]; - - if (Files.Count() > 0) { - - ListBox_ResetContent(window); - - /* - ** Now add every file's name to the list box - */ - for (int i = 0; i < Files.Count(); i++) { - fdata = Files[i]; - - int row = ListBox_AddString(window, fdata); - - if (fdata->Type != GAME_NORMAL) { - thecell.type = OwnerDraw::CellData::TEXT; - thecell.string.set("*"); - SendMessage(window, OD_SETCELL, MAKEWPARAM(200, row), (LPARAM)&thecell); - } - - if (fdata->DateTime.dwHighDateTime != -1 && fdata->DateTime.dwLowDateTime != -1) { - FILETIME ft; - SYSTEMTIME time; - FileTimeToLocalFileTime(&fdata->DateTime, &ft); - FileTimeToSystemTime(&ft, &time); - GetDateFormat(LANG_USER_DEFAULT, TIME_NOMINUTESORSECONDS, &time, NULL, buffer, sizeof(buffer)); - thecell.type = OwnerDraw::CellData::TEXT; - thecell.string.set(buffer); - SendMessage(window, OD_SETCELL, MAKEWPARAM(255, row), (LPARAM)&thecell); - GetTimeFormat(LANG_USER_DEFAULT, TIME_NOSECONDS, &time, NULL, buffer, sizeof(buffer)); - thecell.type = OwnerDraw::CellData::TEXT; - thecell.string.set(buffer); - SendMessage(window, OD_SETCELL, MAKEWPARAM(315, row), (LPARAM)&thecell); - } - - ListBox_SetItemData(window, row, (LPARAM)fdata); - } - - ListBox_SetCurSel(window, selected); - ListBox_SetTopIndex(window, selected); - } -} - - /// /// Are there any save games available to load? /// This routine is used to decide whether the load option should be offered to the @@ -842,21 +454,20 @@ int __cdecl LoadOptionsClass::Compare(const void * p1, const void * p2) /// /// Restores the game held in the file specified. -/// A message box is displayed while the load runs, and the scenario is taken out of +/// A wait box is displayed while the load runs, and the scenario is taken out of /// play first so that nothing tries to tick while the game state is being replaced. /// /// bool; Was the game loaded? bool LoadOptionsClass::Load_File(const char * file_name) { - HWND dialog = OwnerDraw::Custom_Message_Box(Fetch_String(TXT_LOADING), NULL, NULL); - if (dialog != 0) { - OwnerDraw::Display_Dialog(dialog); - } + bool const box = UI_Wait_Box_Open(Fetch_String(TXT_LOADING), NULL, NULL); + Keyboard->Clear(); ScenarioActive = false; TacticalActive = false; bool loaded = Load_Game(file_name); - if (dialog != 0) { - OwnerDraw::End_Dialog(dialog); + if (box) { + Keyboard->Clear(); + UI_Wait_Box_Close(); } return(loaded); } @@ -864,21 +475,20 @@ bool LoadOptionsClass::Load_File(const char * file_name) /// /// Saves the current game to the file specified. -/// A message box is displayed while the save runs, since writing a save game takes long +/// A wait box is displayed while the save runs, since writing a save game takes long /// enough that the player would otherwise think the game had locked up. /// /// The description to record alongside the saved game. /// bool; Was the game saved? bool LoadOptionsClass::Save_File(const char * file_name, const char * descr) { - HWND dialog = OwnerDraw::Custom_Message_Box(Fetch_String(TXT_SAVING_GAME), NULL, NULL); - if (dialog != 0) { - OwnerDraw::Display_Dialog(dialog); - } + bool const box = UI_Wait_Box_Open(Fetch_String(TXT_SAVING_GAME), NULL, NULL); + Keyboard->Clear(); bool saved = SaveManager.Request_Save_Game(file_name, descr, false, SaveManagerClass::NoticeType::Requested); - if (dialog != 0) { - OwnerDraw::End_Dialog(dialog); + if (box) { + Keyboard->Clear(); + UI_Wait_Box_Close(); } return(saved); } diff --git a/code/loaddlg.h b/code/loaddlg.h index 9a04d5ec3..f1a849350 100644 --- a/code/loaddlg.h +++ b/code/loaddlg.h @@ -120,8 +120,6 @@ class LoadOptionsClass ** Internal routines */ void Clear_List (void); // clears the list & game # array - void Fill_List (HWND window, int selected); // puts the list on the control - void Sync_List (HWND list, HWND dialog, class UISaveBrowserPresenterClass & screen); int Num_From_Ext (char *fname); // translates filename to file # static int __cdecl Compare(const void *p1, const void *p2); // for qsort() @@ -136,13 +134,7 @@ class LoadOptionsClass /* * These handlers are members so that they can reach the dialog's protected data. */ - static void Load_Dialog_On_WM_COMMAND(HWND window, WPARAM wparam, LPARAM lparam, int id); - static void Save_Dialog_On_WM_COMMAND(HWND window, WPARAM wparam, LPARAM lparam, int id); - static void Delete_Dialog_On_WM_COMMAND(HWND window, WPARAM wparam, LPARAM lparam, int id); - static INT_PTR CALLBACK Load_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); - static INT_PTR CALLBACK Save_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); - static INT_PTR CALLBACK Delete_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); /* ** This is the requested style of the dialog diff --git a/code/mainopt.cpp b/code/mainopt.cpp index 1acafc140..566444409 100644 --- a/code/mainopt.cpp +++ b/code/mainopt.cpp @@ -29,7 +29,6 @@ #include "mixfile.h" #include "msgbox.h" #include "newmenu.h" -#include "ownrdraw.h" #include "sidebar.h" #include "sounddlg.h" #include "stimer.h" @@ -38,32 +37,12 @@ #include "ui/uidisplayconfirm.h" #include "ui/uidisplayoptions.h" #include "ui/uimainoptions.h" -#include "ui/uishell.h" #include "color.hh" -INT_PTR CALLBACK Main_Options_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -INT_PTR CALLBACK Display_Options_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); bool Change_Display_Mode(int width, int height); bool Test_Display_Mode_Dialog(int width, int height); -INT_PTR CALLBACK Test_Display_Mode_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); - - -// The screens the dialog procedures read and write. A driver owns one for the whole life of -// its dialog, which is the lifetime DWLP_USER gave the result pointer each replaces. -static UIMainOptionsPresenterClass * _MainScreen = NULL; -static UIDisplayOptionsPresenterClass * _DisplayScreen = NULL; -static UIDisplayConfirmPresenterClass * _ConfirmScreen = NULL; - - -static void Options_Queue(UIPresenterClass & screen, char const * action, int value = 0) -{ - UIIntent intent; - intent.Action = action; - intent.Value = value; - screen.Queue(intent); -} /// @@ -79,8 +58,6 @@ void Main_Options_Dialog(void) screen.Begin(); screen.Refresh(); - _MainScreen = &screen; - while (true) { // The screen is opened again on each pass round the family, so what the last close // left behind is cleared first. @@ -88,58 +65,19 @@ void Main_Options_Dialog(void) screen.IsClosing = false; screen.Choice = UIMainOptionsPresenterClass::CHOICE_NONE; - // The selection is latched here, at screen entry, and the legacy dialog opens only - // when the document could not be prepared. - if (UI_Use_Rml()) { - UIResult const result = UI_Main_Options_Screen(screen); - if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { - if (screen.Exits()) { - break; - } - screen.Run_Pending(); - continue; - } - screen.IsClosing = false; - screen.Result.reset(); - } - - HWND main_handle; - do { - main_handle = OwnerDraw::Begin_Dialog(IDD_OPT_MAIN, Main_Options_Dialog_Proc); - } while (main_handle == 0); - - OwnerDraw::Move_Dialog(main_handle, -1, (HiddenSurface->Get_Height() - 400) / 2 + 147); - OwnerDraw::Display_Dialog(main_handle); - - while (!screen.Result.has_value()) { - if (OwnerDraw::Dialog_Message_Handler() == true) { - // A session that ended underneath the screen leaves the family, which is - // what the driver's own unanswered result did on the way to its default arm. - UIResult ended; - ended.Outcome = UIResult::OUTCOME_SESSION_ENDED; - ended.GameEnded = true; - screen.Choice = UIMainOptionsPresenterClass::CHOICE_EXIT; - screen.Result = ended; - break; - } - - screen.Drain(); - screen.Service(); + if (UI_Main_Options_Screen(screen).Outcome == UIResult::OUTCOME_FAILED_TO_OPEN) { + break; } - OwnerDraw::End_Dialog(main_handle); - if (screen.Exits()) { break; } - // The sub-screen runs with this one destroyed, which is the coexistence rule the - // driver already kept. + // The sub-screen runs with this one gone, which is the coexistence rule the driver + // already kept. screen.Run_Pending(); } - _MainScreen = NULL; - screen.End(); } @@ -155,43 +93,8 @@ void Display_Options_Dialog(void) UIDisplayOptionsPresenterClass screen; screen.Refresh(); - // The selection is latched here, at screen entry, and the legacy dialog opens only - // when the document could not be prepared. - bool shown = false; - if (UI_Use_Rml()) { - UIResult const result = UI_Display_Options_Screen(screen); - if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { - shown = true; - } else { - screen.IsClosing = false; - screen.Result.reset(); - } - } - - if (!shown) { - _DisplayScreen = &screen; - - HWND handle; - do { - handle = OwnerDraw::Begin_Dialog(IDD_OPT_DISPLAY, Display_Options_Dialog_Proc); - } while (handle == 0); - OwnerDraw::Display_Dialog(handle); - - while (!screen.Result.has_value()) { - if (OwnerDraw::Dialog_Message_Handler() == true) { - UIResult ended; - ended.Outcome = UIResult::OUTCOME_SESSION_ENDED; - ended.GameEnded = true; - screen.Result = ended; - break; - } - - screen.Drain(); - screen.Service(); - } - - OwnerDraw::End_Dialog(handle); - _DisplayScreen = NULL; + if (UI_Display_Options_Screen(screen).Outcome == UIResult::OUTCOME_FAILED_TO_OPEN) { + break; } if (screen.Choice != UIDisplayOptionsPresenterClass::CHOICE_ACCEPT) { @@ -213,63 +116,6 @@ void Display_Options_Dialog(void) } -/// -/// Handles the main options dialog. -/// The procedure queues what the player pressed for the driver to execute after the pump, -/// and disables the sound button when there is no audio hardware to talk to. -/// -INT_PTR CALLBACK Main_Options_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - HWND handle; - - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - if (rc == 0) { - if (_MainScreen == NULL) { - return(0); - } - - UIMainOptionsPresenterClass & screen = *_MainScreen; - - switch (message) { - - case WM_COMMAND: - switch (LOWORD(wparam)) { - case IDC_OPTMAIN_SOUND: - Options_Queue(screen, UI_MAINOPT_SOUND); - break; - - case IDC_OPTMAIN_DISPLAY: - Options_Queue(screen, UI_MAINOPT_DISPLAY); - break; - - case IDC_OPTMAIN_KEYBOARD: - Options_Queue(screen, UI_MAINOPT_KEYBOARD); - break; - - case IDC_OPTMAIN_GAME_SETTINGS: - Options_Queue(screen, UI_MAINOPT_SETTINGS); - break; - - default: - Options_Queue(screen, UI_MAINOPT_EXIT); - break; - } - break; - - case WM_INITDIALOG: - handle = GetDlgItem(window, IDC_OPTMAIN_SOUND); - if (handle) { - EnableWindow(handle, screen.SoundAvailable); - } - break; - - } - return(0); - } - return(rc); -} - - /// /// Switches the game over to a new render resolution. /// Every drawing surface is destroyed and recreated at the new size, so any pointer held @@ -444,155 +290,10 @@ bool Test_Display_Mode_Dialog(int width, int height) UIDisplayConfirmPresenterClass screen; screen.Refresh(); - // The selection is latched here, at screen entry, and the legacy dialog opens only when - // the document could not be prepared. - if (UI_Use_Rml()) { - UIResult const result = UI_Display_Confirm_Screen(screen); - if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { - return(Keep_Or_Reset_Display_Mode(width, height, - screen.Choice == UIDisplayConfirmPresenterClass::CHOICE_ACCEPT)); - } - screen.IsClosing = false; - screen.Result.reset(); - } - - _ConfirmScreen = &screen; - - bool accepted = true; - - HWND dialog = OwnerDraw::Begin_Dialog(IDD_OPT_CONFIRM_MODE, Test_Display_Mode_Dialog_Proc); - if (dialog) { - OwnerDraw::Display_Dialog(dialog); - - while (!screen.Result.has_value()) { - if (OwnerDraw::Dialog_Message_Handler() == true) { - break; - } - - screen.Drain(); - screen.Service(); - } - - OwnerDraw::End_Dialog(dialog); - - accepted = (screen.Choice == UIDisplayConfirmPresenterClass::CHOICE_ACCEPT); - } - - _ConfirmScreen = NULL; + // A mode whose confirmation could not be shown is refused, because the screen it would + // have been read on may be the unreadable one. + bool const accepted = UI_Display_Confirm_Screen(screen).Outcome != UIResult::OUTCOME_FAILED_TO_OPEN + && screen.Choice == UIDisplayConfirmPresenterClass::CHOICE_ACCEPT; return(Keep_Or_Reset_Display_Mode(width, height, accepted)); } - - -/// -/// Handles the mode confirmation dialog. -/// The procedure queues what the player pressed. Anything that is not the accept button is -/// a refusal, which is what the driver's test against IDOK made of every other identifier -/// the dialog could produce. -/// -INT_PTR CALLBACK Test_Display_Mode_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - if (rc == 0) { - if (_ConfirmScreen == NULL) { - return(0); - } - - switch (message) { - case WM_COMMAND: { - int const id = LOWORD(wparam); - if (id > 0 && id <= IDCANCEL) { - Options_Queue(*_ConfirmScreen, (id == IDOK) ? UI_MODECONFIRM_ACCEPT : UI_MODECONFIRM_CANCEL); - } - break; - } - } - return(0); - } - return(rc); -} - - -/// -/// Handles the display options dialog messages. -/// The procedure fills the resolution list from the view-model, queues the row and the -/// movie stretching preference the player left it on, and hands the driver what the player -/// pressed to execute after the pump. -/// -static __forceinline BOOL Display_Options_Dialog_Body(HWND window, UINT message, WPARAM wparam) -{ - if (_DisplayScreen == NULL) { - return(0); - } - - UIDisplayOptionsPresenterClass & screen = *_DisplayScreen; - - switch (message) { - case WM_COMMAND: - switch (LOWORD(wparam)) { - default: - return(0); - - case IDC_DISPLAY_RESLIST: { - HWND list = GetDlgItem(window, IDC_DISPLAY_RESLIST); - if (list) { - Options_Queue(screen, UI_DISPLAY_SELECT, ListBox_GetCurSel(list)); - } - } - return(0); - - case IDOK: { - HWND list = GetDlgItem(window, IDC_DISPLAY_RESLIST); - if (list) { - Center_Window_Within_Window(window, MainWindow); - Options_Queue(screen, UI_DISPLAY_SELECT, ListBox_GetCurSel(list)); - } - HWND button = GetDlgItem(window, IDC_STRETCH_MOVIES); - if (button) { - Options_Queue(screen, UI_DISPLAY_STRETCH, Button_GetCheck(button) == BST_CHECKED ? 1 : 0); - } - Options_Queue(screen, UI_DISPLAY_ACCEPT); - } - break; - - case IDCANCEL: - Options_Queue(screen, UI_DISPLAY_CANCEL); - break; - } - break; - - case WM_INITDIALOG: { - HWND list = GetDlgItem(window, IDC_DISPLAY_RESLIST); - if (list) { - for (UIDisplayOptionsPresenterClass::ModeType const & mode : screen.Modes) { - int const index = ListBox_AddString(list, mode.Label.c_str()); - ListBox_SetItemData(list, index, index); - } - ListBox_SetCurSel(list, screen.Selected); - } - - HWND button = GetDlgItem(window, IDC_STRETCH_MOVIES); - if (button) { - Button_SetCheck(button, screen.StretchMovies != false); - } - } - break; - - } - return(0); -} - - -/// -/// Handles the display options dialog. -/// This routine gives the owner draw dialog system first refusal on the message and only -/// deals with what it leaves behind. -/// -INT_PTR CALLBACK Display_Options_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - if (rc == 0) { - return(Display_Options_Dialog_Body(window, message, wparam)); - } - return(rc); -} diff --git a/code/mplayer.cpp b/code/mplayer.cpp index 2134c0661..7aca35fa8 100644 --- a/code/mplayer.cpp +++ b/code/mplayer.cpp @@ -45,17 +45,11 @@ #include "addon.h" #include "init.h" #include "msgbox.h" -#include "ownrdraw.h" #include "session.h" #include "ui/uimpselect.h" -#include "ui/uishell.h" class ListClass; -INT_PTR CALLBACK Select_MPlayer_Game_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); - -static UIMPSelectPresenterClass * _MPSelectScreen = NULL; - /// /// Prompts the player for which kind of multiplayer game to start. /// @@ -71,112 +65,15 @@ GameType Select_MPlayer_Game (void) UIMPSelectPresenterClass screen; screen.Refresh(); - // The selection is latched here, at screen entry, and the legacy dialog opens only when - // the document could not be prepared. - if (UI_Use_Rml()) { - if (UI_MPlayer_Select_Screen(screen).Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { - retval = (GameType)screen.Session_Type(); - Session.Read_Scenario_Descriptions(); - return(retval); - } - - screen.IsClosing = false; - screen.Result.reset(); - } - - _MPSelectScreen = &screen; - - HWND dialog; - - if (screen.Variant == UIMPSelectPresenterClass::VARIANT_FIRESTORM) { - dialog = OwnerDraw::Begin_Dialog(IDD_MPLAYER_SELECT_GAME_FS, Select_MPlayer_Game_Dialog_Proc); - } else { - dialog = OwnerDraw::Begin_Dialog(IDD_MPLAYER_SELECT_GAME, Select_MPlayer_Game_Dialog_Proc); - } - - - if (dialog) { - - OwnerDraw::Move_Dialog(dialog, -1, (HiddenSurface->Get_Height() - 400) / 2 + 147); - OwnerDraw::Display_Dialog(dialog); - - while (!screen.Result.has_value()) { - if (OwnerDraw::Dialog_Message_Handler() == true) { - break; - } - - screen.Drain(); - screen.Service(); - } - - ShowWindow(dialog, SW_HIDE); - UpdateWindow(MainWindow); - + if (UI_MPlayer_Select_Screen(screen).Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { retval = (GameType)screen.Session_Type(); - - OwnerDraw::End_Dialog(dialog); Session.Read_Scenario_Descriptions(); } - _MPSelectScreen = NULL; - return(retval); } /* end of Select_MPlayer_Game */ -/// -/// Handles the messages for the multiplayer game type dialog. -/// -/// Returns with the result of the ownerdraw handler, or false when the message was -/// left unhandled. -INT_PTR CALLBACK Select_MPlayer_Game_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - HWND handle; - - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - - if (message == WM_INITDIALOG && _MPSelectScreen != NULL) { - handle = GetDlgItem(window, IDC_INTERNET); - if (handle) { - EnableWindow(handle, _MPSelectScreen->InternetAvailable ? TRUE : FALSE); - } - handle = GetDlgItem(window, IDC_WORLDDOM); - if (handle) { - EnableWindow(handle, _MPSelectScreen->WorldDominationAvailable ? TRUE : FALSE); - } - } - - if (rc != 0) { - return(rc); - } - - if (message == WM_COMMAND && _MPSelectScreen != NULL) { - switch (LOWORD(wparam)) { - case IDC_NETWORK: - _MPSelectScreen->Queue(UIIntent{UI_MPSELECT_NETWORK, "", 0}); - break; - - case IDC_SKIRMISH: - _MPSelectScreen->Queue(UIIntent{UI_MPSELECT_SKIRMISH, "", 0}); - break; - - case IDC_INTERNET: - _MPSelectScreen->Queue(UIIntent{UI_MPSELECT_INTERNET, "", 0}); - break; - - case IDC_WORLDDOM: - _MPSelectScreen->Queue(UIIntent{UI_MPSELECT_WORLDDOM, "", 0}); - break; - - default: - _MPSelectScreen->Queue(UIIntent{UI_MPSELECT_BACK, "", 0}); - break; - } - } - return(false); -} - - /*************************************************************************** * Surrender_Dialog -- Prompts user for surrendering * * * diff --git a/code/msgbox.cpp b/code/msgbox.cpp index b7355d326..301242322 100644 --- a/code/msgbox.cpp +++ b/code/msgbox.cpp @@ -38,15 +38,9 @@ #include "data.h" #include "globals.h" #include "init.h" -#include "ownrdraw.h" #include "ui/uimessagebox.h" -#include "ui/uishell.h" #include "winfix.h" -INT_PTR CALLBACK Message_Box_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -void Message_Box_On_WM_COMMAND(HWND window, int id, int control, int notify_code); - -int _default_response = 0; /*********************************************************************************************** * WWMessageBox::Process -- pops up a message with yes/no, etc * @@ -73,164 +67,18 @@ int _default_response = 0; * 05/18/1995 JLB : Uses new font and dialog style. * * 08/24/1995 JLB : Handles three buttons. * *=============================================================================================*/ -#define BUTTON_1 IDC_MSGBOX_OK -#define BUTTON_2 IDCANCEL -#define BUTTON_3 IDC_MSGBOX_BTN3 -#define BUTTON_FLAG 0x8000 int WWMessageBox::_Process(const char * msg, int defresponse, const char * b1txt, const char * b2txt, const char * b3txt, bool preserve) { - int retval = -1; - int numbuttons = 0; - - if (UI_Use_Rml()) { - UIResult const result = UI_Message_Box_Screen(msg, defresponse, b1txt, b2txt, b3txt); - - // A session that ended under the box is what the dialog driver reported by leaving - // the result unset, and the caller reads that as the -1 it started from. - if (result.Outcome == UIResult::OUTCOME_SESSION_ENDED) { - return(-1); - } - - if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { - return(result.Value); - } - } - - _default_response = defresponse; - - HWND dialog = OwnerDraw::Begin_Dialog(IDD_MSGBOX_3, Message_Box_Proc); - - if (dialog != NULL) { - SetWindowLongPtr(dialog, DWLP_USER, (LONG_PTR)&retval); - - if (msg != NULL && msg[0] != '\0') { - SetDlgItemText(dialog, IDC_MSGBOX_TEXT, msg); - } + UIResult const result = UI_Message_Box_Screen(msg, defresponse, b1txt, b2txt, b3txt); - if (b1txt != NULL && b1txt[0] != '\0') { - SetDlgItemText(dialog, BUTTON_1, b1txt); - numbuttons = 1; - } else { - EnableWindow(GetDlgItem(dialog, BUTTON_1), FALSE); - ShowWindow(GetDlgItem(dialog, BUTTON_1), SW_HIDE); - } - - if (b2txt != NULL && b2txt[0] != '\0') { - SetDlgItemText(dialog, BUTTON_2, b2txt); - numbuttons = 2; - } else { - EnableWindow(GetDlgItem(dialog, BUTTON_2), FALSE); - ShowWindow(GetDlgItem(dialog, BUTTON_2), SW_HIDE); - } - - if (b3txt != NULL && b3txt[0] != '\0') { - SetDlgItemText(dialog, BUTTON_3, b3txt); - numbuttons = 3; - } else { - EnableWindow(GetDlgItem(dialog, BUTTON_3), FALSE); - ShowWindow(GetDlgItem(dialog, BUTTON_3), SW_HIDE); - - if (numbuttons == 1) { - RECT rect; - GetWindowRect(GetDlgItem(dialog, BUTTON_3), &rect); - ScreenToClient(dialog, (LPPOINT)&rect); - ScreenToClient(dialog, (LPPOINT)&rect.right); - MoveWindow(GetDlgItem(dialog, BUTTON_1), rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, FALSE); - } - } - - OwnerDraw::Display_Dialog(dialog); - - if (numbuttons > 0) { - while (retval < 0) { - if (OwnerDraw::Dialog_Message_Handler() == true) { - break; - } - - if (!GameActive) { - Title_Screen_Restore(); - } - } - } else { - retval = 0; - } - - OwnerDraw::End_Dialog(dialog); + // A session that ended under the box, and a box that could not be shown at all, are + // both reported as the -1 the dialog driver left its caller holding. + if (result.Outcome == UIResult::OUTCOME_SESSION_ENDED || + result.Outcome == UIResult::OUTCOME_FAILED_TO_OPEN) { + return(-1); } - return(retval); -} - - -/// -/// Handles the dialog messages for the message box. -/// This routine gives the owner draw system first crack at every message and only steps -/// in for the button notifications and the window dragging that it does not already deal -/// with. -/// -/// BOOL; Was the message handled here? -INT_PTR CALLBACK Message_Box_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - - if (rc == FALSE) { - switch (message) { - - case WM_COMMAND: - Message_Box_On_WM_COMMAND(window, LOWORD(wparam), 0, HIWORD(wparam)); - rc = FALSE; - break; - - case WM_MOVING: - rc = On_WM_MOVING(window, wparam, lparam); - break; - - default: - rc = FALSE; - break; - } - } - - return(rc); -} - - -/// -/// Handles a button press within the message box dialog. -/// This routine records which of the buttons the player picked in the result slot that -/// the message box is waiting on. The Enter key arrives here as IDOK and yields the -/// default response the caller asked for. -/// -/// The identifier of the control that sent the notification. -void Message_Box_On_WM_COMMAND(HWND window, int id, int control, int notify_code) -{ - int *retval = (int*)GetWindowLongPtr(window, DWLP_USER); - switch (id) { - - case IDOK: - if (notify_code == BN_CLICKED) { - *retval = _default_response; - } - break; - - case BUTTON_1: - if (notify_code == BN_CLICKED) { - *retval = 0; - } - break; - - case IDCANCEL: - if (notify_code == BN_CLICKED) { - *retval = 1; - } - break; - - case BUTTON_3: - if (notify_code == BN_CLICKED) { - *retval = 2; - } - break; - } + return(result.Value); } diff --git a/code/options.cpp b/code/options.cpp index 06be04d16..1db5ea329 100644 --- a/code/options.cpp +++ b/code/options.cpp @@ -76,7 +76,6 @@ #include "language/language.h" #include "mouse.h" #include "msgbox.h" -#include "ownrdraw.h" #include "rules.h" #include "session.h" #include "techno.h" @@ -85,7 +84,6 @@ #include "video.h" #include "vox.h" #include "ui/uikeyboard.h" -#include "ui/uishell.h" #include "diff.hh" @@ -595,212 +593,17 @@ int OptionsClass::Normalize_Volume(int volume) const } -// The screen the hotkey dialog procedure reads and writes. The driver owns it for the whole -// life of the dialog, which is the lifetime DWLP_USER gave the result pointer it replaces. -static UIKeyboardPresenterClass * _KeyboardScreen = NULL; - - -static void Hotkey_Queue(UIKeyboardPresenterClass & screen, char const * action, int value = 0) -{ - UIIntent intent; - intent.Action = action; - intent.Value = value; - screen.Queue(intent); -} - - -// Puts the view-model's text and lists back on the controls. The dialog did this from its -// own private messages; the driver now does it after the queue has been executed, so a -// handler that only queues still leaves the screen looking right. -static void Hotkey_Sync_Controls(HWND window, UIKeyboardPresenterClass const & screen) -{ - HWND handle; - - handle = GetDlgItem(window, IDC_KEY_COMMANDS); - if (handle && ListBox_GetCount(handle) != (int)screen.Commands.size()) { - ListBox_ResetContent(handle); - for (UIKeyboardPresenterClass::CommandType const & command : screen.Commands) { - ListBox_AddString(handle, command.Label.c_str()); - } - ListBox_SetCurSel(handle, screen.SelectedCommand); - } - - handle = GetDlgItem(window, IDC_KEY_DESCRIPTION); - if (handle) { - SetWindowText(handle, screen.Description.c_str()); - } - - handle = GetDlgItem(window, IDC_KEY_CURRENT_SHORTCUT); - if (handle) { - SetWindowText(handle, screen.CurrentShortcut.c_str()); - } - - handle = GetDlgItem(window, IDC_KEY_ASSIGNED_TO); - if (handle) { - SetWindowText(handle, screen.AssignedTo.c_str()); - } - - handle = GetDlgItem(window, IDC_KEY_HOTKEY); - if (handle && SendMessage(handle, HKM_GETHOTKEY, 0, 0) != screen.CapturedKey) { - SendMessage(handle, HKM_SETHOTKEY, screen.CapturedKey, 0); - } -} - - -/// -/// Handles the messages for the keyboard configuration dialog. -/// The procedure primes its controls from the view-model and queues what the player did for -/// the driver to execute after the pump. -/// -/// Returns with TRUE if the message was consumed by this dialog. -INT_PTR CALLBACK Hotkey_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - INT_PTR result = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - if (result) { - return(result); - } - - if (_KeyboardScreen == NULL) { - return(FALSE); - } - - UIKeyboardPresenterClass & screen = *_KeyboardScreen; - - switch (message) { - case WM_COMMAND: - switch (LOWORD(wparam)) { - case IDOK: - if (HIWORD(wparam) == BN_CLICKED) { - Hotkey_Queue(screen, UI_KEYBOARD_ACCEPT); - return(TRUE); - } - break; - - case IDCANCEL: - if (HIWORD(wparam) == BN_CLICKED) { - Hotkey_Queue(screen, UI_KEYBOARD_CANCEL); - return(TRUE); - } - break; - - case IDC_KEY_COMMANDS: - if (HIWORD(wparam) == LBN_SELCHANGE) { - HWND list = GetDlgItem(window, IDC_KEY_COMMANDS); - if (list) { - Hotkey_Queue(screen, UI_KEYBOARD_COMMAND, ListBox_GetCurSel(list)); - } - HWND hotkey = GetDlgItem(window, IDC_KEY_HOTKEY); - if (hotkey != NULL) { - SetFocus(hotkey); - return(TRUE); - } - } - break; - - case IDC_KEY_ASSIGN: - Hotkey_Queue(screen, UI_KEYBOARD_ASSIGN); - return(TRUE); - - case IDC_KEY_HOTKEY: - if (HIWORD(wparam) == EN_CHANGE) { - Hotkey_Queue(screen, UI_KEYBOARD_CAPTURE, (int)SendMessage((HWND)lparam, HKM_GETHOTKEY, 0, 0)); - return(TRUE); - } - break; - - case IDC_KEY_RESET_ALL: - if (HIWORD(wparam) == BN_CLICKED) { - Hotkey_Queue(screen, UI_KEYBOARD_RESET); - return(TRUE); - } - break; - - case IDC_KEY_CATEGORY: - if (HIWORD(wparam) == CBN_SELCHANGE) { - HWND combo = GetDlgItem(window, IDC_KEY_CATEGORY); - if (combo) { - Hotkey_Queue(screen, UI_KEYBOARD_CATEGORY, ComboBox_GetCurSel(combo)); - } - return(TRUE); - } - break; - } - return(TRUE); - - case WM_INITDIALOG: { - HWND combo = GetDlgItem(window, IDC_KEY_CATEGORY); - if (combo) { - ComboBox_ResetContent(combo); - for (std::string const & category : screen.Categories) { - ComboBox_AddString(combo, category.c_str()); - } - ComboBox_SetCurSel(combo, screen.SelectedCategory); - } - - HWND list = GetDlgItem(window, IDC_KEY_COMMANDS); - if (list) { - ListBox_ResetContent(list); - for (UIKeyboardPresenterClass::CommandType const & command : screen.Commands) { - ListBox_AddString(list, command.Label.c_str()); - } - ListBox_SetCurSel(list, screen.SelectedCommand); - } - return(FALSE); - } - } - - return(FALSE); -} - - /// /// Displays the keyboard configuration dialog. -/// This routine brings up the hotkey assignment dialog and does not return until the player -/// dismisses it. The title screen is kept refreshed while the dialog is up outside of a +/// This routine brings up the hotkey assignment screen and does not return until the player +/// dismisses it. The title screen is kept refreshed while the screen is up outside of a /// game. /// bool OptionsClass::Hotkey_Dialog(void) { UIKeyboardPresenterClass screen; screen.Refresh(); - - // The selection is latched here, at screen entry, and the legacy dialog opens only when - // the document could not be prepared. - if (UI_Use_Rml()) { - UIResult const result = UI_Keyboard_Screen(screen); - if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { - return(true); - } - - screen.IsClosing = false; - screen.Result.reset(); - } - - _KeyboardScreen = &screen; - - HWND handle = OwnerDraw::Begin_Dialog(IDD_OPT_KEYBOARD, Hotkey_Dialog_Proc); - - if (handle != NULL) { - OwnerDraw::Display_Dialog(handle); - - while (!screen.Result.has_value()) { - if (OwnerDraw::Dialog_Message_Handler() == true) { - UIResult ended; - ended.Outcome = UIResult::OUTCOME_SESSION_ENDED; - ended.GameEnded = true; - screen.Result = ended; - break; - } - - screen.Drain(); - Hotkey_Sync_Controls(handle, screen); - screen.Service(); - } - - OwnerDraw::End_Dialog(handle); - } - - _KeyboardScreen = NULL; + UI_Keyboard_Screen(screen); return(true); } diff --git a/code/savemgr.cpp b/code/savemgr.cpp index a509a99cd..2047b10e1 100644 --- a/code/savemgr.cpp +++ b/code/savemgr.cpp @@ -9,6 +9,12 @@ #include "hostclock.h" #include "always.h" +#include "conquer.h" +#include "_keyboar.h" +#include "keyboard.h" +#include "msgloop.h" +#include "ui/uiinternal.h" +#include "ui/uimessagebox.h" #include "savemgr.h" @@ -26,7 +32,6 @@ #include "msgbox.h" #include "netdlg.h" #include "netglobal.h" -#include "ownrdraw.h" #include "rawfile.h" #include "rules.h" #include "saveload.h" @@ -151,16 +156,15 @@ void SaveManagerClass::Process_Pending_Save_Game(void) PendingSaveNotice = NoticeType::None; if (MultiplayerSavingAllowed) { - HWND dialog = 0; + bool box = false; if (!quiet) { - dialog = OwnerDraw::Custom_Message_Box(Fetch_String(TXT_SAVING_GAME), NULL, NULL); - } - if (dialog != 0) { - OwnerDraw::Display_Dialog(dialog); + box = UI_Wait_Box_Open(Fetch_String(TXT_SAVING_GAME), NULL, NULL); + Keyboard->Clear(); } bool saved = Save_Game(file_name.c_str(), description.c_str()); - if (dialog != 0) { - OwnerDraw::End_Dialog(dialog); + if (box) { + Keyboard->Clear(); + UI_Wait_Box_Close(); } Record_Save_Outcome(notice, saved); if (saved && SpawnCopyPending) { @@ -315,14 +319,13 @@ void SaveManagerClass::Quick_Save_Service(void) char description[512]; std::snprintf(description, sizeof(description), Fetch_String(TXT_QUICKSAVE_DESCRIPTION), Scen->Description); - HWND dialog = OwnerDraw::Custom_Message_Box(Fetch_String(TXT_SAVING_GAME), NULL, NULL); - if (dialog != 0) { - OwnerDraw::Display_Dialog(dialog); - } + bool const box = UI_Wait_Box_Open(Fetch_String(TXT_SAVING_GAME), NULL, NULL); + Keyboard->Clear(); Request_Save_Game(Quick_Save_File_Name(Single_Player_Kind()).c_str(), description, false, NoticeType::Requested); - if (dialog != 0) { - OwnerDraw::End_Dialog(dialog); + if (box) { + Keyboard->Clear(); + UI_Wait_Box_Close(); } } @@ -594,27 +597,31 @@ void SaveManagerClass::Process_Pending_Load_Game(void) Session.Suspended++; TacticalActive = false; - HWND dialog = OwnerDraw::Custom_Message_Box(Fetch_String(TXT_LOADING_SAVED_GAME), NULL, NULL); - if (dialog != 0) { - OwnerDraw::Display_Dialog(dialog); - } + bool const box = UI_Wait_Box_Open(Fetch_String(TXT_LOADING_SAVED_GAME), NULL, NULL); + Keyboard->Clear(); int shown = -1; while (!MultiplayerLoad.Is_Due(Monotonic_Milliseconds())) { int seconds = MultiplayerLoad.Seconds_Left(Monotonic_Milliseconds()); - if (dialog != 0 && seconds != shown) { + if (box && seconds != shown) { shown = seconds; char buffer[128]; std::snprintf(buffer, sizeof(buffer), Fetch_String(seconds == 1 ? TXT_LOADING_IN_SECOND : TXT_LOADING_IN_SECONDS), seconds); - OwnerDraw::Set_Custom_Message_Box_Text(dialog, buffer); + UI_Wait_Box_Set_Text(buffer); } - OwnerDraw::Dialog_Message_Handler(); + + // The countdown runs with the session suspended, which is the branch the dialog + // driver's own pump took here. + Windows_Message_Handler(); + Call_Back(); + UI_Paint_Now(false); Host_Sleep(10); } - if (dialog != 0) { - OwnerDraw::End_Dialog(dialog); + if (box) { + Keyboard->Clear(); + UI_Wait_Box_Close(); } Session.Suspended--; TacticalActive = true; diff --git a/code/skirmish.cpp b/code/skirmish.cpp index 688f6903a..17e4ea4ee 100644 --- a/code/skirmish.cpp +++ b/code/skirmish.cpp @@ -24,156 +24,11 @@ #include "msgbox.h" #include "netshare.h" #include "newmenu.h" -#include "ownrdraw.h" #include "rules.h" #include "ui/uiskirmish.h" -#include "ui/uishell.h" #include "win.h" -INT_PTR CALLBACK Skirmish_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -BOOL Skirmish_On_WM_INITDIALOG(HWND window, WPARAM wparam, LPARAM lparam); - - -/// -/// Reads the controls the screen takes its settings from and queues what they hold. -/// The dialog read its sliders, name field and boxes when a button was pressed rather than -/// tracking them, because a keyboard or page move changes a track bar without raising the -/// notification a tracking handler would follow. -/// -static void Skirmish_Read_Controls(HWND window, UISkirmishPresenterClass & screen) -{ - static struct { int id; char const * name; } const sliders[] = { - { IDC_SKIRMISH_UNITCOUNT, UI_SKIRMISH_UNITCOUNT }, - { IDC_SKIRMISH_CREDITS, UI_SKIRMISH_CREDITS }, - { IDC_SKIRMISH_TECHLEVEL, UI_SKIRMISH_TECHLEVEL }, - { IDC_DIFFICULTY_SLIDER, UI_SKIRMISH_AILEVEL }, - { IDC_SKIRMISH_AIPLAYERS, UI_SKIRMISH_AIPLAYERS }, - { IDC_GAME_SPEED_SLIDER, UI_SKIRMISH_GAMESPEED }, - }; - - for (auto const & entry : sliders) { - HWND const handle = GetDlgItem(window, entry.id); - if (handle) { - screen.Queue(UIIntent{UI_SKIRMISH_SLIDER, entry.name, Slider_GetPos(handle)}); - } - } - - char buffer[128]; - GetWindowText(GetDlgItem(window, IDC_SKIRMISH_NAME), buffer, sizeof(buffer)); - screen.Queue(UIIntent{UI_SKIRMISH_HANDLE, buffer, 0}); - - HWND handle = GetDlgItem(window, IDC_SKIRMISH_SIDE); - if (handle) { - int const country = Country_From_Box(handle); - for (int row = 0; row < (int)screen.Sides.size(); row++) { - if (screen.Sides[row].Country == country) { - screen.Queue(UIIntent{UI_SKIRMISH_SIDE, "", row}); - break; - } - } - } - - handle = GetDlgItem(window, IDC_SKIRMISH_COLOR); - if (handle) { - screen.Queue(UIIntent{UI_SKIRMISH_COLOR, "", (int)ComboBox_GetCurSel(handle)}); - } -} - - -/// -/// Handles a control notification from the skirmish dialog. -/// The controls are read into the view-model and the command is queued as an intent; the -/// driver executes the queue after the pump returns. -/// -void Skirmish_On_WM_COMMAND(HWND window, int message, WPARAM wparam, LPARAM lparam) -{ - UISkirmishPresenterClass * const screen = - (UISkirmishPresenterClass *)GetWindowLongPtr(window, DWLP_USER); - if (screen == NULL) { - return; - } - - switch (message) { - case IDOK: - if (lparam == 0) { - EnableWindow(GetDlgItem(window, 1), FALSE); - Skirmish_Read_Controls(window, *screen); - screen->Queue(UIIntent{UI_SKIRMISH_ACCEPT, "", 0}); - } - break; - - case IDCANCEL: - if (!lparam) { - Skirmish_Read_Controls(window, *screen); - screen->Queue(UIIntent{UI_SKIRMISH_CANCEL, "", 0}); - } - break; - - case IDC_SHORT_GAME: - screen->Queue(UIIntent{UI_SKIRMISH_TOGGLE, UI_SKIRMISH_SHORTGAME, 0}); - break; - - case IDC_SKIRMISH_BASES: - screen->Queue(UIIntent{UI_SKIRMISH_TOGGLE, UI_SKIRMISH_BASES, 0}); - break; - - case IDC_SKIRMISH_CRATES: - screen->Queue(UIIntent{UI_SKIRMISH_TOGGLE, UI_SKIRMISH_CRATES, 0}); - break; - - case IDC_SKIRMISH_FOG: - screen->Queue(UIIntent{UI_SKIRMISH_TOGGLE, UI_SKIRMISH_FOG, 0}); - break; - - case IDC_SKIRMISH_BRIDGES: - screen->Queue(UIIntent{UI_SKIRMISH_TOGGLE, UI_SKIRMISH_BRIDGES, 0}); - break; - - case IDC_REDEPLOY_MCV: - screen->Queue(UIIntent{UI_SKIRMISH_TOGGLE, UI_SKIRMISH_MCV, 0}); - break; - - case IDC_MULTI_ENGINEER: - screen->Queue(UIIntent{UI_SKIRMISH_TOGGLE, UI_SKIRMISH_ENGINEER, 0}); - break; - - case IDC_MULTIMAP: - screen->Queue(UIIntent{UI_SKIRMISH_PICK_MAP, "", 0}); - break; - } -} - - -/// -/// Puts the view-model on the dialog's own controls. -/// -static void Skirmish_Sync_Controls(HWND window, UISkirmishPresenterClass & screen) -{ - static struct { int id; bool UISkirmishPresenterClass::* field; } const boxes[] = { - { IDC_SKIRMISH_BASES, &UISkirmishPresenterClass::Bases }, - { IDC_SKIRMISH_CRATES, &UISkirmishPresenterClass::Crates }, - { IDC_SKIRMISH_FOG, &UISkirmishPresenterClass::FogOfWar }, - { IDC_SKIRMISH_BRIDGES, &UISkirmishPresenterClass::Bridges }, - { IDC_REDEPLOY_MCV, &UISkirmishPresenterClass::MCVRedeploy }, - { IDC_SHORT_GAME, &UISkirmishPresenterClass::ShortGame }, - { IDC_MULTI_ENGINEER, &UISkirmishPresenterClass::MultiEngineer }, - }; - - for (auto const & entry : boxes) { - HWND const handle = GetDlgItem(window, entry.id); - if (handle == NULL) continue; - - int const wanted = (screen.*(entry.field)) ? BST_CHECKED : BST_UNCHECKED; - if (Button_GetCheck(handle) != wanted) { - Button_SetCheck(handle, wanted); - } - } - - SendDlgItemMessage(window, IDC_SCENARIONAME, WM_SETTEXT, 0, (LPARAM)screen.ScenarioName.c_str()); - EnableWindow(GetDlgItem(window, 1), screen.CanAccept ? TRUE : FALSE); -} - /// /// Handles the skirmish game setup dialog. @@ -196,40 +51,7 @@ bool Skirmish_Mode_Dialog(void) UISkirmishPresenterClass screen; screen.Refresh(); - if (UI_Use_Rml()) { - UIResult const result = UI_Skirmish_Screen(screen); - if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { - rc = screen.Accepted() ? IDOK : IDCANCEL; - } - } - - HWND dialog = rc == -1 ? OwnerDraw::Begin_Dialog(IDD_SKIRMISH, Skirmish_Dialog_Proc) : NULL; - if (dialog) { - SetWindowLongPtr(dialog, DWLP_USER, (LONG_PTR)&screen); - Skirmish_Sync_Controls(dialog, screen); - OwnerDraw::Display_Dialog(dialog); - while (!screen.Result.has_value()) { - if (OwnerDraw::Dialog_Message_Handler() == IDOK) { - break; - } - - // A control handler queues rather than acts, so the queue is executed here, - // after the pump has returned. - screen.Drain(); - - // The map selection screen draws where this one is, so the dialog gets out of - // its way, which is what its own ShowWindow did. - if (screen.Pending != UISkirmishPresenterClass::SUB_NONE) { - ShowWindow(dialog, SW_HIDE); - screen.Run_Pending(); - ShowWindow(dialog, SW_SHOW); - InvalidateRect(dialog, NULL, FALSE); - } - - Skirmish_Sync_Controls(dialog, screen); - screen.Service(); - } - OwnerDraw::End_Dialog(dialog); + if (UI_Skirmish_Screen(screen).Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { rc = screen.Accepted() ? IDOK : IDCANCEL; } @@ -251,180 +73,3 @@ bool Skirmish_Mode_Dialog(void) return(false); } - - -/// -/// Handles the messages sent to the skirmish setup dialog. -/// The owner draw dialog handler is given first refusal on every message. Anything it -/// leaves alone is dealt with here -- dialog setup, button and slider notifications, and -/// repainting the map preview. -/// -/// Returns with TRUE if the message was handled, otherwise FALSE so that Windows -/// performs its default processing. -INT_PTR CALLBACK Skirmish_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - - if (rc == 0) { - - switch (message) { - case WM_COMMAND: - Skirmish_On_WM_COMMAND(window, LOWORD(wparam), lparam, HIWORD(wparam)); - return(TRUE); - - case WM_INITDIALOG: - return(Skirmish_On_WM_INITDIALOG(window, wparam, lparam)); - - case WM_PAINT: - if (MultiplayerMapPreview) { - MultiplayerMapPreview->Blit_Preview(window); - } - ValidateRect(window, NULL); - break; - - case WM_HSCROLL: { - int code = LOWORD(wparam); - if (code != SB_THUMBPOSITION && code != SB_THUMBTRACK) { - Slider_GetPos((HWND)lparam); - } - switch (GetDlgCtrlID((HWND)lparam)) { - case IDC_SKIRMISH_UNITCOUNT: - GetDlgItem(window, IDC_SKIRMISH_UNITCOUNT_LABEL); - break; - case IDC_SKIRMISH_TECHLEVEL: - GetDlgItem(window, IDC_SKIRMISH_TECHLEVEL_LABEL); - break; - case IDC_DIFFICULTY_SLIDER: - GetDlgItem(window, IDC_SKIRMISH_AILEVEL_LABEL); - break; - case IDC_SKIRMISH_AIPLAYERS: - GetDlgItem(window, IDC_SKIRMISH_AIPLAYERS_LABEL); - break; - } - } - break; - } - return(FALSE); - } - return(rc); -} - - -/// -/// Prepares the skirmish dialog for display. -/// This routine fills the sliders, side and color combo boxes, and option check boxes -/// with the player's current multiplayer settings, selects the starting scenario, and -/// puts up its map preview. -/// -/// Always FALSE, so that Windows leaves the keyboard focus where the dialog -/// template put it. -BOOL Skirmish_On_WM_INITDIALOG(HWND window, WPARAM wparam, LPARAM lparam) -{ - #define MP_MIN_MONEY 2500 - - HWND handle = GetDlgItem(window, IDC_SKIRMISH_UNITCOUNT); - if (handle) { - Slider_SetRange(handle, SessionClass::CountMin[1], SessionClass::CountMax[1]); - Slider_SetPos(handle, Session.Options.UnitCount); - } - - handle = GetDlgItem(window, IDC_SKIRMISH_UNITCOUNT_LABEL); - if (handle) { - // - } - - handle = GetDlgItem(window, IDC_SKIRMISH_TECHLEVEL); - if (handle) { - Slider_SetRange(handle, 1, MPLAYER_BUILD_LEVEL_MAX); - Slider_SetPos(handle, BuildLevel); - } - - handle = GetDlgItem(window, IDC_SKIRMISH_TECHLEVEL_LABEL); - if (handle) { - // - } - - handle = GetDlgItem(window, IDC_DIFFICULTY_SLIDER); - if (handle) { - Slider_SetRange(handle, 0, 2); - Slider_SetPos(handle, Session.Options.AIDifficulty); - } - - handle = GetDlgItem(window, IDC_SKIRMISH_AILEVEL_LABEL); - if (handle) { - // - } - - handle = GetDlgItem(window, IDC_SKIRMISH_CREDITS); - if (handle) { - Slider_SetRange(handle, MP_MIN_MONEY, Rule->MPMaxMoney); - Slider_SetPos(handle, Session.Options.Credits); - SendMessage(handle, OD_SETTRACKSTEP, 0, 250); - } - - handle = GetDlgItem(window, IDC_SKIRMISH_AIPLAYERS); - if (handle) { - Slider_SetRange(handle, 1, 7); - Slider_SetPos(handle, Session.Options.AIPlayers > 1 ? Session.Options.AIPlayers : 1); - } - - handle = GetDlgItem(window, IDC_GAME_SPEED_SLIDER); - if (handle) { - Slider_SetRange(handle, 0, 6); - Slider_SetPos(handle, 6 - Session.Options.GameSpeed); - } - - handle = GetDlgItem(window, IDC_SKIRMISH_NAME); - if (handle) SetWindowText(handle, Session.Handle); - - handle = GetDlgItem(window, IDC_SKIRMISH_SIDE); - if (handle) { - Fill_Country_Box(handle); - Select_Country_In_Box(handle, Session.House); - } - - SendDlgItemMessage(window, IDC_SKIRMISH_COLOR, CB_RESETCONTENT, 0, 0); - SendDlgItemMessage(window, IDC_SKIRMISH_COLOR, CB_INSERTSTRING, -1, (LPARAM)Fetch_String(TXT_GOLD)); - SendDlgItemMessage(window, IDC_SKIRMISH_COLOR, CB_INSERTSTRING, -1, (LPARAM)Fetch_String(TXT_RED)); - SendDlgItemMessage(window, IDC_SKIRMISH_COLOR, CB_INSERTSTRING, -1, (LPARAM)Fetch_String(TXT_BLUE)); - SendDlgItemMessage(window, IDC_SKIRMISH_COLOR, CB_INSERTSTRING, -1, (LPARAM)Fetch_String(TXT_GREEN)); - SendDlgItemMessage(window, IDC_SKIRMISH_COLOR, CB_INSERTSTRING, -1, (LPARAM)Fetch_String(TXT_ORANGE)); - SendDlgItemMessage(window, IDC_SKIRMISH_COLOR, CB_INSERTSTRING, -1, (LPARAM)Fetch_String(TXT_SKY_BLUE)); - SendDlgItemMessage(window, IDC_SKIRMISH_COLOR, CB_INSERTSTRING, -1, (LPARAM)Fetch_String(TXT_PURPLE)); - SendDlgItemMessage(window, IDC_SKIRMISH_COLOR, CB_INSERTSTRING, -1, (LPARAM)Fetch_String(TXT_PINK)); - SendDlgItemMessage(window, IDC_SKIRMISH_COLOR, CB_SETCURSEL, Session.PrefColor, 0); - - for (int player = 0; player < MAX_PLAYERS; player++) { - SendDlgItemMessage(window, IDC_SKIRMISH_COLOR, OD_SETCOLOR, player, (LPARAM)PlayerColorTable[player]); - } - - Set_Scenario_Info_From_Index(0); - Session.Options.ScenarioIndex = 0; - SendDlgItemMessage(window, IDC_SCENARIONAME, WM_SETTEXT, 0, (LPARAM)Session.Options.ScenarioDescription); - Clear_Vector(&Session.Players); - Clear_Vector(&Session.Computers); - - handle = GetDlgItem(window, IDC_SKIRMISH_BASES); - if (handle) Button_SetCheck(handle, Session.Options.Bases ? BST_CHECKED : BST_UNCHECKED); - - handle = GetDlgItem(window, IDC_SKIRMISH_CRATES); - if (handle) Button_SetCheck(handle, Session.Options.Goodies ? BST_CHECKED : BST_UNCHECKED); - - handle = GetDlgItem(window, IDC_SKIRMISH_FOG); - if (handle) Button_SetCheck(handle, Session.Options.FogOfWar ? BST_CHECKED : BST_UNCHECKED); - - handle = GetDlgItem(window, IDC_SKIRMISH_BRIDGES); - if (handle) Button_SetCheck(handle, Session.Options.BridgeDestruction ? BST_CHECKED : BST_UNCHECKED); - - handle = GetDlgItem(window, IDC_REDEPLOY_MCV); - if (handle) Button_SetCheck(handle, Session.Options.MCVRedeploy ? BST_CHECKED : BST_UNCHECKED); - - handle = GetDlgItem(window, IDC_MULTI_ENGINEER); - if (handle) Button_SetCheck(handle, Session.Options.CrapEngineers ? BST_CHECKED : BST_UNCHECKED); - - handle = GetDlgItem(window, IDC_SHORT_GAME); - if (handle) Button_SetCheck(handle, Session.Options.ShortGame ? BST_CHECKED : BST_UNCHECKED); - - Update_Network_Dialog_Preview(window); - return(FALSE); -} diff --git a/code/sounddlg.cpp b/code/sounddlg.cpp index ac081609e..c6053f7f5 100644 --- a/code/sounddlg.cpp +++ b/code/sounddlg.cpp @@ -43,279 +43,26 @@ #include "incdec.h" #include "init.h" #include "language/language.h" -#include "ownrdraw.h" #include "theme.h" -#include "ui/uishell.h" #include "ui/uisound.h" #include "winfix.h" -bool DialogInitialized = false; - -// The screen the dialog procedure reads and writes. A dialog procedure is reached by -// Windows rather than by its driver, so this is how it finds the presenter its driver made. -static UISoundPresenterClass * _Screen = nullptr; - - -/// -/// Puts the view-model back into the controls that an executed intent can have changed. -/// Only the check boxes need it: shuffle and repeat exclude one another, so checking one -/// clears the other, and nothing else changes a control from underneath the player. -/// -static void Sound_Sync_Controls(HWND window, UISoundPresenterClass const & screen) -{ - HWND button = GetDlgItem(window, IDC_SOUND_SHUFFLE); - if (button) { - Button_SetCheck(button, screen.Shuffle ? BST_CHECKED : BST_UNCHECKED); - } - - button = GetDlgItem(window, IDC_SOUND_REPEAT); - if (button) { - Button_SetCheck(button, screen.Repeat ? BST_CHECKED : BST_UNCHECKED); - } -} - /// /// Handles the sound and music options dialog. -/// This routine brings up the sound controls and then services the owner draw dialog -/// handler until the player dismisses them. A cut down version of the dialog is used -/// when there is no game in progress, since the in game options do not apply there. +/// This routine brings up the sound controls and runs them until the player dismisses +/// them. A cut down version of the screen is used when there is no game in progress, since +/// the in game options do not apply there. /// /// This routine will not return until the player closes the dialog. void SoundControlsClass::Dialog(void) { DebugString("SoundControls: GameSpeed = %d, ScrollRate = %d, Detail = %d\n", Options.GameSpeed, Options.ScrollRate, Options.DetailLevel); - DialogInitialized = false; UISoundPresenterClass screen; screen.Refresh(); - - // The selection is latched here, at screen entry, and the legacy dialog opens only when - // the document could not be prepared. - if (UI_Use_Rml()) { - UIResult const result = UI_Sound_Screen(screen); - if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { - DebugString("SoundControls: GameSpeed = %d, ScrollRate = %d, Detail = %d\n", Options.GameSpeed, Options.ScrollRate, Options.DetailLevel); - return; - } - screen.IsClosing = false; - screen.Result.reset(); - } - - _Screen = &screen; - - HWND dialog; - if (screen.Is_Lite()) { - dialog = OwnerDraw::Begin_Dialog(IDD_SOUND_OPTIONS_DIALOG_LITE, Sound_Option_Dialog_Func); - } else { - dialog = OwnerDraw::Begin_Dialog(IDD_SOUND_OPTIONS_DIALOG, Sound_Option_Dialog_Func); - } - - if (dialog) { - - OwnerDraw::Display_Dialog(dialog); - - while (!screen.Result.has_value()) { - if (OwnerDraw::Dialog_Message_Handler() == true) { - UIResult ended; - ended.Outcome = UIResult::OUTCOME_SESSION_ENDED; - ended.GameEnded = true; - screen.Result = ended; - } - - // A control handler queues rather than acts, so the queue is executed here, - // after the pump has returned and before the pass's maintenance. - screen.Drain(); - Sound_Sync_Controls(dialog, screen); - - screen.Service(); - } - - OwnerDraw::End_Dialog(dialog); - } - - _Screen = nullptr; + UI_Sound_Screen(screen); DebugString("SoundControls: GameSpeed = %d, ScrollRate = %d, Detail = %d\n", Options.GameSpeed, Options.ScrollRate, Options.DetailLevel); } - -/*********************************************************************************************** - * SoundControlsClass::Process -- Handles all the options graphic interface. * - * * - * This routine is the main control for the visual representation of the options * - * screen. It handles the visual overlay and the player input. * - * * - * INPUT: none * - * * - * OUTPUT: none * - * * - * WARNINGS: none * - * * - * HISTORY: 12/31/1994 MML : Created. * - *=============================================================================================*/ -INT_PTR CALLBACK SoundControlsClass::Sound_Option_Dialog_Func(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - - if (rc != 0) { - return(rc); - } - - // The driver owns the screen for the whole life of the dialog, so a message that - // arrives without one has nothing to act on. - if (_Screen == nullptr) { - return(FALSE); - } - - UISoundPresenterClass & screen = *_Screen; - - switch (message) { - case WM_INITDIALOG: { - DialogInitialized = false; - - /* - ** Music volume slider. - */ - HWND track = GetDlgItem(window, IDC_MUSIC_VOLUME); - if (track) { - SendMessage(track, OD_TRACKSILENT, 0, 0); - Slider_SetRange(track, 0, UISoundPresenterClass::VOLUME_LEVELS); - Slider_SetPos(track, screen.MusicVolume); - EnableWindow(track, screen.Available); - } - - /* - ** Sound volume slider. - */ - track = GetDlgItem(window, IDC_SOUND_VOLUME); - if (track) { - SendMessage(track, OD_TRACKSILENT, 0, 0); - Slider_SetRange(track, 0, UISoundPresenterClass::VOLUME_LEVELS); - Slider_SetPos(track, screen.SoundVolume); - EnableWindow(track, screen.Available); - } - - track = GetDlgItem(window, IDC_VOICE_VOLUME); - if (track) { - SendMessage(track, OD_TRACKSILENT, 0, 0); - Slider_SetRange(track, 0, UISoundPresenterClass::VOLUME_LEVELS); - Slider_SetPos(track, screen.VoiceVolume); - EnableWindow(track, screen.Available); - } - - if (screen.HasMusic) { - - /* - ** Shuffle control. - */ - HWND button = GetDlgItem(window, IDC_SOUND_SHUFFLE); - if (button) { - Button_SetCheck(button, screen.Shuffle ? BST_CHECKED : BST_UNCHECKED); - EnableWindow(button, screen.Available); - } - - /* - ** Repeat control. - */ - button = GetDlgItem(window, IDC_SOUND_REPEAT); - if (button) { - Button_SetCheck(button, screen.Repeat ? BST_CHECKED : BST_UNCHECKED); - EnableWindow(button, screen.Available); - } - - /* - ** Add the eligible themes to the list box, in the order the screen - ** built them, and show the one that is playing. - */ - HWND list = GetDlgItem(window, IDC_SOUND_TRACKLIST); - if (list) { - ListBox_ResetContent(list); - - for (int index = 0; index < (int)screen.Tracks.size(); index++) { - int const row = ListBox_AddString(list, screen.Tracks[index].Label.c_str()); - if (row != LB_ERR) { - ListBox_SetItemData(list, row, index); - } - } - - ListBox_SetCurSel(list, screen.Selected); - ListBox_SetTopIndex(list, screen.Selected); - EnableWindow(list, screen.Available); - } - } - - DialogInitialized = true; - } - - break; - - case WM_COMMAND: - switch (LOWORD(wparam)) { - - /* - ** Toggle the shuffle button. - */ - case IDC_SOUND_SHUFFLE: - screen.Queue(UIIntent{UI_SOUND_SHUFFLE, "", Button_GetCheck((HWND)lparam) == BST_CHECKED}); - break; - - /* - ** Toggle the repeat button. - */ - case IDC_SOUND_REPEAT: - screen.Queue(UIIntent{UI_SOUND_REPEAT, "", Button_GetCheck((HWND)lparam) == BST_CHECKED}); - break; - - /* - ** Stop all themes from playing. - */ - case IDC_SOUND_STOP: - if (HIWORD(wparam) == 0) { - screen.Queue(UIIntent{UI_SOUND_STOP, "", 0}); - } - break; - - case IDOK: - if (HIWORD(wparam) == 0) { - screen.Queue(UIIntent{UI_SOUND_ACCEPT, "", 0}); - } - break; - - /* - ** Start the currently selected theme to play. - */ - case IDC_SOUND_PLAY: - if (HIWORD(wparam) == 0) { - HWND list = GetDlgItem(window, IDC_SOUND_TRACKLIST); - if (list) { - int const row = ListBox_GetCurSel(list); - if (row != LB_ERR) { - screen.Queue(UIIntent{UI_SOUND_SELECT, "", (int)ListBox_GetItemData(list, row)}); - screen.Queue(UIIntent{UI_SOUND_PLAY, "", 0}); - } - } - } - break; - } - break; - - /* - * Control volume. - */ - case WM_HSCROLL: - if (DialogInitialized) { - HWND track = (HWND)lparam; - if (track == GetDlgItem(window, IDC_MUSIC_VOLUME)) { - screen.Queue(UIIntent{UI_SOUND_MUSIC, "", Slider_GetPos(track)}); - } else if (track == GetDlgItem(window, IDC_SOUND_VOLUME)) { - screen.Queue(UIIntent{UI_SOUND_SOUND, "", Slider_GetPos(track)}); - } else if (track == GetDlgItem(window, IDC_VOICE_VOLUME)) { - screen.Queue(UIIntent{UI_SOUND_VOICE, "", Slider_GetPos(track)}); - } - } - break; - } - - return(FALSE); -} diff --git a/code/sounddlg.h b/code/sounddlg.h index 55856b1a9..cdeaf7c8b 100644 --- a/code/sounddlg.h +++ b/code/sounddlg.h @@ -43,6 +43,4 @@ class SoundControlsClass public: SoundControlsClass(void) {} void Dialog(void); - - static INT_PTR CALLBACK Sound_Option_Dialog_Func(HWND window, UINT message, WPARAM wparam, LPARAM lparam); }; From edaace9b5b0bbc357c0a8e683d693582bb93024e Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 13:22:20 +0100 Subject: [PATCH 157/179] refactor(ui): retire the OwnerDraw dialog system Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/desyncdlg.cpp | 474 +-- code/desyncdlg.h | 23 +- code/drawhelp.cpp | 816 +++++ code/drawhelp.h | 62 + code/egos.cpp | 3 +- code/globals.cpp | 1 - code/globals.h | 1 - code/grphmenu.cpp | 6 +- code/gscreen.cpp | 22 - code/gscreen.h | 1 - code/init.cpp | 409 +-- code/keyboard.cpp | 68 + code/keyboard.h | 4 + code/mapgen.cpp | 678 +--- code/mapgen.h | 7 +- code/mpscore.cpp | 3 - code/msanim.cpp | 2 +- code/msgloop.cpp | 83 - code/msgloop.h | 5 - code/netdlg2.cpp | 821 +---- code/netdlg2.h | 3 - code/netshare.cpp | 689 +--- code/netshare.h | 12 +- code/ownrdraw.cpp | 7055 --------------------------------------- code/ownrdraw.h | 509 --- code/preview.cpp | 3 +- code/progress.cpp | 71 +- code/progress.h | 16 +- code/queue.cpp | 229 +- code/restate.cpp | 2 +- code/scenario.cpp | 7 +- code/score.cpp | 5 - code/sidebar.cpp | 4 - code/srfcache.cpp | 5 +- code/ui/uikeyboard.cpp | 2 +- code/ui/uimapgen.cpp | 6 +- code/ui/uireconnect.cpp | 6 - code/ui/uishell.cpp | 52 +- code/ui/uishell.h | 2 - code/video.cpp | 6 + code/wdtprops.cpp | 1 - code/wdtsel.cpp | 6 +- code/windlg.cpp | 755 ----- code/windlg.h | 57 - code/winfix.cpp | 3 +- code/winstub.cpp | 6 - code/worlddom.cpp | 46 - 47 files changed, 1079 insertions(+), 11968 deletions(-) create mode 100644 code/drawhelp.cpp create mode 100644 code/drawhelp.h delete mode 100644 code/ownrdraw.cpp delete mode 100644 code/ownrdraw.h delete mode 100644 code/windlg.cpp delete mode 100644 code/windlg.h diff --git a/code/desyncdlg.cpp b/code/desyncdlg.cpp index 566206a21..50d689df0 100644 --- a/code/desyncdlg.cpp +++ b/code/desyncdlg.cpp @@ -30,42 +30,19 @@ #include "mpload.h" #include "netdlg.h" #include "netglobal.h" -#include "ownrdraw.h" #include "savemgr.h" #include "session.h" -#include "srfcache.h" #include "ui/uishell.h" #include "syncreport.h" #include "win.h" -#include "windlg.h" #include "winfix.h" -#include #include #include #include -namespace { - - // Column positions within the player list, in the units the lobby's lists use. - constexpr int HOST_COLUMN_X = 2; - constexpr int NAME_COLUMN_X = 20; - constexpr int STATUS_COLUMN_WIDTH = 56; - constexpr int CHAT_BACKLOG_MAX = 50; - - - // The dialog's pixel size follows the presentation layout, so the column is measured. - int Status_Column_X(HWND list) - { - RECT rect = {}; - GetClientRect(list, &rect); - return(rect.right - STATUS_COLUMN_WIDTH); - } - -} // namespace - /// /// Shows the screen and runs it until the master has decided, or this player has quit. Game @@ -76,7 +53,7 @@ DesyncDialogClass::OutcomeType DesyncDialogClass::Run(void) { DebugString("Out-of-sync dialog opening on frame %d\n", Frame); - // A raised suspension makes a nested dialog's pump service the network instead of the game. + // A raised suspension makes the runner's pump service the network instead of the game. TacticalActive = false; Session.Suspended++; @@ -86,19 +63,9 @@ DesyncDialogClass::OutcomeType DesyncDialogClass::Run(void) OutcomeType outcome = OutcomeType::Continue; - // The presentation is latched here, at screen entry, and a document that will not - // prepare drops the screen back to the legacy dialog. - bool answered = false; - if (UI_Use_Rml()) { - UIResult const answer = UI_Desync_Run(Screen); - answered = answer.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN; - } + UI_Desync_Run(Screen); UI_Desync_Close_View(); - if (!answered) { - Run_Legacy(); - } - switch (Screen.Outcome) { case UIDesyncPresenterClass::OUTCOME_LOAD: outcome = OutcomeType::Load; break; case UIDesyncPresenterClass::OUTCOME_QUIT: outcome = OutcomeType::Quit; break; @@ -117,72 +84,10 @@ DesyncDialogClass::OutcomeType DesyncDialogClass::Run(void) } -/// -/// Runs the OwnerDraw dialog against the same screen, for a build whose documents will not -/// prepare. It puts the model on its controls and queues an intent from a control. -/// -DesyncDialogClass::OutcomeType DesyncDialogClass::Run_Legacy(void) -{ - CountdownShown = false; - DrawnMessages = 0; - - Create_Dialog(); - if (Window == NULL) { - DebugString("The out-of-sync dialog could not be created; continuing\n"); - return(OutcomeType::Continue); - } - - while (!Screen.Result.has_value()) { - Call_Back(); - - Screen.Service(); - Screen.Drain(); - - Become_Host_If_Promoted(); - - if (Screen.PromptPending) { - EnableWindow(Window, FALSE); - Screen.Run_Pending(); - EnableWindow(Window, TRUE); - SetFocus(GetDlgItem(Window, IDC_DESYNC_PLAYER_LIST)); - } - - if (Screen.PlayersChanged) { - Update_Player_List(); - Screen.PlayersChanged = false; - } - if (Screen.MessagesChanged) { - Refill_Chat_List(); - Screen.MessagesChanged = false; - } - - EnableWindow(GetDlgItem(Window, IDC_DESYNC_QUIT), Screen.CanQuit ? TRUE : FALSE); - if (IsHostDialog) { - EnableWindow(GetDlgItem(Window, IDC_DESYNC_LOAD), Screen.CanLoad ? TRUE : FALSE); - EnableWindow(GetDlgItem(Window, IDC_DESYNC_CONTINUE), Screen.CanContinue ? TRUE : FALSE); - } - - Update_Countdown(); - - Host_Sleep(10); - } - - Destroy_Dialog(); - return(OutcomeType::Continue); -} - - void DesyncDialogClass::Service(void) { - if (!Is_Active()) { - return; - } - - // The RmlUi runner services the screen itself; this is the path the network maintenance - // takes while a nested dialog owns the pump. - if (Window != NULL) { - Screen.Service(); - } + // The runner services the screen itself, so the network maintenance has nothing of its + // own to do here while the screen is up. } @@ -232,374 +137,3 @@ void DesyncDialogClass::Notify_Master_Changed(void) Screen.Master_Changed(); } - - -/// -/// Creates the variant the local player gets: the decision dialog for the master, the wait -/// dialog for everyone else. -/// -void DesyncDialogClass::Create_Dialog(void) -{ - IsHostDialog = Screen.IsMaster; - int const id = IsHostDialog ? IDD_DESYNC_HOST : IDD_DESYNC_WAIT; - - Window = WS_Create_Dialog(ProgramInstance, id, MainWindow, Dialog_Proc, FALSE); - if (Window == NULL) { - return; - } - - Fit_To_Screen(); - Center_Window_Within_Window(Window); - - RECT placed; - GetWindowRect(Window, &placed); - MapWindowPoints(HWND_DESKTOP, MainWindow, (POINT *)&placed, 2); - DebugString("Out-of-sync dialog placed at %d,%d size %dx%d in a %dx%d view\n", - placed.left, placed.top, placed.right - placed.left, placed.bottom - placed.top, VideoModeWidth, VideoModeHeight); - - // The name column goes first: the list draws each row's own string in the first column added. - HWND list = GetDlgItem(Window, IDC_DESYNC_PLAYER_LIST); - if (list != NULL) { - int const status_x = Status_Column_X(list); - SendMessage(list, OD_ADDCOLUMN, status_x - NAME_COLUMN_X - 6, NAME_COLUMN_X); - SendMessage(list, OD_ADDCOLUMN, 0, HOST_COLUMN_X); - SendMessage(list, OD_ADDCOLUMN, 0, status_x); - } - Update_Player_List(); - - if (IsHostDialog) { - EnableWindow(GetDlgItem(Window, IDC_DESYNC_LOAD), Screen.CanLoad); - EnableWindow(GetDlgItem(Window, IDC_DESYNC_CONTINUE), Screen.CanContinue); - } - EnableWindow(GetDlgItem(Window, IDC_DESYNC_QUIT), Screen.CanQuit); - - Refill_Chat_List(); - - HWND edit = GetDlgItem(Window, IDC_DESYNC_CHAT_EDIT); - if (edit != NULL) { - SetWindowText(edit, Fetch_String(TXT_CHAT_HINT)); - ChatPlaceholderActive = true; - } - - CountdownShown = false; - Update_Countdown(); - - MouseCursor->Hide_Mouse(); - ShowWindow(Window, SW_SHOWNORMAL); - UpdateWindow(Window); - MouseCursor->Show_Mouse(); - - // The player list takes the focus, or the dialog would hand it to the chat box and clear the hint. - SetForegroundWindow(Window); - SetFocus(GetDlgItem(Window, IDC_DESYNC_PLAYER_LIST)); -} - - -void DesyncDialogClass::Destroy_Dialog(void) -{ - if (Window != NULL) { - WS_Destroy_Dialog(Window, 0); - Window = NULL; - } -} - - -/// -/// Takes the excess height out of the chat list when the presented dialog is taller than the -/// screen, and moves everything below the list up by the same amount. -/// -void DesyncDialogClass::Fit_To_Screen(void) -{ - RECT dialog_rect; - GetWindowRect(Window, &dialog_rect); - int const dialog_height = dialog_rect.bottom - dialog_rect.top; - if (dialog_height <= VideoModeHeight) { - return; - } - - HWND chat = GetDlgItem(Window, IDC_DESYNC_CHAT_LIST); - if (chat == NULL) { - return; - } - RECT chat_rect; - GetWindowRect(chat, &chat_rect); - int const chat_height = chat_rect.bottom - chat_rect.top; - - int const delta = std::min(dialog_height - VideoModeHeight, chat_height * 2 / 3); - SetWindowPos(chat, NULL, 0, 0, chat_rect.right - chat_rect.left, chat_height - delta, - SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); - - for (int id : {IDC_DESYNC_CHAT_EDIT, IDC_DESYNC_COUNTDOWN_TEXT, IDC_DESYNC_COUNTDOWN_BAR, - IDC_DESYNC_LOAD, IDC_DESYNC_CONTINUE, IDC_DESYNC_QUIT}) { - HWND control = GetDlgItem(Window, id); - if (control != NULL) { - RECT rect; - GetWindowRect(control, &rect); - MapWindowPoints(HWND_DESKTOP, Window, (POINT *)&rect, 1); - SetWindowPos(control, NULL, rect.left, rect.top - delta, 0, 0, - SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); - } - } - - SetWindowPos(Window, NULL, 0, 0, dialog_rect.right - dialog_rect.left, dialog_height - delta, - SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); -} - - -/// -/// Replaces the wait dialog with the decision dialog once this machine has become master, -/// unless a load is already counting down, when there is nothing left to decide. -/// -void DesyncDialogClass::Become_Host_If_Promoted(void) -{ - if (Window == NULL || IsHostDialog == Screen.IsMaster) { - return; - } - - DebugString("This machine is the new master; switching to the decision dialog\n"); - Destroy_Dialog(); - Create_Dialog(); -} - - -void DesyncDialogClass::Update_Player_List(void) -{ - if (Window == NULL) { - return; - } - - HWND list = GetDlgItem(Window, IDC_DESYNC_PLAYER_LIST); - if (list == NULL) { - return; - } - - ListBox_ResetContent(list); - - int const status_x = Status_Column_X(list); - - for (UIDesyncPresenterClass::PlayerRowType const & player : Screen.Players) { - int const row = ListBox_AddString(list, player.Name.c_str()); - if (row < 0) { - continue; - } - - if (player.IsHost) { - OwnerDraw::CellData host; - host.type = OwnerDraw::CellData::SURFACE; - host.surf = SurfaceCache.GetSurface("wolhost.pcx"); - host.hint.set(""); - SendMessage(list, OD_SETCELL, MAKEWPARAM(HOST_COLUMN_X, row), (LPARAM)&host); - } - - int text = TXT_OK; - COLORREF color = RGB(0, 200, 0); - if (player.Status == UIDesyncPresenterClass::STATUS_LEFT) { - text = TXT_SYNC_STATUS_LEFT; - color = RGB(200, 0, 0); - } else if (player.Status == UIDesyncPresenterClass::STATUS_OUT_OF_SYNC) { - text = TXT_SYNC_STATUS_OUT; - color = RGB(200, 200, 0); - } - - OwnerDraw::CellData status; - status.type = OwnerDraw::CellData::TEXT; - status.string.set(Fetch_String(text)); - status.hint.set(""); - status.color = color; - SendMessage(list, OD_SETCELL, MAKEWPARAM(status_x, row), (LPARAM)&status); - } - - InvalidateRect(list, NULL, FALSE); -} - - -void DesyncDialogClass::Refill_Chat_List(void) -{ - if (Window == NULL) { - return; - } - - HWND list = GetDlgItem(Window, IDC_DESYNC_CHAT_LIST); - if (list == NULL) { - return; - } - - ListBox_ResetContent(list); - for (std::string const & line : Screen.Messages) { - ListBox_AddString(list, line.c_str()); - } - ListBox_SetTopIndex(list, ListBox_GetCount(list) - 1); -} - - -void DesyncDialogClass::Send_Chat(void) -{ - if (Window == NULL || ChatPlaceholderActive) { - return; - } - - HWND edit = GetDlgItem(Window, IDC_DESYNC_CHAT_EDIT); - if (edit == NULL) { - return; - } - - char buffer[MAX_MESSAGE_LENGTH]; - GetWindowText(edit, buffer, sizeof(buffer)); - if (buffer[0] == '\0') { - return; - } - - SetWindowText(edit, ""); - SetFocus(edit); - - Screen.Queue(UIIntent{UI_DESYNC_SAY, buffer, 0}); -} - - -void DesyncDialogClass::On_Chat_Edit_Focus(bool gained) -{ - if (Window == NULL) { - return; - } - - HWND edit = GetDlgItem(Window, IDC_DESYNC_CHAT_EDIT); - if (edit == NULL) { - return; - } - - if (gained && ChatPlaceholderActive) { - SetWindowText(edit, ""); - ChatPlaceholderActive = false; - } else if (!gained && GetWindowTextLength(edit) == 0) { - SetWindowText(edit, Fetch_String(TXT_CHAT_HINT)); - ChatPlaceholderActive = true; - } -} - - -/// -/// Shows the countdown once a load is scheduled and keeps its text and bar current. -/// -void DesyncDialogClass::Update_Countdown(void) -{ - if (Window == NULL || !Screen.CountdownActive) { - return; - } - - if (!CountdownShown) { - CountdownShown = true; - ShowWindow(GetDlgItem(Window, IDC_DESYNC_COUNTDOWN_TEXT), SW_SHOW); - ShowWindow(GetDlgItem(Window, IDC_DESYNC_COUNTDOWN_BAR), SW_SHOW); - } - - SetDlgItemText(Window, IDC_DESYNC_COUNTDOWN_TEXT, Screen.CountdownText.c_str()); - InvalidateRect(Window, NULL, FALSE); -} - - -/// -/// Draws the countdown bar over its placeholder the way the reconnect dialog draws its sync -/// bars: shrinking, and green to yellow to red as the load nears. -/// -void DesyncDialogClass::Draw_Countdown_Bar(HWND window) -{ - if (!CountdownShown || DesyncDialog.Screen.CountdownTotal <= 0) { - return; - } - - HWND bar = GetDlgItem(window, IDC_DESYNC_COUNTDOWN_BAR); - if (bar == NULL) { - return; - } - - RECT winrect; - Get_Display_Rect(bar, &winrect); - - Rect bar_rect; - bar_rect.X = winrect.left; - bar_rect.Y = winrect.top; - bar_rect.Width = winrect.right - winrect.left; - bar_rect.Height = winrect.bottom - winrect.top; - - int const total = DesyncDialog.Screen.CountdownTotal; - int const remaining = std::clamp(DesyncDialog.Screen.CountdownRemaining, 0, total); - int const elapsed = total - remaining; - - unsigned short color = DSurface::Build_Hicolor_Pixel(0, 200, 0); - if (elapsed > total * 2 / 5) { - color = DSurface::Build_Hicolor_Pixel(200, 200, 0); - if (elapsed > total * 4 / 5) { - color = DSurface::Build_Hicolor_Pixel(200, 0, 0); - } - } - - bar_rect.Width = std::max(6, bar_rect.Width * remaining / total); - - AlternateSurface->Fill_Rect(AlternateSurface->Get_Rect(), bar_rect, color); -} - - -INT_PTR CALLBACK DesyncDialogClass::Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - switch (message) { - case WM_INITDIALOG: - OwnerDraw::Subclass_Dialog(window, 0); - break; - - case WM_DRAWITEM: - OwnerDraw::Draw_Item((DRAWITEMSTRUCT *)lparam); - return(TRUE); - - case WM_PAINT: - OwnerDraw::Draw_Dialog_Back(window); - DesyncDialog.Draw_Countdown_Bar(window); - ValidateRect(window, NULL); - break; - - case WM_MOVING: - return(On_WM_MOVING(window, wparam, lparam)); - - case WM_CTLCOLORMSGBOX: - case WM_CTLCOLOREDIT: - case WM_CTLCOLORLISTBOX: - case WM_CTLCOLORBTN: - case WM_CTLCOLORDLG: - case WM_CTLCOLORSCROLLBAR: - case WM_CTLCOLORSTATIC: - return((INT_PTR)GetStockObject(BLACK_BRUSH)); - - case WM_ERASEBKGND: - return(TRUE); - - case WM_COMMAND: - switch (LOWORD(wparam)) { - case IDC_DESYNC_LOAD: - DesyncDialog.Screen.Queue(UIIntent{UI_DESYNC_LOAD, "", 0}); - break; - - case IDC_DESYNC_CONTINUE: - DesyncDialog.Screen.Queue(UIIntent{UI_DESYNC_CONTINUE, "", 0}); - break; - - case IDC_DESYNC_QUIT: - DesyncDialog.Screen.Queue(UIIntent{UI_DESYNC_QUIT, "", 0}); - break; - - // Enter in the chat box arrives as IDOK, since the dialog has no default button. - case IDOK: - DesyncDialog.Send_Chat(); - break; - - case IDC_DESYNC_CHAT_EDIT: - if (HIWORD(wparam) == EN_SETFOCUS) { - DesyncDialog.On_Chat_Edit_Focus(true); - } else if (HIWORD(wparam) == EN_KILLFOCUS) { - DesyncDialog.On_Chat_Edit_Focus(false); - } - break; - } - break; - } - - return(FALSE); -} diff --git a/code/desyncdlg.h b/code/desyncdlg.h index cb761282c..58c6c1749 100644 --- a/code/desyncdlg.h +++ b/code/desyncdlg.h @@ -17,7 +17,7 @@ #include /* - * The dialog shown when a network game goes out of sync. The master chooses to load a saved + * The screen shown when a network game goes out of sync. The master chooses to load a saved * game, to continue without the players out of sync, or to quit; everyone else waits. Both * variants list the players with their state and carry a chat box. Game logic is halted while * it is up, and the network is kept alive with heartbeats. @@ -48,29 +48,10 @@ class DesyncDialogClass void Notify_Master_Changed(void); private: - OutcomeType Run_Legacy(void); - void Create_Dialog(void); - void Destroy_Dialog(void); - void Fit_To_Screen(void); - void Become_Host_If_Promoted(void); - void Update_Player_List(void); - void Refill_Chat_List(void); - void Send_Chat(void); - void On_Chat_Edit_Focus(bool gained); - void Update_Countdown(void); - void Draw_Countdown_Bar(HWND window); - static INT_PTR CALLBACK Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); - - // The screen's whole behavior. The dialog reads this model onto its controls and - // queues an intent from a control; it decides nothing itself. + // The screen's whole behavior. UIDesyncPresenterClass Screen; - HWND Window = NULL; bool IsRunning = false; - bool IsHostDialog = false; - bool ChatPlaceholderActive = false; - bool CountdownShown = false; - std::size_t DrawnMessages = 0; }; extern DesyncDialogClass DesyncDialog; diff --git a/code/drawhelp.cpp b/code/drawhelp.cpp new file mode 100644 index 000000000..4a77c574b --- /dev/null +++ b/code/drawhelp.cpp @@ -0,0 +1,816 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#include "always.h" + +#include "drawhelp.h" + +#include "_surface.h" +#include "_xmouse.h" +#include "arraylist.h" +#include "dbgprint.h" +#include "dict.h" +#include "dsurface.h" +#include "hsv.h" +#include "misc.h" +#include "rgb.h" +#include "srfcache.h" +#include "utf8.h" +#include "wstring.h" + +#include + + +extern unsigned int Wstring_Hash(Wstring & string); + + +COLORREF ODColorText = RGB(112, 255, 0); + +unsigned short ODRComponentMask; +unsigned short ODGComponentMask; +unsigned short ODBComponentMask; + + +/* + * Measurements of one of the remap fonts, cached by ODGetFontMetrics. + */ +struct FontMetrics { + int charWidths[256]; /// inked width of each character, indexed by character code + int glyphWidth; /// width of the inked part of a glyph cell + int glyphHeight; /// height of the inked part of a glyph cell + int topMargin; /// blank rows above each row of glyphs + int leftMargin; /// blank columns before each glyph +}; + + +static bool ODGetFontMetrics(char const * font_name, FontMetrics * metrics); +static void ODDrawCharRemap(Surface & dst_surf, const char * text, int max_chars, Rect const & rect, char const * font_name, COLORREF color, char flags, int char_spacing); +static int ODColorToHiColor(COLORREF color); + + +static unsigned char OD_Glyph(char32_t code) +{ + if (code < ' ') { + return((unsigned char)code); + } + int index = UTF8::Windows_1252_Glyph(code); + return((unsigned char)(index < 0 ? '?' : index)); +} + + +/// +/// Sets up the color component masks used for blending. +/// The masks depend on how the display surface packs its pixels, so this routine cannot +/// run until the video mode is known. +/// +static void ODInitMasks(void) +{ + ODRComponentMask = 255; + ODRComponentMask = ODRComponentMask >> DSurface::Get_Red_Left(); + ODRComponentMask <<= DSurface::Get_Red_Right(); + + ODGComponentMask = 255; + ODGComponentMask = ODGComponentMask >> DSurface::Get_Green_Left(); + ODGComponentMask <<= DSurface::Get_Green_Right(); + + ODBComponentMask = 255; + ODBComponentMask = ODBComponentMask >> DSurface::Get_Blue_Left(); + ODBComponentMask <<= DSurface::Get_Blue_Right(); +} + + +/// +/// Converts a Windows color reference into a display pixel. +/// The dialog colors are all written as RGB() values, so they have to be packed into the +/// pixel layout of the display surface before anything can be drawn with them. +/// +/// Returns with the packed pixel value. An all-ones color is passed through +/// unchanged. +static int ODColorToHiColor(COLORREF color) +{ + if (color == 0xFFFFFFFF) { + return(0xFFFFFFFF); + } + /// Do not replace the union with direct byte extraction. It improves several callers and + /// breaks ProgressBarCtrlProc, which is otherwise exact -- and an exact caller outranks the + /// partial ones. + union { + struct { + unsigned int red : 8; + unsigned int green : 8; + unsigned int blue : 8; + unsigned int a : 8; + }; + int v; + } c; + + c.v = color; + + return(DSurface::Build_Hicolor_Pixel(c.red, c.green, c.blue)); +} + + +/// +/// Draws word wrapped text with a remapped bitmap font. +/// This routine breaks the text into lines that will fit the rectangle -- honoring the +/// newlines already in it and breaking at a space wherever one can be found -- and hands +/// each line in turn to ODDrawCharRemap. +/// +/// The base name of the font sheets to draw with. +/// The OD_DRAW_CHAR alignment flags to lay each line out with. +/// The extra spacing to insert between characters. +int OD_Draw_Text_Remap(Surface & surface, const char * text, Rect const & rect, char const * name, COLORREF color, int flags, int char_spacing) +{ + int line_len = strlen(text); + char const * line_ptr = text; + Rect draw_rect = rect; + + FontMetrics data; + if (!ODGetFontMetrics(name, &data)) { + return(0); + } + + while (line_len) { + if (line_ptr) { + char const * nl_ptr = strchr(line_ptr, '\n'); + if (nl_ptr) { + int nl_len = (int)(nl_ptr - line_ptr) + 1; + if (line_len >= nl_len) { + line_len = nl_len; + } + } + } + + if ((unsigned char)*line_ptr <= ' ') { + ++line_ptr; + if (--line_len == 0) { + return(0); + } + } + + int text_width = 0; + for (char const * cursor = text; cursor - text < line_len; ) { + text_width += char_spacing + data.charWidths[OD_Glyph(UTF8::Decode(cursor))]; + } + + if (text_width > draw_rect.Width - draw_rect.X) { + int fallback = (int)UTF8::Boundary_Before(line_ptr, line_len - 1); + int cut = line_len - 1; + + flags &= ~4; + + while (cut > 0) { + if ((unsigned char)line_ptr[cut] <= ' ') { + break; + } + --cut; + } + if (cut > 0) { + line_len = cut; + if (cut != -1) { + continue; + } + } + + line_len = fallback; + } else { + ODDrawCharRemap(surface, line_ptr, line_len, draw_rect, name, color, (char)flags, char_spacing); + line_ptr += line_len; + draw_rect.Y += data.glyphHeight; + line_len = strlen(line_ptr); + } + } + + return(0); +} + + +/// +/// Determines how strongly a hue should be remapped. +/// The font remapper uses this to pull its hue shift back around the primary colors, so +/// that text tinted near one of them does not swing away from the color asked for. +/// +/// The hue to compute the factor for. +/// Returns with the scale factor; the nearer the hue sits to a primary, the smaller +/// it gets. +static float ODCalcTextRemapFactor(int hue) +{ + float val = 1.0f; + + int arr[3]; + arr[0] = 43; + arr[1] = 128; + arr[2] = 213; + + for (int i = 0; i < 3; i++) { + int value = arr[i]; + + if (hue > value - 16 && hue <= value) { + val = float(value - hue); + val *= (1.0f / 16); + val *= (60.0f / 100); + val += (40.0f / 100); + } else if (hue > value && hue <= value + 16) { + val = float(hue - value); + val *= (1.0f / 16); + val *= (60.0f / 100); + val += (40.0f / 100); + } + } + return(val); +} + + +/// +/// Draws a line of text with a remapped bitmap font. +/// This routine builds a table that shifts the font's own palette toward the color asked +/// for and then alpha blends each character onto the destination surface. It is the low +/// level draw that all of the owner-draw remapped text ends up going through. +/// +/// The maximum number of characters of the text to draw. +/// The rectangle to align the text within. +/// The base name of the font sheets to draw with. +/// The OD_DRAW_CHAR alignment flags to lay the text out with. +/// The extra spacing to insert between characters. +static void ODDrawCharRemap(Surface & dst_surf, const char *text, int max_chars, Rect const & rect, char const *font_name, COLORREF color, char flags, int char_spacing) +{ + int i; + Rect draw_rect = rect; + + char name_i[64]; + strcpy(name_i, font_name); + strcat(name_i, "i.pcx"); + + char palette[768]; + Surface *sheet_i = SurfaceCache.GetSurface(name_i, palette); + if (sheet_i == NULL) { + return; + } + + char name_a[64]; + strcpy(name_a, font_name); + strcat(name_a, "a.pcx"); + + Surface *sheet_a = SurfaceCache.GetSurface(name_a, NULL); + if (sheet_a == NULL) { + return; + } + + RGBClass remap_rgb((unsigned char)color, (unsigned char)(color >> 8), (unsigned char)(color >> 16)); + HSVClass remap_hsv = remap_rgb; + RGBClass pal_rgb; + HSVClass out_hsv; + + int hue = remap_hsv.Get_Hue(); + + int end = int(hue + 15.0); + float min_factor = 1.0f; + for (i = int(hue - 15.0); i <= end; ++i) { + float factor = ODCalcTextRemapFactor(i); + if (factor < min_factor) { + min_factor = factor; + } + } + + unsigned char sat = (unsigned char)remap_hsv.Get_Saturation(); + unsigned char val = (unsigned char)remap_hsv.Get_Value(); + + unsigned short remap_table[256]; + float hue_float = (float)hue; + unsigned char *pal = (unsigned char *)&palette; + for (i = 0; i < 256; ++i) { + pal_rgb.Set_Red(pal[0]); + pal_rgb.Set_Green(pal[1]); + pal_rgb.Set_Blue(pal[2]); + HSVClass pal_hsv = pal_rgb; + + /* + * Start from the palette entry's HSV and adjust each channel. The + * wholesale copy is fully overwritten below. + */ + out_hsv = pal_hsv; + out_hsv.Set_Hue((unsigned char)(int)(hue_float - (int)(68.0f - pal_hsv.Get_Hue()) * min_factor)); + out_hsv.Set_Saturation((unsigned char)((sat * out_hsv.Get_Saturation()) >> 8)); + out_hsv.Set_Value((unsigned char)((val * out_hsv.Get_Value()) >> 8)); + + RGBClass out_rgb = out_hsv; + pal_rgb = out_rgb; + + int packed = (((out_rgb.Get_Blue() << 8) | out_rgb.Get_Green()) << 8) | out_rgb.Get_Red(); + remap_table[i] = (unsigned short)ODColorToHiColor(packed); + pal += 3; + } + + FontMetrics font_data; + if (!ODGetFontMetrics(font_name, &font_data)) { + return; + } + + if ((int)strlen(text) < max_chars) { + max_chars = strlen(text); + } + + int total_width = 0; + for (char const * cursor = text; cursor - text < max_chars; ) { + total_width += font_data.charWidths[OD_Glyph(UTF8::Decode(cursor))] + char_spacing; + } + + if ((flags & OD_DRAW_CHAR_FLAG_HORIZONTAL_CENTER) != 0) { + draw_rect.X += (draw_rect.Width - draw_rect.X - total_width) / 2; + } else if ((flags & OD_DRAW_CHAR_ALIGN_FLAG_RIGHT) != 0) { + draw_rect.X = draw_rect.Width - total_width - 1; + } + + if ((flags & OD_DRAW_CHAR_FLAG_VERTICAL_CENTER) != 0) { + draw_rect.Y = draw_rect.Y + (draw_rect.Height - font_data.glyphHeight - draw_rect.Y) / 2; + } + + draw_rect.Y -= font_data.topMargin; + --draw_rect.X; + + unsigned char *src_i = (unsigned char *)sheet_i->Lock(); + unsigned char *src_a = (unsigned char *)sheet_a->Lock(); + unsigned char *dst = (unsigned char *)dst_surf.Lock(); + + if (src_i != NULL && src_a != NULL && dst != NULL) { + int cell_w = font_data.glyphWidth + font_data.leftMargin; + int cell_h = font_data.glyphHeight + font_data.topMargin; + int chars_per_row = sheet_i->Get_Width() / (font_data.glyphWidth + font_data.leftMargin); + int dst_stride = dst_surf.Stride() / 2; + int src_stride = sheet_i->Stride(); + + int x = draw_rect.X; + for (char const * cursor = text; cursor - text < max_chars; ) { + + unsigned char index = OD_Glyph(UTF8::Decode(cursor)); + if (index <= ' ') { + x += font_data.charWidths[index] + char_spacing; + } else { + int glyph = index + 1; + int src_x = (glyph % chars_per_row) * cell_w; + int src_y = (glyph / chars_per_row) * cell_h; + + int src_y_end = src_y + cell_h; + int src_delta = src_i - src_a; + unsigned char *alpha_col = src_a + (src_y * src_stride + src_x); + unsigned char *dst_col = dst + 2 * (dst_stride * draw_rect.Y + x); + + for (int sx = src_x; sx < src_x + cell_w; ++sx) { + if (src_y < src_y_end) { + unsigned short *dst_px = (unsigned short *)dst_col; + unsigned char *alpha_px = alpha_col; + + int sy = src_y_end - src_y; + do { + unsigned char alpha = *alpha_px; + if (alpha != 0) { + unsigned char index = alpha_px[src_delta]; + *dst_px = OD_Blend_Color(*dst_px, remap_table[index], alpha); + } + + dst_px += dst_stride; + alpha_px += src_stride; + --sy; + } while (sy != 0); + } + + ++alpha_col; + dst_col += 2; + } + + x += font_data.charWidths[index] + char_spacing; + } + } + } + + if (&dst_surf != NULL) { + dst_surf.Unlock(); + } + sheet_a->Unlock(); + sheet_i->Unlock(); +} + + +/// +/// Fetches the metrics of a remappable bitmap font. +/// This routine measures the font's sheet -- the margins, the size of a character cell and +/// the inked width of every character -- so that the remap text routines know how to lay +/// characters out. Measuring is expensive, so the result is kept by font name. +/// +/// The base name of the font, without the sheet suffix. +/// Buffer to fill in with the measurements. +/// bool; Were the metrics available? +static bool ODGetFontMetrics(char const * font_name, FontMetrics * metrics) +{ + static Dictionary metricsDict(Wstring_Hash); + + char buf[64]; + strcpy(buf, font_name); + strcat(buf, "a.pcx"); + + Wstring name; + name = (char *)font_name; + name.toLower(); + + FontMetrics * found = NULL; + if (metricsDict.getPointer(name, &found)) { + if (metrics != NULL) { + *metrics = *found; + return(true); + } + } + + DebugString("TS: Computing font metrics....\n"); + + FontMetrics temp; + memset(&temp, 0, sizeof(temp)); + + char palette[768]; + Surface * surf = SurfaceCache.GetSurface(buf, palette); + if (surf == NULL) { + return(false); + } + + char * basePtr = (char *)surf->Lock(); + int stride = surf->Stride(); + + /* + * ---------------------------------------------------------------- + * Vertical metrics: topMargin = blank rows above the glyph row, + * glyphHeight = inked rows (probed at column 4). + * ---------------------------------------------------------------- + */ + temp.topMargin = 0; + while (temp.topMargin < surf->Get_Height()) { + if (basePtr[stride * temp.topMargin + 4] != 0) break; + ++temp.topMargin; + } + int y = temp.topMargin; + while (y < surf->Get_Height()) { + if (basePtr[stride * y + 4] == 0) break; + ++y; + ++temp.glyphHeight; + } + + /* + * ---------------------------------------------------------------- + * Horizontal metrics: leftMargin = blank columns before the glyphs, + * glyphWidth = inked columns (probed along row 'top'). + * ---------------------------------------------------------------- + */ + temp.leftMargin = 0; + while (temp.leftMargin < surf->Get_Width()) { + if (basePtr[stride * temp.topMargin + temp.leftMargin] != 0) break; + ++temp.leftMargin; + } + int left = temp.leftMargin; + + int x; + x = left; + while (x < surf->Get_Width()) { + if (basePtr[stride * temp.topMargin + x] == 0) break; + ++x; + ++temp.glyphWidth; + } + + /* + * ---------------------------------------------------------------- + * Compute per-character metrics + * ---------------------------------------------------------------- + */ + int width = surf->Get_Width(); + int charsPerRow = width / (left + temp.glyphWidth); + for (int ch = 0; ch < 256; ++ch) { + + int left = temp.leftMargin; + int fontHeight = temp.glyphHeight; + int top = temp.topMargin; + int fontWidth = temp.glyphWidth; + + int glyphY = top + (fontHeight + top) * ((ch + 1) / charsPerRow); + int glyphX = left + (left + fontWidth) * ((ch + 1) % charsPerRow); + + int first = -1; + int last = 0; + + for (int x = glyphX; x < glyphX + fontWidth; ++x) { + int nonEmpty = 0; + for (int y = glyphY; y < glyphY + fontHeight; ++y) { + if (basePtr[stride * y + x] != 0) ++nonEmpty; + } + if (nonEmpty) { + last = x; + if (first == -1) first = x; + } + } + + if (first != -1) { + temp.charWidths[ch] = (last - first + 1); + } else { + temp.charWidths[ch] = (fontWidth / 3 + 1); + } + } + + surf->Unlock(); + + /* + * ---------------------------------------------------------------- + * Store result in caller's buffer + * ---------------------------------------------------------------- + */ + memcpy(metrics, &temp, sizeof(FontMetrics)); + + metricsDict.add(name, temp); + + return(true); +} + + +/// +/// Draws a line of text onto a surface. +/// This routine borrows a device context from the surface, unlocking it as often as it +/// must beforehand, and lets Windows put the text out aligned within the rectangle given. +/// Nothing is drawn while the game does not hold the focus. +/// +/// The number of characters of the text to draw. +/// The surface to draw upon, or NULL to draw on the alternate +/// surface. +/// Returns with the pixel width of the text. +int OD_Draw_Text(COLORREF color, HFONT font, Rect const & rect, const char * text, int len, int x_alignment, int y_alignment, Surface * surface) +{ + if (!GameInFocus && !WindowedMode) { + return(0); + } + + DSurface *destsurf = (DSurface *)surface; + if (!surface) { + destsurf = (DSurface *)AlternateSurface; + } + + int lock_count = 0; + while (destsurf->Is_Locked()) { + lock_count++; + destsurf->Unlock(); + } + + SIZE text_size; + + HDC hDC = destsurf->GetDC(); + if (hDC) { + + if (font) { + SelectObject(hDC, font); + } + + SetTextColor(hDC, color); + SetBkMode(hDC, TRANSPARENT); + + GetTextExtentPoint32(hDC, text, len, &text_size); + + int x_offset = rect.X; + int y_offset = rect.Y; + + if (x_alignment == OD_TEXT_ALIGN_MIN) { + x_offset += (rect.Width - text_size.cx + 1) / 2; + } else if (x_alignment == OD_TEXT_ALIGN_CENTER) { + x_offset += (text_size.cx + 1) / -2; + } else if (x_alignment == OD_TEXT_ALIGN_MAX) { + x_offset += -1 - text_size.cx; + } + + if (y_alignment == OD_TEXT_ALIGN_MIN) { + y_offset += (rect.Height - text_size.cy + 1) / 2; + } else if (y_alignment == OD_TEXT_ALIGN_CENTER) { + y_offset += (text_size.cy + 1) / -2; + } else if (y_alignment == OD_TEXT_ALIGN_MAX) { + y_offset += -1 - text_size.cy; + } + + TextOut(hDC, x_offset, y_offset, text, len); + destsurf->ReleaseDC(hDC); + } else { + text_size.cx = 0; + } + + while (lock_count) { + destsurf->Lock(); + lock_count--; + } + + return(text_size.cx); +} + + + +/// +/// Fetches a window's rectangle relative to the main game window. +/// The dialog layout code works in the main window's client space rather than in screen +/// coordinates, so it uses this routine in place of GetWindowRect. +/// +/// Receives the window rectangle, offset into the main window's +/// client area. +/// bool; Was the window rectangle available? +BOOL Get_Display_Rect(HWND window, LPRECT rect) +{ + RECT client; + BOOL res = GetWindowRect(window, rect); + if (!res) { + return(res); + } + GetClientRect(MainWindow, &client); + ClientToScreen(MainWindow, (LPPOINT)&client); + rect->left -= client.left; + rect->right -= client.left; + rect->top -= client.top; + rect->bottom -= client.top; + return(res); +} + + +struct EzFont { + char FaceName[128]; + int DeciPtWidth; + int DeciPtHeight; + int Attributes; + HFONT FontHandle; +}; + +static ArrayList g_EzFonts; + + +/// derived from MSDN "Moving Your Game to Windows, Part III" ttfont.cpp + +#define EZ_ATTR_BOLD 1 +#define EZ_ATTR_ITALIC 2 +#define EZ_ATTR_UNDERLINE 4 +#define EZ_ATTR_STRIKEOUT 8 + +static HFONT Ez_Create_Font(HDC hdc, const char * face_name, int decipt_width, int decipt_height, int attributes); + + +/// +/// Fetches a font of the typeface and point size requested. +/// This routine keeps every font it has built, so repeated requests for the same +/// description hand back the same handle rather than burning another GDI object. +/// The dialog drawing code calls this routine wherever it needs a font. +/// +/// The device context to build the font for. If this is NULL, the +/// font is only looked up and never created. +/// The character width in tenths of a point. +/// The character height in tenths of a point. +/// Bit flags of the EZ_ATTR_ style attributes to apply. +/// Returns with a handle to the font, or NULL if it was neither cached nor +/// able to be created. +/// The returned handle stays owned by the font cache. Do not delete it. +HFONT WS_Get_Font(HDC hdc, const char * face_name, int decipt_width, int decipt_height, int attributes) +{ + EzFont font; + + for (int index = 0; index < g_EzFonts.length(); index++) { + g_EzFonts.get(font, index); + if (!strcmp(font.FaceName, face_name) && font.DeciPtWidth == decipt_width && font.DeciPtHeight == decipt_height && font.Attributes == attributes) { + return(font.FontHandle); + } + } + + if (hdc == NULL) { + return(NULL); + } + + HFONT hFont = Ez_Create_Font(hdc, face_name, decipt_width, decipt_height, attributes); + + if (hFont == NULL) { + return(NULL); + } + + strcpy(font.FaceName, face_name); + font.DeciPtWidth = decipt_width; + font.DeciPtHeight = decipt_height; + font.Attributes = attributes; + font.FontHandle = hFont; + + if (g_EzFonts.addTail(font)) { + return(hFont); + } + + return(NULL); +} + + + +/// +/// Creates a font of the typeface and point size requested. +/// This routine maps the requested decipoint dimensions through the device context's +/// current transform, so the font it builds matches the coordinate space the caller +/// draws in. Use WS_Get_Font in preference to this routine -- that one caches its fonts. +/// +/// The device context the font is to be built for. +/// The character width in tenths of a point. Zero lets the +/// typeface choose its own aspect. +/// The character height in tenths of a point. +/// Bit flags of the EZ_ATTR_ style attributes to apply. +/// Returns with a handle to the font created, or NULL if it could not be +/// created. +/// The caller takes ownership of the font handle. +static HFONT Ez_Create_Font(HDC hdc, const char * face_name, int decipt_width, + int decipt_height, int attributes) +{ + HFONT hFont ; + LOGFONT lf ; + POINT pt ; + TEXTMETRIC tm ; + + SaveDC (hdc) ; + + SetGraphicsMode (hdc, GM_ADVANCED) ; + ModifyWorldTransform (hdc, NULL, MWT_IDENTITY) ; + SetViewportOrgEx (hdc, 0, 0, NULL) ; + SetWindowOrgEx (hdc, 0, 0, NULL) ; + + pt.x = decipt_width ; + pt.y = decipt_height ; + + DPtoLP (hdc, &pt, 1) ; + + lf.lfHeight = -pt.y ; + lf.lfWidth = 0 ; + lf.lfEscapement = 0 ; + lf.lfOrientation = 0 ; + lf.lfWeight = attributes & EZ_ATTR_BOLD ? 700 : 0 ; + lf.lfItalic = attributes & EZ_ATTR_ITALIC ? 1 : 0 ; + lf.lfUnderline = attributes & EZ_ATTR_UNDERLINE ? 1 : 0 ; + lf.lfStrikeOut = attributes & EZ_ATTR_STRIKEOUT ? 1 : 0 ; + lf.lfCharSet = ANSI_CHARSET ; + lf.lfOutPrecision = 0 ; + lf.lfClipPrecision = 0 ; + lf.lfQuality = 0 ; + lf.lfPitchAndFamily = 0 ; + + strcpy (lf.lfFaceName, face_name) ; + + hFont = CreateFontIndirect (&lf) ; + + if (decipt_width != 0) { + hFont = (HFONT) SelectObject (hdc, hFont) ; + GetTextMetrics (hdc, &tm) ; + DeleteObject (SelectObject (hdc, hFont)) ; + lf.lfWidth = (int) (tm.tmAveCharWidth * + fabs (pt.x) / fabs (pt.y) + 0.5); + hFont = CreateFontIndirect (&lf) ; + } + + RestoreDC (hdc, -1); + return(hFont); +} + + +static int _pointer_depth; + + +/// +/// Hands the mouse pointer to the host while a screen of its own is shown. +/// With the game's mouse released, WM_SETCURSOR falls through to the window class and the +/// host draws an arrow. A front end has no game pointer of its own, so without this a +/// screen shows none. +/// +/// Each call must be matched by a call to Recapture_Pointer. +void Release_Pointer_To_Host(void) +{ + if (MouseCursor != nullptr && MouseCursor->Is_Captured()) { + MouseCursor->Release_Mouse(); + } + + _pointer_depth++; +} + + +/// +/// Takes the pointer back once the last screen holding it has gone. +/// +void Recapture_Pointer(void) +{ + if (_pointer_depth > 0) { + _pointer_depth--; + } + + if (_pointer_depth == 0 && MouseCursor != nullptr && !MouseCursor->Is_Captured()) { + MouseCursor->Capture_Mouse(); + } +} + + +/// +/// Builds the color masks the blending helpers paint with. +/// The masks depend on how the display surface packs its pixels, so the video mode has to +/// be up before this runs. +/// +void Prepare_Draw_Resources(void) +{ + ODInitMasks(); +} diff --git a/code/drawhelp.h b/code/drawhelp.h new file mode 100644 index 000000000..f19b1973d --- /dev/null +++ b/code/drawhelp.h @@ -0,0 +1,62 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +#pragma once + +#include "surface.h" +#include "win.h" + +/* + * Drawing and window helpers shared by the screens that draw into the game's own + * surfaces. The OD_ and WS_ names are inherited from the owner-draw dialogs these + * routines were first written for; nothing here has anything to do with a dialog. + */ + +#define OD_TEXT_ALIGN_MIN 1 +#define OD_TEXT_ALIGN_CENTER 2 +#define OD_TEXT_ALIGN_MAX 3 + +/// Flags for OD_Draw_Text_Remap. +#define OD_DRAW_CHAR_FLAG_HORIZONTAL_CENTER 1 +#define OD_DRAW_CHAR_ALIGN_FLAG_RIGHT 2 +#define OD_DRAW_CHAR_FLAG_VERTICAL_CENTER 4 + +int OD_Draw_Text_Remap(Surface & surface, const char * string, Rect const & rect, char const * name, COLORREF color, int flags, int char_spacing); +int OD_Draw_Text(COLORREF color, HFONT font, Rect const & rect, const char * text, int len, int x_alignment, int y_alignment, Surface * surface); + +HFONT WS_Get_Font(HDC hdc, const char * face_name, int decipt_width, int decipt_height, int attributes); + +BOOL Get_Display_Rect(HWND window, LPRECT rect); + +void Prepare_Draw_Resources(void); + +void Release_Pointer_To_Host(void); +void Recapture_Pointer(void); + +extern COLORREF ODColorText; + +extern unsigned short ODRComponentMask; +extern unsigned short ODGComponentMask; +extern unsigned short ODBComponentMask; + + +/// +/// Blends a color over a display pixel. +/// +/// How much of the color to mix in, from 0 to 255. +inline unsigned short OD_Blend_Color(unsigned short pixel, unsigned short color, unsigned char alpha) +{ + unsigned blend_color_alpha = alpha; + unsigned blend_pixel_alpha = 255 - alpha; + + unsigned short r = ((((pixel & ODRComponentMask) * blend_pixel_alpha) + ((color & ODRComponentMask) * blend_color_alpha)) >> 8) & ODRComponentMask; + unsigned short g = ((((pixel & ODGComponentMask) * blend_pixel_alpha) + ((color & ODGComponentMask) * blend_color_alpha)) >> 8) & ODGComponentMask; + unsigned short b = (((pixel & ODBComponentMask) * blend_pixel_alpha) + ((color & ODBComponentMask) * blend_color_alpha)) >> 8; + return((unsigned short)(r | g | b)); +} diff --git a/code/egos.cpp b/code/egos.cpp index 8c00d0db5..9746ae5e6 100644 --- a/code/egos.cpp +++ b/code/egos.cpp @@ -56,12 +56,11 @@ #include "gscreen.h" #include "language/language.h" #include "misc.h" -#include "ownrdraw.h" +#include "drawhelp.h" #include "scheme.h" #include "theme.h" #include "utf8.h" #include "vector.h" -#include "windlg.h" #include "color.hh" #include "dialog.hh" diff --git a/code/globals.cpp b/code/globals.cpp index 1c61a8f0e..d1e88541f 100644 --- a/code/globals.cpp +++ b/code/globals.cpp @@ -251,7 +251,6 @@ bool AllowVoice = true; int Frame = 0; -int _dialog_count = 0; /*************************************************************************** diff --git a/code/globals.h b/code/globals.h index 13660c365..4e301e141 100644 --- a/code/globals.h +++ b/code/globals.h @@ -242,7 +242,6 @@ extern int NewMaxAheadFrame2; extern bool VisceroidsAsSnoBees; extern bool Just4Fun; -extern int _dialog_count; extern int Seed; extern int CustomSeed; extern bool IgnoreInput; diff --git a/code/grphmenu.cpp b/code/grphmenu.cpp index 9f814fbf9..ba2238e4b 100644 --- a/code/grphmenu.cpp +++ b/code/grphmenu.cpp @@ -19,7 +19,7 @@ #include "ini.h" #include "keyboard.h" #include "msanim.h" -#include "ownrdraw.h" +#include "drawhelp.h" #include "theme.h" GraphicMenu * _Graphic_Menu(INIClass const & ini, const char * name); @@ -164,7 +164,7 @@ int GraphicMenu::Presentation(void) { Theme.Play_Song(Theme.From_Name(ThemeName.Peek())); - OwnerDraw::Capture_Mouse(); + Release_Pointer_To_Host(); HiddenSurface->Fill(0); AlternateSurface->Fill(0); @@ -224,7 +224,7 @@ int GraphicMenu::Presentation(void) item->Action(&Engine); } - OwnerDraw::Release_Mouse(); + Recapture_Pointer(); Theme.Fade_Out(); diff --git a/code/gscreen.cpp b/code/gscreen.cpp index 30316d80b..b5e9fee1d 100644 --- a/code/gscreen.cpp +++ b/code/gscreen.cpp @@ -450,27 +450,6 @@ void GScreenClass::Blit_Display(void) } -/// -/// Repaints the dialog controls that the last frame drew over. -/// The dialogs are ordinary child windows that paint themselves onto the game's own -/// surfaces, so a frame put on top of them takes their pixels with it. Windows is asked -/// to repaint them straight away, and the controls are grandchildren of the main window -/// rather than children, so the whole subtree has to be included. -/// -void Heal_Dialog_Controls(void) -{ - if (_dialog_count <= 0 || MainWindow == NULL) { - return; - } - - for (HWND child = GetWindow(MainWindow, GW_CHILD); child != NULL; child = GetWindow(child, GW_HWNDNEXT)) { - if (IsWindowVisible(child)) { - RedrawWindow(child, NULL, NULL, RDW_INVALIDATE|RDW_UPDATENOW|RDW_ERASE|RDW_ALLCHILDREN); - } - } -} - - /// /// Presents a rendered surface onto the visible surface. /// This is the low level routine that gets a finished frame in front of the player. The @@ -578,7 +557,6 @@ void Update_Visible_Surface(Surface *surface, Rect *rect) */ VisibleSurface->Blit_From(dest_rect, *surface, src_rect, false, true); - Heal_Dialog_Controls(); Video_Present_If_Dirty(); } diff --git a/code/gscreen.h b/code/gscreen.h index 37f9623cb..d091d889b 100644 --- a/code/gscreen.h +++ b/code/gscreen.h @@ -144,4 +144,3 @@ class GScreenClass }; void Update_Visible_Surface(Surface *surface = HiddenSurface, Rect *rect = NULL); -void Heal_Dialog_Controls(void); diff --git a/code/init.cpp b/code/init.cpp index 74bd4b5f4..de21f0c68 100644 --- a/code/init.cpp +++ b/code/init.cpp @@ -148,7 +148,6 @@ #include "overlay.h" #include "overtype.h" #include "ovrlight.h" -#include "ownrdraw.h" #include "partsys.h" #include "pcx.h" #include "queue.h" @@ -188,6 +187,7 @@ #include "vqoption.h" #include "wave.h" #include "waypoint.h" +#include "winfix.h" #include "winstub.h" #include "wsproto.h" #include "wspudp.h" @@ -717,110 +717,6 @@ void Prepare_Side_Roster(void) -static UICampaignPresenterClass * _CampaignScreen = NULL; -static UIMainMenuPresenterClass * _MainMenuScreen = NULL; - - -/// -/// Puts the view-model on the campaign dialog's controls. -/// -static void Campaign_Sync_Controls(HWND window, UICampaignPresenterClass const & screen) -{ - HWND handle = GetDlgItem(window, IDC_DIFFICULTY_LABEL); - if (handle) { - SetWindowText(handle, screen.DifficultyLabel.c_str()); - } -} - - -/// -/// Handles the messages for the campaign choice dialog. -/// This routine lists the campaigns that the player is entitled to play, drives the -/// difficulty slider, and leaves the choice where Choose_Campaign will collect it. -/// -static INT_PTR CALLBACK Campaign_Choice_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - HWND item; - - INT_PTR rc; - rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - - if (rc) { - return(rc); - } - - if (_CampaignScreen == NULL) { - return(FALSE); - } - - UICampaignPresenterClass & screen = *_CampaignScreen; - - switch (message) { - - case WM_INITDIALOG: - item = GetDlgItem(window, IDC_LIST); - - if (item != NULL) { - DebugString("Initializing Choose_Campaign() Dialog.\n"); - for (UICampaignPresenterClass::EntryType const & entry : screen.Campaigns) { - ListBox_AddString(item, entry.Label.c_str()); - } - ListBox_SetCurSel(item, screen.Selected); - } - - item = GetDlgItem(window, IDC_DIFFICULTY_SLIDER); - - if (item != NULL) { - SendMessage(item, OD_TRACKNUMBERS, 0, 0); - Slider_SetRange(item, 0, UICampaignPresenterClass::DIFFICULTY_STEPS - 1); - Slider_SetPos(item, screen.Difficulty); - } - break; - - case WM_COMMAND: - switch (LOWORD(wparam)) { - case IDOK: - if (HIWORD(wparam) == BN_CLICKED) { - item = GetDlgItem(window, IDC_LIST); - if (item != NULL) { - screen.Queue(UIIntent{UI_CAMPAIGN_SELECT, "", ListBox_GetCurSel(item)}); - } - - // The slider is read back here rather than tracked, because a - // keyboard or page move changes a track bar without raising the - // thumb notification the label follows. - item = GetDlgItem(window, IDC_DIFFICULTY_SLIDER); - if (item != NULL) { - screen.Queue(UIIntent{UI_CAMPAIGN_DIFFICULTY, "", Slider_GetPos(item)}); - } - - screen.Queue(UIIntent{UI_CAMPAIGN_ACCEPT, "", 0}); - } - break; - - case IDCANCEL: - if (HIWORD(wparam) == BN_CLICKED) { - screen.Queue(UIIntent{UI_CAMPAIGN_CANCEL, "", 0}); - } - - break; - } - break; - - case WM_HSCROLL: { - if ((HWND)lparam == GetDlgItem(window, IDC_DIFFICULTY_SLIDER)) { - screen.Queue(UIIntent{UI_CAMPAIGN_DIFFICULTY, "", (int)HIWORD(wparam)}); - } - break; - } - - default: - break; - } - - return(FALSE); -} - /// /// Asks the player which campaign to play. @@ -830,8 +726,6 @@ static INT_PTR CALLBACK Campaign_Choice_Dialog_Proc(HWND window, UINT message, W /// Returns with the campaign chosen, or CAMPAIGN_NONE if the player backed out. static CampaignType Choose_Campaign(void) { - HWND dialog; - if (Campaigns.Count() == 0) { Init_Campaigns(); @@ -843,39 +737,7 @@ static CampaignType Choose_Campaign(void) UICampaignPresenterClass screen; screen.Refresh(); - // The selection is latched here, at screen entry, and the legacy dialog opens only when - // the document could not be prepared. - if (UI_Use_Rml()) { - if (UI_Campaign_Screen(screen).Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { - return((CampaignType)screen.Chosen()); - } - - screen.IsClosing = false; - screen.Result.reset(); - } - - _CampaignScreen = &screen; - - dialog = OwnerDraw::Begin_Dialog(IDD_CAMPAIGN, Campaign_Choice_Dialog_Proc); - - if (dialog != NULL) { - OwnerDraw::Move_Dialog(dialog, -1, (HiddenSurface->Get_Height() - 400) / 2 + 147); - OwnerDraw::Display_Dialog(dialog); - - while (!screen.Result.has_value()) { - if (OwnerDraw::Dialog_Message_Handler() == true) { - break; - } - - screen.Drain(); - Campaign_Sync_Controls(dialog, screen); - screen.Service(); - } - - OwnerDraw::End_Dialog(dialog); - } - - _CampaignScreen = NULL; + UI_Campaign_Screen(screen); return((CampaignType)screen.Chosen()); } @@ -2982,115 +2844,13 @@ bool Cheat_Key_Process(char chr) /// -/// Handles the messages for the version information dialog. -/// This routine fills the list box with the game's title, its version numbers, the build -/// stamp, and a description of the processor it finds itself running upon. It is the -/// first thing to ask for when a player reports a problem. -/// -INT_PTR CALLBACK Version_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - HWND handle; - int *res; - char buffer[256]; - - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - - if (rc) { - return(rc); - } - - res = (int *)GetWindowLongPtr(window, DWLP_USER); - - switch (message) { - case WM_INITDIALOG: - handle = GetDlgItem(window, IDC_VERSION_INFO); - - if (Addon_Installed(ADDON_FIRESTORM) == true) { - strcpy(buffer, Fetch_String(TXT_SHORT_TITLE)); - strcat(buffer, ": "); - strcat(buffer, Get_Addon_Title(ADDON_FIRESTORM)); - ListBox_AddString(handle, buffer); - } else { - ListBox_AddString(handle, Fetch_String(TXT_SHORT_TITLE)); - } - - sprintf(buffer, "Version %s", Version_Name()); - ListBox_AddString(handle, buffer); - - sprintf(buffer, "Internal Version %s", VerNum.Version_Name()); - ListBox_AddString(handle, buffer); - -#ifdef _DEBUG - sprintf(buffer, "Debug Build: %s - %s", OPENTS_BUILD_DESCRIPTION, OPENTS_COMMIT_DATE); -#else - sprintf(buffer, "Release Build: %s - %s", OPENTS_BUILD_DESCRIPTION, OPENTS_COMMIT_DATE); -#endif - ListBox_AddString(handle, buffer); - - // The braces keep the 'case' label from jumping over these initializations. - { - int cpu_type = 5; - char vendor[32]; - vendor[0] = '\0'; - Get_CPU_Type(cpu_type, vendor, sizeof(vendor) - 1); - - sprintf(buffer, "CPU vendor: %s", vendor); - ListBox_AddString(handle, buffer); - } - - Get_Language_Version(buffer); - ListBox_AddString(handle, buffer); - break; - - case WM_COMMAND: - switch (LOWORD(wparam)) { - case IDCANCEL: - case IDOK: - *res = LOWORD(wparam); - break; - } - break; - } - - return(FALSE); -} - - -/// -/// Displays the version information dialog. -/// This routine does not return until the player dismisses the dialog, and keeps the +/// Displays the version information screen. +/// This routine does not return until the player dismisses the screen, and keeps the /// title screen alive behind it while it waits. /// void Version_Dialog(void) { - HWND dialog; - int res = 0; - - /* - ** The migrated screen, unless the player has asked for the dialog it replaced. A view - ** that could not be prepared reports so rather than showing nothing, and the legacy - ** dialog below is what it falls back to for as long as that dialog exists. - */ - if (UI_Use_Rml()) { - if (UI_Version_Screen().Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { - return; - } - } - - dialog = OwnerDraw::Begin_Dialog(IDD_VERSION, Version_Dialog_Proc); - - if (dialog != NULL) { - SetWindowLongPtr(dialog, DWLP_USER, (LONG_PTR)&res); - OwnerDraw::Display_Dialog(dialog); - - while (res == 0) { - if (OwnerDraw::Dialog_Message_Handler() == true) { - break; - } - Title_Screen_Restore(); - } - OwnerDraw::End_Dialog(dialog); - } + UI_Version_Screen(); } @@ -3134,7 +2894,6 @@ static void Seed_Crypto_Random(void) *=========================================================================*/ int Main_Menu(unsigned int timeout) { - HWND dialog; int retval = SEL_NONE; timeout = 0; @@ -3142,166 +2901,24 @@ int Main_Menu(unsigned int timeout) UIMainMenuPresenterClass screen; screen.Refresh(); - _MainMenuScreen = &screen; - - // The selection is latched here, at screen entry, and the legacy dialog opens only when - // the document could not be prepared. - if (UI_Use_Rml()) { - Draw_Title_Screen(); - - UIResult const result = UI_Main_Menu_Screen(screen); - - if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { - // A session that ended underneath the screen leaves the menu, which is what the - // driver's own exit intent did for the same condition. - if (result.Outcome == UIResult::OUTCOME_SESSION_ENDED) { - screen.Choice = UIMainMenuPresenterClass::CHOICE_EXIT; - } - - retval = screen.Selection(); - Seed_Crypto_Random(); + Draw_Title_Screen(); - _MainMenuScreen = NULL; - SetFocus(MainWindow); - return(retval); - } + UIResult const result = UI_Main_Menu_Screen(screen); - screen.IsClosing = false; - screen.Result.reset(); + // A session that ended underneath the screen leaves the menu, which is what the driver's + // own exit intent did for the same condition. + if (result.Outcome == UIResult::OUTCOME_SESSION_ENDED) { + screen.Choice = UIMainMenuPresenterClass::CHOICE_EXIT; } - dialog = OwnerDraw::Begin_Dialog(IDD_MAIN_MENU, Main_Menu_Dialog_Proc); - assert(dialog != NULL); - - if (dialog != NULL) { - Draw_Title_Screen(); - OwnerDraw::Move_Dialog(dialog, -1, (HiddenSurface->Get_Height() - 400) / 2 + 147); - OwnerDraw::Display_Dialog(dialog); - SetFocus(MainWindow); - - do { - if (OwnerDraw::Dialog_Message_Handler() == true) { - screen.Queue(UIIntent{UI_MAINMENU_EXIT, "", 0}); - } - - screen.Drain(); - screen.Service(); - - if (Keyboard->Check()) { - KeyNumType input = Keyboard->Get(); - - switch ((unsigned int)input) { - case (KN_V | KN_CTRL_BIT): - screen.Queue(UIIntent{UI_MAINMENU_VERSION, "", 0}); - break; - - case VK_C | KN_CTRL_BIT | KN_ALT_BIT: - screen.Queue(UIIntent{UI_MAINMENU_CREDITS, "", 0}); - break; - - default: - if ((input & KN_RLSE_BIT) == 0) { - screen.Queue(UIIntent{UI_MAINMENU_TYPED, "", (int)(char)input}); - } - break; - } - - screen.Drain(); - } - - // The version screen is a screen of a different kind, so it nests; getting out - // of the way of it is what the dialog's ShowWindow did. - if (screen.VersionPending) { - ShowWindow(dialog, SW_HIDE); - UpdateWindow(MainWindow); - screen.Run_Pending(); - ShowWindow(dialog, SW_SHOW); - UpdateWindow(dialog); - SetFocus(MainWindow); - } - } - while (!screen.Result.has_value()); - - retval = screen.Selection(); - - OwnerDraw::End_Dialog(dialog); - - Seed_Crypto_Random(); - } else { - retval = SEL_EXIT; - } - - _MainMenuScreen = NULL; + retval = screen.Selection(); + Seed_Crypto_Random(); SetFocus(MainWindow); return(retval); } -/// -/// Handles the messages for the main menu dialog. -/// This routine records the button the player pressed into the result that Main_Menu is -/// waiting upon, and greys out the load button when there is nothing to load. -/// -INT_PTR CALLBACK Main_Menu_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - if (rc) { - return(rc); - } - - if (_MainMenuScreen == NULL) { - return(FALSE); - } - - UIMainMenuPresenterClass & screen = *_MainMenuScreen; - - switch (message) { - case WM_INITDIALOG: { - HWND control = GetDlgItem(window, IDC_LOAD_MISSION); - if (control) { - EnableWindow(control, screen.CanLoad ? TRUE : FALSE); - if (screen.CanLoad) { - return(FALSE); - } - } - } - break; - - case WM_COMMAND: { - switch (LOWORD(wparam)) { - case IDC_OPTIONS: - screen.Queue(UIIntent{UI_MAINMENU_OPTIONS, "", 0}); - break; - - case IDC_EXIT_GAME: - screen.Queue(UIIntent{UI_MAINMENU_EXIT, "", 0}); - break; - - case IDC_INTRO: - screen.Queue(UIIntent{UI_MAINMENU_INTRO, "", 0}); - break; - - case IDC_NEWCAMPAIGN: - screen.Queue(UIIntent{UI_MAINMENU_CAMPAIGN, "", 0}); - break; - - case IDC_MULTIPLAYER_GAME: - screen.Queue(UIIntent{UI_MAINMENU_MULTIPLAYER, "", 0}); - break; - - case IDC_LOAD_MISSION: - screen.Queue(UIIntent{UI_MAINMENU_LOAD, "", 0}); - break; - } - } - break; - } - - return(false); -} - - /// /// Redraws the title screen if the display surfaces have been lost. /// The menu dialogs call this routine from their message loops, so that the background diff --git a/code/keyboard.cpp b/code/keyboard.cpp index fbae09bbf..7e43d4737 100644 --- a/code/keyboard.cpp +++ b/code/keyboard.cpp @@ -801,3 +801,71 @@ int WWKeyboardClass::Noop(void) const { return(0); } + + +/// +/// Converts a key code into its printable name. +/// This routine is used by the hotkey control to show a binding the way the player's own +/// keyboard layout names it, with the modifier names spelled out ahead of the key. +/// +/// The key, complete with its modifier bits, to spell out. +/// Buffer to build the name in. +/// Be sure that the buffer is big enough for the modifier names as well. +int Build_Hotkey_String(KeyNumType key, char * buffer) +{ + char key_name[32]; + unsigned char modifier = HIBYTE(key); + + buffer[0] = '\0'; + + UINT lparam; + + /// (p << 16) - places the scan code into bits 16-23. + /// (1 << 0) - purpose unknown; Windows does not document this bit. + /// (1 << 24) - Extended-key bit. Distinguishes some keys on an enhanced keyboard. + /// (1 << 25) - "Don't care" bit. Should not distinguish between left and right ctrl and shift keys. + + if ((modifier & (WWKEY_ALT_BIT >> 8)) != 0) { + lparam = MapVirtualKey(VK_MENU, 0) ; + lparam = (lparam << 16); + lparam |= (1 << 0); + lparam |= (1 << 25); + GetKeyNameText(lparam, key_name, sizeof(key_name)); + strcat(buffer, key_name); + strcat(buffer, "+"); + } + + if ((modifier & (WWKEY_CTRL_BIT >> 8)) != 0) { + lparam = MapVirtualKey(VK_CONTROL, 0); + lparam = (lparam << 16); + lparam |= (1 << 0); + lparam |= (1 << 25); + GetKeyNameText(lparam, key_name, sizeof(key_name)); + strcat(buffer, key_name); + strcat(buffer, "+"); + } + + if ((modifier & (WWKEY_SHIFT_BIT >> 8)) != 0) { + lparam = MapVirtualKey(VK_SHIFT, 0); + lparam = (lparam << 16); + lparam |= (1 << 0); + lparam |= (1 << 25); + GetKeyNameText(lparam, key_name, sizeof(key_name)); + strcat(buffer, key_name); + strcat(buffer, "+"); + } + + lparam = MapVirtualKey(key & 0xFF, 0); + lparam = (lparam << 16); + lparam |= (1 << 0); + lparam |= (1 << 25); + + if ((modifier & (WWKEY_RLS_BIT >> 8)) != 0) { + lparam |= (1 << 24); + } + + GetKeyNameText(lparam, key_name, sizeof(key_name)); + strcat(buffer, key_name); + + return(0); +} diff --git a/code/keyboard.h b/code/keyboard.h index 018a4e63a..2866e38ac 100644 --- a/code/keyboard.h +++ b/code/keyboard.h @@ -671,3 +671,7 @@ struct KeyboardClass : public WWKeyboardClass int Mouse_X(void) {return(Get_Mouse_X());}; int Mouse_Y(void) {return(Get_Mouse_Y());}; }; + + +// Spells a key, with its modifiers, into a human readable name. +int Build_Hotkey_String(KeyNumType key, char * buffer); diff --git a/code/mapgen.cpp b/code/mapgen.cpp index 59114f543..b1b8a4fe0 100644 --- a/code/mapgen.cpp +++ b/code/mapgen.cpp @@ -42,7 +42,6 @@ #include "netshare.h" #include "nodes.h" #include "overtype.h" -#include "ownrdraw.h" #include "pcx.h" #include "progress.h" #include "rules.h" @@ -72,7 +71,6 @@ bool (*RMGCallback)() = MapGen_Call_Back; -INT_PTR CALLBACK Map_Seed_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); double Random_Fraction(void); @@ -3256,54 +3254,19 @@ MapGeneratorClass::~MapGeneratorClass(void) /// skirmish or multiplayer game begins. It does not return until the player accepts the map /// or gives up on it, and the title screen behind is kept alive in the meantime. /// -/// Progress callback to run while the dialog is up. -/// Returns with the dialog result -- 1 if the player accepted the map, 2 if the -/// dialog was canceled, and 0 if it could not be opened at all. +/// Progress callback to run while the screen is up. +/// Returns with the screen's result -- 1 if the player accepted the map, 2 if the +/// screen was canceled, and 0 if it could not be opened at all. int Do_Random_Map_Dialog(bool (*callback)()) { - WDTTerritory *wdt = NULL; LONG res = 0; - if (Session.Type == GAME_INTERNET && Session.IsWDT) { - wdt = WDT_Get_Territory(Session.WDTTerritory); - } - - // The presentation is latched here, at screen entry. A document that will not prepare - // drops the screen back to the dialog, which works from the same settings. - if (UI_Use_Rml()) { - UIMapGenPresenterClass screen; - screen.Open(callback); - - RMGCallback = callback; - RandomMapGen.SeedData.Callback = callback; - res = UI_MapGen_Run(screen); - } - - HWND dialog = NULL; - if (res == 0) { - if (Addon_Enabled(ADDON_FIRESTORM)) { - dialog = OwnerDraw::Begin_Dialog(wdt != NULL ? IDD_MAPGEN_WDT : IDD_MAPGEN_FS, Map_Seed_Dialog_Proc); - } else { - dialog = OwnerDraw::Begin_Dialog(IDD_MAPGEN, Map_Seed_Dialog_Proc); - } - } + UIMapGenPresenterClass screen; + screen.Open(callback); - if (dialog) { - RMGCallback = callback; - RandomMapGen.SeedData.Callback = callback; - SetWindowLongPtrA(dialog, DWLP_USER, (LONG_PTR)&res); - OwnerDraw::Display_Dialog(dialog); - while (res == 0) { - if (OwnerDraw::Dialog_Message_Handler() == 1) { - break; - } - if (callback != NULL) { - callback(); - } - Title_Screen_Restore(false); - } - OwnerDraw::End_Dialog(dialog); - } + RMGCallback = callback; + RandomMapGen.SeedData.Callback = callback; + res = UI_MapGen_Run(screen); RMGCallback = MapGen_Call_Back; RandomMapGen.SeedData.Callback = NULL; @@ -3410,603 +3373,6 @@ void Clean_Up_RMCache(void) } -/// -/// Generates the random map the player has asked for. -/// This routine is what the map generator dialog's preview and generate buttons come down -/// to. A map that has been built for these exact settings before is kept in a cache and -/// merely fetched back, so flipping between two seeds costs nothing the second time. The -/// finished preview is left in RandMap.img for the lobby to show. -/// -/// The dialog to show generation progress within. -/// Progress callback to run while the map is being built. -void Do_Random_Map(HWND dialog, bool (*callback)()) -{ - if (Session.Type == GAME_INTERNET && Session.IsWDT && WDT_Get_Territory(Session.WDTTerritory) != NULL) { - RandomMapGen.SeedData.NumPlayers = 4; - } - char *digest = CalcRandomMapDigest(); - char name[128]; - memset(name, 0, sizeof(name)); - strncpy(name, "rmcache\\", sizeof(name)); - strncat(name, digest, sizeof(name)); - delete digest; - strncat(name, ".mmp", sizeof(name)); - DebugString("Cache filename is %s\n", name); - CCFileClass cfile(name); - - if (cfile.Is_Available()) { - if (RandomMapGen.MapPreview == NULL) { - RandomMapGen.MapPreview = new MapPreviewClass; - } - if (RandomMapGen.MapPreview->Read_PCX_Preview(name)) { - RawFileClass file("RandMap.img"); - Write_PCX_File(file, *RandomMapGen.MapPreview->Get_Preview_Surface(), &GamePalette); - delete RandomMapGen.MapPreview; - RandomMapGen.MapPreview = NULL; - return; - } - - delete RandomMapGen.MapPreview; - RandomMapGen.MapPreview = NULL; - } - - RMGCallback = callback; - RandomMapGen.SeedData.Callback = callback; - if (RandomMapGen.SeedData.Seed == -1) { - RandomMapGen.SeedData.Seed = Sim_Random_Pick(0U, 65535U); - } - RandomMapGen.Generate_Random_Map(true, dialog); - RandomMapGen.MapPreview->Create_Preview(); - - if (RandomMapGen.MapSeeder != NULL) { - delete RandomMapGen.MapSeeder; - } - - RandomMapGen.MapSeeder = new MapSeedClass; - memcpy(RandomMapGen.MapSeeder, &RandomMapGen.SeedData, sizeof(RandomMapGen.SeedData)); - - Scen->Set_Scenario_Name(Fetch_String(TXT_RANDOM_MAP_DESCRIPTION)); - Title_Screen_Restore(); - - if (RandomMapGen.MapPreview != NULL) { - if (RandomMapGen.MapPreview->Get_Preview_Surface() != NULL) { - RawFileClass file("RandMap.img"); - Write_PCX_File(file, *RandomMapGen.MapPreview->Get_Preview_Surface(), &GamePalette); - WIN32_FIND_DATA ff; - if (FindFirstFile("rmcache", &ff) == INVALID_HANDLE_VALUE) { - CreateDirectory("rmcache", 0); - } - CopyFile("RandMap.img", name, FALSE); - Clean_Up_RMCache(); - } - delete RandomMapGen.MapPreview; - RandomMapGen.MapPreview = NULL; - } - - if (RandomMapGen.MapSeeder != NULL) { - delete RandomMapGen.MapSeeder; - RandomMapGen.MapSeeder = NULL; - } -} - - -/// -/// Dialog procedure for the random map generator ("Map Seed") dialog. -/// Handles previewing, generating, saving, loading and deleting random maps, and randomizing -/// the generator settings. The dialog's result code is written through the DWLP_USER -/// pointer set up by Do_Random_Map_Dialog so that writing it ends that dialog's modal -/// message loop. -/// -/// Handle to the dialog window. -/// Window message identifier. -/// Message-specific first parameter. -/// Message-specific second parameter. -/// TRUE if the message was processed, FALSE otherwise. -INT_PTR CALLBACK Map_Seed_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - static int _unused = -1; - - INT_PTR result = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - if (result) { - return(result); - } - - LONG * state = (LONG *)GetWindowLongPtrA(window, DWLP_USER); - - switch (message) { - - /* - * Repaint the map preview, if one exists. - */ - case WM_PAINT: - if (RandomMapGen.MapPreview != NULL) { - RandomMapGen.MapPreview->Blit_Preview(window); - } - ValidateRect(window, NULL); - return(0); - - /* - * Initialize the dialog controls from the current seed settings. - */ - case WM_INITDIALOG: { - _unused = -1; - HWND handle = GetDlgItem(window, IDC_MAPGEN_PREVIEW); - if (Debug_Map) { - EnableWindow(handle, false); - } else { - EnableWindow(handle, true); - } - if (RandomMapGen.SeedData.Seed == -1) { - RandomMapGen.SeedData.Seed = Sim_Random_Pick(0U, 65535U); - } - RandomMapGen.SeedData.Set_Settings(window); - - bool enable = RandomMapGen.SeedData.Files_Present(); - handle = GetDlgItem(window, IDC_MAPGEN_LOAD_MAP); - if (handle != NULL) { - EnableWindow(handle, enable); - } - handle = GetDlgItem(window, IDC_MAPGEN_DELETE_MAP); - if (handle == NULL) { - return(0); - } - EnableWindow(handle, enable); - return(0); - } - - case WM_COMMAND: - switch (LOWORD(wparam)) { - - /* - * Generate the map and accept the dialog. - */ - case IDOK: - RandomMapGen.SeedData.Get_Settings(window); - if (Debug_Map) { - RandomMapGen.Generate_Random_Map(false, window); - Scen->Set_Scenario_Name(Fetch_String(TXT_RANDOM_MAP_DESCRIPTION)); - Write_Scenario_INI("RandMap.Map", true); - } else { - if (RandomMapGen.MapPreview == NULL || RandomMapGen.MapPreview->Get_Preview_Surface() == NULL) { - RandomMapGen.Generate_Random_Map(true, window); - if (Debug_Map) { - Scen->Set_Scenario_Name(Fetch_String(TXT_RANDOM_MAP_DESCRIPTION)); - Write_Scenario_INI("RandMap.Map", true); - } - } - } - *state = 1; - return(1); - - /* - * Cancel the dialog. - */ - case IDCANCEL: - *state = 2; - return(1); - - /* - * Load a saved map seed. - */ - case IDC_MAPGEN_LOAD_MAP: - RandomMapGen.SeedData.Get_Settings(window); - if (RandomMapGen.SeedData.LoadOptionsClass::Load() == true) { - PostMessageA(window, WM_COMMAND, MAKEWPARAM(IDC_MAPGEN_PREVIEW, BN_CLICKED), (LPARAM)GetDlgItem(window, IDC_MAPGEN_PREVIEW)); - } - RandomMapGen.SeedData.Set_Settings(window); - return(0); - - /* - * Save the current map seed. - */ - case IDC_MAPGEN_SAVE_MAP: { - RandomMapGen.SeedData.Get_Settings(window); - RandomMapGen.SeedData.MapDescription[0] = '\0'; - RandomMapGen.SeedData.LoadOptionsClass::Save(RandomMapGen.SeedData.MapDescription); - - bool enable = RandomMapGen.SeedData.Files_Present(); - HWND handle = GetDlgItem(window, IDC_MAPGEN_LOAD_MAP); - if (handle != NULL) { - EnableWindow(handle, enable); - } - handle = GetDlgItem(window, IDC_MAPGEN_DELETE_MAP); - if (handle == NULL) { - return(0); - } - EnableWindow(handle, enable); - return(0); - } - - /* - * Delete the saved map seed. - */ - case IDC_MAPGEN_DELETE_MAP: { - RandomMapGen.SeedData.Get_Settings(window); - RandomMapGen.SeedData.LoadOptionsClass::Delete(); - - bool enable = RandomMapGen.SeedData.Files_Present(); - HWND handle = GetDlgItem(window, IDC_MAPGEN_LOAD_MAP); - if (handle != NULL) { - EnableWindow(handle, enable); - } - handle = GetDlgItem(window, IDC_MAPGEN_DELETE_MAP); - if (handle == NULL) { - return(0); - } - EnableWindow(handle, enable); - return(0); - } - - /* - * Build and display a preview of the current map seed. - */ - case IDC_MAPGEN_PREVIEW: - RandomMapGen.SeedData.Get_Settings(window); - RandomMapGen.Generate_Random_Map(true, window); - RandomMapGen.MapPreview->Create_Preview(); - if (RandomMapGen.MapSeeder != NULL) { - delete RandomMapGen.MapSeeder; - } - RandomMapGen.MapSeeder = new MapSeedClass; - memcpy(RandomMapGen.MapSeeder, &RandomMapGen.SeedData, sizeof(MapSeedClass)); - PostMessageA(window, WM_PAINT, 0, 0); - return(0); - - /* - * Randomize the generator settings. - */ - case IDC_MAPGEN_SURPRISE: - RandomMapGen.SeedData.Get_Settings(window); - RandomMapGen.SeedData.Randomize(); - RandomMapGen.SeedData.Set_Settings(window); - return(0); - - default: - return(0); - } - - default: - return(0); - } -} - - -/// -/// Reads the map generator dialog into these settings. -/// This routine is called before a preview or a generate, so that whatever the player has -/// dialed in on the controls becomes the seed the generator works from. The settings taken -/// off the dialog are run through Fixup_Settings, so an impossible combination can never -/// reach the generator. The Firestorm settings are cleared away when that addon is absent. -/// -/// The map generator dialog to read. -void MapSeedClass::Get_Settings(HWND dialog) -{ - WDTTerritory * wdt = NULL; - if (Session.Type == GAME_INTERNET && Session.IsWDT) { - wdt = WDT_Get_Territory(Session.WDTTerritory); - } - - HWND handle; - char str[30]; - - handle = GetDlgItem(dialog, IDC_MAPGEN_ENVIRONMENT); - Biome = ComboBox_GetItemData(handle, ComboBox_GetCurSel(handle)); - - handle = GetDlgItem(dialog, IDC_MAPGEN_TIME_OF_DAY); - Time = ComboBox_GetItemData(handle, ComboBox_GetCurSel(handle)); - - handle = GetDlgItem(dialog, IDC_MAPGEN_MAP_WIDTH); - Width = ComboBox_GetItemData(handle, ComboBox_GetCurSel(handle)); - - handle = GetDlgItem(dialog, IDC_MAPGEN_MAP_HEIGHT); - Height = ComboBox_GetItemData(handle, ComboBox_GetCurSel(handle)); - - handle = GetDlgItem(dialog, IDC_MAPGEN_DIMENSION_EDIT); - GetWindowText(handle, str, ARRAY_SIZE(str)); - Seed = atoi(str); - - handle = GetDlgItem(dialog, IDC_MAPGEN_TIBERIUM_AMOUNT); - Tiberium = Slider_GetPos(handle); - - if (wdt != NULL) { - NumPlayers = 4; - } else { - handle = GetDlgItem(dialog, IDC_MAPGEN_PLAYERS); - NumPlayers = Slider_GetPos(handle); - } - - handle = GetDlgItem(dialog, IDC_MAPGEN_HILLS); - Hills = Slider_GetPos(handle); - - handle = GetDlgItem(dialog, IDC_MAPGEN_WATER); - WaterAmount = Slider_GetPos(handle); - - handle = GetDlgItem(dialog, IDC_MAPGEN_CLIFFS); - Cliffs = Slider_GetPos(handle); - - handle = GetDlgItem(dialog, IDC_MAPGEN_VEGETATION); - Vegetation = Slider_GetPos(handle); - - handle = GetDlgItem(dialog, IDC_MAPGEN_CITIES); - Cities = Slider_GetPos(handle); - - handle = GetDlgItem(dialog, IDC_MAPGEN_ACCESSIBILITY); - Accessibility = Slider_GetPos(handle); - - handle = GetDlgItem(dialog, IDC_MAPGEN_TIBERIUM_FIELDS); - TiberiumLayout = Slider_GetPos(handle); - - TiberiumWildlife = 0; - VeinholeMonsters = 0; - UseIonStorms = false; - UseTransitions = false; - UseBlueTiberium = false; - - if (Addon_Enabled(ADDON_FIRESTORM)) { - handle = GetDlgItem(dialog, IDC_MAPGEN_LIFEFORMS); - if (handle != NULL) { - TiberiumWildlife = Button_GetCheck(handle) == BST_CHECKED ? 30 : 0; - } - - handle = GetDlgItem(dialog, IDC_MAPGEN_VEINHOLES); - if (handle != NULL) { - VeinholeMonsters = Slider_GetPos(handle); - } - - handle = GetDlgItem(dialog, IDC_MAPGEN_ION_STORMS); - if (handle != NULL) { - UseIonStorms = Button_GetCheck(handle) == BST_CHECKED; - } - - handle = GetDlgItem(dialog, IDC_MAPGEN_TRANSITIONS); - if (handle != NULL) { - UseTransitions = Button_GetCheck(handle) == BST_CHECKED; - } - - UseBlueTiberium = (double)Tiberium > 0.75; - } - - Fixup_Settings(); -} - - -/// -/// Fills the map generator dialog in from these settings. -/// This routine is the counterpart of Get_Settings, and is called whenever the dialog must -/// show a different set of options -- when it first appears, after a randomize, and after a -/// load. In a tournament game the controls are further restricted, or locked outright, to -/// whatever the territory permits the player to meddle with. -/// -/// The map generator dialog to fill in. -void MapSeedClass::Set_Settings(HWND dialog) -{ - static char _win_name[24]; - - static int _biome_names[BIOME_COUNT] = { - TXT_BIOME_TUNDRA, - TXT_BIOME_TAIGA, - TXT_BIOME_TEMPERATE, - TXT_BIOME_DESERT, - TXT_BIOME_MUTATED - }; - - static int _time_names[TIME_OF_DAY_COUNT] = { - TXT_TIME_MORNING, - TXT_TIME_AFTERNOON, - TXT_TIME_DUSK, - TXT_TIME_NIGHT - }; - - static int _map_size_names[MAPSIZE_COUNT] = { - TXT_MAPSIZE_SMALL, - TXT_MAPSIZE_MEDIUM, - TXT_MAPSIZE_LARGE, - TXT_MAPSIZE_VERY_LARGE - }; - - WDTTerritory *wdt = NULL; - if (Session.Type == GAME_INTERNET && Session.IsWDT) { - wdt = WDT_Get_Territory(Session.WDTTerritory); - } - - Fixup_Settings(); - - HWND handle; - LRESULT item; - int i; - - handle = GetDlgItem(dialog, IDC_MAPGEN_ENVIRONMENT); - while (SendMessageA(handle, CB_GETCOUNT, 0, 0) > 0) { - SendMessageA(handle, CB_DELETESTRING, 0, 0); - } - for (i = BIOME_FIRST; i < BIOME_COUNT; i++) { - if (i != BIOME_MUTATED || Addon_Enabled(ADDON_FIRESTORM)) { - item = SendMessageA(handle, CB_ADDSTRING, 0, (LPARAM)Fetch_String(_biome_names[i])); - SendMessageA(handle, CB_SETITEMDATA, item, i); - } - } - item = SendMessageA(handle, CB_FINDSTRING, 0, (LPARAM)Fetch_String(_biome_names[Biome])); - SendMessageA(handle, CB_SETCURSEL, item, 0); - - handle = GetDlgItem(dialog, IDC_MAPGEN_TIME_OF_DAY); - while (SendMessageA(handle, CB_GETCOUNT, 0, 0) > 0) { - SendMessageA(handle, CB_DELETESTRING, 0, 0); - } - for (i = TIME_OF_DAY_FIRST; i < TIME_OF_DAY_COUNT; i++) { - item = SendMessageA(handle, CB_ADDSTRING, 0, (LPARAM)Fetch_String(_time_names[i])); - SendMessageA(handle, CB_SETITEMDATA, item, i); - } - item = SendMessageA(handle, CB_FINDSTRING, 0, (LPARAM)Fetch_String(_time_names[Time])); - SendMessageA(handle, CB_SETCURSEL, item, 0); - - handle = GetDlgItem(dialog, IDC_MAPGEN_MAP_WIDTH); - while (SendMessageA(handle, CB_GETCOUNT, 0, 0) > 0) { - SendMessageA(handle, CB_DELETESTRING, 0, 0); - } - for (i = 0; i < MAPSIZE_COUNT; i++) { - item = SendMessageA(handle, CB_ADDSTRING, 0, (LPARAM)Fetch_String(_map_size_names[i])); - SendMessageA(handle, CB_SETITEMDATA, item, i); - } - item = SendMessageA(handle, CB_FINDSTRING, 0, (LPARAM)Fetch_String(_map_size_names[Width])); - SendMessageA(handle, CB_SETCURSEL, item, 0); - - handle = GetDlgItem(dialog, IDC_MAPGEN_MAP_HEIGHT); - while (SendMessageA(handle, CB_GETCOUNT, 0, 0) > 0) { - SendMessageA(handle, CB_DELETESTRING, 0, 0); - } - for (i = 0; i < MAPSIZE_COUNT; i++) { - item = SendMessageA(handle, CB_ADDSTRING, 0, (LPARAM)Fetch_String(_map_size_names[i])); - SendMessageA(handle, CB_SETITEMDATA, item, i); - } - item = SendMessageA(handle, CB_FINDSTRING, 0, (LPARAM)Fetch_String(_map_size_names[Height])); - SendMessageA(handle, CB_SETCURSEL, item, 0); - - handle = GetDlgItem(dialog, IDC_MAPGEN_DIMENSION_EDIT); - sprintf(_win_name, "%d", Seed); - SetWindowTextA(handle, _win_name); - if (wdt != NULL) { - EnableWindow(handle, wdt->UserModSeed ? TRUE : FALSE); - } - - handle = GetDlgItem(dialog, IDC_MAPGEN_TIBERIUM_AMOUNT); - if (wdt != NULL) { - Set_Scroll_Bar(handle, wdt->TiberiumAmountMin, wdt->TiberiumAmountMax, Tiberium, wdt->UserModTiberiumAmount); - - CheckDlgButton(dialog, IDC_WDT_1ON1, FALSE); - CheckDlgButton(dialog, IDC_WDT_2ON2, TRUE); - EnableWindow(GetDlgItem(dialog, IDC_WDT_1ON1), FALSE); - EnableWindow(GetDlgItem(dialog, IDC_WDT_2ON2), FALSE); - } else { - Set_Scroll_Bar(handle, 1, 100, Tiberium, TRUE); - - handle = GetDlgItem(dialog, IDC_MAPGEN_PLAYERS); - Set_Scroll_Bar(handle, 2, MAX_PLAYERS, NumPlayers, TRUE); - } - - handle = GetDlgItem(dialog, IDC_MAPGEN_HILLS); - if (wdt != NULL) { - Set_Scroll_Bar(handle, wdt->HillsMin, wdt->HillsMax, Hills, wdt->UserModHills); - } else { - Set_Scroll_Bar(handle, 0, 100, Hills, TRUE); - } - - handle = GetDlgItem(dialog, IDC_MAPGEN_WATER); - if (wdt != NULL) { - Set_Scroll_Bar(handle, wdt->WaterMin, wdt->WaterMax, WaterAmount, wdt->UserModWater); - } else { - Set_Scroll_Bar(handle, 0, 100, WaterAmount, TRUE); - } - - handle = GetDlgItem(dialog, IDC_MAPGEN_CLIFFS); - if (wdt != NULL) { - Set_Scroll_Bar(handle, wdt->CliffsMin, wdt->CliffsMax, Cliffs, wdt->UserModCliffs); - } else { - Set_Scroll_Bar(handle, 0, 100, Cliffs, TRUE); - } - - handle = GetDlgItem(dialog, IDC_MAPGEN_VEGETATION); - if (wdt != NULL) { - Set_Scroll_Bar(handle, wdt->VegetationMin, wdt->VegetationMax, Vegetation, wdt->UserModVegetation); - } else { - Set_Scroll_Bar(handle, 0, 100, Vegetation, TRUE); - } - - handle = GetDlgItem(dialog, IDC_MAPGEN_CITIES); - if (wdt != NULL) { - Set_Scroll_Bar(handle, wdt->CitiesMin, wdt->CitiesMax, Cities, wdt->UserModCities); - } else { - Set_Scroll_Bar(handle, 0, 100, Cities, TRUE); - } - - handle = GetDlgItem(dialog, IDC_MAPGEN_TIBERIUM_FIELDS); - if (wdt != NULL) { - Set_Scroll_Bar(handle, wdt->TiberiumFieldsMin, wdt->TiberiumFieldsMax, TiberiumLayout, wdt->UserModTiberiumFields); - } else { - Set_Scroll_Bar(handle, 0, 100, TiberiumLayout, TRUE); - } - - handle = GetDlgItem(dialog, IDC_MAPGEN_ACCESSIBILITY); - if (wdt != NULL) { - Set_Scroll_Bar(handle, wdt->AccessibilityMin, wdt->AccessibilityMax, Accessibility, wdt->UserModAccessability); - - EnableWindow(GetDlgItem(dialog, IDC_MAPGEN_ENVIRONMENT), wdt->UserModBiome ? TRUE : FALSE); - EnableWindow(GetDlgItem(dialog, IDC_MAPGEN_TIME_OF_DAY), wdt->UserModTime ? TRUE : FALSE); - EnableWindow(GetDlgItem(dialog, IDC_MAPGEN_MAP_WIDTH), wdt->UserModWidth ? TRUE : FALSE); - EnableWindow(GetDlgItem(dialog, IDC_MAPGEN_MAP_HEIGHT), wdt->UserModHeight ? TRUE : FALSE); - - Set_Checkbox(GetDlgItem(dialog, IDC_MAPGEN_LIFEFORMS), TiberiumWildlife > 0, wdt->UserModTiberiumCreatures); - - Set_Scroll_Bar(GetDlgItem(dialog, IDC_MAPGEN_VEINHOLES), 0, 5, VeinholeMonsters, wdt->UserModVeinholeMonsters); - - Set_Checkbox(GetDlgItem(dialog, IDC_MAPGEN_TRANSITIONS), UseTransitions, wdt->UserModTimeTransitions); - - Set_Checkbox(GetDlgItem(dialog, IDC_MAPGEN_ION_STORMS), UseIonStorms, TRUE); - - if (!wdt->UserModBiome && !wdt->UserModTime && !wdt->UserModCliffs && !wdt->UserModAccessability && - !wdt->UserModHills && !wdt->UserModTiberiumAmount && !wdt->UserModTiberiumFields && !wdt->UserModWater && - !wdt->UserModVegetation && !wdt->UserModCities && !wdt->UserModWidth && !wdt->UserModHeight && - !wdt->UserModVeinholeMonsters) { - EnableWindow(GetDlgItem(dialog, IDC_MAPGEN_SURPRISE), FALSE); - } - - } else { - - Set_Scroll_Bar(handle, 0, 100, Accessibility, TRUE); - - Set_Checkbox(GetDlgItem(dialog, IDC_MAPGEN_LIFEFORMS), TiberiumWildlife > 0, TRUE); - - Set_Scroll_Bar(GetDlgItem(dialog, IDC_MAPGEN_VEINHOLES), 0, 5, VeinholeMonsters, TRUE); - - Set_Checkbox(GetDlgItem(dialog, IDC_MAPGEN_TRANSITIONS), UseTransitions, TRUE); - - Set_Checkbox(GetDlgItem(dialog, IDC_MAPGEN_ION_STORMS), UseIonStorms, TRUE); - } - - InvalidateRect(dialog, 0, 0); -} - - -/// -/// Sets up one of the map generation sliders. -/// This routine is used by Set_Settings to point a slider at the span of values its setting -/// is permitted to take. A setting with nothing left to choose between is shown disabled -/// rather than hidden, so the dialog keeps its shape. -/// -/// The slider control to set up. -/// The lowest value the slider may be dragged to. -/// The highest value the slider may be dragged to. -/// Where the thumb should sit. -/// Should the player be allowed to move this slider? -void MapSeedClass::Set_Scroll_Bar(HWND handle, unsigned int min, unsigned int max, int position, bool enable) -{ - if (max <= min) { - EnableWindow(handle, FALSE); - Slider_SetRange(handle, 0, 100); - Slider_SetPos(handle, position); - } else { - EnableWindow(handle, enable); - Slider_SetRange(handle, min, max); - Slider_SetPos(handle, position); - } -} - - -/// -/// Sets up one of the map generation checkboxes. -/// This routine is the companion of Set_Scroll_Bar, and is used by Set_Settings to show a -/// setting the dialog offers as a simple yes or no. A setting the player is not allowed to -/// touch is shown disabled rather than hidden, so the dialog keeps its shape. -/// -/// The checkbox control to set up. -/// Should the box be shown checked? -/// Should the player be allowed to change this setting? -void MapSeedClass::Set_Checkbox(HWND handle, bool state, bool enable) -{ - Button_SetCheck(handle, state != 0); - Button_Enable(handle, enable); -} - - /// /// Rolls a fresh set of map generation settings. /// This routine is what the dialog's randomize button calls, handing the player a whole new @@ -4718,24 +4084,16 @@ double Sample_Truncated_Normal(double mean, double scale, double lower_bound, do /// /// Should the scenario be rebuilt from scratch and the preview redrawn /// between phases? -/// The map generator dialog to repaint as the preview is refreshed. /// /// Puts the freshly drawn preview on screen while a map is being built. /// -/// The dialog to repaint, or NULL when a document is showing the -/// picture instead. -static void Repaint_Map_Preview(HWND dialog) +static void Repaint_Map_Preview(void) { - if (dialog != NULL) { - Repaint_Map_Preview(dialog); - return; - } - UI_MapGen_Preview_Changed(); } -void MapGeneratorClass::Generate_Random_Map(bool full_init, HWND dialog) +void MapGeneratorClass::Generate_Random_Map(bool full_init) { if (RMGCallback != NULL) RMGCallback(); @@ -4762,7 +4120,7 @@ void MapGeneratorClass::Generate_Random_Map(bool full_init, HWND dialog) if (full_init) { RandomMapGen.MapPreview->Create_Preview(); - Repaint_Map_Preview(dialog); + Repaint_Map_Preview(); } if (RMGCallback != NULL) RMGCallback(); @@ -4779,7 +4137,7 @@ void MapGeneratorClass::Generate_Random_Map(bool full_init, HWND dialog) if (full_init) { RandomMapGen.MapPreview->Create_Preview(); - Repaint_Map_Preview(dialog); + Repaint_Map_Preview(); } if (RMGCallback != NULL) RMGCallback(); @@ -4795,7 +4153,7 @@ void MapGeneratorClass::Generate_Random_Map(bool full_init, HWND dialog) if (full_init) { RandomMapGen.MapPreview->Create_Preview(); - Repaint_Map_Preview(dialog); + Repaint_Map_Preview(); } if (RMGCallback != NULL) RMGCallback(); @@ -4822,7 +4180,7 @@ void MapGeneratorClass::Generate_Random_Map(bool full_init, HWND dialog) if (full_init) { RandomMapGen.MapPreview->Create_Preview(); - Repaint_Map_Preview(dialog); + Repaint_Map_Preview(); } if (RMGCallback != NULL) RMGCallback(); @@ -4883,7 +4241,7 @@ void MapGeneratorClass::Generate_Random_Map(bool full_init, HWND dialog) if (full_init) { RandomMapGen.MapPreview->Create_Preview(); - Repaint_Map_Preview(dialog); + Repaint_Map_Preview(); } if (RMGCallback != NULL) RMGCallback(); @@ -4902,7 +4260,7 @@ void MapGeneratorClass::Generate_Random_Map(bool full_init, HWND dialog) if (full_init) { RandomMapGen.MapPreview->Create_Preview(); - Repaint_Map_Preview(dialog); + Repaint_Map_Preview(); } if (RMGCallback != NULL) RMGCallback(); @@ -4926,7 +4284,7 @@ void MapGeneratorClass::Generate_Random_Map(bool full_init, HWND dialog) if (full_init) { RandomMapGen.MapPreview->Create_Preview(); - Repaint_Map_Preview(dialog); + Repaint_Map_Preview(); } if (RMGCallback != NULL) RMGCallback(); @@ -4949,7 +4307,7 @@ void MapGeneratorClass::Generate_Random_Map(bool full_init, HWND dialog) if (full_init) { RandomMapGen.MapPreview->Create_Preview(); - Repaint_Map_Preview(dialog); + Repaint_Map_Preview(); } ScenarioInit--; diff --git a/code/mapgen.h b/code/mapgen.h index e72914c36..7b3f0da0e 100644 --- a/code/mapgen.h +++ b/code/mapgen.h @@ -352,10 +352,6 @@ class MapSeedClass : public LoadOptionsClass /* * Dialog interaction. */ - void Get_Settings(HWND dialog); - void Set_Settings(HWND dialog); - void Set_Scroll_Bar(HWND handle, unsigned int min, unsigned int max, int position, bool enable); - void Set_Checkbox(HWND handle, bool state, bool enable); /* * Settings adjustment. @@ -497,7 +493,7 @@ class MapGeneratorClass /* * Top-level generation and housekeeping. */ - void Generate_Random_Map(bool full_init, HWND dialog); + void Generate_Random_Map(bool full_init); void Init_Map(bool full_init); void Cleanup(void); void Update_Progress(int percent_progress); @@ -683,7 +679,6 @@ inline bool My_In_Radar(Cell const &cell) y + x <= MapRegionClass::MapEndDiagonal) ? true : false; } -void Do_Random_Map(HWND, bool (*callback)()); int Do_Random_Map_Dialog(bool (*callback)()); extern MapRegionClass::CellData *RMGCellData; diff --git a/code/mpscore.cpp b/code/mpscore.cpp index 447d0b280..2495417f0 100644 --- a/code/mpscore.cpp +++ b/code/mpscore.cpp @@ -35,7 +35,6 @@ #include "session.h" #include "stats.h" #include "surface.h" -#include "windlg.h" #include "winstub.h" #include "color.hh" @@ -183,8 +182,6 @@ bool MultiScore::Multi_Presentation(void) return(false); } - while (WS_Destroy_Dialog(0, 0)) { } - if (Init() == true) { Keyboard->Clear(); Callback(); diff --git a/code/msanim.cpp b/code/msanim.cpp index 535a9e6a1..3da373d3d 100644 --- a/code/msanim.cpp +++ b/code/msanim.cpp @@ -25,7 +25,7 @@ #include "mixfile.h" #include "movies.h" #include "msfont.h" -#include "ownrdraw.h" +#include "drawhelp.h" #include "pcx.h" #include "shapeset.h" #include "srfcache.h" diff --git a/code/msgloop.cpp b/code/msgloop.cpp index dcbe981cb..b95ca0db6 100644 --- a/code/msgloop.cpp +++ b/code/msgloop.cpp @@ -28,9 +28,7 @@ *---------------------------------------------------------------------------------------------* * Functions: * * Add_Accelerator -- Adds a keyboard accelerator to the message handler. * - * Add_Modeless_Dialog -- Adds a modeless dialog box to the message handler. * * Remove_Accelerator -- Removes an accelerator from the message processor. * - * Remove_Modeless_Dialog -- Removes the dialog box from the message tracking handler. * * Windows_Message_Handler -- Handles windows message. * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ @@ -49,7 +47,6 @@ ** box handles and then determining if the windows message applies to the dialog box. If it ** does, then the default message handling should not be performed. */ -DynamicVectorClass _ModelessDialogs; /* @@ -112,19 +109,7 @@ void Windows_Message_Handler(void) ToolTips->Message_Handler(&msg); } - /* - ** Pass the windows message through any modeless dialogs that may - ** be active. If one of the dialogs processes the message, then - ** it must not be processed by the normal window message handler. - */ bool processed = false; - for (int index = 0; index < _ModelessDialogs.Count(); index++) { - if (IsDialogMessage(_ModelessDialogs[index], &msg)) { - processed = true; - break; - } - } - if (processed) continue; /* ** Pass the message through any loaded accelerators. If the message @@ -168,74 +153,6 @@ void Windows_Message_Handler(void) } -/*********************************************************************************************** - * Add_Modeless_Dialog -- Adds a modeless dialog box to the message handler. * - * * - * When a modeless dialog box becomes active, the messages processed by the main message * - * handler must be handled different. This routine is used to inform the message handler * - * that a dialog box is active and messages must be fed to it as appropriate. * - * * - * INPUT: dialog -- Handle to the modeless dialog box. * - * * - * OUTPUT: none * - * * - * WARNINGS: The modeless dialog box must be removed from the tracking system by calling * - * Remove_Modeless_Dialog. Failure to do so when the dialog is destroyed will * - * result in undefined behavior. * - * * - * HISTORY: * - * 05/17/1997 JLB : Created. * - *=============================================================================================*/ -void Add_Modeless_Dialog(HWND dialog) -{ - _ModelessDialogs.Add(dialog); -} - - -/*********************************************************************************************** - * Remove_Modeless_Dialog -- Removes the dialog box from the message tracking handler. * - * * - * This routine must be called when a modeless dialog is being removed. * - * * - * INPUT: dialog -- Handle to the modeless dialog that was previously submitted to * - * Add_Modeless_Dialog(). * - * * - * OUTPUT: none * - * * - * WARNINGS: Failure to call this routine will result in undefined behavior when the dialog * - * is destroyed. * - * * - * HISTORY: * - * 05/17/1997 JLB : Created. * - *=============================================================================================*/ -void Remove_Modeless_Dialog(HWND dialog) -{ - _ModelessDialogs.Delete(dialog); -} - - -/// -/// Fetches a tracked modeless dialog by its window title. -/// This routine searches the dialogs submitted by Add_Modeless_Dialog for one whose caption -/// matches the name given. Use this routine when only the title of the dialog is known. -/// -/// The window title of the dialog to look for. -/// Returns with the handle of the matching dialog, or NULL if no tracked dialog -/// carries that title. -HWND Get_Modeless_Dialog_From_Name(const char *name) -{ - static char _wname[100]; - - for (int i = 0; i < _ModelessDialogs.Count(); i++) { - GetWindowText(_ModelessDialogs[i], _wname, sizeof(_wname) - 1); - if (!strcmp(_wname, name)) { - return(_ModelessDialogs[i]); - } - } - return(NULL); -} - - /*********************************************************************************************** * Add_Accelerator -- Adds a keyboard accelerator to the message handler. * * * diff --git a/code/msgloop.h b/code/msgloop.h index af1c7d712..f6d4fdd74 100644 --- a/code/msgloop.h +++ b/code/msgloop.h @@ -36,11 +36,6 @@ // Main message handler. void Windows_Message_Handler(void); -// Modeless dialog box support routines. -void Remove_Modeless_Dialog(HWND dialog); -void Add_Modeless_Dialog(HWND dialog); -HWND Get_Modeless_Dialog_From_Name(const char *name); - // Accelerator keys support routines. void Add_Accelerator(HWND window, HACCEL accelerator); void Remove_Accelerator(HACCEL accelerator); diff --git a/code/netdlg2.cpp b/code/netdlg2.cpp index dc795a744..dacc3accf 100644 --- a/code/netdlg2.cpp +++ b/code/netdlg2.cpp @@ -36,7 +36,6 @@ #include "netglobal.h" #include "netshare.h" #include "newmenu.h" -#include "ownrdraw.h" #include "rules.h" #include "scenario.h" #include "sendfile.h" @@ -46,7 +45,6 @@ #include "utf8.h" #include "ui/uilobby.h" #include "ui/uishell.h" -#include "windlg.h" #include "winstub.h" #include "wsproto.h" @@ -61,9 +59,6 @@ static void Unjoin_Game(int game_index); static void Get_Join_Responses(void); static bool Lobby_Seat_Is_Valid(int house, int color); -INT_PTR CALLBACK MPlayer_Guest_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -INT_PTR CALLBACK MPlayer_Game_List_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -INT_PTR CALLBACK MPlayer_Host_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); bool Net2ReadyToGo(int load_game); int CurGame; @@ -93,28 +88,27 @@ static UILobbyPresenterClass * Lobby_Screen(void) // Is the lobby being shown through RmlUi? Latched when the lobby opens, the way every // migrated screen latches its selection at screen entry. -static bool _LobbyRml = false; int Net2LobbyScreenID(void) { UILobbyPresenterClass const * const screen = Lobby_Screen(); - if (_LobbyRml && screen != NULL) { - switch (screen->Showing) { - case UILobbyPresenterClass::SCREEN_GAME_LIST: return(IDD_MPLAYER_GAME_LIST); - case UILobbyPresenterClass::SCREEN_HOST: return(IDD_MPLAYER_HOST); - case UILobbyPresenterClass::SCREEN_GUEST: return(IDD_MPLAYER_GUEST); - default: return(0); - } + if (screen == NULL) { + return(0); } - return(WS_Top_Window_ID()); + switch (screen->Showing) { + case UILobbyPresenterClass::SCREEN_GAME_LIST: return(IDD_MPLAYER_GAME_LIST); + case UILobbyPresenterClass::SCREEN_HOST: return(IDD_MPLAYER_HOST); + case UILobbyPresenterClass::SCREEN_GUEST: return(IDD_MPLAYER_GUEST); + default: return(0); + } } /// -/// Shows one of the lobby's three screens, as a document or as its legacy dialog. +/// Shows one of the lobby's three screens. /// The screen the lobby moved away from stays alive underneath, which is what the lobby's /// own dialogs did. /// @@ -127,48 +121,13 @@ static void Lobby_Open_Screen(UILobbyPresenterClass::ScreenType kind) screen->Showing = kind; - if (_LobbyRml) { - // What the legacy dialog's WM_INITDIALOG did before it put anything on a control. - switch (kind) { - case UILobbyPresenterClass::SCREEN_GAME_LIST: screen->Open(); break; - case UILobbyPresenterClass::SCREEN_HOST: screen->Open_Host(); break; - case UILobbyPresenterClass::SCREEN_GUEST: screen->Open_Guest(); break; - default: break; - } - return; - } - - int identifier = IDD_MPLAYER_GAME_LIST; - DLGPROC procedure = MPlayer_Game_List_Dialog_Proc; - if (kind == UILobbyPresenterClass::SCREEN_HOST) { - identifier = IDD_MPLAYER_HOST; - procedure = MPlayer_Host_Dialog_Proc; - } else if (kind == UILobbyPresenterClass::SCREEN_GUEST) { - identifier = IDD_MPLAYER_GUEST; - procedure = MPlayer_Guest_Dialog_Proc; - } - - HWND const dialog = WS_Create_Dialog(ProgramInstance, identifier, MainWindow, procedure, FALSE); - Center_Window_Within_Window(dialog); - OwnerDraw::Subclass_Dialog(dialog, 0); - if (kind == UILobbyPresenterClass::SCREEN_HOST) { - SendMessage(dialog, OD_SETTOP, 0, 1); - } - ShowWindow(dialog, SW_SHOWNORMAL); -} - - -/// -/// Takes the lobby's topmost screen away. -/// -/// bool; Was there one to take away? -static bool Lobby_Close_Screen(void) -{ - if (_LobbyRml) { - return(true); + // What the legacy dialog's WM_INITDIALOG did before it put anything on a control. + switch (kind) { + case UILobbyPresenterClass::SCREEN_GAME_LIST: screen->Open(); break; + case UILobbyPresenterClass::SCREEN_HOST: screen->Open_Host(); break; + case UILobbyPresenterClass::SCREEN_GUEST: screen->Open_Guest(); break; + default: break; } - - return(WS_Destroy_Dialog(NULL, 0)); } @@ -226,53 +185,6 @@ static void Net2AnswerLobby(int response) } -/// -/// Fills a side box with the multiplayable countries, each entry carrying its country index. -/// -void Fill_Country_Box(HWND combo) -{ - SendMessage(combo, CB_RESETCONTENT, 0, 0); - for (int index = 0; index < HouseTypes.Count(); index++) { - HouseTypeClass * house = HouseTypes[index]; - if (house->IsMultiplay) { - LRESULT item = SendMessage(combo, CB_INSERTSTRING, (WPARAM)-1, (LPARAM)(char const *)house->GivenName); - SendMessage(combo, CB_SETITEMDATA, item, index); - } - } -} - - -/// -/// Fetches the country behind a side box's selection. -/// -/// Returns with the country index, or the first country with nothing selected. -int Country_From_Box(HWND combo) -{ - LRESULT item = SendMessage(combo, CB_GETCURSEL, 0, 0); - if (item == CB_ERR) { - return(HOUSE_FIRST); - } - LRESULT country = SendMessage(combo, CB_GETITEMDATA, item, 0); - return(country == CB_ERR ? HOUSE_FIRST : (int)country); -} - - -/// -/// Selects the entry of a side box carrying the given country, or the first entry when none does. -/// -void Select_Country_In_Box(HWND combo, int country) -{ - LRESULT count = SendMessage(combo, CB_GETCOUNT, 0, 0); - for (LRESULT item = 0; item < count; item++) { - if (SendMessage(combo, CB_GETITEMDATA, item, 0) == country) { - SendMessage(combo, CB_SETCURSEL, item, 0); - return; - } - } - SendMessage(combo, CB_SETCURSEL, count > 0 ? 0 : (WPARAM)-1, 0); -} - - /// /// Fetches a player color that nobody else has claimed. /// This routine is used when a player asks for a color, so that no two players in the @@ -364,72 +276,7 @@ void _Net2DisplayUsers(void) return; } - // The rows are built before anything is drawn, and before the window is even asked for, - // because this is the point the roster is known to have moved. A presentation that is not - // a window reads the model and would otherwise never be told. Lobby_Screen()->Build_User_Rows(); - - HWND win = WS_Top_Window(); - HWND userwin = win ? GetDlgItem(win, IDC_USERS) : NULL; - - if (win == NULL || userwin == NULL) { - return; - } - - OwnerDraw::CellData thecell; - - int topindex = SendDlgItemMessage(win, IDC_USERS, LB_GETTOPINDEX, 0, 0); - - SendDlgItemMessage(win, IDC_USERS, OD_DISABLEPAINT, 0, TRUE); - - Dictionary lbdict(Wstring_Hash); - LBSaveSelections(userwin, lbdict); - - SendDlgItemMessage(win, IDC_USERS, LB_RESETCONTENT, NULL, NULL); - - bool const inlobby = CurGame == 0; - - for (int i = 0; i < (int)Lobby_Screen()->Users.size(); i++) { - UILobbyPresenterClass::UserRowType const & row = Lobby_Screen()->Users[i]; - - SendDlgItemMessage(win, IDC_USERS, LB_INSERTSTRING, (WPARAM)(inlobby ? i : -1), (LPARAM)row.Name.c_str()); - - if (inlobby) { - continue; - } - - // Only two icons ship, so every side past the first borrows the second's. - Surface * surf = row.Side == SIDE_GDI - ? SurfaceCache.GetSurface("gdii.pcx") - : SurfaceCache.GetSurface("nodi.pcx"); - - thecell.type = OwnerDraw::CellData::PRIMARY; - thecell.color = PlayerColorTable[row.Color]; - thecell.hint.set(""); - SendDlgItemMessage(win, IDC_USERS, OD_SETCELL, MAKEWPARAM(Net2_g_Col_Name, i), (LPARAM)&thecell); - - thecell.type = OwnerDraw::CellData::SURFACE; - thecell.hint.set(row.SideName.c_str()); - thecell.surf = surf; - SendDlgItemMessage(win, IDC_USERS, OD_SETCELL, MAKEWPARAM(Net2_g_Col_House, i), (LPARAM)&thecell); - - thecell.hint.set(""); - thecell.type = OwnerDraw::CellData::SURFACE; - if (row.IsHost) { - thecell.surf = SurfaceCache.GetSurface("wolhost.pcx"); - } else if (row.HasAccepted) { - thecell.surf = SurfaceCache.GetSurface("wolacpt.pcx"); - } else { - thecell.type = OwnerDraw::CellData::INVALID; - } - SendDlgItemMessage(win, IDC_USERS, OD_SETCELL, MAKEWPARAM(Net2_g_Col_Accept, i), (LPARAM)&thecell); - } - - LBRestoreSelections(userwin, lbdict); - SendDlgItemMessage(win, IDC_USERS, LB_SETTOPINDEX, (WPARAM)topindex, 0); - SendDlgItemMessage(win, IDC_USERS, OD_DISABLEPAINT, 0, 0); - InvalidateRect(userwin, NULL, 0); - UpdateWindow(userwin); } @@ -522,38 +369,6 @@ void Net2DisplayGameList(void) } Lobby_Screen()->Build_Game_Rows(); - - HWND window = WS_Top_Window(); - - if (window == NULL) { - return; - } - - int top = SendDlgItemMessage(window, IDC_GAMELIST, LB_GETTOPINDEX, 0, 0); - - SendDlgItemMessage(window, IDC_GAMELIST, OD_DISABLEPAINT, 0, 1); - SendDlgItemMessage(window, IDC_GAMELIST, LB_RESETCONTENT, 0, 0); - - for (int i = 0; i < (int)Lobby_Screen()->Games.size(); i++) { - UILobbyPresenterClass::GameRowType const & row = Lobby_Screen()->Games[i]; - - if (i == 0) { - SendDlgItemMessage(window, IDC_GAMELIST, LB_INSERTSTRING, -1, (LPARAM)row.Label.c_str()); - continue; - } - - char buffer[80]; - sprintf(buffer, Fetch_String(row.IsOpen ? TXT_THATGUYS_GAME : TXT_THATGUYS_GAME_BRACKET), row.Label.c_str()); - SendDlgItemMessage(window, IDC_GAMELIST, LB_INSERTSTRING, -1, (LPARAM)buffer); - } - - SendDlgItemMessage(window, IDC_GAMELIST, LB_SETCURSEL, Lobby_Screen()->SelectedGame, 0); - SendDlgItemMessage(window, IDC_GAMELIST, LB_SETTOPINDEX, top, 0); - SendDlgItemMessage(window, IDC_GAMELIST, OD_DISABLEPAINT, 0, 0); - - HWND handle = GetDlgItem(window, IDC_GAMELIST); - InvalidateRect(handle, NULL, FALSE); - UpdateWindow(handle); } @@ -701,15 +516,7 @@ int Net2SetHouseAndColor(char *who, int house, int color) } if (offset == 0) { - HWND win=WS_Find_Dialog(IDD_MPLAYER_HOST); Session.PrefColor = color; - if (win == NULL) { - win = WS_Find_Dialog(IDD_MPLAYER_GUEST); - } - if ((SendDlgItemMessage(win,IDC_YOURCOLOR,CB_GETCURSEL,0,0) != color) && - (SendDlgItemMessage(win,IDC_YOURCOLOR,CB_GETDROPPEDSTATE,0,0) == FALSE)) { - SendDlgItemMessage(win,IDC_YOURCOLOR,CB_SETCURSEL,color,0); - } } if (offset == 0) { @@ -858,15 +665,9 @@ bool Net2Remote_Connect(void) Net2GameStarted = false; - OwnerDraw::Register_Control_Classes(); - UILobbyPresenterClass screen; UI_Set_Lobby_Screen(&screen); - // The presentation is latched here, at screen entry, and a document that will not - // prepare drops the whole family back to the legacy dialogs. - _LobbyRml = UI_Use_Rml(); - Lobby_Open_Screen(UILobbyPresenterClass::SCREEN_GAME_LIST); Net2DisplayUsers(); @@ -883,87 +684,13 @@ bool Net2Remote_Connect(void) // Pop up the network Join/New dialog //..................................................................... while (_netresponse == 0) { - if (_LobbyRml) { - UIResult const answer = UI_Lobby_Run(screen); - if (answer.Outcome == UIResult::OUTCOME_FAILED_TO_OPEN) { - // Preparation failed, so the family opens its legacy view instead, which - // is what every migrated screen does with a resource it cannot load. - UI_Lobby_Close_Views(); - _LobbyRml = false; - Lobby_Open_Screen(screen.Showing); - continue; - } - - screen.Result.reset(); - if (screen.Response != UILobbyPresenterClass::RESPONSE_NONE) { - _netresponse = Lobby_Response_Identifier(screen.Response); - screen.Response = UILobbyPresenterClass::RESPONSE_NONE; - } - continue; - } - - Sleep(0); - screen.Service(); - - MSG msg; - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - Call_Back(); + UI_Lobby_Run(screen); - // A control handler queues rather than acts, so the queue is executed here, - // after the pump has returned. The lobby's own answer is one of the - // presenter's, and the other two screens still write theirs directly. - screen.Drain(); screen.Result.reset(); if (screen.Response != UILobbyPresenterClass::RESPONSE_NONE) { _netresponse = Lobby_Response_Identifier(screen.Response); screen.Response = UILobbyPresenterClass::RESPONSE_NONE; } - - // A roster an executed intent moved is put on the controls here, after the - // queue, which is where the other rewired drivers sync their views. - if (screen.UsersChanged) { - _Net2DisplayUsers(); - } - if (screen.GamesChanged) { - Net2DisplayGameList(); - } - if (screen.OptionsChanged) { - HWND const setup = GameoptWindow(); - if (setup != NULL) { - DisplayGameopts(setup, 0); - SendDlgItemMessage(setup, IDC_SCENARIONAME, WM_SETTEXT, 0, (LPARAM)screen.ScenarioName.c_str()); - InvalidateRect(setup, NULL, FALSE); - } - } - screen.UsersChanged = false; - screen.GamesChanged = false; - screen.OptionsChanged = false; - screen.MessagesChanged = false; - - // The scenario picker draws where the host screen is, so the screen steps aside - // for it, which is what the dialog's own ShowWindow did. - if (screen.Pending != UILobbyPresenterClass::SUB_NONE) { - HWND const host = WS_Find_Dialog(IDD_MPLAYER_HOST); - if (host != NULL) { - ShowWindow(host, SW_HIDE); - } - screen.Run_Pending(); - if (host != NULL) { - ShowWindow(host, SW_SHOW); - DisplayGameopts(host, 0); - SendDlgItemMessage(host, IDC_SCENARIONAME, WM_SETTEXT, 0, (LPARAM)screen.ScenarioName.c_str()); - InvalidateRect(host, NULL, FALSE); - } - screen.OptionsChanged = false; - } - - if (_netresponse != 0) { - break; - } } //..................................................................... @@ -976,7 +703,6 @@ bool Net2Remote_Connect(void) Unjoin_Game(CurGame); Ipx.Service(); } - Lobby_Close_Screen(); UI_Lobby_Close_Views(); Clear_Vector(&Session.Players); Clear_Vector(&Session.Games); @@ -990,7 +716,6 @@ bool Net2Remote_Connect(void) if (Net2LobbyScreenID() == IDD_MPLAYER_HOST) { Unjoin_Game(CurGame); JoinState = JOIN_NOTHING; - Lobby_Close_Screen(); Lobby_Open_Screen(UILobbyPresenterClass::SCREEN_GAME_LIST); Send_Join_Queries(false, false, true, false); } @@ -1040,7 +765,6 @@ bool Net2Remote_Connect(void) Session.GameName[0] = '\0'; JoinState = JOIN_NOTHING; - Lobby_Close_Screen(); _netresponse = 0; CurGame = 0; Clear_Vector(&Session.Players); @@ -1098,7 +822,6 @@ bool Net2Remote_Connect(void) Session.PlayingAgainstVersion = VerNum.Version_Number(); Set_Scenario_Info_From_Index(Session.Options.ScenarioIndex); - Lobby_Close_Screen(); _netresponse = 0; //------------------------------------------------------------------------ @@ -1150,7 +873,6 @@ bool Net2Remote_Connect(void) _netresponse = 0; screen.CanStart = true; Net2GameStarted = false; - EnableWindow(GetDlgItem(WS_Find_Dialog(IDD_MPLAYER_HOST), IDC_GO), TRUE); } if (_netresponse == IDC_GO) { @@ -1160,8 +882,7 @@ bool Net2Remote_Connect(void) _netresponse = 0; screen.CanStart = true; Net2GameStarted = false; - EnableWindow(GetDlgItem(WS_Find_Dialog(IDD_MPLAYER_HOST), IDC_GO), TRUE); - break; + break; } } } @@ -1173,7 +894,6 @@ bool Net2Remote_Connect(void) * The guest accepted the host's "go" -- tear down the dialogs, run * the pregame setup, compute the packet timing and leave the loop. */ - while (Lobby_Close_Screen() == true) {} UI_Lobby_Close_Views(); _netresponse = 0; @@ -1207,7 +927,6 @@ bool Net2Remote_Connect(void) PMessagePrintf(-1, Fetch_String(TXT_SCENARIO_TOO_SMALL)); screen.CanStart = true; Net2GameStarted = false; - EnableWindow(GetDlgItem(WS_Find_Dialog(IDD_MPLAYER_HOST), IDC_GO), TRUE); _netresponse = 0; } else { if (_netresponse != IDC_GO) { @@ -1329,7 +1048,6 @@ bool Net2Remote_Connect(void) Hide_Mouse(); Draw_Menu_Background(); Show_Mouse(); - Lobby_Close_Screen(); UI_Lobby_Close_Views(); break; } @@ -1345,357 +1063,6 @@ bool Net2Remote_Connect(void) } /* end of Remote_Connect */ -/// -/// Handles the multiplayer game list dialog. -/// This is the lobby a player lands in before hosting or joining anything. It keeps the -/// game and user lists current, carries the lobby chat, and records which button was -/// pressed so that the driver loop knows whether to move on to the host or guest dialog. -/// -/// Returns with TRUE if the message was consumed by this dialog. -INT_PTR CALLBACK MPlayer_Game_List_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - switch (message) { - - case WM_INITDIALOG: { - if (Lobby_Screen() == NULL) { - return(0); - } - - Lobby_Screen()->Open(); - - SendDlgItemMessage(window, IDC_YOURNAME, EM_SETLIMITTEXT, UILobbyPresenterClass::HANDLE_LIMIT, 0); - SetWindowText(GetDlgItem(window, IDC_YOURNAME), Lobby_Screen()->Handle.c_str()); - return(0); - } - - case WM_COMMAND: { - if (Lobby_Screen() == NULL) { - return(0); - } - - switch (LOWORD(wparam)) { - - case IDC_YOURNAME: { - char name_buf[64]; - - SendDlgItemMessage(window, IDC_YOURNAME, WM_GETTEXT, 63, (LPARAM)name_buf); - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_RENAME, name_buf, 0}); - return(0); - } - - case IDCANCEL: { - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_CANCEL, "", 0}); - return(0); - } - - case IDC_GAMELIST_NEW: { - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_NEW, "", 0}); - return(0); - } - - case IDC_INPUT: { - if (HIWORD(wparam) != EN_MAXTEXT) { - return(0); - } - - char text[260]; - SendDlgItemMessage(window, IDC_INPUT, WM_GETTEXT, 256, (LPARAM)text); - SendDlgItemMessage(window, IDC_INPUT, WM_SETTEXT, 0, (LPARAM) ""); - - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SAY, text, 0}); - return(0); - } - - case IDC_YOURCOLOR: { - if (HIWORD(wparam) == LBN_SELCHANGE) { - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_COLOR, "", - (int)SendDlgItemMessage(window, IDC_YOURCOLOR, LB_GETCURSEL, 0, 0)}); - } - return(0); - } - - case IDC_GAMELIST: { - if (HIWORD(wparam) == LBN_SELCHANGE) { - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_PICK_GAME, "", - (int)SendDlgItemMessage(window, IDC_GAMELIST, LB_GETCURSEL, 0, 0)}); - return(0); - } - - if (HIWORD(wparam) == LBN_DBLCLK) { - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_JOIN, "", 0}); - return(0); - } - - return(0); - } - - case IDC_GAMELIST_JOIN: { - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_JOIN, "", 0}); - return(0); - } - } - - return(0); - } - - case OD_SUBCLASSED: { - Net2DisplayGameList(); - _Net2DisplayUsers(); - OwnerDraw::Draw_Dialog_Back(window); - return(0); - } - - case WM_DRAWITEM: - OwnerDraw::Draw_Item((DRAWITEMSTRUCT *)lparam); - return(1); - - case WM_PAINT: - OwnerDraw::Draw_Dialog_Back(window); - ValidateRect(window, NULL); - return(0); - - case WM_ERASEBKGND: - return(1); - } - - return(0); -} - - -/// -/// Handles the multiplayer host dialog. -/// This is the setup dialog belonging to the player who created the game. It owns the -/// game option controls, the scenario picker, and the player list along with the means to -/// kick somebody out of it -- and finally the button that starts the match. -/// -/// Returns with TRUE if the message was consumed by this dialog. -INT_PTR CALLBACK MPlayer_Host_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - switch (message) { - - case WM_DESTROY: - return(0); - - case WM_DRAWITEM: - OwnerDraw::Draw_Item((DRAWITEMSTRUCT *)lparam); - - case WM_ERASEBKGND: - return(TRUE); - - case WM_PAINT: - OwnerDraw::Draw_Dialog_Back(window); - if (MultiplayerMapPreview != NULL) { - MultiplayerMapPreview->Blit_Preview(window); - } - ValidateRect(window, NULL); - return(0); - - case WM_INITDIALOG: { - if (Lobby_Screen() == NULL) { - return(0); - } - - Center_Window_Within_Window(window); - - Lobby_Screen()->Open_Host(); - - Fill_Country_Box(GetDlgItem(window, IDC_YOURSIDE)); - SendDlgItemMessage(window, IDC_YOURSIDE, CB_SETCURSEL, Lobby_Screen()->SelectedSide, 0); - - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_RESETCONTENT, 0, 0); - for (std::string const & name : Lobby_Screen()->Colors) { - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_INSERTSTRING, (WPARAM)-1, (LPARAM)name.c_str()); - } - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_SETCURSEL, Lobby_Screen()->Color, 0); - - SendDlgItemMessage(window, IDC_SCENARIONAME, WM_SETTEXT, 0, (LPARAM)Lobby_Screen()->ScenarioName.c_str()); - - DisplayGameopts(window, 1); - InvalidateRect(window, NULL, FALSE); - - return(0); - } - - case WM_HSCROLL: - case WM_VSCROLL: { - if (Net2GameStarted || Lobby_Screen() == NULL) return(0); - - // Every bar is read back on any one of them moving, which is what the dialog's own - // handler did rather than reading only the control that reported. - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SLIDER, UI_LOBBY_UNITCOUNT, - (int)SendDlgItemMessage(window, IDC_UNITCOUNT, TBM_GETPOS, 0, 0)}); - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SLIDER, UI_LOBBY_TECHLEVEL, - (int)SendDlgItemMessage(window, IDC_TECHLEVEL, TBM_GETPOS, 0, 0)}); - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SLIDER, UI_LOBBY_CREDITS, - (int)SendDlgItemMessage(window, IDC_CREDITS, TBM_GETPOS, 0, 0)}); - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SLIDER, UI_LOBBY_AIPLAYERS, - (int)SendDlgItemMessage(window, IDC_AIPLAYERS, TBM_GETPOS, 0, 0)}); - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SLIDER, UI_LOBBY_AILEVEL, - (int)SendDlgItemMessage(window, IDC_AILEVEL_SLIDER, TBM_GETPOS, 0, 0)}); - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SLIDER, UI_LOBBY_GAMESPEED, - (int)SendDlgItemMessage(window, IDC_GAME_SPEED_SLIDER, TBM_GETPOS, 0, 0)}); - - return(0); - } - - case WM_COMMAND: { - if (Lobby_Screen() == NULL) { - return(0); - } - - switch (LOWORD(wparam)) { - - case IDC_YOURSIDE: - if (HIWORD(wparam) == CBN_SELCHANGE && !Net2GameStarted) { - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_HOST_SIDE, "", - (int)SendDlgItemMessage(window, IDC_YOURSIDE, CB_GETCURSEL, 0, 0)}); - } - return(0); - - case IDC_YOURCOLOR: - if (HIWORD(wparam) == CBN_SELCHANGE && !Net2GameStarted) { - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_HOST_COLOR, "", - (int)SendDlgItemMessage(window, IDC_YOURCOLOR, CB_GETCURSEL, 0, 0)}); - } - return(0); - - case IDC_INPUT: { - if (HIWORD(wparam) != EN_MAXTEXT) { - return(0); - } - - char text[260]; - SendDlgItemMessage(window, IDC_INPUT, WM_GETTEXT, 256, (LPARAM)text); - SendDlgItemMessage(window, IDC_INPUT, WM_SETTEXT, 0, (LPARAM)""); - - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SAY, text, 0}); - return(0); - } - - case IDCANCEL: - if (!Net2GameStarted) { - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_CANCEL, "", 0}); - } - return(0); - - case IDC_GO: - // Taking the button away is the view's, the way it is on the guest screen; the - // driver puts it back when it refuses to start the game. - EnableWindow(GetDlgItem(window, IDC_GO), FALSE); - Net2GameStarted = true; - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_GO, "", 0}); - return(0); - - case IDC_KICK: { - HWND const userwin = GetDlgItem(window, IDC_USERS); - int const count = (int)SendMessage(userwin, LB_GETSELCOUNT, 0, 0); - if (count > 0) { - std::vector rows((std::size_t)count, 0); - SendMessage(userwin, LB_GETSELITEMS, (WPARAM)count, (LPARAM)rows.data()); - for (int const row : rows) { - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_PICK_USER, "", row}); - } - } - - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_KICK, "", 0}); - - SendMessage(userwin, LB_SELITEMRANGE, 0, MAKELPARAM(0, -1)); - return(0); - } - - case IDC_MULTIMAP: - if (Net2GameStarted) return(0); - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_PICK_MAP, "", 0}); - return(0); - - case IDC_BASES: - if (Net2GameStarted) return(0); - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_TOGGLE, UI_LOBBY_BASES, 0}); - return(0); - - case IDC_SHORT_GAME: - if (Net2GameStarted) return(0); - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_TOGGLE, UI_LOBBY_SHORTGAME, 0}); - return(0); - - case IDC_CRATES: - if (Net2GameStarted) return(0); - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_TOGGLE, UI_LOBBY_CRATES, 0}); - return(0); - - case IDC_FOG_OF_WAR: - if (Net2GameStarted) return(0); - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_TOGGLE, UI_LOBBY_FOG, 0}); - return(0); - - case IDC_BRIDGE_DESTROY: - if (Net2GameStarted) return(0); - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_TOGGLE, UI_LOBBY_BRIDGES, 0}); - return(0); - - case IDC_REDEPLOY_MCV: - if (Net2GameStarted) return(0); - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_TOGGLE, UI_LOBBY_MCV, 0}); - return(0); - - case IDC_MULTI_ENGINEER: - if (Net2GameStarted) return(0); - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_TOGGLE, UI_LOBBY_ENGINEER, 0}); - return(0); - - case IDC_ALLIES: - if (Net2GameStarted) return(0); - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_TOGGLE, UI_LOBBY_ALLIES, 0}); - return(0); - - case IDC_HARVTRUCE: - if (Net2GameStarted) return(0); - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_TOGGLE, UI_LOBBY_HARVTRUCE, 0}); - return(0); - - default: - return(0); - } - } - - case OD_SUBCLASSED: { - Net2_g_Col_Accept = 5; - Net2_g_Col_Name = 45; - Net2_g_Col_House = 25; - - SendDlgItemMessage(window, IDC_USERS, OD_ADDCOLUMN, 0, Net2_g_Col_Name); - SendDlgItemMessage(window, IDC_USERS, OD_ADDCOLUMN, 0, Net2_g_Col_House); - SendDlgItemMessage(window, IDC_USERS, OD_ADDCOLUMN, 0, Net2_g_Col_Accept); - SendDlgItemMessage(window, IDC_USERS, OD_TOOLTIPS, 0, 1); - - for (int i = 0; i < ARRAY_SIZE(PlayerColorTable); i++) { - SendDlgItemMessage(window, IDC_YOURCOLOR, OD_SETCOLOR, i, (LPARAM)PlayerColorTable[i]); - } - - SendDlgItemMessage(window, IDC_KICK, OD_TOOLTIPS, 0, 1); - SendDlgItemMessage(window, IDC_KICK, OD_SETIMAGE, 0, (LPARAM)SurfaceCache.GetSurface("woukick.pcx")); - SendDlgItemMessage(window, IDC_KICK, OD_SETALTIMAGE, 0, (LPARAM)SurfaceCache.GetSurface("wodkick.pcx")); - - if (!Net2GameStarted) { - DisplayGameopts(window, 1); - } - - _Net2DisplayUsers(); - Net2DisplayGameList(); - return(0); - } - - case OD_GETTIPTEXT: { - HWND ctrl = GetDlgItem(window, wparam); - GetWindowText(ctrl, (LPSTR)lparam, 127); - return(0); - } - } - - return(0); -} - - /*************************************************************************** * Request_To_Join -- Sends a JOIN request packet to game owner * * * @@ -2369,7 +1736,6 @@ static void Get_Join_Responses(void) if (Lobby_Screen() != NULL) { Lobby_Screen()->CanAccept = true; } - EnableWindow(GetDlgItem(GameoptWindow(), IDC_ACCEPT), TRUE); } } @@ -2409,7 +1775,6 @@ static void Get_Join_Responses(void) Session.Players.Add (who); Net2IsGameListActive = false; - Lobby_Close_Screen(); _netresponse = 0; Lobby_Open_Screen(UILobbyPresenterClass::SCREEN_GUEST); display_users = true; @@ -2504,7 +1869,7 @@ static void Get_Join_Responses(void) item = (char *)Fetch_String(TXT_SERIAL_DUP); } if (item) { - ODMessageBox(item, 0, Net2Callback, 0); + ODMessageBox(item, 0, Net2Callback); } if ( Net2LobbyScreenID() != IDD_MPLAYER_GAME_LIST ) { Net2AnswerLobby(IDCANCEL); @@ -2693,7 +2058,6 @@ static void Get_Join_Responses(void) if (Lobby_Screen() != NULL) { Lobby_Screen()->CanAccept = true; } - EnableWindow(GetDlgItem(GameoptWindow(), IDC_ACCEPT), TRUE); } } } @@ -3142,152 +2506,3 @@ bool Net2ReadyToGo(int load_game) return(true); } - - -/// -/// Handles the multiplayer guest dialog. -/// This is the setup dialog a player works in after joining somebody else's game. The -/// guest picks a side and a color here, chats with the rest of the players, and tells the -/// host when it is happy for the game to begin. -/// -/// Returns with TRUE if the message was consumed by this dialog. -INT_PTR CALLBACK MPlayer_Guest_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - switch (message) { - - case WM_INITDIALOG: { - Fill_Country_Box(GetDlgItem(window, IDC_YOURSIDE)); - Select_Country_In_Box(GetDlgItem(window, IDC_YOURSIDE), Session.House); - - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_RESETCONTENT, 0, 0); - - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_INSERTSTRING, (WPARAM)-1, (LPARAM)Fetch_String(TXT_GOLD)); - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_INSERTSTRING, (WPARAM)-1, (LPARAM)Fetch_String(TXT_RED)); - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_INSERTSTRING, (WPARAM)-1, (LPARAM)Fetch_String(TXT_BLUE)); - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_INSERTSTRING, (WPARAM)-1, (LPARAM)Fetch_String(TXT_GREEN)); - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_INSERTSTRING, (WPARAM)-1, (LPARAM)Fetch_String(TXT_ORANGE)); - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_INSERTSTRING, (WPARAM)-1, (LPARAM)Fetch_String(TXT_SKY_BLUE)); - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_INSERTSTRING, (WPARAM)-1, (LPARAM)Fetch_String(TXT_PURPLE)); - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_INSERTSTRING, (WPARAM)-1, (LPARAM)Fetch_String(TXT_PINK)); - - SendDlgItemMessage(window, IDC_YOURCOLOR, CB_SETCURSEL, Session.ColorIdx, 0); - - EnableWindow(GetDlgItem(window, IDC_ACCEPT), FALSE); - - if (Lobby_Screen() != NULL) { - Lobby_Screen()->Open_Guest(); - EnableWindow(GetDlgItem(window, IDC_ACCEPT), Lobby_Screen()->CanAccept ? TRUE : FALSE); - } - - _Net2DisplayUsers(); - return(0); - } - - case WM_COMMAND: { - if (Lobby_Screen() == NULL) { - return(0); - } - - switch (LOWORD(wparam)) { - - case IDC_ACCEPT: { - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_ACCEPT, "", 0}); - - // Taking the button away is the view's, the way getting out of a browser's way - // stayed with the view at step 7. What the host does to put it back is not - // extracted yet. - EnableWindow(GetDlgItem(window, IDC_ACCEPT), FALSE); - InvalidateRect(GetDlgItem(window, IDC_ACCEPT), NULL, FALSE); - return(0); - } - - case IDC_YOURSIDE: - case IDC_YOURCOLOR: { - if (HIWORD(wparam) == CBN_SELCHANGE) { - // The side is recorded ahead of the color, because the dialog read both of - // its boxes and sent one packet carrying the pair. - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SIDE, "", - Country_From_Box(GetDlgItem(window, IDC_YOURSIDE))}); - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_IDENTITY, "", - (int)SendDlgItemMessage(window, IDC_YOURCOLOR, CB_GETCURSEL, 0, 0)}); - } - return(0); - } - - case IDCANCEL: { - if (!Net2GameStarted) { - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_CANCEL, "", 0}); - } - return(0); - } - - case IDC_INPUT: { - if (HIWORD(wparam) != EN_MAXTEXT) { - return(0); - } - - char text[260]; - SendDlgItemMessage(window, IDC_INPUT, WM_GETTEXT, 256, (LPARAM)text); - SendDlgItemMessage(window, IDC_INPUT, WM_SETTEXT, 0, (LPARAM)""); - - Lobby_Screen()->Queue(UIIntent{UI_LOBBY_SAY, text, 0}); - return(0); - } - } - - return(0); - } - - case OD_SUBCLASSED: { - Net2_g_Col_Accept = 5; - Net2_g_Col_Name = 45; - Net2_g_Col_House = 25; - - SendDlgItemMessage(window, IDC_USERS, OD_ADDCOLUMN, 0, 45); - SendDlgItemMessage(window, IDC_USERS, OD_ADDCOLUMN, 0, Net2_g_Col_House); - SendDlgItemMessage(window, IDC_USERS, OD_ADDCOLUMN, 0, Net2_g_Col_Accept); - SendDlgItemMessage(window, IDC_USERS, OD_TOOLTIPS, 0, 1); - - for (int i = 0; i < ARRAY_SIZE(PlayerColorTable); i++) { - SendDlgItemMessage(window, IDC_YOURCOLOR, OD_SETCOLOR, i, (LPARAM)PlayerColorTable[i]); - } - - _Net2DisplayUsers(); - Net2DisplayGameList(); - DisplayGameopts(window, 1); - - HWND combo = GetDlgItem(window, IDC_YOURSIDE); - SendMessage(window, WM_COMMAND, MAKEWPARAM(IDC_YOURSIDE, CBN_SELCHANGE), (LPARAM)combo); - - return(0); - } - - case WM_DRAWITEM: - OwnerDraw::Draw_Item((DRAWITEMSTRUCT *)lparam); - return(1); - - case WM_DESTROY: { - if (MultiplayerMapPreview != NULL) { - delete MultiplayerMapPreview; - MultiplayerMapPreview = 0; - } - return(0); - } - - case WM_PAINT: { - OwnerDraw::Draw_Dialog_Back(window); - - if (MultiplayerMapPreview) { - MultiplayerMapPreview->Blit_Preview(window); - } - - ValidateRect(window, NULL); - return(0); - } - - case WM_ERASEBKGND: - return(1); - } - - return(0); -} diff --git a/code/netdlg2.h b/code/netdlg2.h index 33eca0267..490b752e3 100644 --- a/code/netdlg2.h +++ b/code/netdlg2.h @@ -40,9 +40,6 @@ void Net2ServiceLobby(void); int Net2LobbyScreenID(void); int Net2FirstFreeColor(int reqcolor, int index); -void Fill_Country_Box(HWND combo); -int Country_From_Box(HWND combo); -void Select_Country_In_Box(HWND combo, int country); bool Net2Callback(void); void Net2DisplayUsers(void); bool Net2Init_Network(void); diff --git a/code/netshare.cpp b/code/netshare.cpp index b78431222..e4700b186 100644 --- a/code/netshare.cpp +++ b/code/netshare.cpp @@ -30,14 +30,12 @@ #include "netdlg.h" #include "netdlg2.h" #include "newmenu.h" -#include "ownrdraw.h" #include "rules.h" #include "scenario.h" #include "sendfile.h" #include "session.h" #include "stimer.h" #include "wdtnet.h" -#include "windlg.h" #include "utf8.h" #include "worlddom.h" #include "wstring.h" @@ -104,30 +102,6 @@ unsigned int Wstring_Hash(Wstring & string) } -/// -/// Fetches the game options dialog that is currently up. -/// The same options are presented by four different dialogs depending on how the game was -/// started. Use this routine rather than trying to remember which one the player is looking -/// at. -/// -/// Returns with the handle of the open game options dialog. NULL is returned if -/// none of them is up. -HWND GameoptWindow(void) -{ - HWND dialog; - - dialog = WS_Find_Dialog(IDD_MPLAYER_HOST); - if (dialog) { - return(dialog); - } - dialog = WS_Find_Dialog(IDD_MPLAYER_GUEST); - if (dialog) { - return(dialog); - } - return(0); -} - - /// /// Prints a formatted chat message to the player. /// This routine hunts down the topmost dialog that has somewhere to show public and private @@ -152,177 +126,6 @@ void __cdecl PMessagePrintf(int color, const char * fmt, ...) lobby->Record_Message(color, buffer); } - if (WS_Top_Window() != 0) { - HWND top = WS_Top_Window(); - HWND msg = GetDlgItem(top, IDC_PMESSAGES); - while (msg == 0) { - top = WS_Next_Lower_Dialog(top); - if (top == 0) { - msg = 0; - break; - } - msg = GetDlgItem(top, IDC_PMESSAGES); - } - - if (msg != 0) { - _DrawMessage(color, buffer, msg); - } - } -} - - -/// -/// Prints a formatted system message to the player. -/// This routine hunts down the topmost dialog that has somewhere to show system messages and -/// puts the text there, so the caller does not have to know which dialog the player is -/// looking at. If no dialog wants system messages, the message is quietly dropped. -/// -/// Color to display the message in, or -1 for the default. -/// Printf style format string for the message. -void __cdecl SMessagePrintf(int color, const char * fmt, ...) -{ - va_list va; - static char buffer[1024]; - memset(buffer, 0, sizeof(buffer)); - - va_start(va, fmt); - vsprintf(buffer, fmt, va); - va_end(va); - - if (WS_Top_Window() != 0) { - HWND top = WS_Top_Window(); - HWND msg = GetDlgItem(top, IDC_SMESSAGES); - while (msg == 0) { - top = WS_Next_Lower_Dialog(top); - if (top == 0) { - msg = 0; - break; - } - msg = GetDlgItem(top, IDC_SMESSAGES); - } - - if (msg != 0) { - _DrawMessage(color, buffer, msg); - } - } -} - - -/// -/// Draws a message into a message list box. -/// This routine word wraps the message to the width of the list box and adds each resulting -/// line as its own entry, so that a long chat message stays readable. Embedded newlines -/// break the text as well. -/// -/// Color to display the message in, or -1 for the list box -/// default. -/// The text to display. -/// The message list box to display the text in. -void _DrawMessage(int color, const char * message, HWND window) -{ - RECT rect; - - int length = strlen(message); - - int offset = 18; - Get_Display_Rect(window, &rect); - if (SendMessage(window, OD_HASATTACHED, 0, 0)) { - offset = 1; - } - - HDC hdc = GetDC(window); - SendMessage(window, OD_RESTOREDC, 0, (LPARAM)hdc); - - while (length) { - if (message != NULL) { - char const * newline = strchr(message, '\n'); - if (newline != NULL) { - int linelen = newline - message + 1; - if (length >= linelen) { - length = linelen; - } - } - } - - SIZE size; - GetTextExtentPoint32(hdc, message, length, &size); - int maxWidth = rect.right - rect.left - offset; - - if (size.cx >= maxWidth - 4) { - int reduceBy; - if (size.cx / 2 > maxWidth) { - reduceBy = 10; - length -= reduceBy; - } else { - reduceBy = 1; - } - - int found = -1; - int idx = length - 1; - - while (idx > 0) { - if (!isgraph((unsigned char)message[idx])) { - found = idx; - break; - } - idx--; - } - - if (found == -1) { - length -= reduceBy; - found = length; - } - length = found; - } else { - _SetMessageString(window, message, length, color); - message += length; - length = strlen(message); - } - } - - ReleaseDC(window, hdc); -} - - -/// -/// Adds a single line of text to a message list box. -/// This is the low level routine that _DrawMessage uses once it has decided where the text -/// should break. The list box is capped, so a long game does not pile up messages without -/// limit. -/// -/// The message list box to add the line to. -/// The text to add; only the leading characters are taken. -/// Number of characters of the message to add. -/// Color to display the line in, or -1 for the list box default. -void _SetMessageString(HWND window, const char * message, int length, int color) -{ - static char buffer[1024]; - memset(buffer, 0, sizeof(buffer)); - strncpy(buffer, message, length); - char * line_end = strchr(buffer, '\r'); - if (line_end != NULL) { - line_end[0] = '\0'; - } else { - line_end = strchr(buffer, '\n'); - if (line_end != NULL) { - line_end[0] = '\0'; - } - } - - int old = SendMessage(window, OD_DISABLEPAINT, 0, 1); - int topindex = ListBox_GetCount(window); - if (topindex > 500) { - ListBox_DeleteString(window, 0); - topindex--; - } - - int index = ListBox_InsertString(window, -1, buffer); - if (color != -1) { - SendMessage(window, OD_SETCOLOR, index, color); - } - - ListBox_SetTopIndex(window, topindex); - SendMessage(window, OD_DISABLEPAINT, 0, old); } @@ -373,186 +176,32 @@ int CountAliveTeams(HouseClass * house) /// message. /// The button layout to use; MB_OK, MB_OKCANCEL or MB_YESNO. /// Idle routine to poll while the box is up. -/// Should the large version of the box be used? /// Returns with the control ID of the button the player pressed. Zero is returned /// if there was nothing to display. -int ODMessageBox(const char * text, int type, bool (*callback)(void), bool large) +int ODMessageBox(const char * text, int type, bool (*callback)(void)) { if (text != NULL && strlen(text) > 0) { - // The presentation is latched here, at screen entry. The box carries the captions the - // three templates hold and the caller's poll goes to the screen's service, which is - // what WS_Wait_Dialog did with it. - if (UI_Use_Rml()) { - char const * const ok = Fetch_String(TXT_OK); - char const * first = ok; - char const * second = NULL; - if (type == MB_OKCANCEL) { - second = Fetch_String(TXT_CANCEL); - } else if (type == MB_YESNO) { - first = Fetch_String(TXT_YES); - second = Fetch_String(TXT_NO); - } - - UIResult const result = UI_Message_Box_Screen(text, 0, first, second, NULL, callback); - if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { - if (type == MB_YESNO) { - return(result.Value == 0 ? IDYES : IDNO); - } - return(result.Value == 0 ? IDOK : IDCANCEL); - } - } - - HWND dialog; + // The box carries the captions the three templates held and the caller's poll goes to + // the screen's service, which is what the dialog's wait loop did with it. + char const * const ok = Fetch_String(TXT_OK); + char const * first = ok; + char const * second = NULL; if (type == MB_OKCANCEL) { - dialog = WS_Create_Dialog(ProgramInstance, IDD_MSGBOX_2, MainWindow, ODMessageBox_Proc, false); - } else { - if (large) { - dialog = WS_Create_Dialog(ProgramInstance, IDD_MSGBOX_3_LARGE, MainWindow, ODMessageBox_Proc, false); - } else { - dialog = WS_Create_Dialog(ProgramInstance, IDD_MSGBOX_3_SMALL, MainWindow, ODMessageBox_Proc, false); - } - if (type == MB_OK) { - HWND ok = GetDlgItem(dialog, IDOK); - SetWindowLong(ok, GWL_STYLE, GetWindowLong(ok, GWL_STYLE) | WS_VISIBLE); - } - if (type == MB_YESNO) { - HWND yes = GetDlgItem(dialog, IDYES); - SetWindowLong(yes, GWL_STYLE, GetWindowLong(yes, GWL_STYLE) | WS_VISIBLE); - HWND no = GetDlgItem(dialog, IDNO); - SetWindowLong(no, GWL_STYLE, GetWindowLong(no, GWL_STYLE) | WS_VISIBLE); - } + second = Fetch_String(TXT_CANCEL); + } else if (type == MB_YESNO) { + first = Fetch_String(TXT_YES); + second = Fetch_String(TXT_NO); } - Center_Window_Within_Window(dialog); - SendDlgItemMessage(dialog, IDC_MSGBOX_TEXT, WM_SETTEXT, 0, (LPARAM)text); - OwnerDraw::Subclass_Dialog(dialog, 0); - ShowWindow(dialog, SW_NORMAL); - return(WS_Wait_Dialog(dialog, callback)); - } - return(0); -} + UIResult const result = UI_Message_Box_Screen(text, 0, first, second, NULL, callback); -/// -/// Handles the messages for the owner drawn message box. -/// This routine paints the box through the owner draw system and tears it down with -/// whichever of the buttons the player pressed. -/// -/// Returns with TRUE if the message was dealt with here, FALSE otherwise. -INT_PTR CALLBACK ODMessageBox_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - switch (message) { - case WM_DRAWITEM: - OwnerDraw::Draw_Item((LPDRAWITEMSTRUCT)lparam); - return(TRUE); - - case WM_PAINT: - OwnerDraw::Draw_Dialog_Back(window); - ValidateRect(window, NULL); - return(TRUE); - - case WM_ERASEBKGND: - return(TRUE); - - case WM_COMMAND: - switch (LOWORD(wparam)) { - case IDOK: - case IDCANCEL: - case IDYES: - case IDNO: - WS_Destroy_Dialog(window, LOWORD(wparam)); - return(TRUE); - } - break; - } - return(FALSE); -} - - -/// -/// Displays the current game options in the setup dialog. -/// This routine pushes the session options out to the sliders and check boxes. On the -/// initializing pass it also establishes the slider ranges and greys out whichever options a -/// World Domination Tour territory refuses to let the players meddle with. -/// -/// The game options dialog to update. -/// Is this the first call for a freshly created dialog? -void DisplayGameopts(HWND window, BOOL initialize) -{ - #define MP_MIN_MONEY 2500 - - if (initialize) { - if (Session.Type == GAME_INTERNET && Session.IsWDT) { - WDTTerritory * territory = WDT_Get_Territory(Session.WDTTerritory); - if (territory != NULL) { - EnableWindow(GetDlgItem(window, IDC_YOURSIDE), FALSE); - EnableWindow(GetDlgItem(window, IDC_AIPLAYERS), FALSE); - EnableWindow(GetDlgItem(window, IDC_AILEVEL_SLIDER), FALSE); /// AI Difficulty - - if (!territory->UserModUnitCount) { - EnableWindow(GetDlgItem(window, IDC_UNITCOUNT), FALSE); - } - if (!territory->UserModTechLevel) { - EnableWindow(GetDlgItem(window, IDC_TECHLEVEL), FALSE); - } - if (!territory->UserModCredits) { - EnableWindow(GetDlgItem(window, IDC_CREDITS), FALSE); - } - if (!territory->UserModAlliances) { - EnableWindow(GetDlgItem(window, IDC_ALLIES), FALSE); - } - if (!territory->UserModHarvesterTruce) { - EnableWindow(GetDlgItem(window, IDC_HARVTRUCE), FALSE); - } - if (!territory->UserModBases) { - EnableWindow(GetDlgItem(window, IDC_BASES), FALSE); - } - if (!territory->UserModMCVRedeploy) { - EnableWindow(GetDlgItem(window, IDC_REDEPLOY_MCV), FALSE); /// Re-Deployable MCV - } - if (!territory->UserModFogOfWar) { - EnableWindow(GetDlgItem(window, IDC_FOG_OF_WAR), FALSE); /// Fog of War - } - if (!territory->UserModBridgeDestruction) { - EnableWindow(GetDlgItem(window, IDC_BRIDGE_DESTROY), FALSE); - } - if (!territory->UserModCrates) { - EnableWindow(GetDlgItem(window, IDC_CRATES), FALSE); - } - if (!territory->UserModShortGame) { - EnableWindow(GetDlgItem(window, IDC_SHORT_GAME), FALSE); /// Short Game - } - if (!territory->UserModCrapEngineer) { - EnableWindow(GetDlgItem(window, IDC_MULTI_ENGINEER), FALSE); /// Crap Engineers - } - } + if (type == MB_YESNO) { + return(result.Value == 0 ? IDYES : IDNO); } - SendDlgItemMessage(window, IDC_UNITCOUNT, TBM_SETRANGE, TRUE, MAKELONG(1, 10)); - SendDlgItemMessage(window, IDC_TECHLEVEL, TBM_SETRANGE, TRUE, MAKELONG(1, MPLAYER_BUILD_LEVEL_MAX)); - SendDlgItemMessage(window, IDC_CREDITS, TBM_SETRANGE, TRUE, MAKELONG(MP_MIN_MONEY, Rule->MPMaxMoney)); - SendDlgItemMessage(window, IDC_CREDITS, OD_SETTRACKSTEP, 0, 100); - SendDlgItemMessage(window, IDC_AIPLAYERS, TBM_SETRANGE, TRUE, MAKELONG(0, 6)); - SendDlgItemMessage(window, IDC_AILEVEL_SLIDER, TBM_SETRANGE, TRUE, MAKELONG(0, 2)); /// AI Difficulty - SendDlgItemMessage(window, IDC_GAME_SPEED_SLIDER, TBM_SETRANGE, TRUE, MAKELONG(0, 6)); /// Game Speed + return(result.Value == 0 ? IDOK : IDCANCEL); } - - SendDlgItemMessage(window, IDC_UNITCOUNT, TBM_SETPOS, TRUE, Session.Options.UnitCount); - SendDlgItemMessage(window, IDC_TECHLEVEL, TBM_SETPOS, TRUE, BuildLevel); - SendDlgItemMessage(window, IDC_CREDITS, TBM_SETPOS, TRUE, Session.Options.Credits); - SendDlgItemMessage(window, IDC_AIPLAYERS, TBM_SETPOS, TRUE, Session.Options.AIPlayers); - SendDlgItemMessage(window, IDC_AILEVEL_SLIDER, TBM_SETPOS, TRUE, Session.Options.AIDifficulty); - SendDlgItemMessage(window, IDC_GAME_SPEED_SLIDER, TBM_SETPOS, TRUE, 6 - Session.Options.GameSpeed); - - int button_state[] = { BST_UNCHECKED, BST_CHECKED }; - SendDlgItemMessage(window, IDC_BRIDGE_DESTROY, BM_SETCHECK, button_state[Session.Options.BridgeDestruction], 0); - SendDlgItemMessage(window, IDC_FOG_OF_WAR, BM_SETCHECK, button_state[Session.Options.FogOfWar], 0); - SendDlgItemMessage(window, IDC_CRATES, BM_SETCHECK, button_state[Session.Options.Goodies], 0); - SendDlgItemMessage(window, IDC_ALLIES, BM_SETCHECK, button_state[Session.Options.AlliesAllowed], 0); - SendDlgItemMessage(window, IDC_HARVTRUCE, BM_SETCHECK, button_state[Session.Options.HarvTruce], 0); - SendDlgItemMessage(window, IDC_BASES, BM_SETCHECK, button_state[Session.Options.Bases], 0); - SendDlgItemMessage(window, IDC_REDEPLOY_MCV, BM_SETCHECK, button_state[Session.Options.MCVRedeploy], 0); - SendDlgItemMessage(window, IDC_SHORT_GAME, BM_SETCHECK, button_state[Session.Options.ShortGame], 0); - SendDlgItemMessage(window, IDC_MULTI_ENGINEER, BM_SETCHECK, button_state[Session.Options.CrapEngineers], 0); + return(0); } @@ -774,7 +423,6 @@ bool DecodePubGameopt(char * options, char * name) return(false); } - SendDlgItemMessage(GameoptWindow(), IDC_USERS, OD_DISABLEPAINT, 0, 1); DebugString("Decoding game options %s\n", options); token = strtok(token, ","); @@ -918,8 +566,6 @@ bool DecodePubGameopt(char * options, char * name) } } - SendDlgItemMessage(GameoptWindow(), IDC_SCENARIONAME, WM_SETTEXT, 0, (LPARAM)Session.Options.ScenarioDescription); - Scen->Scenario = -1; Frame = 0; Session.CommProtocol = DEFAULT_COMM_PROTOCOL; @@ -930,7 +576,7 @@ bool DecodePubGameopt(char * options, char * name) if (!same_scenario) { DebugString("Not same scenario..."); - Update_Network_Dialog_Preview(GameoptWindow()); + Rebuild_Network_Map_Preview(); } if (digest == NULL) { @@ -972,8 +618,6 @@ bool DecodePubGameopt(char * options, char * name) lobby->Options_Received(); } - DisplayGameopts(GameoptWindow(), false); - if (_last_unit_count != Session.Options.UnitCount) do_decode = true; if (_last_tech_level != BuildLevel) do_decode = true; if (_last_credits != Session.Options.Credits) do_decode = true; @@ -1002,25 +646,13 @@ bool DecodePubGameopt(char * options, char * name) char buffer[64]; sprintf(buffer, "A0"); SendPublicGameopts(buffer); + } - if (UILobbyPresenterClass * const lobby = UI_Lobby_Screen()) { - lobby->CanAccept = true; - } - EnableWindow(GetDlgItem(GameoptWindow(), IDC_ACCEPT), TRUE); - InvalidateRect(GetDlgItem(GameoptWindow(), IDC_ACCEPT), NULL, FALSE); - } else { - if (UILobbyPresenterClass * const lobby = UI_Lobby_Screen()) { - lobby->CanAccept = true; - } - if (!IsWindowEnabled(GetDlgItem(GameoptWindow(), IDC_ACCEPT))) { - EnableWindow(GetDlgItem(GameoptWindow(), IDC_ACCEPT), TRUE); - InvalidateRect(GetDlgItem(GameoptWindow(), IDC_ACCEPT), NULL, FALSE); - } + if (UILobbyPresenterClass * const lobby = UI_Lobby_Screen()) { + lobby->CanAccept = true; } } - SendDlgItemMessage(GameoptWindow(), IDC_USERS, OD_DISABLEPAINT, 0, 0); - Net2DisplayUsers(); _last_unit_count = Session.Options.UnitCount; @@ -1047,83 +679,6 @@ bool DecodePubGameopt(char * options, char * name) } -/// -/// Saves the current selection of a list box. -/// The multiplayer dialogs rebuild their list boxes from scratch whenever the game state -/// changes. Call this routine first so that whatever the player had highlighted can be put -/// back afterwards. -/// -/// The list box to record the selection of. -/// The dictionary to record the selected entries into. -void LBSaveSelections(HWND listbox, Dictionary & lbdict) -{ - int count = ListBox_GetCount(listbox); - char buffer[128]; - Wstring key; - - if (count) { - int style = GetWindowLong(listbox, GWL_STYLE); - if (style & LBS_MULTIPLESEL) { - for (int i = 0; i < count; i++) { - if (ListBox_GetSel(listbox, i) != 0) { - ListBox_GetText(listbox, i, buffer); - key = buffer; - bool value = true; - lbdict.add(key, value); - } - } - } else if (!(style & LBS_NOSEL)) { - int index = ListBox_GetCurSel(listbox); - if (index >= 0) { - buffer[0] = '\0'; - ListBox_GetText(listbox, index, buffer); - key = buffer; - bool value = true; - lbdict.add(key, value); - } - } - } -} - - -/// -/// Restores a list box selection that was saved earlier. -/// This routine is the other half of LBSaveSelections. Call it once the list box has been -/// refilled to put the player's highlight back where it was. -/// -/// The list box to restore the selection within. -/// The dictionary the selection was saved into. -void LBRestoreSelections(HWND listbox, Dictionary & lbdict) -{ - int count = ListBox_GetCount(listbox); - Wstring key; - - if (count) { - int style = GetWindowLong(listbox, GWL_STYLE); - if (style & LBS_MULTIPLESEL) { - for (int i = 0; i < count; i++) { - char buffer[128]; - ListBox_GetText(listbox, i, buffer); - key = buffer; - if (lbdict.contains(key)) { - ListBox_SetSel(listbox, TRUE, i); - } - } - } else if (!(style & LBS_NOSEL)) { - bool value; - if (lbdict.removeAny(key, value)) { - char buffer[128]; - strcpy(buffer, key.get()); - int index = ListBox_FindStringExact(listbox, -1, buffer); - if (index != LB_ERR) { - ListBox_SetCurSel(listbox, index); - } - } - } - } -} - - /// /// Fetches the number of starting positions a scenario offers. /// This routine is used to check that there is somewhere to put every player before a map is @@ -1163,195 +718,20 @@ int RandomMapWaypointCount(int index) static int LastPreviewedScenario; -static HWND ScenarioPick; - -// The screen the map selection dialog is showing. The dialog's own driver is a wait -// callback with no argument, so the screen it is driving is held here the way the dialog -// held its result in DWLP_USER. -static UIScenarioPickPresenterClass * ScenarioScreen; - - -/// -/// Puts the view-model on the dialog's own controls. -/// -static void Scenario_Sync_Controls(HWND window, UIScenarioPickPresenterClass & screen) -{ - if (screen.ListChanged) { - screen.ListChanged = false; - SendDlgItemMessage(window, IDC_SELECTMAP_LIST, LB_RESETCONTENT, 0, 0); - for (int index = 0; index < Session.Scenarios.Count(); index++) { - SendDlgItemMessage(window, IDC_SELECTMAP_LIST, LB_INSERTSTRING, -1, (LPARAM)Session.Scenarios[index]); - } - SendDlgItemMessage(window, IDC_SELECTMAP_LIST, LB_SETCURSEL, screen.Selected, 0); - InvalidateRect(window, NULL, FALSE); - } -} - - -/// -/// Handles the idle processing while the map selection dialog is up. -/// The screen's own maintenance keeps the preview in step with whichever map is highlighted -/// and pumps the network layer, so a game sitting in the lobby does not stall while the host -/// browses for a scenario. -/// -/// bool; Should the dialog be shut down? -bool Scenario_Select_Callback(void) -{ - if (ScenarioScreen == NULL) { - Call_Back(); - return(false); - } - - int const index = SendDlgItemMessage(ScenarioPick, IDC_SELECTMAP_LIST, LB_GETCURSEL, 0, 0); - if (index != -1 && index != ScenarioScreen->Selected) { - ScenarioScreen->Queue(UIIntent{UI_SCENARIOPICK_SELECT, "", index}); - } - - // A control handler queues rather than acts, so the queue is executed here. - ScenarioScreen->Drain(); - - unsigned int const generation = ScenarioScreen->PreviewGeneration; - - // The map generator draws where this screen is, so the dialog gets out of its way. - if (ScenarioScreen->Pending != UIScenarioPickPresenterClass::SUB_NONE) { - ShowWindow(ScenarioPick, SW_HIDE); - ScenarioScreen->Run_Pending(); - ShowWindow(ScenarioPick, SW_SHOW); - } - - ScenarioScreen->Service(); - Scenario_Sync_Controls(ScenarioPick, *ScenarioScreen); - - if (ScenarioScreen->PreviewGeneration != generation) { - InvalidateRect(ScenarioPick, NULL, FALSE); - } - - if (ScenarioScreen->Result.has_value()) { - WS_Destroy_Dialog(ScenarioPick, - ScenarioScreen->Result->Outcome == UIResult::OUTCOME_ACCEPTED ? IDOK : IDCANCEL); - return(true); - } - - if (Session.Type != GAME_IPX && Session.Type != GAME_INTERNET) { - Call_Back(); - } - return(false); -} - -INT_PTR CALLBACK Scenario_DlgProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); - - -/// -/// Brings up the multiplayer map selection dialog. -/// Use this routine to let the host choose the scenario for the game. The dialog does not -/// return until the player settles on a map or backs out. -/// -/// The window to parent and center the dialog against. -/// Returns with the control ID that dismissed the dialog, either IDOK or -/// IDCANCEL. -int Scenario_Dialog(HWND top) -{ - UIScenarioPickPresenterClass screen; - screen.Refresh(); - - ScenarioScreen = &screen; - - Hide_Mouse(); - Draw_Menu_Background(); - Show_Mouse(); - ScenarioPick = WS_Create_Dialog(ProgramInstance, IDD_MPLAYER_SELECT_MAP, top, Scenario_DlgProc, FALSE); - Center_Window_Within_Window(ScenarioPick); - OwnerDraw::Subclass_Dialog(ScenarioPick, 0); - Scenario_Sync_Controls(ScenarioPick, screen); - ShowWindow(ScenarioPick, SW_NORMAL); - int const rc = WS_Wait_Dialog(ScenarioPick, Scenario_Select_Callback); - - ScenarioScreen = NULL; - return(rc); -} /// /// Runs the map selection screen. -/// This is the entry a screen uses rather than the dialog, because a presenter names no +/// This is the entry a screen uses rather than a dialog, because a presenter names no /// window. /// /// bool; Did the player settle on a map? bool Pick_Scenario_Screen(void) { - if (UI_Use_Rml()) { - UIScenarioPickPresenterClass screen; - screen.Refresh(); - - UIResult const result = UI_Scenario_Pick_Screen(screen); - if (result.Outcome != UIResult::OUTCOME_FAILED_TO_OPEN) { - return(result.Outcome == UIResult::OUTCOME_ACCEPTED); - } - } - - return(Scenario_Dialog(MainWindow) == IDOK); -} - - -/// -/// Handles the messages for the multiplayer map selection dialog. -/// This routine paints the preview of the highlighted map and queues what its buttons stand -/// for; the wait callback executes the queue. -/// -/// Returns with TRUE if the message was dealt with here, FALSE to leave it to the -/// dialog manager. -INT_PTR CALLBACK Scenario_DlgProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - switch (message) { - case WM_NCDESTROY: - On_WM_NCDESTROY(window); - break; - - case WM_PAINT: - OwnerDraw::Draw_Dialog_Back(window); - if (MultiplayerMapPreview) { - MultiplayerMapPreview->Blit_Preview(window); - } - ValidateRect(window, NULL); - break; - - case WM_ERASEBKGND: - return(TRUE); - - case WM_DRAWITEM: - OwnerDraw::Draw_Item((DRAWITEMSTRUCT *)lparam); - return(TRUE); - - case WM_COMMAND: - if (ScenarioScreen == NULL) { - break; - } - switch (LOWORD(wparam)) { - case IDC_SELECTMAP_LIST: - return(FALSE); - - case IDOK: - ScenarioScreen->Queue(UIIntent{UI_SCENARIOPICK_ACCEPT, "", 0}); - break; - - case IDCANCEL: - ScenarioScreen->Queue(UIIntent{UI_SCENARIOPICK_CANCEL, "", 0}); - break; - - case IDC_CREATE_RANDOM_MAP: - ScenarioScreen->Queue(UIIntent{UI_SCENARIOPICK_RANDOM, "", 0}); - break; - } - break; + UIScenarioPickPresenterClass screen; + screen.Refresh(); - case OD_SUBCLASSED: - if (ScenarioScreen != NULL) { - ScenarioScreen->ListChanged = true; - Scenario_Sync_Controls(window, *ScenarioScreen); - } - break; - } - return(FALSE); + return(UI_Scenario_Pick_Screen(screen).Outcome == UIResult::OUTCOME_ACCEPTED); } @@ -1387,23 +767,6 @@ void PregameSetup(void) } -/// -/// Updates the map preview shown in a network game dialog. -/// This routine is called whenever the selected scenario changes. A guest that does not have -/// the scenario locally asks the host for a preview instead of building one, so the picture -/// may not appear until that download arrives. -/// -/// The dialog window that displays the preview. -void Update_Network_Dialog_Preview(HWND win) -{ - Rebuild_Network_Map_Preview(); - - if (MultiplayerMapPreview != NULL) { - InvalidateRect(win, NULL, FALSE); - } -} - - /// /// Rebuilds the map preview for the scenario the session currently names. /// This is the half of the update that owns the preview itself, split from the half that @@ -1534,7 +897,11 @@ void Receive_Random_Map_Preview(void) if (!MultiplayerMapPreview->Create_Preview_Surface(preview, decompressed)) { DebugString("Preview block does not describe a usable picture\n"); } - InvalidateRect(WS_Top_Window(), NULL, FALSE); + // The picture arrived from the host rather than being rebuilt here, so the screen showing + // it is told directly; the dialog got the same news from an InvalidateRect. + if (UILobbyPresenterClass * const lobby = UI_Lobby_Screen()) { + lobby->PreviewGeneration++; + } DebugString("Cleaning up the temporary decompression buffers\n"); delete [] preview; diff --git a/code/netshare.h b/code/netshare.h index d87991c9b..bde469c63 100644 --- a/code/netshare.h +++ b/code/netshare.h @@ -16,13 +16,11 @@ class HouseClass; -int ODMessageBox(const char *text, int type, bool (*callback)(void), bool large = false); -INT_PTR CALLBACK ODMessageBox_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); +int ODMessageBox(const char *text, int type, bool (*callback)(void)); bool Set_Scenario_Info_From_Index(int index); void Commit_Session_Specials(void); void PregameSetup(void); -void Update_Network_Dialog_Preview(HWND win); void Rebuild_Network_Map_Preview(void); // Runs the map selection screen and reports whether the player settled on a map. This is @@ -33,27 +31,19 @@ void Send_Preview_To_Guests(void); int CountAliveTeams(HouseClass * house); int RandomMapWaypointCount(int index); -int Scenario_Dialog(HWND hWndParent); unsigned int Wstring_Hash(Wstring & string); void __cdecl PMessagePrintf(int color, const char * fmt, ...); -void __cdecl SMessagePrintf(int color, const char * fmt, ...); -void _DrawMessage(int color, const char * msg, HWND window); -void _SetMessageString(HWND window, const char * msg, int len, int color); -HWND GameoptWindow(void); void PumpGameopts(bool, bool = false); bool DecodePubGameopt(char * options, char * name); void SendPublicGameopts(char const * options); void SendPrivateGameopts(char const * player, char const * options); -void DisplayGameopts(HWND window, BOOL initialize); -void LBSaveSelections(HWND win, Dictionary & lbdict); -void LBRestoreSelections(HWND win, Dictionary & lbdict); char * CalcRandomMapDigest(void); int CreateRandomMap(void); diff --git a/code/ownrdraw.cpp b/code/ownrdraw.cpp deleted file mode 100644 index 01c4ee7f2..000000000 --- a/code/ownrdraw.cpp +++ /dev/null @@ -1,7055 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#include "always.h" - -#include "ownrdraw.h" - -#include "_keyboar.h" -#include "_mixfile.h" -#include "_rules.h" -#include "_surface.h" -#include "_xmouse.h" -#include "arraylist.h" -#include "bsurface.h" -#include "conquer.h" -#include "data.h" -#include "dbgprint.h" -#include "dict.h" -#include "audio/audioengine.h" -#include "dsurface.h" -#include "globals.h" -#include "goptions.h" -#include "hsv.h" -#include "keyboard.h" -#include "language/language.h" -#include "mainloop.h" -#include "misc.h" -#include "msgroute.h" -#include "vidscale.h" -#include "video.h" -#include "mixfile.h" -#include "msgloop.h" -#include "rgb.h" -#include "rules.h" -#include "session.h" -#include "srfcache.h" -#include "theme.h" -#include "ui/uimessagebox.h" -#include "ui/uishell.h" -#include "utf8.h" -#include "voc.h" -#include "vox.h" -#include "windlg.h" - -#include -#include -#include -#include - - - -using namespace OwnerDraw; - -extern unsigned int Wstring_Hash(Wstring & string); - - -int _mouse_counter; -int _surface_count; - -/* - * globals - */ -int ODBorderThickness; -int ODColorSteps; -int ODScrollBarAdj; -COLORREF ODColorText; -COLORREF ODColorTextDim; -COLORREF ODColorDisabled; -COLORREF ODColorFrame; -COLORREF ODListBoxColor; -static COLORREF ODTooltipBoxColor; -COLORREF ODColorUnused1; - -/* - * fonts - */ -HFONT ODFontPtr; -HFONT ODListFontPtr; -char const * ODFontName = "MS Sans Serif"; -char const * ODListFontName = "MS Sans Serif"; -int ODFontSize = 14; -int ODListFontSize = 12; - - -#define RECT_WIDTH(rc) ((rc).right - (rc).left) -#define RECT_HEIGHT(rc) ((rc).bottom - (rc).top) - - -void ODDrawDimmedBackground(Rect const & rect, HWND hWndc); -void ODDrawGradientRect(Rect const & rect, Surface & surf, int color, int scale); -void ODDrawBevelDarken(Rect const & rect, Surface & surf, int xpos, int ypos); - -void ODInitMasks(void); -void ODCacheImages(void); -int ODColorToHiColor(COLORREF color); - - -/* - * private forward declarations - */ -LRESULT CALLBACK ComboDropWinCtrlProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); -LRESULT CALLBACK CtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -LRESULT CALLBACK DefaultCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -LRESULT CALLBACK ButtonCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -LRESULT CALLBACK TextBoxCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -LRESULT CALLBACK EditBoxCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -LRESULT CALLBACK StaticCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -LRESULT CALLBACK CheckBoxCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -LRESULT CALLBACK ComboBoxCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -LRESULT CALLBACK ListBoxCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -LRESULT CALLBACK ScrollBarCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -LRESULT CALLBACK ProgressBarCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -LRESULT CALLBACK TrackBarCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -LRESULT CALLBACK GroupBoxCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -LRESULT CALLBACK HotkeyCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -INT_PTR CALLBACK Custom_Message_Box_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); - - -/// -/// The handle that stands for the rebuilt wait box. -/// That box is a document rather than a window, but its callers hold a handle and pass it -/// back to Display_Dialog, Set_Custom_Message_Box_Text and End_Dialog, so it is given one -/// no window can have. The handle and the three tests that answer to it go with OwnerDraw. -/// -static HWND Wait_Box_Handle(void) -{ - static HWND__ _token; - return(&_token); -} - - -BOOL CALLBACK ODRemoveFromDict(HWND window, LPARAM); -int WINAPI ODUpdateWindowRect(HWND window, RECT *rect); -bool ODGetFontMetrics(char const *font_name, FontMetrics *metrics); -int ODDrawTextBG(Surface & surface, LPCSTR string, LPRECT rect, HGDIOBJ font, COLORREF color, UINT format); -void ODFillRectTrans(Rect const & rect, Surface & surf, int color, int trans); -void ODDrawArrowBitmap(Surface & surface, Rect const & rect, BOOL upward, BOOL pressed); -void ODDrawEdgeGlows(Surface & surface, Rect const & rect, BOOL raised, int count, int left_alpha, int top_alpha, int right_alpha, int bottom_alpha); -BOOL CALLBACK ODAddWindowToList(HWND window, ArrayList * list); - -BOOL CALLBACK SetUserData2(HWND window, LPARAM lparam); -BOOL CALLBACK InitializeCtrl(HWND window, LPARAM lparam); -void ODDrawCharRemap(Surface & dst_surf, const char *text, int max_chars, Rect const & rect, char const *font_name, COLORREF color, char flags, int char_spacing); - - -// The dialog fonts are 256-cell sheets in Windows-1252 order; a code point without a cell -// draws as '?'. -static unsigned char OD_Glyph(char32_t code) -{ - if (code < ' ') { - return((unsigned char)code); - } - int index = UTF8::Windows_1252_Glyph(code); - return((unsigned char)(index < 0 ? '?' : index)); -} - - -/////////////////////////////////// - -/// -/// Constructs an empty list box cell. -/// -OwnerDraw::CellData::CellData(void) -{ - type = CellData::INVALID; - color = -1; - pingtime = -1; - surf = NULL; -} - - -/// -/// Fetches the hash value for a window handle. -/// This is the hash routine handed to the dictionaries that key their entries by the -/// window handle of a subclassed control. -/// -/// Returns with the handle itself, taken as an unsigned value. -unsigned int Hash_HWND(HWND &key) -{ - return((unsigned int)(uintptr_t)key); -} - - -/// -/// Fetches the hash value for a control message key. -/// This is the hash routine handed to the dictionary CtrlProc keeps of the messages it is -/// already in the middle of handling. -/// -/// Returns with the hash value formed from the window and the message. -unsigned int Hash_CtrlMsg(CtrlMsgData &key) -{ - return((unsigned int)((uintptr_t)key.message * (uintptr_t)key.window)); -} - - -/// -/// Determines if two control message keys refer to the same thing. -/// -/// bool; Do both keys name the same window and the same message? -bool OwnerDraw::CtrlMsgData::operator ==(CtrlMsgData const & that) const -{ - if (that.window == window && that.message == message){ - return(true); - } - return(false); -} - - -/// -/// Sets the owner-draw metrics and colors to their defaults. -/// This routine establishes the border thickness, the blend strength and the family of -/// colors that every owner-draw control paints with. Each control asks for it as it is -/// subclassed, so the values are always current. -/// -void OwnerDraw::Initialize(void) -{ - ODBorderThickness = 1; - ODColorSteps = 40; - ODScrollBarAdj = 127; - ODColorText = RGB(112,255,0); - ODColorTextDim = RGB(16,144,16); - ODColorDisabled = RGB(144,144,144); - ODColorFrame = RGB(78, 182, 220); - ODListBoxColor = RGB(34,80,97); - ODTooltipBoxColor = RGB(11, 27, 34); - ODColorUnused1 = RGB(22, 55, 68); -} - - - -SurfaceCacheClass SurfaceCache; - -/* - * OriginalWndProcs contains original Win32 procs. - * CustomWndProcs contains per-control custom procs. - * The main proc for all controls is CtrlProc, it calls the custom proc, - * and the custom proc usually calls the Win32 proc. - */ -Dictionary OriginalWndProcs(Hash_HWND); -Dictionary CustomWndProcs(Hash_HWND); -Dictionary ODWinData(Hash_HWND); - - -/// -/// Registers the window classes the owner-draw system needs. -/// The combo box drop-down is a class of its own rather than a stock control, so it has to -/// be registered with Windows before any dialog is subclassed. Later calls do nothing. -/// -void OwnerDraw::Register_Control_Classes(void) -{ - static int registered = 0; - if (registered == 1) { - return; - } else { - registered = 1; - WNDCLASS wc; - memset(&wc, 0, sizeof(wc)); - wc.style = CS_HREDRAW | CS_VREDRAW; - wc.lpfnWndProc = ComboDropWinCtrlProc; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; - wc.hInstance = ProgramInstance; - wc.hIcon = NULL; - wc.hCursor = NULL; - wc.hbrBackground = NULL; - wc.lpszMenuName = "ComboDropWin"; - wc.lpszClassName = "ComboDropWin"; - RegisterClass(&wc); - } -} - -static HWND _dropdown_window = NULL; -static HWND _dropdown_owner = NULL; - - -/// -/// Handles the messages for a combo box drop-down window. -/// The dropped list is a window of its own rather than a stock Windows list, so that it -/// can be painted over a dimmed copy of the dialog background. This routine tracks the -/// item under the mouse, attaches or removes a scroll bar as the item count demands, and -/// folds the list back into the owning combo box once a selection is made. -/// -/// Returns with zero for the messages handled here; otherwise with the result of -/// the default window procedure. -static LRESULT CALLBACK ComboDropWinCtrlProc_Internal(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); - - -/// -/// Hands the drop-down its messages, in frame coordinates, and presents what it paints. -/// -LRESULT CALLBACK ComboDropWinCtrlProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) -{ - LPARAM translated_lparam; - if (Route_Mouse_Message(hWnd, Msg, wParam, lParam, &translated_lparam)) { - return(0); - } - - LRESULT result = ComboDropWinCtrlProc_Internal(hWnd, Msg, wParam, translated_lparam); - - if (Msg == WM_PAINT) { - Video_Present_If_Dirty(); - } - - return(result); -} - - -static LRESULT CALLBACK ComboDropWinCtrlProc_Internal(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) -{ - static HBRUSH SolidBrush = CreateSolidBrush(RGB(48,96,48)); - static HWND OwnerComboHandle; - (void)SolidBrush; - - RECT rect; - Get_Display_Rect(hWnd, &rect); - - RECT client; - GetClientRect(hWnd, &client); - - RECT parent_rect; - memset(&parent_rect, 0, sizeof(parent_rect)); - - HWND hWndParent = NULL; - WinData * parent_data = NULL; - - int scrollbar_width = 2 * ODBorderThickness + 18; - int need_scrollbar = -1; - int max_top_index = 0; - - if (OwnerComboHandle) { - hWndParent = GetParent(OwnerComboHandle); - } - - if (hWndParent) { - ODWinData.getPointer(hWndParent, &parent_data); - Get_Display_Rect(hWndParent, &parent_rect); - } - - _dropdown_owner = hWndParent; - _dropdown_window = hWnd; - - WinData * data = NULL; - WinData * master_data = NULL; - - ODWinData.getPointer(hWnd, &data); - if (data == NULL) { - DebugString("ComboBox dropdown windata = NULL\n"); - } - - if (OwnerComboHandle) { - ODWinData.getPointer(OwnerComboHandle, &master_data); - } - - if (Msg != CB_GETCOUNT && Msg != CB_GETITEMHEIGHT && Msg != WM_VSCROLL) { - LRESULT item_count = SendMessage(OwnerComboHandle, CB_GETCOUNT, 0, 0); - LRESULT item_height = SendMessage(OwnerComboHandle, CB_GETITEMHEIGHT, 0, 0); - if (item_height <= 1) { - item_height = 1; - } - - need_scrollbar = (item_count * item_height > client.bottom - client.top); - max_top_index = item_count - (client.bottom - client.top) / item_height; - - if (data) { - if ((UINT_PTR)data->attachedWindow > 1) { - SCROLLINFO info; - info.cbSize = sizeof(SCROLLINFO); - info.fMask = SIF_RANGE | SIF_POS; - info.nMin = 0; - info.nMax = max_top_index; - info.nPos = data->ComboDrop.scrollTop; - SendMessage(data->attachedWindow, SBM_SETSCROLLINFO, 0, (LPARAM)&info); - } - if (data->attachedWindow != NULL) { - BringWindowToTop(data->attachedWindow); - } - } - } - - switch (Msg) { - case WM_ERASEBKGND: - return(0); - - case WM_CREATE: - SetCapture(hWnd); - OwnerComboHandle = *(HWND *)lParam; - return(0); - - case WM_PAINT: { - LRESULT item_count = SendMessage(OwnerComboHandle, CB_GETCOUNT, 0, 0); - LRESULT item_height = SendMessage(OwnerComboHandle, CB_GETITEMHEIGHT, 0, 0); - int client_height = client.bottom - client.top; - int selected_index = data->ComboDrop.selection; - - Rect dest_rect(rect.left, rect.top, rect.right - rect.left, client_height); - int source_x = rect.left - parent_rect.left; - int source_y = rect.top - parent_rect.top; - - if (data->cachedSurface == NULL) { - Rect dst(0, 0, client.right, client.bottom); - Rect src(source_x, source_y, client.right, client.bottom); - BSurface * surface = new BSurface(client.right, client.bottom, 2); - data->cachedSurface = surface; - ++_surface_count; - - if (parent_data != NULL && parent_data->cachedSurface != NULL) { - surface->Blit_From(dst, *parent_data->cachedSurface, src, false, true); - } - - int total = client.right * client.bottom; - unsigned short * pixels = (unsigned short *)surface->Lock(); - for (int i = 0; i < total; ++i) { - pixels[i] = OD_Blend_Color(pixels[i], 0, 180); - } - surface->Unlock(); - } - - { - Rect src(0, 0, rect.right - rect.left, client_height); - AlternateSurface->Blit_From(dest_rect, *data->cachedSurface, src, false, true); - } - - { - Rect border(rect.left + 1, rect.top + 1, rect.right - rect.left - 2, client_height - 2); - OD_Draw_Rect(*AlternateSurface, border, 1, 0xFFFFFFFF); - } - - FontMetrics font_data; - int have_font = ODGetFontMetrics("dlgsys", &font_data); - - int index = data->ComboDrop.scrollTop; - if (index < item_count) { - int row_base = item_height * index; - int row = row_base; - - while (1) { - int left = rect.left + 1; - int width = rect.right - rect.left - 2; - int top = row - row_base + rect.top; - - if (item_height + row - row_base > client_height) { - break; - } - - char text[128]; - SendMessage(OwnerComboHandle, CB_GETLBTEXT, (WPARAM)index, (LPARAM)text); - - if (index == selected_index) { - Rect fill(left, top + 1, width, item_height - 2); - int fill_color = ODListBoxColor; - if (fill_color != -1) { - fill_color = ODColorToHiColor(fill_color); - } - AlternateSurface->Fill_Rect(fill, fill_color); - } - - COLORREF text_color = ODColorText; - if (index < 50 && master_data != NULL && master_data->ComboBox.itemColors[index] != -1) { - text_color = master_data->ComboBox.itemColors[index]; - } - - if (have_font) { - int text_width = 0; - for (char const * cursor = text; *cursor; ) { - text_width += font_data.charWidths[OD_Glyph(UTF8::Decode(cursor))]; - } - - int max_width = width - 10; - int ellipsis_width = 3 * font_data.charWidths['.']; - int clipped = 0; - - if (text_width > max_width) { - while (strlen(text) > 0) { - char * last = UTF8::Previous(text, text + strlen(text)); - text_width -= font_data.charWidths[OD_Glyph(UTF8::Peek(last))]; - *last = '\0'; - if (!clipped) { - text_width += ellipsis_width; - } - clipped = 1; - if (text_width <= max_width) { - strcat(text, "..."); - break; - } - } - } - } - - RECT text_rect; - text_rect.left = left + 3; - text_rect.top = top; - text_rect.right = left + width; - text_rect.bottom = top + item_height; - OD_Draw_Text_Remap(*AlternateSurface, text, *(Rect *)&text_rect, "dlgsys", text_color, 4, 0); - - ++index; - row += item_height; - if (index >= item_count) { - break; - } - } - } - - AlternateSurface->Lock(); - VisibleSurface->Lock(); - - RECT src_rect; - src_rect.left = rect.left; - src_rect.top = rect.top; - src_rect.right = rect.right - rect.left; - src_rect.bottom = client_height; - - RECT window_rect; - GetWindowRect(hWnd, &window_rect); - - RECT dst_rect = src_rect; - - VisibleSurface->Blit_From(*(Rect *)&dst_rect, *AlternateSurface, *(Rect *)&src_rect, false, true); - - VisibleSurface->Unlock(); - AlternateSurface->Unlock(); - - ValidateRect(hWnd, NULL); - return(0); - } - - case WM_NCDESTROY: - _dropdown_window = NULL; - _dropdown_owner = NULL; - ReleaseCapture(); - break; - - case WM_VSCROLL: { - LRESULT top_index = SendMessage(data->attachedWindow, SBM_GETPOS, 0, 0); - if (top_index != SendMessage(hWnd, CB_GETTOPINDEX, 0, 0)) { - SendMessage(hWnd, CB_SETTOPINDEX, top_index, 0); - } - break; - } - - case CB_GETTOPINDEX: - if (data) { - return(data->ComboDrop.scrollTop); - } - break; - - case WM_MOUSEMOVE: { - int x = (unsigned short)LOWORD(lParam); - int y = (unsigned short)HIWORD(lParam); - - if (x >= 0 && y >= 0 && x < client.right && y < client.bottom) { - LRESULT item_height = SendMessage(OwnerComboHandle, CB_GETITEMHEIGHT, 0, 0); - LRESULT item_count = SendMessage(OwnerComboHandle, CB_GETCOUNT, 0, 0); - int index = y / item_height; - if (data) { - index += data->ComboDrop.scrollTop; - } - - int clamped = index < 0 ? 0 : index; - int max_index = (int)item_count - 1; - if (max_index < clamped) { - clamped = max_index; - } - - if (data->ComboDrop.selection != clamped) { - InvalidateRect(hWnd, NULL, FALSE); - } - data->ComboDrop.selection = clamped; - return(0); - } - return(0); - } - - case CB_SETTOPINDEX: { - int new_top = (int)wParam; - LRESULT item_count = SendMessage(OwnerComboHandle, CB_GETCOUNT, 0, 0); - LRESULT item_height = SendMessage(OwnerComboHandle, CB_GETITEMHEIGHT, 0, 0); - if (!item_count || !item_height) { - break; - } - - int visible = (client.bottom - client.top) / item_height; - if (new_top < 0) { - new_top = 0; - } - - int max_top = (int)item_count - visible; - if (max_top > 0) { - if (new_top > max_top) { - new_top = max_top; - } - } else { - new_top = 0; - } - - if (new_top != data->ComboDrop.scrollTop) { - data->ComboDrop.scrollTop = new_top; - InvalidateRect(hWnd, NULL, FALSE); - } - return(0); - } - - case WM_LBUTTONDOWN: - case WM_LBUTTONDBLCLK: { - int x = (unsigned short)LOWORD(lParam); - int y = (unsigned short)HIWORD(lParam); - - if (x > client.right && x < client.right + data->scrollBarWidth && y > 0 && y < client.bottom) { - SendMessage(data->attachedWindow, Msg, wParam, lParam); - break; - } - - Sound_Effect(Rule->GenericClick); - - if (x >= 0 && y >= 0 && x <= client.right && y <= client.bottom) { - LRESULT item_height = SendMessage(OwnerComboHandle, CB_GETITEMHEIGHT, 0, 0); - LRESULT item_count = SendMessage(OwnerComboHandle, CB_GETCOUNT, 0, 0); - - int index = y / item_height; - if (data) { - index += data->ComboDrop.scrollTop; - } - - int clamped = index < 0 ? 0 : index; - int max_index = (int)item_count - 1; - if (max_index < clamped) { - clamped = max_index; - } - - SendMessage(OwnerComboHandle, CB_SETCURSEL, clamped, 0); - - if (need_scrollbar) { - DestroyWindow(data->attachedWindow); - } - - ReleaseCapture(); - SendMessage(OwnerComboHandle, CB_SHOWDROPDOWN, FALSE, 0); - - SendMessage( - hWndParent, - WM_COMMAND, - MAKEWPARAM((UINT)GetWindowLong(OwnerComboHandle, GWL_ID), CBN_SELCHANGE), - (LPARAM)OwnerComboHandle); - return(0); - } - - if (need_scrollbar) { - DestroyWindow(data->attachedWindow); - } - ReleaseCapture(); - SendMessage(OwnerComboHandle, CB_SHOWDROPDOWN, FALSE, 0); - return(0); - } - - case OD_DROPSUBCLASSED: { - WinData * drop_data = NULL; - ODWinData.getPointer(hWnd, &drop_data); - data->ComboDrop.selection = SendMessage(OwnerComboHandle, CB_GETCURSEL, 0, 0); - return(0); - } - - default: - break; - } - - if (need_scrollbar == 1) { - if (data && !data->attachedWindow) { - - data->attachedWindow = (HWND)1; - GetWindowLong(hWnd, GWL_ID); - - hWndParent = GetParent(hWnd); - - RECT parent_display_rect; - Get_Display_Rect(hWndParent, &parent_display_rect); - - RECT drop_display_rect; - Get_Display_Rect(hWnd, &drop_display_rect); - - int left = drop_display_rect.left - parent_display_rect.left; - int top = drop_display_rect.top - parent_display_rect.top; - - HWND scroll_wnd = CreateWindowEx( - 0, - "Scrollbar", - NULL, - 0x50010001u, - left + client.right - scrollbar_width, - top + client.top, - scrollbar_width, - drop_display_rect.bottom - drop_display_rect.top, - hWndParent, - NULL, - ProgramInstance, - NULL); - - data->attachedWindow = scroll_wnd; - data->scrollBarWidth = scrollbar_width; - - InitializeCtrl(scroll_wnd, 0); - - WinData *scroll_data = NULL; - ODWinData.getPointer(scroll_wnd, &scroll_data); - scroll_data->ownerWindow = hWnd; - - SCROLLINFO info; - info.cbSize = sizeof(SCROLLINFO); - info.fMask = SIF_RANGE | SIF_POS; - info.nMin = 0; - info.nMax = max_top_index; - info.nPos = data->ComboDrop.scrollTop; - SendMessage(scroll_wnd, SBM_SETSCROLLINFO, 0, (LPARAM)&info); - - SetWindowPos( - hWnd, - NULL, - 0, - 0, - (drop_display_rect.right - drop_display_rect.left) - scrollbar_width, - drop_display_rect.bottom - drop_display_rect.top, - SWP_NOMOVE); - - ShowWindow(scroll_wnd, SW_SHOW); - BringWindowToTop(scroll_wnd); - InvalidateRect(scroll_wnd, NULL, FALSE); - UpdateWindow(scroll_wnd); - - SendMessage(scroll_wnd, OD_SETTOP, (WPARAM)scroll_wnd, 1); - SendMessage(scroll_wnd, OD_SETKEEPCAPTURE, 0, 1); - - RECT validate_rect; - validate_rect.left = left; - validate_rect.top = top; - validate_rect.right = left + client.right + 1; - validate_rect.bottom = top + client.bottom + 1; - ValidateRect(hWndParent, &validate_rect); - } - - return(DefWindowProc(hWnd, Msg, wParam, lParam)); - } - - if (need_scrollbar == 0 && data && data->attachedWindow && !data->paintDisabled) { - HWND scroll_wnd = data->attachedWindow; - DestroyWindow(scroll_wnd); - - OriginalWndProcs.remove(scroll_wnd); - ODWinData.remove(scroll_wnd); - CustomWndProcs.remove(scroll_wnd); - - data->attachedWindow = NULL; - data->scrollBarWidth = 0; - - SetWindowPos( - hWnd, - NULL, - 0, - 0, - ODBorderThickness + client.right - client.left + scrollbar_width + 1, - client.bottom + 2 * ODBorderThickness - client.top, - SWP_NOMOVE); - - hWndParent = GetParent(hWnd); - Get_Display_Rect(hWndParent, &parent_rect); - - RECT validate_rect; - validate_rect.left = rect.left - parent_rect.left - 1; - validate_rect.top = rect.top - parent_rect.top - 1; - validate_rect.right = rect.left - parent_rect.left + client.right + scrollbar_width; - validate_rect.bottom = rect.top - parent_rect.top + client.bottom + 1; - ValidateRect(hWndParent, &validate_rect); - } - - return(DefWindowProc(hWnd, Msg, wParam, lParam)); -} - - -/// -/// Subclasses a dialog and every one of its controls for owner drawing. -/// This is the routine a dialog procedure calls when it is first created. Painting is -/// suppressed across the whole window while the controls are being hooked, so the dialog -/// never flickers through its stock Windows appearance on the way. -/// -/// Caller supplied value to store with each control for its own use. -bool OwnerDraw::Subclass_Dialog(HWND window, LPARAM lparam) -{ - OwnerDraw::Register_Control_Classes(); - - EnumChildWindows(window, SetUserData2, 1); - SetUserData2(window, 1); - - EnumChildWindows(window, InitializeCtrl, lparam); - InitializeCtrl(window, lparam); - - EnumChildWindows(window, SetUserData2, 0); - SetUserData2(window, 0); - return(true); -} - - -/// -/// Sets the paint suppression flag for one window. -/// This is the enumeration callback Subclass_Dialog uses to silence, and later re-enable, -/// painting across a whole dialog. A window with no owner-draw record yet is given one. -/// -/// Should painting be suppressed for this window? -BOOL CALLBACK SetUserData2(HWND window, LPARAM lparam) -{ - WinData * data = NULL; - WinData temp; - - if (!ODWinData.getPointer(window, &data)) { - memset(&temp, 0, sizeof(temp)); - ODWinData.add(window, temp); - ODWinData.getPointer(window, &data); - } - - data->paintDisabled = lparam; - - return(TRUE); -} - - -/// -/// Builds the artwork, masks and fonts every owner-draw control paints with. Later calls do -/// nothing, so anything drawing with those resources may ask for them. -/// -/// Supplies the display context the fonts are made against. -void OwnerDraw::Prepare_Resources(HWND window) -{ - Initialize(); - - static int _inited = false; - if (!_inited) { - ODInitMasks(); - ODCacheImages(); - HDC hdc = GetDC(window); - ODFontPtr = WS_Get_Font(hdc, ODFontName, 0, ODFontSize, 0); - ODListFontPtr = WS_Get_Font(hdc, ODListFontName, 0, ODListFontSize, 0); - ReleaseDC(window, hdc); - _inited = 1; - } -} - - -/// -/// Prepares one control for owner drawing. -/// This is the enumeration callback Subclass_Dialog uses. The control's window class and style -/// pick the custom procedure that will paint it, its original procedure is displaced by -/// CtrlProc and remembered, and the control is then told that it has been subclassed. The -/// shared fonts, color masks and cached artwork are built on the first control to arrive. -/// -/// Caller supplied value to store with the control for its own use. -BOOL CALLBACK InitializeCtrl(HWND window, LPARAM lparam) -{ - char class_name[128]; - GetClassName(window, class_name, sizeof(class_name)); - - LONG style = GetWindowLong(window, GWL_STYLE); - - RECT rect1; - Get_Display_Rect(window, &rect1); - RECT rect2; - GetClientRect(window, &rect2); - - OwnerDraw::Prepare_Resources(window); - - WNDPROC customProc = NULL; - - if (strcmp(class_name, WC_SCROLLBAR) == 0) { - customProc = ScrollBarCtrlProc; - } else if (strcmp(class_name, WC_LISTBOX) == 0) { - customProc = ListBoxCtrlProc; - } else if (strcmp(class_name, WC_COMBOBOX) == 0) { - customProc = ComboBoxCtrlProc; - } else if (strcmp(class_name, TRACKBAR_CLASS) == 0) { - customProc = TrackBarCtrlProc; - } else if (strcmp(class_name, PROGRESS_CLASS) == 0) { - customProc = ProgressBarCtrlProc; - } else if (strcmp(class_name, WC_EDIT) == 0) { - customProc = EditBoxCtrlProc; - } else if (strcmp(class_name, WC_STATIC) == 0) { - customProc = StaticCtrlProc; - } else if (strcmp(class_name, WC_TABCONTROL) == 0) { - customProc = TextBoxCtrlProc; - } else if (strcmp(class_name, WC_BUTTON) == 0) { - if ((style & BS_GROUPBOX) == BS_GROUPBOX) { - customProc = GroupBoxCtrlProc; - } else if ((style & BS_OWNERDRAW) == BS_OWNERDRAW) { - customProc = ButtonCtrlProc; - } else if ((style & BS_AUTOCHECKBOX) == BS_AUTOCHECKBOX) { - customProc = CheckBoxCtrlProc; - } - } else if (strcmp(class_name, HOTKEY_CLASS) == 0) { - customProc = HotkeyCtrlProc; - } else { - customProc = DefaultCtrlProc; - } - - WNDPROC originalProc = (WNDPROC)SetWindowLongPtr(window, GWLP_WNDPROC, (LONG_PTR)CtrlProc); - - if (!CustomWndProcs.contains(window)) { - CustomWndProcs.add(window, customProc); - } - - if (!OriginalWndProcs.contains(window)) { - OriginalWndProcs.add(window, originalProc); - } - - WinData * data = NULL; - WinData temp; - - if (!ODWinData.getPointer(window, &data)) { - memset(&temp, 0, sizeof(temp)); - ODWinData.add(window, temp); - ODWinData.getPointer(window, &data); - } - - data->userData = lparam; - - SendMessage(window, OD_SUBCLASSED, 0, 0); - - return(TRUE); -} - - -/// -/// Removes a dialog and all of its controls from the owner-draw dictionaries. -/// Use this routine as a subclassed dialog is torn down. Windows is free to hand the same -/// handles out again, and a stale entry would misdirect the next dialog to use them. -/// -BOOL ODCleanupDicts(HWND window) -{ - EnumChildWindows(window, ODRemoveFromDict, 0); - ODRemoveFromDict(window, 0); - - return(TRUE); -} - - -/// -/// Removes one window from the owner-draw dictionaries. -/// This is the enumeration callback ODCleanupDicts uses. It drops the window's original -/// procedure, its custom procedure and its owner-draw record. -/// -BOOL CALLBACK ODRemoveFromDict(HWND window, LPARAM) -{ - OriginalWndProcs.remove(window); - ODWinData.remove(window); - CustomWndProcs.remove(window); - - return(TRUE); -} - - -Tooltip ODTooltip; -int ODLastTooltipTime; - - -/// -/// Constructs an empty tooltip. -/// -Tooltip::Tooltip(void) -{ - bounds.Set(0,0,0,0); - background = NULL; - text[0] = '\0'; - isActive = false; - isHidden = false; - window = (HWND)NULL; -} - - -/// -/// Starts displaying a tooltip over the given area. -/// Any tooltip already on screen is taken down first, since there is only ever one. The -/// area underneath is saved so the tooltip can be hidden again without the dialog having -/// to repaint itself. -/// -/// The area of the screen the tooltip is to occupy. -/// The text to display, or NULL for the placeholder text. -/// The control the tooltip belongs to. -/// bool; Was the tooltip displayed? -bool OwnerDraw::Start_Tooltip(Rect const & rect, char const * text, HWND window) -{ - OwnerDraw::End_Tooltip(); - - ODLastTooltipTime = time(NULL); - - sprintf(ODTooltip.text, "Tool Tip"); - - if (text != NULL) { - strcpy(ODTooltip.text, text); - } - - ODTooltip.bounds = rect; - ODTooltip.window = window; - ODTooltip.isActive = true; - - return(OwnerDraw::Show_Tooltip(true)); -} - - -/// -/// Draws the tooltip onto the visible surface. -/// Use this routine to bring the tooltip back after a repaint has forced it into hiding. -/// The background is only captured when the caller asks for it, since redrawing the -/// tooltip must not save the tooltip as its own background. -/// -/// Should the area under the tooltip be captured first? -/// bool; Was the tooltip drawn? -bool OwnerDraw::Show_Tooltip(bool save_background) -{ - if (ODTooltip.isActive == false) { - return(false); - } - - if (save_background) { - if (ODTooltip.background != NULL) { - delete ODTooltip.background; - } - ODTooltip.background = NULL; - - Surface * backgd = new BSurface(ODTooltip.bounds.Width, ODTooltip.bounds.Height, 2); - ODTooltip.background = backgd; - - Rect drect(0, 0, ODTooltip.bounds.Width, ODTooltip.bounds.Height); - backgd->Blit_From(drect, *VisibleSurface, ODTooltip.bounds); - } - - ODTooltip.isHidden = false; - - Rect rect = ODTooltip.bounds; - - int color = ODColorToHiColor(ODTooltipBoxColor); - - VisibleSurface->Lock(); - VisibleSurface->Fill_Rect(rect, color); - VisibleSurface->Unlock(); - - ODDrawBevelDarken(rect, *VisibleSurface, 8, 4); - - OD_Draw_Text(ODColorText, ODFontPtr, rect, ODTooltip.text, strlen(ODTooltip.text), 1, 1, VisibleSurface); - rect.X += 1; - rect.Y += 1; - rect.Width -= 2; - rect.Height -= 2; - OD_Draw_Rect(*VisibleSurface, rect, 1, -1); - - return(true); -} - - -/// -/// Hides the tooltip by restoring the area it covered. -/// The tooltip stays active while hidden, so it can be put back with Show_Tooltip once -/// whatever prompted the hide has finished painting. -/// -/// bool; Was the tooltip hidden? -bool OwnerDraw::Hide_Tooltip(void) -{ - if (ODTooltip.isActive == false) { - return(false); - } - - if (ODTooltip.isHidden == (int)true) { - return(false); - } - - if (ODTooltip.background == NULL) { - return(false); - } - - Rect drect = ODTooltip.bounds; - Rect srect(0,0, ODTooltip.bounds.Width, ODTooltip.bounds.Height); - VisibleSurface->Blit_From(drect, *ODTooltip.background, srect); - ODTooltip.isHidden = true; - return(true); -} - - -/// -/// Takes the tooltip down for good. -/// The area it covered is restored and the saved background released. Use this routine -/// when the mouse leaves the control, or when the control itself is going away. -/// -/// bool; Was there a tooltip to take down? -bool OwnerDraw::End_Tooltip(void) -{ - if (ODTooltip.isActive == false) { - return(false); - } - - OwnerDraw::Hide_Tooltip(); - - if (ODTooltip.background != NULL) { - delete ODTooltip.background; - } - ODTooltip.background = NULL; - - ODTooltip.isActive = false; - ODTooltip.isHidden = false; - return(true); -} - - -/// -/// Handles every message sent to a subclassed control. -/// This is the procedure that displaces the stock Windows procedure of each control, and -/// it is the heart of the owner-draw system. It enforces the modal window stack, guards -/// against a message re-entering the same control, drives the tooltip and hover timers, -/// accumulates the area that has to reach the screen, and then hands the message to the -/// control's own custom procedure -- which is usually the one that calls the original -/// Windows procedure. -/// -/// Returns with the result of the control's custom procedure, or zero when the -/// message was swallowed here. -static LRESULT CALLBACK CtrlProc_Internal(HWND window, UINT message, WPARAM wparam, LPARAM lparam); - - -/// -/// Hands a control its messages, in frame coordinates, and puts what it paints on screen. -/// The controls draw into the game's own surfaces rather than into their windows, so -/// every repaint has to be followed by a present to be seen. -/// -LRESULT CALLBACK CtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - LPARAM translated_lparam; - if (Route_Mouse_Message(window, message, wparam, lparam, &translated_lparam)) { - return(0); - } - - LRESULT result = CtrlProc_Internal(window, message, wparam, translated_lparam); - - if (message == WM_PAINT) { - Video_Present_If_Dirty(); - } - - return(result); -} - - -static LRESULT CALLBACK CtrlProc_Internal(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - static POINT min_update_rect = {0xFFFFFF, 0xFFFFFF}; - static POINT max_update_rect; - static int num_rect_updates; - - /* - * Tracks whether the game window had focus on the previous call so the - * controls can be refreshed when focus is regained. - */ - static bool was_in_focus = true; - - /* - * Set to 1 around the SetWindowPos() call issued by the OD_SETTOP handler, - * and checked by the WM_WINDOWPOSCHANGING handler so the engine does not - * fight its own z-order change. - */ - static char in_programmatic_reorder; - - if (message == WM_SETCURSOR) { - return(1); - } - - WNDPROC specific_proc = NULL; - CustomWndProcs.getValue(window, specific_proc); - - WNDPROC original_proc = NULL; - OriginalWndProcs.getValue(window, original_proc); - - if (message == WM_SYSKEYUP && wparam == VK_TAB) { - SendMessage(MainWindow, WM_SYSKEYUP, VK_TAB, lparam); - } - - LRESULT result = 0; - BOOL show_tooltip = FALSE; - int anim_state = 0; - - static Dictionary ctrlmessages(Hash_CtrlMsg); - - CtrlMsgData key; - key.window = window; - key.message = message; - - RECT display_rect; - Get_Display_Rect(window, &display_rect); - RECT window_rect; - GetWindowRect(window, &window_rect); - int offset_x = 0; - int offset_y = 0; - - bool mouse_over = false; - - static ArrayList hwndarray; - - HWND owner; - WinData *ownerdata; - int state; - - /* - * When a window has been pushed to the top (modal) via OD_SETTOP, swallow - * mouse messages that are not directed at it or one of its children. - */ - if (hwndarray.length() != 0) { - HWND topwindow; - hwndarray.getTail(topwindow); - - BOOL allow = FALSE; - BOOL forward = FALSE; - if (GetParent(window) == NULL) { - allow = TRUE; - } - if (GetWindowLong(window, GWL_ID) <= 0) { - allow = TRUE; - } - - HWND parent = window; - while (parent != NULL) { - if (parent == topwindow) { - allow = TRUE; - break; - } - parent = GetParent(parent); - } - - if (message < WM_MOUSEMOVE || message > WM_MBUTTONDBLCLK) { - forward = TRUE; - } - if (message >= WM_NCMOUSEMOVE && message <= WM_KEYLAST) { - forward = FALSE; - } - if (message == WM_SYSKEYUP || message == WM_SYSKEYDOWN || message == WM_SYSCOMMAND || message == WM_SYSCHAR) { - forward = TRUE; - } - if (message == OD_GETTIPTEXT || message == WM_TIMER || message == OD_GETCELLTIP) { - forward = FALSE; - } - if (!allow && !forward) { - return(0); - } - } - - /* - * Guard against re-entrant processing of the same message for the same - * window. A handful of messages are allowed to re-enter. - */ - if (ctrlmessages.contains(key)) { - if (message != WM_COMMAND && message != WM_SYSKEYDOWN && message != WM_SYSKEYUP - && message != WM_SYSCOMMAND && message != WM_SYSCHAR) { - return(0); - } - } - - bool processing = true; - ctrlmessages.remove(key); - ctrlmessages.add(key, processing); - - RECT client_rect; - GetClientRect(window, &client_rect); - RECT disp_rect; - Get_Display_Rect(window, &disp_rect); - - bool in_focus = GameInFocus; - - bool is_paint = false; - if (message == WM_PAINT) { - is_paint = true; - // A windowed game keeps presenting without the focus, so its dialogs keep painting too. - if (!in_focus && !WindowedMode) { - ValidateRect(window, NULL); - ctrlmessages.remove(key); - return(0); - } - } - - if (in_focus == true && !was_in_focus) { - // The dialogs skipped their paints while the focus was away, so they need one too. - RedrawWindow(MainWindow, NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN); - in_focus = GameInFocus; - } - was_in_focus = in_focus; - - RECT update_rect; - if (is_paint) { - ++num_rect_updates; - GetUpdateRect(window, &update_rect, FALSE); - update_rect.left += disp_rect.left; - update_rect.right += disp_rect.left; - update_rect.top += disp_rect.top; - update_rect.bottom += disp_rect.top; - } - - WinData *data = NULL; - ODWinData.getPointer(window, &data); - - if (message == OD_HASATTACHED) { - result = (data->attachedWindow != NULL); - goto cleanup; - } - - /* - * Save the device context's current font / colors so they can be - * restored by OD_RESTOREDC. - */ - if (message == OD_SAVEDC) { - HDC hdc = (HDC)lparam; - HGDIOBJ old = SelectObject(hdc, GetStockObject(SYSTEM_FONT)); - data->font = (HFONT)old; - SelectObject(hdc, old); - data->bkMode = GetBkMode(hdc); - data->bkColor = GetBkColor(hdc); - data->textColor = GetTextColor(hdc); - result = 1; - goto cleanup; - } - - /* - * Restore the device context state saved by OD_SAVEDC. - */ - if (message == OD_RESTOREDC) { - HDC hdc = (HDC)lparam; - SelectObject(hdc, data->font); - SetBkMode(hdc, data->bkMode); - SetBkColor(hdc, data->bkColor); - SetTextColor(hdc, data->textColor); - result = 1; - goto cleanup; - } - - /* - * Toggle paint suppression for this control (and its linked window). - */ - if (message == OD_DISABLEPAINT) { - { - HWND linked = (HWND)data->attachedWindow; - result = data->paintDisabled; - data->paintDisabled = lparam; - if (linked != NULL) { - WinData *linkeddata = NULL; - ODWinData.getPointer((HWND &)data->attachedWindow, &linkeddata); - if (linkeddata != NULL) { - linkeddata->paintDisabled = lparam; - } - } - } - - call_custom_proc: - if (specific_proc != NULL) { - result = CallWindowProc(specific_proc, window, message, wparam, lparam); - } - - after_proc: - if (message == WM_NCDESTROY) { - On_WM_NCDESTROY(window); - ODRemoveFromDict(window, 0); - } - goto cleanup; - } - - /* - * Push a window to the top of the modal stack. - */ - if (message == OD_SETTOP) { - HWND topwindow = NULL; - hwndarray.getTail(topwindow); - result = (LRESULT)topwindow; - - HWND target = window; - if (wparam) { - target = (HWND)wparam; - } - - HWND found = NULL; - int scan = 0; - for (int index = 0; index < hwndarray.length(); index++) { - hwndarray.get(found, scan); - if (found == target) { - hwndarray.remove(scan); - } else { - scan++; - } - } - - if (lparam) { - hwndarray.addTail(target); - in_programmatic_reorder = 1; - SetWindowPos(target, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); - in_programmatic_reorder = 0; - } - goto cleanup; - } - - if (hwndarray.length() != 0) { - - /* - * Keep the modal window pinned on top when Windows tries to reorder it. - */ - if (message == WM_WINDOWPOSCHANGING) { - if (in_programmatic_reorder != 1) { - HWND found = NULL; - for (int index = 0; index < hwndarray.length(); index++) { - hwndarray.get(found, index); - if (found == window) { - WINDOWPOS *wp = (WINDOWPOS *)lparam; - if (index == hwndarray.length() - 1 && wp->hwndInsertAfter != NULL && (wp->flags & SWP_NOZORDER) == 0) { - wp->hwndInsertAfter = NULL; - InvalidateRect(window, NULL, FALSE); - result = 0; - } else { - wp->flags |= SWP_NOOWNERZORDER | SWP_NOZORDER; - InvalidateRect(window, NULL, FALSE); - result = 0; - } - goto cleanup; - } - } - } - goto call_custom_proc; - } - - /* - * Drop a destroyed window from the modal stack. - */ - if (message == WM_DESTROY) { - HWND found = NULL; - int scan = 0; - for (int index = 0; index < hwndarray.length(); index++) { - hwndarray.get(found, scan); - if (found == window) { - hwndarray.remove(scan); - } else { - scan++; - } - } - } - } - - /* - * Tear down the tooltip when its owning window is destroyed, hidden, or - * loses focus. - */ - if (window == ODTooltip.window - && (message == WM_NCDESTROY || message == WM_SHOWWINDOW || message == WM_KILLFOCUS) - && ODTooltip.isActive) { - OwnerDraw::End_Tooltip(); - } - - if (message == WM_ERASEBKGND) { - result = 1; - goto cleanup; - } else if (message == WM_SETFOCUS) { - if (specific_proc == ButtonCtrlProc || specific_proc == ListBoxCtrlProc) { - SetFocus((HWND)wparam); - } - } else if (message == WM_SHOWWINDOW) { - if (wparam == 0) { - data->animState = 0; - } - } else if (message == OD_SETIMAGE) { - result = (LRESULT)data->image; - data->image = (Surface *)lparam; - goto cleanup; - } else if (message == OD_SETALTIMAGE) { - result = (LRESULT)data->altImage; - data->altImage = (Surface *)lparam; - goto cleanup; - } else if (message == OD_TOOLTIPS) { - result = data->toolTipsEnabled; - data->toolTipsEnabled = lparam; - goto cleanup; - } - - if (data->toolTipsEnabled) { - - if (message == WM_TIMER || (message >= WM_MOUSEMOVE && message <= WM_MOUSELAST)) { - Rect corner; - GetWindowRect(window, (LPRECT)&corner); - if (WindowFromPoint(*(POINT *)&corner) == window) { - mouse_over = true; - } - } - - if (data->toolTipsEnabled && mouse_over) { - - if (message == WM_MOUSEMOVE) { - int mx = LOWORD(lparam); - int my = HIWORD(lparam); - - if (specific_proc == ListBoxCtrlProc) { - if (OwnerDraw::End_Tooltip()) { - ReleaseCapture(); - } - KillTimer(window, 0); - } - - if (mx >= 0 && my >= 0 && mx < client_rect.right && my < client_rect.bottom) { - if ((wparam & 0x13) == 0) { - UINT delay = 1000; - if (time(NULL) - ODLastTooltipTime <= 1) { - delay = 300; - } - SetTimer(window, 0, delay, NULL); - } - } else { - if (OwnerDraw::End_Tooltip()) { - ReleaseCapture(); - } - KillTimer(window, 0); - } - - } else { - if (message >= WM_MOUSEMOVE && message <= WM_MBUTTONDBLCLK) { - if (OwnerDraw::End_Tooltip()) { - ReleaseCapture(); - } - KillTimer(window, 0); - } - - if (message == WM_TIMER) { - POINT pt; - Get_Logical_Cursor_Pos(window, pt); - int mx = pt.x; - int my = pt.y; - - BOOL inside = FALSE; - if (pt.x >= 0 && pt.y >= 0 && pt.x < client_rect.right && pt.y < client_rect.bottom) { - inside = TRUE; - } - - CHAR buf[128]; - memset(buf, 0, sizeof(buf)); - if (inside) { - WPARAM ctrl_id = GetWindowLong(window, GWL_ID); - HWND parent = GetParent(window); - SendMessage(parent, OD_GETTIPTEXT, ctrl_id, (LPARAM)buf); - if (strlen(buf) == 0) { - SendMessage(window, OD_GETCELLTIP, MAKELONG(mx, my), (LPARAM)buf); - } - - if (strcmp(buf, ODTooltip.text) != 0 && ODTooltip.isActive) { - if (OwnerDraw::End_Tooltip()) { - ReleaseCapture(); - } - } - - if (!OwnerDraw::Show_Tooltip(false)) { - if (strlen(buf)) { - HDC hdc = GetDC(window); - HFONT font = WS_Get_Font(hdc, ODFontName, 0, ODFontSize, 0); - if (font != NULL) { - SelectObject(hdc, font); - } - SetTextColor(hdc, ODColorText); - SetBkMode(hdc, TRANSPARENT); - SIZE size; - GetTextExtentPoint32(hdc, buf, strlen(buf), &size); - ReleaseDC(window, hdc); - - POINT cursor; - Get_Logical_Cursor_Pos(NULL, cursor); - Rect tooltip_rect; - tooltip_rect.X = cursor.x; - tooltip_rect.Y = cursor.y + 16; - tooltip_rect.Width = size.cx + 8; - tooltip_rect.Height = size.cy + 6; - - Rect mainclient = VisibleSurface->Get_Rect(); - if (tooltip_rect.Width + tooltip_rect.X >= mainclient.Width) { - tooltip_rect.X = mainclient.Width - tooltip_rect.Width; - } - if (tooltip_rect.Height + tooltip_rect.Y >= mainclient.Height) { - tooltip_rect.Y = mainclient.Height - tooltip_rect.Height; - } - - SetCapture(window); - OwnerDraw::Start_Tooltip(tooltip_rect, buf, window); - } - } else { - SetCapture(window); - } - } - } - } - } - } - - if (is_paint) { - - /* - * Paint path: when painting is disabled, just tell the control to repaint - * its frame. - */ - if (data->paintDisabled) { - ValidateRect(window, NULL); - result = CallWindowProc(specific_proc, window, OD_REFRESHNOPAINT, wparam, lparam); - } else { - - /* - * If this paint overlaps the tooltip, hide the tooltip first and re-show it - * once painting is finished. - */ - { - RECT tooltip_rect; - tooltip_rect.left = ODTooltip.bounds.X; - tooltip_rect.right = ODTooltip.bounds.Width + ODTooltip.bounds.X + 1; - tooltip_rect.top = ODTooltip.bounds.Y; - tooltip_rect.bottom = ODTooltip.bounds.Height + ODTooltip.bounds.Y + 1; - if (num_rect_updates == 1) { - RECT intersect; - if (IntersectRect(&intersect, &disp_rect, &tooltip_rect)) { - if (ODTooltip.isActive && ODTooltip.isHidden != 1 && ODTooltip.background != NULL) { - Rect drect = ODTooltip.bounds; - Rect srect(0, 0, ODTooltip.bounds.Width, ODTooltip.bounds.Height); - VisibleSurface->Blit_From(drect, *ODTooltip.background, srect); - ODTooltip.isHidden = true; - show_tooltip = true; - } - } - } - } - - if (min_update_rect.x >= disp_rect.left) { - min_update_rect.x = disp_rect.left; - } - if (min_update_rect.y >= disp_rect.top) { - min_update_rect.y = disp_rect.top; - } - if (max_update_rect.x <= disp_rect.right) { - max_update_rect.x = disp_rect.right; - } - if (max_update_rect.y <= disp_rect.bottom) { - max_update_rect.y = disp_rect.bottom; - } - - /* - * Walk up to the owning dialog window (the first ancestor that draws its own - * background) and read its animation state. - */ - owner = window; - while (owner != NULL) { - if (GetWindowLongPtr(owner, DWLP_DLGPROC) != 0) { - break; - } - owner = GetParent(owner); - } - - ownerdata = NULL; - if (owner != NULL) { - ODWinData.getPointer(owner, &ownerdata); - } - - if (owner == window) { - if (ownerdata->animState < 1) { - state = 1; - anim_state = 1; - goto call_proc_paint; - } - state = ownerdata->animState; - } else { - if (ownerdata == NULL) { - state = anim_state; - ValidateRect(window, NULL); - result = 1; - goto finalize_paint; - } - state = ownerdata->animState; - } - - anim_state = state; - if (state < 1) { - ValidateRect(window, NULL); - result = 1; - } else { - - call_proc_paint: - result = CallWindowProc(specific_proc, window, message, wparam, lparam); - if (ownerdata != NULL) { - ownerdata->animState = state; - } - - { - ArrayList children; - EnumChildWindows(window, (WNDENUMPROC)ODAddWindowToList, (LPARAM)&children); - - HWND combo_owner = NULL; - HWND child = NULL; - for (int index = 0; index < children.length(); index++) { - children.get(child, index); - - WNDPROC childproc = NULL; - OriginalWndProcs.getValue(child, childproc); - - if (childproc != (WNDPROC)ComboDropWinCtrlProc) { - InvalidateRect(child, NULL, FALSE); - UpdateWindow(child); - } else { - combo_owner = child; - } - } - - if (combo_owner != NULL) { - InvalidateRect(combo_owner, NULL, FALSE); - UpdateWindow(combo_owner); - } else if (_dropdown_window != NULL) { - if (_dropdown_owner == owner) { - Rect drop_rect; - Get_Display_Rect(_dropdown_window, (LPRECT)&drop_rect); - RECT intersect; - if (IntersectRect(&intersect, &disp_rect, (const RECT *)&drop_rect)) { - InvalidateRect(_dropdown_window, NULL, FALSE); - UpdateWindow(_dropdown_window); - } - } - } - } - - state = anim_state; - } - - finalize_paint: - if (state > 0) { - HWND sibling = owner; - while (window == owner || num_rect_updates == 1) { - if (sibling == NULL) { - break; - } - sibling = GetWindow(sibling, GW_HWNDPREV); - if (sibling == NULL) { - break; - } - if (GetWindowLongPtr(sibling, DWLP_DLGPROC)) { - Rect sibling_rect; - Get_Display_Rect(sibling, (LPRECT)&sibling_rect); - RECT intersect; - if (IntersectRect(&intersect, &disp_rect, (const RECT *)&sibling_rect)) { - InvalidateRect(sibling, NULL, FALSE); - UpdateWindow(sibling); - break; - } - } - } - } - } - goto after_proc; - } else { - goto call_custom_proc; - } - -cleanup: - ctrlmessages.remove(key); - - if (is_paint) { - if (GetWindowLongPtr(window, DWLP_DLGPROC)) { - if (num_rect_updates > 1) { - data->animState = 2; - } - } - - if (--num_rect_updates == 0) { - if (!data->paintDisabled && anim_state >= 1) { - - Rect rect2; - Rect screen_rect; - rect2.X = min_update_rect.x; - rect2.Y = min_update_rect.y; - rect2.Width = max_update_rect.x - min_update_rect.x; - rect2.Height = max_update_rect.y - min_update_rect.y; - screen_rect.X = min_update_rect.x; - screen_rect.Y = min_update_rect.y; - screen_rect.Width = max_update_rect.x - min_update_rect.x; - screen_rect.Height = max_update_rect.y - min_update_rect.y; - - if (GetWindowLongPtr(window, DWLP_DLGPROC) && data->animState == 1) { - - /* - * Animated dialog reveal -- the screen wipes open from the - * center outward with sliding "leftbar"/"rightbar" edges. - */ - if (Options.SoundVolume > 0.0) { - AudioEngine.Play_Sample(MixFileClass::Retrieve("EMBLEM.AUD"), AUDIO_GROUP_SFX, 64.0f / 255.0f, 255); - } - - struct _timeb start_time; - _ftime(&start_time); - - int half = rect2.Width / 2; - int frame = 0; - int center = rect2.Width / 2 + rect2.X; - - Surface *leftbar = SurfaceCache.GetSurface("leftbar.pcx", 0); - Surface *rightbar = SurfaceCache.GetSurface("rightbar.pcx", 0); - - Rect barsrc(0, 0, leftbar->Get_Width(), leftbar->Get_Height()); - Rect bardst = barsrc; - - int bar_width = leftbar->Get_Width(); - int bar_height = leftbar->Get_Height(); - - int step = 0; - int counter = 0; - int left_x = center - 12; - - while (step < half) { - { - int advance = bar_width; - if (bar_width >= step) { - advance = step; - } - - /* - * Reveal the next slice on the left of the wipe. The slice - * is one stride (12 pixels) wider than the bar so that it - * completely covers the bar stamped by the previous frame. - */ - int reveal_x = left_x; - rect2.X = left_x; - int width_cache = advance + 12; - rect2.Width = advance + 12; - int reveal_w = advance + 12; - if (left_x < min_update_rect.x) { - reveal_x = min_update_rect.x; - rect2.X = min_update_rect.x; - reveal_w = reveal_w + left_x - min_update_rect.x; - rect2.Width = reveal_w; - } - screen_rect.Width = reveal_w; - screen_rect.Height = rect2.Height; - screen_rect.X = offset_x + reveal_x; - screen_rect.Y = offset_y + rect2.Y; - VisibleSurface->Lock(); - AlternateSurface->Lock(); - VisibleSurface->Blit_From(screen_rect, *AlternateSurface, rect2); - AlternateSurface->Unlock(); - VisibleSurface->Unlock(); - - for (int y = 0; y < rect2.Height; y += bar_height) { - int bar_x = rect2.X - bar_width; - if (bar_x < rect2.X) { - bar_x = rect2.X; - } - bardst.Y = y + rect2.Y; - if (bar_height + y >= rect2.Height) { - int clip = rect2.Height - bar_height - y; - barsrc.Height += clip; - bardst.Height += clip; - } - screen_rect = bardst; - screen_rect.Y += offset_y; - screen_rect.X = bar_x; - screen_rect.X += offset_x; - VisibleSurface->Blit_From(screen_rect, *leftbar, barsrc); - barsrc.Height = bar_height; - bardst.Height = bar_height; - } - - /* - * Reveal the matching slice on the right of the wipe. - */ - int right_dst = center + step - advance; - rect2.Width = width_cache; - rect2.X = right_dst; - if (width_cache + right_dst >= max_update_rect.x) { - rect2.Width = max_update_rect.x - right_dst; - } - screen_rect.X = offset_x + rect2.X; - screen_rect.Y = offset_y + rect2.Y; - screen_rect.Width = rect2.Width; - screen_rect.Height = rect2.Height; - VisibleSurface->Lock(); - AlternateSurface->Lock(); - VisibleSurface->Blit_From(screen_rect, *AlternateSurface, rect2); - VisibleSurface->Unlock(); - AlternateSurface->Unlock(); - - for (int ry = 0; ry < rect2.Height; ry += bar_height) { - int bar_x = rect2.X + rect2.Width + bar_width; - if (bar_x > rect2.X + rect2.Width - bar_width) { - bar_x = rect2.X + rect2.Width - bar_width; - } - bardst.Y = ry + rect2.Y; - if (ry + bar_height >= rect2.Height) { - int clip = rect2.Height - ry - bar_height; - bardst.Height += clip; - barsrc.Height += clip; - } - screen_rect = bardst; - screen_rect.Y += offset_y; - screen_rect.X = bar_x; - screen_rect.X += offset_x; - VisibleSurface->Blit_From(screen_rect, *rightbar, barsrc); - barsrc.Height = bar_height; - bardst.Height = bar_height; - } - - struct _timeb now; - _ftime(&now); - frame++; - int wait = start_time.millitm + frame * (40 - counter / half) + 1000 * (start_time.time - now.time) - now.millitm; - if (wait > 0) { - Sleep(wait); - } - - if (AudioEngine.Is_Available() && GameInFocus == true) { - AudioEngine.Sound_Callback(); - Theme.AI(); - Speak_AI(); - } - - /* - * The whole animation runs inside one paint, so each step - * has to reach the screen from here. - */ - Video_Present_If_Dirty(); - - Sleep(0); - - step += 12; - counter += 240; - left_x -= 12; - }; - } - - data->animState = 2; - - Rect whole_rect; - whole_rect.X = min_update_rect.x; - whole_rect.Y = min_update_rect.y; - whole_rect.Width = max_update_rect.x - min_update_rect.x; - whole_rect.Height = max_update_rect.y - min_update_rect.y; - screen_rect.X = offset_x + min_update_rect.x; - screen_rect.Y = offset_y + min_update_rect.y; - screen_rect.Width = max_update_rect.x - min_update_rect.x; - screen_rect.Height = max_update_rect.y - min_update_rect.y; - VisibleSurface->Lock(); - AlternateSurface->Lock(); - VisibleSurface->Blit_From(screen_rect, *AlternateSurface, whole_rect); - AlternateSurface->Unlock(); - VisibleSurface->Unlock(); - - ArrayList children; - EnumChildWindows(window, (WNDENUMPROC)ODAddWindowToList, (LPARAM)&children); - HWND child = NULL; - for (int index = 0; index < children.length(); index++) { - children.get(child, index); - SendMessage(child, OD_ACTIVATE, 0, 0); - } - - } else { - - /* - * Unanimated update -- copy the dirty rectangle straight from - * the back buffer to the screen. - */ - data->animState = 2; - screen_rect.Width = rect2.Width; - screen_rect.Height = rect2.Height; - screen_rect.Y = offset_y + rect2.Y; - screen_rect.X = offset_x + rect2.X; - VisibleSurface->Lock(); - AlternateSurface->Lock(); - VisibleSurface->Blit_From(screen_rect, *AlternateSurface, rect2); - AlternateSurface->Unlock(); - VisibleSurface->Unlock(); - } - } - - min_update_rect.x = 0xFFFFFF; - min_update_rect.y = 0xFFFFFF; - max_update_rect.x = 0; - max_update_rect.y = 0; - } - } - - if (show_tooltip) { - OwnerDraw::Show_Tooltip(true); - } - - if (message == WM_INITDIALOG) { - result = 0; - } - return(result); -} - - -/// -/// Handles the messages for a control with no owner-draw procedure of its own. -/// This is the fallback custom procedure InitializeCtrl hands to any control class the -/// owner-draw system does not paint. Only the edit coloring message is claimed, so that a -/// child edit box picks up the dialog's own font and text color. -/// -/// Returns with a null background brush for the edit coloring message; otherwise -/// with the result of the original window procedure. -LRESULT CALLBACK DefaultCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - WNDPROC proc = NULL; - - OriginalWndProcs.getValue(window, proc); - - RECT rect1; - Get_Display_Rect(window, &rect1); - - RECT rect2; - GetClientRect(window, &rect2); - - WinData *data = NULL; - ODWinData.getPointer(window, &data); - - if (message == WM_CTLCOLOREDIT) { - HDC hdc = (HDC)wparam; - HWND ctrl = (HWND)lparam; - SetTextColor(hdc, ODColorText); - SetBkMode(hdc, TRANSPARENT); - - HFONT font = WS_Get_Font(hdc, ODFontName, 0, ODFontSize, 0); - if (font != NULL) { - SendMessage(ctrl, WM_SETFONT, (WPARAM)font, 0); - } - - return((LRESULT)GetStockObject(NULL_BRUSH)); - } - - return(CallWindowProc(proc, window, message, wparam, lparam)); -} - - -/// -/// Handles the messages for an owner-drawn push button. -/// The button is painted either from an image supplied by the dialog or from the button -/// artwork fitted to the control, with the caption drawn over it and the whole control -/// dimmed while it is disabled. The click sound is played as the button goes down. -/// -/// Returns with the result of the original window procedure for anything this -/// routine does not handle itself. -LRESULT CALLBACK ButtonCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - WNDPROC proc = NULL; - OriginalWndProcs.getValue(window, proc); - - RECT drect; - memset(&drect, 0, sizeof(drect)); - Get_Display_Rect(window, &drect); - - Rect origin(drect.left, drect.top, drect.right - drect.left, drect.bottom - drect.top); - - RECT crect; - memset(&crect, 0, sizeof(crect)); - GetClientRect(window, &crect); - - WinData * data = NULL; - ODWinData.getPointer(window, &data); - - int state = data->Button.state; - LONG style = GetWindowLong(window, GWL_STYLE); - - switch (message) { - - case WM_PAINT: { - Rect rect = origin; - COLORREF color = ODColorText; - - /* - * Lazily build the render-cache surface that holds the dimmed - * background captured behind the button. - */ - if (data->cachedSurface == NULL) { - BSurface * surface = new BSurface(crect.right + 1, crect.bottom + 1, 2); - data->cachedSurface = surface; - _surface_count++; - - Rect dest; - Rect src; - dest.X = 0; - dest.Y = 0; - dest.Width = crect.right + 1; - dest.Height = crect.bottom + 1; - src.X = drect.left; - src.Y = drect.top; - src.Width = crect.right + 1; - src.Height = crect.bottom + 1; - - surface->Blit_From(dest, *AlternateSurface, src); - } - - static char _prev_state = 'u'; - - if (data->image != NULL) { - - /* - * A user image was supplied -- blit it directly, choosing the - * pressed variant when the button is down. - */ - Surface * image = data->image; - if ((state & 1) && data->altImage != NULL) { - image = data->altImage; - } - - Rect src = rect; - src.X = 0; - src.Y = 0; - AlternateSurface->Blit_From(rect, *image, src); - - } else { - - /* - * No image -- draw the button from its skin pieces and play the - * click sound when it first goes down. - */ - char updown = 'u'; - if (state & 1) { - updown = 'd'; - } - if (style & WS_DISABLED) { - updown = 'u'; - } else if (updown == 'd' && _prev_state == 'u') { - Sound_Effect(Rule->GenericClick); - } - - int widths[2] = {7, 7}; - _prev_state = updown; - int margins[2] = {10, 10}; - int heights[2] = {24, 30}; - - int index = 0; - for (unsigned int i = 0; i < 2; i++) { - if (heights[i] > rect.Height && i != 0) { - break; - } - index = i; - } - - int height = heights[index]; - int w = widths[index]; - int margin = margins[index]; - - /* - * Restore the cached background before drawing the skin. - */ - if (data->cachedSurface != NULL) { - AlternateSurface->Blit_From( - Rect(drect.left, drect.top, crect.right + 1, crect.bottom + 1), - *data->cachedSurface, - Rect(0, 0, crect.right + 1, crect.bottom + 1)); - InvalidateRect(window, NULL, FALSE); - } - - origin.Y += (rect.Height - height) / 2; - if (state & 1) { - origin.Y += 2; - } - - Rect destrect; - Rect sourcerect; - char buffer[40]; - - sprintf(buffer, "b%c%c_li%d.pcx", updown, 'e', height); - Surface * left = SurfaceCache.GetSurface(buffer); - origin.Height = left->Get_Height(); - destrect = origin; - destrect.Width = w; - sourcerect.Width = w; - sourcerect.Y = 0; - sourcerect.X = 0; - destrect.Height = height; - sourcerect.Height = height; - AlternateSurface->Blit_From(destrect, *left, sourcerect); - - sprintf(buffer, "b%c%c_mi%d.pcx", updown, 'e', height); - Surface * mid = SurfaceCache.GetSurface(buffer); - sourcerect = origin; - sourcerect.X += w; - sourcerect.Width -= margin; - sourcerect.Height = mid->Get_Height(); - SurfaceCache.Draw(sourcerect, *AlternateSurface, *mid, 0, 0); - - sprintf(buffer, "b%c%c_ri%d.pcx", updown, 'e', height); - Surface * right = SurfaceCache.GetSurface(buffer); - destrect = origin; - destrect.X += origin.Width - margin; - destrect.Width = margin; - destrect.Height = right->Get_Height(); - sourcerect.Height = destrect.Height; - sourcerect.Width = destrect.Width; - sourcerect.Y = 0; - sourcerect.X = 0; - AlternateSurface->Blit_From(destrect, *right, sourcerect); - } - - /* - * Render the caption text (no user image case only). - */ - if (data->image == NULL) { - RECT client; - GetClientRect(window, &client); - static char buffer2[256]; - GetWindowText(window, buffer2, 256); - - Rect text_rect( - origin.X, - origin.Y + 1, - origin.Width + origin.X - 2, - origin.Height + origin.Y - 2); - if (state & 1) { - text_rect.X += 2; - text_rect.Y += 4; - } - OD_Draw_Text_Remap(*AlternateSurface, buffer2, text_rect, "dlgsys", color, 5, 0); - } - - if (style & WS_DISABLED) { - ODFillRectTrans(rect, *AlternateSurface, 0, 128); - } - ValidateRect(window, NULL); - } - /// Fall through. - - case WM_ACTIVATE: - case WM_KILLFOCUS: - case WM_MOUSEACTIVATE: - return(0); - - default: - return(CallWindowProc(proc, window, message, wparam, lparam)); - } -} - - -/// -/// Handles the messages for an owner-drawn tab control. -/// The body of the control is painted as dimmed dialog background and each tab is built -/// from its corner and middle artwork, with the caption drawn over it in the remapped -/// font. The active tab gets the brighter text color. -/// -/// Returns with the result of the original window procedure for anything this -/// routine does not paint itself. -LRESULT CALLBACK TextBoxCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - static char string1[64]; - - WinData * data = NULL; - ODWinData.getPointer(window, &data); - - WNDPROC proc = NULL; - OriginalWndProcs.getValue(window, proc); - - LRESULT result = 0; - - if (message == WM_ERASEBKGND) { - return(1); - } - - if (message == WM_NCPAINT) { - return(0); - } - - if (message == OD_SUBCLASSED) { - RECT rect; - Get_Display_Rect(window, &rect); - - Surface * surf = SurfaceCache.GetSurface("tab_tlu.pcx"); - SendMessage(window, TCM_SETITEMSIZE, 0, MAKELPARAM(89, surf->Get_Height() - 1)); - - if (proc) { - return(CallWindowProc(proc, window, message, wparam, lparam)); - } - return(result); - } - - if (message == WM_PAINT) { - RECT winrect; - Get_Display_Rect(window, &winrect); - - TabCtrl_GetItemCount(window); - - RECT tabrect; - TabCtrl_GetItemRect(window, 0, &tabrect); - TabCtrl_GetItemRect(window, 0, &tabrect); - - int y = winrect.top + tabrect.bottom - tabrect.top + 3; - - Rect dimrect; - dimrect.X = winrect.left; - dimrect.Y = y; - dimrect.Width = winrect.right - winrect.left; - dimrect.Height = winrect.bottom - y; - - winrect.top = y; - - ODDrawDimmedBackground(dimrect, window); - ValidateRect(window, NULL); - - /* - * Refilled with the full display rect, although nothing reads it before - * the tab loop overwrites it again. - */ - dimrect.X = winrect.left; - dimrect.Y = winrect.top; - dimrect.Width = winrect.right - winrect.left; - dimrect.Height = winrect.bottom - winrect.top; - - Rect rect; - Rect src; - - Surface * image = SurfaceCache.GetSurface("tab_fml.pcx"); - if (image) { - image->Get_Height(); - rect.Width = image->Get_Width(); - rect.X = winrect.left; - rect.Y = winrect.top; - rect.Height = winrect.bottom - winrect.top; - SurfaceCache.Draw(rect, *AlternateSurface, *image, 0, 0); - } - - image = SurfaceCache.GetSurface("tab_fmr.pcx"); - if (image) { - image->Get_Height(); - rect.Width = image->Get_Width(); - rect.X = winrect.right - rect.Width; - rect.Y = winrect.top; - rect.Height = winrect.bottom - winrect.top; - SurfaceCache.Draw(rect, *AlternateSurface, *image, 0, 0); - } - - image = SurfaceCache.GetSurface("tab_ftm.pcx"); - if (image) { - int height = image->Get_Height(); - image->Get_Width(); - rect.X = winrect.left; - rect.Y = winrect.top; - rect.Width = winrect.right - winrect.left; - rect.Height = height; - SurfaceCache.Draw(rect, *AlternateSurface, *image, 0, 0); - } - - image = SurfaceCache.GetSurface("tab_fbm.pcx"); - if (image) { - int height = image->Get_Height(); - image->Get_Width(); - rect.X = winrect.left; - rect.Y = winrect.bottom - height; - rect.Width = winrect.right - winrect.left; - rect.Height = height; - SurfaceCache.Draw(rect, *AlternateSurface, *image, 0, 0); - } - - image = SurfaceCache.GetSurface("tab_ftl.pcx"); - if (image) { - int height = image->Get_Height(); - int width = image->Get_Width(); - rect.Y = winrect.top; - rect.X = winrect.left; - src.X = 0; - src.Y = 0; - src.Width = width; - src.Height = height; - rect.Width = width; - rect.Height = height; - AlternateSurface->Blit_From(rect, *image, src); - } - - image = SurfaceCache.GetSurface("tab_ftr.pcx"); - if (image) { - int height = image->Get_Height(); - int width = image->Get_Width(); - rect.Y = winrect.top; - rect.X = winrect.right - width; - src.X = 0; - src.Y = 0; - src.Width = width; - src.Height = height; - rect.Width = width; - rect.Height = height; - AlternateSurface->Blit_From(rect, *image, src); - } - - image = SurfaceCache.GetSurface("tab_fbl.pcx"); - if (image) { - int height = image->Get_Height(); - int width = image->Get_Width(); - rect.X = winrect.left; - rect.Y = winrect.bottom - height; - src.X = 0; - src.Y = 0; - src.Width = width; - src.Height = height; - rect.Width = width; - rect.Height = height; - AlternateSurface->Blit_From(rect, *image, src); - } - - image = SurfaceCache.GetSurface("tab_fbr.pcx"); - if (image) { - int height = image->Get_Height(); - int width = image->Get_Width(); - rect.Y = winrect.bottom - height; - rect.X = winrect.right - width; - src.X = 0; - src.Y = 0; - src.Width = width; - src.Height = height; - rect.Width = width; - rect.Height = height; - AlternateSurface->Blit_From(rect, *image, src); - } - - int current = TabCtrl_GetCurSel(window); - Get_Display_Rect(window, &winrect); - int tab = current + 1; - int itab = tab; - - while (true) { - RECT itemrect; - - while (!TabCtrl_GetItemRect(window, tab, &itemrect)) { - itab = 0; - tab = 0; - } - - TC_ITEM item; - memset(&item, 0, sizeof(item)); - item.pszText = string1; - item.cchTextMax = sizeof(string1); - strcpy(item.pszText, "Title"); - item.mask = TCIF_TEXT; - TabCtrl_GetItem(window, tab, &item); - - char state = 'd'; - if (tab == current) { - state = 'u'; - } - - LONG left = itemrect.left; - if (itemrect.left >= 6) { - left = 6; - } - - Rect tab_rect; - tab_rect.X = winrect.left + itemrect.left - left; - tab_rect.Y = itemrect.top + winrect.top; - - LONG right = itemrect.left; - if (itemrect.left >= 6) { - right = 6; - } - tab_rect.Width = itemrect.right + right - itemrect.left; - tab_rect.Height = itemrect.bottom - itemrect.top; - - char fname[64]; - Surface * tab_lu = SurfaceCache.GetSurface("tab_tlu.pcx"); - sprintf(fname, "tab_tm%c.pcx", state); - image = SurfaceCache.GetSurface(fname); - if (image) { - int height = image->Get_Height(); - image->Get_Width(); - int width = tab_rect.Width - 2 * tab_lu->Get_Width(); - - rect.X = tab_rect.X + tab_lu->Get_Width(); - rect.Y = tab_rect.Y; - rect.Width = width; - rect.Height = height; - SurfaceCache.Draw(rect, *AlternateSurface, *image, 0, 0); - } - - sprintf(fname, "tab_tl%c.pcx", state); - image = SurfaceCache.GetSurface(fname); - if (image) { - int height = image->Get_Height(); - int width = image->Get_Width(); - rect.X = tab_rect.X; - rect.Y = tab_rect.Y; - rect.Width = width; - rect.Height = height; - SurfaceCache.DrawTrans(rect, *AlternateSurface, *image, (255 >> DSurface::RedLeft << DSurface::RedRight) | (255u >> DSurface::BlueLeft << DSurface::BlueRight)); - } - - sprintf(fname, "tab_tr%c.pcx", state); - image = SurfaceCache.GetSurface(fname); - if (image) { - int height = image->Get_Height(); - Rect trrect; - trrect.Width = image->Get_Width(); - trrect.X = tab_rect.X + tab_rect.Width - trrect.Width; - trrect.Y = tab_rect.Y; - trrect.Height = height; - SurfaceCache.DrawTrans(trrect, *AlternateSurface, *image, (255 >> DSurface::RedLeft << DSurface::RedRight) | (255u >> DSurface::BlueLeft << DSurface::BlueRight)); - } - - if (item.pszText) { - strcpy(string1, item.pszText); - } - - Rect text_rect; - text_rect.X = tab_rect.X; - text_rect.Width = tab_rect.X + tab_rect.Width; - text_rect.Y = tab_rect.Y + 6; - text_rect.Height = tab_rect.Y + tab_rect.Height; - - COLORREF color = ODColorTextDim; - if (itab == current) { - color = ODColorText; - } - OD_Draw_Text_Remap(*AlternateSurface, string1, text_rect, "dlgsys", color, 5, 0); - - ValidateRect(window, &itemrect); - if (itab == current) { - break; - } - - itab++; - tab = itab; - } - - ValidateRect(window, NULL); - return(result); - } - - if (proc) { - return(CallWindowProc(proc, window, message, wparam, lparam)); - } - return(result); -} - - -/// -/// Handles the messages for an owner-drawn edit control. -/// The control is inset inside its border and held out of the dialog's tab order until it -/// is deliberately activated, so that a stray keystroke cannot land in it. Return and tab -/// are intercepted -- return notifies the parent dialog, tab moves on to the next control. -/// -/// Returns with the result of the original window procedure for anything this -/// routine does not handle itself. -LRESULT CALLBACK EditBoxCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - WNDPROC proc = NULL; - OriginalWndProcs.getValue(window, proc); - - WinData * data = NULL; - ODWinData.getPointer(window, &data); - - if (GetFocus() == window && !data->Edit.focusEnabled) { - data->Edit.focusPending = 1; - SetFocus(MainWindow); - } - - LONG style = GetWindowLong(window, GWL_STYLE); - char class_name[32]; - - if (message != WM_KEYUP && message != WM_KEYDOWN || wparam != VK_TAB) { - - if (message == OD_SUBCLASSED) { - RECT window_rect; - GetWindowRect(window, &window_rect); - - RECT client_rect; - GetClientRect(window, &client_rect); - - RECT parent_rect; - GetWindowRect(GetParent(window), &parent_rect); - - MoveWindow(window, window_rect.left - parent_rect.left + 1, window_rect.top - parent_rect.top + 1, client_rect.right - 2, client_rect.bottom - 2, FALSE); - - if (GetFocus() == window) { - data->Edit.focusPending = 1; - SetFocus(MainWindow); - } - - if (style & WS_TABSTOP) { - data->Edit.hadTabStop = 1; - SetWindowLong(window, GWL_STYLE, style & ~WS_TABSTOP); - } - } - - else if (message == WM_SETFOCUS) { - SendMessage(window, EM_SETSEL, (WPARAM)-1, (LPARAM)-1); - if (!data->Edit.focusEnabled) { - PostMessage(window, OD_REFOCUS, 0, 0); - } - goto invalidate_and_default; - } - - else if (message == WM_GETTEXT) { - LRESULT text_len = CallWindowProc(proc, window, WM_GETTEXT, wparam, lparam); - char * buffer = new char[text_len + 2]; - memset(buffer, 0, text_len + 2); - - int out_len = 0; - int i = 0; - for (; i < text_len; ++i) { - char ch = ((char *)lparam)[i]; - if (ch != '\r' && ch != '\n') { - buffer[out_len++] = ch; - } - } - - if (i != out_len) { - strcat(buffer, "\r\n"); - out_len += 2; - } - - strcpy((char *)lparam, buffer); - delete[] buffer; - return(out_len); - } - - else if (message == WM_CHAR) { - if (wparam == VK_RETURN) { - if (style & ES_MULTILINE) { - WPARAM len = SendMessage(window, WM_GETTEXTLENGTH, 0, 0) + 3; - char * text = new char[len]; - SendMessage(window, WM_GETTEXT, len, (LPARAM)text); - strcat(text, "\r\n"); - SendMessage(window, WM_SETTEXT, 0, (LPARAM)text); - delete[] text; - - SendMessage(GetParent(window), WM_COMMAND, (unsigned short)GetWindowLong(window, GWL_ID) | 0x5010000, (LPARAM)window); - return(0); - } - } else if (wparam == VK_TAB) { - HWND next = window; - HWND tab_item = GetNextDlgTabItem(GetParent(window), window, FALSE); - if (tab_item != NULL) { - next = tab_item; - } - SetFocus(next); - return(0); - } - goto call_default; - } - - else if (message == OD_ACTIVATE) { - - int focus_state = data->Edit.focusPending; - data->Edit.focusEnabled = 1; - if (focus_state) { - SetFocus(window); - data->Edit.focusPending = 0; - } - if (data->Edit.hadTabStop) { - SetWindowLong(window, GWL_STYLE, style | WS_TABSTOP); - } - } - - else { - if (message == WM_PAINT || message == WM_ERASEBKGND) { - RECT display_rect; - Get_Display_Rect(window, &display_rect); - - GetClassName(GetParent(window), class_name, sizeof(class_name)/2); - bool is_combo = strcmp(class_name, "ComboBox") == 0; - - RECT update_rect; - if (message == WM_PAINT && GetUpdateRect(window, &update_rect, FALSE)) { - update_rect.right += display_rect.left; - update_rect.left += display_rect.left; - update_rect.top += display_rect.top; - update_rect.bottom += display_rect.top; - } - - Rect draw_rect; - draw_rect.X = display_rect.left; - draw_rect.Y = display_rect.top; - draw_rect.Width = display_rect.right - display_rect.left + 1; - draw_rect.Height = display_rect.bottom - display_rect.top + 1; - - ODDrawDimmedBackground(draw_rect, window); - if (!is_combo) { - OD_Draw_Rect(*AlternateSurface, draw_rect, 1, 0xFFFFFFFF); - } - - static char _buffer[512]; - SendMessage(window, WM_GETTEXT, 500, (LPARAM)_buffer); - - Rect text_rect; - text_rect.X = display_rect.left; - text_rect.Y = display_rect.top; - text_rect.Width = 0; - text_rect.Height = 0; - - if (GetWindowLong(window, GWL_STYLE) & ES_PASSWORD) { - for (int i = 0; i < (int)strlen(_buffer); ++i) { - _buffer[i] = '*'; - } - } - - OD_Draw_Text(ODColorText, ODFontPtr, text_rect, _buffer, strlen(_buffer), 0, 0, 0); - - WPARAM em_wparam; - LPARAM em_lparam; - unsigned int sel = ((unsigned int)SendMessage(window, EM_GETSEL, (WPARAM)&em_wparam, (LPARAM)&em_lparam)) >> 16; - if (HIWORD(sel) > LOWORD(sel)) { - sel >>= 16; - } - - int charidx = LOWORD(sel); - if (charidx < (int)strlen(_buffer)) { - SendMessage(window, EM_POSFROMCHAR, charidx, 0); - } - - ValidateRect(window, NULL); - } - - if (message == WM_CONTEXTMENU) { - return(1); - } - - if (message == WM_MOUSEMOVE) { - return(1); - } - - if (message == WM_KEYDOWN || message == WM_KEYUP || message == WM_SYSKEYDOWN || message == WM_SYSKEYUP || message == WM_SYSCHAR || message == WM_SYSDEADCHAR || message == WM_KILLFOCUS || message == WM_LBUTTONDOWN) { - invalidate_and_default: - GetClassName(GetParent(window), class_name, sizeof(class_name)); - if (strcmp(class_name, "ComboBox") == 0) { - InvalidateRect(GetParent(window), NULL, FALSE); - } - InvalidateRect(window, NULL, FALSE); - } - - call_default: - return(CallWindowProc(proc, window, message, wparam, lparam)); - } - } - return(0); -} - - -/// -/// Handles the messages for an owner-drawn static text control. -/// The caption is kept in the control's owner-draw record rather than in the window, so it -/// can be drawn in the remapped font over a cached copy of the dialog background. The -/// dialog can recolor the text at any time with OD_SETCOLOR. -/// -/// Returns with the result of the original window procedure for anything this -/// routine does not handle itself. -LRESULT CALLBACK StaticCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - WinData * data = NULL; - - switch (message) { - - case WM_SETTEXT: { - ODWinData.getPointer(window, &data); - - delete[] data->Static.text; - - const char * text = (const char *)lparam; - unsigned int text_len = strlen(text) + 1; - char * copy = new char[text_len]; - memset(copy, 0, text_len); - strcpy(copy, text); - - data->Static.text = copy; - - Surface * cachedSurf = data->cachedSurface; - if (cachedSurf != NULL) { - RECT drect; - Get_Display_Rect(window, &drect); - - RECT crect; - GetClientRect(window, &crect); - - Rect dst_rect; - dst_rect.X = drect.left; - dst_rect.Y = drect.top; - dst_rect.Width = crect.right + 1; - dst_rect.Height = crect.bottom + 1; - - Rect src_rect; - src_rect.X = 0; - src_rect.Y = 0; - src_rect.Width = crect.right + 1; - src_rect.Height = crect.bottom + 1; - - AlternateSurface->Blit_From(dst_rect, *cachedSurf, src_rect); - InvalidateRect(window, NULL, FALSE); - } - return(1); - } - - case WM_DESTROY: { - ODWinData.getPointer(window, &data); - - if (data->Static.text != NULL) { - delete[] data->Static.text; - data->Static.text = NULL; - } - if (data->cachedSurface != NULL) { - delete data->cachedSurface; - data->cachedSurface = NULL; - } - break; - } - - case WM_MOVE: - case WM_SIZE: - case WM_WINDOWPOSCHANGED: { - if (ODWinData.getPointer(window, &data)) { - if (data != NULL) { - delete data->cachedSurface; - data->cachedSurface = NULL; - } - } - InvalidateRect(window, NULL, FALSE); - return(0); - } - - case WM_GETTEXT: { - ODWinData.getPointer(window, &data); - - const char * text = data->Static.text; - if (strlen(text) + 1 < wparam) { - wparam = strlen(text) + 1; - } - strncpy((char *)lparam, text, wparam); - return(wparam); - } - - case WM_PAINT: { - char text[2048]; - text[2047] = '\0'; - - ODWinData.getPointer(window, &data); - - if (data->cachedSurface == NULL) { - RECT drect; - Get_Display_Rect(window, &drect); - - RECT crect; - GetClientRect(window, &crect); - - BSurface * surf = new BSurface(crect.right + 1, crect.bottom + 1, 2); - data->cachedSurface = surf; - ++_surface_count; - - Rect dst_rect; - dst_rect.X = 0; - dst_rect.Y = 0; - dst_rect.Width = crect.right + 1; - dst_rect.Height = crect.bottom + 1; - - Rect src_rect; - src_rect.X = drect.left; - src_rect.Y = drect.top; - src_rect.Width = crect.right + 1; - src_rect.Height = crect.bottom + 1; - - surf->Blit_From(dst_rect, *AlternateSurface, src_rect); - } - - Rect text_rect; - Get_Display_Rect(window, (LPRECT)&text_rect); - - GetWindowText(window, text, 2047); - - LONG style = GetWindowLong(window, GWL_STYLE); - int draw_flags = 16; - - if (style & SS_CENTER) { - draw_flags = 17; - } else if (style & SS_RIGHT) { - draw_flags = 18; - } - - COLORREF color = data->Static.textColor; - if ((style & WS_DISABLED) != 0) { - color = ODColorDisabled; - } - - OD_Draw_Text_Remap(*AlternateSurface, text, text_rect, "dlgsys", color, draw_flags, 0); - - ValidateRect(window, NULL); - return(0); - } - - case OD_SUBCLASSED: { - WNDPROC proc = NULL; - ODWinData.getPointer(window, &data); - OriginalWndProcs.getValue(window, proc); - - unsigned int len = SendMessage(window, WM_GETTEXTLENGTH, 0, 0) + 1; - char * text = new char[len]; - memset(text, 0, len); - - CallWindowProc(proc, window, WM_GETTEXT, len, (LPARAM)text); - - data->Static.text = text; - data->Static.textColor = ODColorText; - return(0); - } - - case OD_SETCOLOR: { - ODWinData.getPointer(window, &data); - - if (data != NULL) { - if ((COLORREF)lparam != data->Static.textColor) { - InvalidateRect(window, NULL, FALSE); - } - if (lparam == -1) { - data->Static.textColor = ODColorText; - } else { - data->Static.textColor = lparam; - } - } - return(0); - } - - default: - break; - } - - WNDPROC proc = NULL; - OriginalWndProcs.getValue(window, proc); - return(CallWindowProc(proc, window, message, wparam, lparam)); -} - - -/// -/// Handles the messages for an owner-drawn check box. -/// The checked state is kept in the control's owner-draw record and painted from the check -/// box artwork, dimmed when the control is disabled. A click on the box itself toggles the -/// state, plays the click sound and notifies the parent dialog. -/// -/// Returns with the result of the original window procedure for anything this -/// routine does not handle itself. -LRESULT CALLBACK CheckBoxCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - WinData* data = NULL; - ODWinData.getPointer(window, &data); - - switch (message) { - - case BM_GETCHECK: { - return(data->CheckBox.checkState); - } - - case WM_SETFOCUS: - case WM_KILLFOCUS: { - InvalidateRect(window, NULL, FALSE); - break; - } - - - case WM_PAINT: { - /* - * DEAD CODE - */ - RECT cr; - GetClientRect(window, &cr); - Rect trect; - Get_Display_Rect(window, (LPRECT)&trect); - int somebool = 0; - if (data->CheckBox.checkState == 1) { - somebool = 1; - } - RECT disprect; - Get_Display_Rect(window, (LPRECT)&disprect); - Rect drect; - drect.X = disprect.left; - drect.Y = disprect.top; - drect.Width = 18; - drect.Height = 18; - int style = GetWindowLong(window, GWL_STYLE); - char letter = 'u'; - if (somebool) { - letter = 'c'; - } - char buf[64]; - sprintf(buf, "c%ce_i.pcx", letter); - Surface *image = SurfaceCache.GetSurface(buf); - int image_height = image->Get_Height(); - Rect srect; - srect.Width = image->Get_Width(); - srect.X = 0; - srect.Y = 0; - srect.Height = image_height; - AlternateSurface->Blit_From(drect, *image, srect); - - if (style & WS_DISABLED) { - ODFillRectTrans(drect, *AlternateSurface, 0, 128); - } - - static char _wintext[128]; - GetWindowText(window, _wintext, sizeof(_wintext) - 1); - - trect.X += 20; - trect.Width -= 20; - COLORREF color = ODColorText; - if (style & WS_DISABLED) { - color = ODColorDisabled; - } - OD_Draw_Text_Remap(*AlternateSurface, _wintext, trect, "dlgsys", color, 4, 0); - ValidateRect(window, NULL); - return(0); - } - - case BM_SETCHECK: { - data->CheckBox.checkState = wparam; - InvalidateRect(window, NULL, FALSE); - return(0); - } - - case WM_LBUTTONDOWN: - case WM_LBUTTONDBLCLK: { - int xpos = (unsigned short)LOWORD(lparam); - int ypos = (unsigned short)HIWORD(lparam); - if (xpos < 18 && ypos < 18) { - int checked = data->CheckBox.checkState != 1; - data->CheckBox.checkState = checked; - InvalidateRect(window, NULL, FALSE); - Sound_Effect(Rule->GenericClick); - HWND parent = GetParent(window); - SendMessage(parent, WM_COMMAND, MAKEWPARAM(GetWindowLong(window, GWL_ID), checked), (LPARAM)window); - return(0); - } else { - return(0); - } - } - - case OD_SUBCLASSED: { - WNDPROC subproc = NULL; - OriginalWndProcs.getValue(window, subproc); - data->CheckBox.checkState = CallWindowProc(subproc, window, BM_GETCHECK, 0L, 0L); - break; - } - } - - WNDPROC proc = NULL; - OriginalWndProcs.getValue(window, proc); - return(CallWindowProc(proc, window, message, wparam, lparam)); -} - - -/// -/// Handles the messages for an owner-drawn combo box. -/// The closed box is painted with its arrow button and the text of the current selection. -/// A click on the arrow drops the list, which is a ComboDropWin window created and -/// destroyed here rather than the stock Windows list. -/// -/// Returns with the result of the original window procedure for anything this -/// routine does not handle itself. -LRESULT CALLBACK ComboBoxCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - static char _buffer[256]; - RECT display_rect; - RECT client_rect; - RECT window_rect; - Get_Display_Rect(window, &display_rect); - GetClientRect(window, &client_rect); - GetWindowRect(window, &window_rect); - - WinData * data = NULL; - ODWinData.getPointer(window, &data); - - switch (message) { - case WM_LBUTTONDOWN: - case WM_LBUTTONDBLCLK: { - Sound_Effect(Rule->GenericClick); - if ((unsigned short)LOWORD(lparam) > client_rect.right - 20) { - LRESULT dropped = SendMessage(window, CB_GETDROPPEDSTATE, 0, 0); - PostMessage(window, CB_SHOWDROPDOWN, (WPARAM)(dropped != 1), 0); - } - return(0); - } - - case WM_ERASEBKGND: - return(0); - - case WM_DESTROY: - SendMessage(window, CB_SHOWDROPDOWN, FALSE, 0); - break; - - case WM_PAINT: { - LRESULT dropped = SendMessage(window, CB_GETDROPPEDSTATE, 0, 0); - RECT dropped_rect; - RECT wrect; - GetWindowRect(window, &wrect); /// result is not used - SendMessage(window, CB_GETDROPPEDCONTROLRECT, 0, (LPARAM)&dropped_rect); - - Rect rect; - rect.X = display_rect.left; - rect.Y = display_rect.top; - rect.Width = display_rect.right - display_rect.left; - rect.Height = 24; - - dropped_rect.top += (display_rect.bottom - display_rect.top + 1); - dropped_rect.bottom = dropped_rect.bottom + display_rect.top - display_rect.bottom - 1; - - int width = display_rect.right - display_rect.left; - int height = display_rect.bottom - display_rect.top; - - HWND parent = GetParent(window); - WinData * parent_data = NULL; - if (parent) { - ODWinData.getPointer(parent, &parent_data); - } - - RECT parent_rect; - Get_Display_Rect(parent, &parent_rect); - - Rect dst_rect(0, 0, width, height); - Rect src_rect(0, 0, width, height); - if (parent_data != NULL && parent_data->cachedSurface != NULL) { - src_rect.X = display_rect.left - parent_rect.left; - src_rect.Y = display_rect.top - parent_rect.top; - } - - if (data->cachedSurface == NULL) { - Surface * surface = new BSurface(width, height, 2); - data->cachedSurface = surface; - _surface_count++; - - if (parent_data != NULL && parent_data->cachedSurface != NULL) { - surface->Blit_From(dst_rect, *parent_data->cachedSurface, src_rect, false, true); - } - - int total = width * height; - unsigned short * surfptr = (unsigned short *)surface->Lock(); - if (total > 0) { - for (int i = 0; i < total; ++i) { - surfptr[i] = OD_Blend_Color(surfptr[i], 0, ODColorSteps); - } - } - if (surfptr != NULL) { - surface->Unlock(); - } - } - - ODDrawDimmedBackground(rect, window); - ODFillRectTrans(rect, *AlternateSurface, 0, 128); - OD_Draw_Rect(*AlternateSurface, rect, 1, 0xFFFFFFFF); - - Rect arrow_rect; - arrow_rect.X = display_rect.right - 19; - arrow_rect.Y = rect.Y + 1; - arrow_rect.Width = rect.Width; - arrow_rect.Height = rect.Height; - ODDrawArrowBitmap(*AlternateSurface, arrow_rect, dropped, dropped); - - LONG style = GetWindowLong(window, GWL_STYLE); - if ((style & WS_DISABLED) != 0) { - ODFillRectTrans(rect, *AlternateSurface, 0, 128); - } - - if ((style & 3) == 3) { - sprintf(_buffer, "NULL"); - GetWindowText(window, _buffer, sizeof(_buffer)); - - COLORREF text_color = ODColorText; - if ((style & WS_DISABLED) != 0) { - text_color = ODColorDisabled; - } - - FontMetrics font_data; - if (ODGetFontMetrics("dlgsys", &font_data)) { - int text_width = 0; - int max_width = client_rect.right - 28; - int ellipsis_width = 3 * font_data.charWidths['.']; - bool clipped = false; - for (char const * cursor = _buffer; *cursor; ) { - text_width += font_data.charWidths[OD_Glyph(UTF8::Decode(cursor))]; - } - - if (text_width >= max_width) { - while (strlen(_buffer) > 0) { - char * last = UTF8::Previous(_buffer, _buffer + strlen(_buffer)); - text_width -= font_data.charWidths[OD_Glyph(UTF8::Peek(last))]; - *last = '\0'; - if (!clipped) { - text_width += ellipsis_width; - } - clipped = true; - if (text_width < max_width) { - strcat(_buffer, "..."); - break; - } - } - } - } - - RECT text_rect; - text_rect.left = display_rect.left + 2; - text_rect.right = display_rect.right; - text_rect.top = display_rect.top + 3; - text_rect.bottom = display_rect.bottom; - OD_Draw_Text_Remap(*AlternateSurface, _buffer, *(Rect *)&text_rect, "dlgsys", text_color, 4, 0); - ValidateRect(window, NULL); - return(0); - } - - ValidateRect(window, NULL); - return(0); - } - - case CB_SHOWDROPDOWN: { - int result = 1; - if (wparam == 0) { - if (data->ComboBox.dropdown != NULL) { - ReleaseCapture(); - HWND parent = GetParent(window); - SendMessage(parent, OD_SETTOP, (WPARAM)data->ComboBox.dropdown, 0); - - HWND dropdown_window = data->ComboBox.dropdown; - DestroyWindow(dropdown_window); - - WinData * dropdown_data = NULL; - ODWinData.getPointer(dropdown_window, &dropdown_data); - if (dropdown_data != NULL && dropdown_data->cachedSurface != NULL) { - delete dropdown_data->cachedSurface; - dropdown_data->cachedSurface = NULL; - _surface_count--; - } - - ODWinData.remove(dropdown_window); - data->ComboBox.dropdown = NULL; - } - return(result); - } - - if (data->ComboBox.dropdown != NULL) { - return(result); - } - - SetFocus(window); - - RECT parent_rect; - Get_Display_Rect(GetParent(window), &parent_rect); - - int count = (int)SendMessage(window, CB_GETCOUNT, 0, 0); - int item_height = (int)SendMessage(window, CB_GETITEMHEIGHT, 0, 0); - int dropdown_height = count * item_height + 4; - - if (display_rect.bottom + dropdown_height > parent_rect.bottom - 2 * item_height) { - dropdown_height = parent_rect.bottom - (2 * item_height + 4) - display_rect.bottom; - if (dropdown_height < item_height) { - dropdown_height = parent_rect.bottom - display_rect.bottom; - dropdown_height -= dropdown_height % item_height; - } - } - - RECT parent_display; - RECT combo_display; - Get_Display_Rect(GetParent(window), &parent_display); - Get_Display_Rect(window, &combo_display); - - int x = combo_display.left - parent_display.left; - int y = client_rect.bottom + combo_display.top - parent_display.top + 2; - int w = client_rect.right; - HWND dropdown_window = CreateWindowEx( - 0, - "ComboDropWin", - NULL, - WS_CHILD, - x, - y, - w, - dropdown_height, - GetParent(window), - NULL, - ProgramInstance, - window); - - WinData * dropdown_data = NULL; - if (!ODWinData.getPointer(dropdown_window, &dropdown_data)) { - WinData tmp; - memset(&tmp, 0, sizeof(tmp)); - ODWinData.add(dropdown_window, tmp); - } - - SendMessage(dropdown_window, OD_DROPSUBCLASSED, 0, 0); - SendMessage(GetParent(window), OD_SETTOP, (WPARAM)dropdown_window, 1); - SetCapture(dropdown_window); - ShowWindow(dropdown_window, 1); - - data->ComboBox.dropdown = dropdown_window; - return(result); - } - - case OD_SUBCLASSED: { - if (data->itemHeightSet) { - if (SendMessage(window, CB_GETITEMHEIGHT, 0, 0) == ODFontSize + 6) { - memset(data->ComboBox.itemColors, 0xFF, sizeof(data->ComboBox.itemColors)); - break; - } - } - - SendMessage(window, CB_SETITEMHEIGHT, (WPARAM)-1, ODFontSize + 2); - SendMessage(window, CB_SETITEMHEIGHT, 0, ODFontSize + 6); - data->itemHeightSet = 1; - memset(data->ComboBox.itemColors, 0xFF, sizeof(data->ComboBox.itemColors)); - break; - } - - case OD_SETCOLOR: - if ((unsigned int)wparam <= 50) { - data->ComboBox.itemColors[wparam] = lparam; - } - break; - } - - WNDPROC proc = NULL; - OriginalWndProcs.getValue(window, proc); - return(CallWindowProc(proc, window, message, wparam, lparam)); -} - - -/// -/// Handles the messages for an owner-drawn list box. -/// Besides repainting the stock list box, this routine provides the multi-column list the -/// dialogs are built around: the columns and the text, color and icon of each cell live in -/// the control's owner-draw record. A scroll bar is attached to, or taken away from, the -/// list as its contents demand. -/// -/// Returns with the result of the original window procedure for anything this -/// routine does not handle itself. -LRESULT CALLBACK ListBoxCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - HWND parent = GetParent(window); - int needs_scrollbar = -1; - int max_position = 0; - char call_default = 1; - LRESULT result = 0; - char string[512]; - - WinData * data = NULL; - ODWinData.getPointer(window, &data); - - int scrollbar_width = 2 * ODBorderThickness + 18; - - RECT client_rect; - GetClientRect(window, &client_rect); - RECT display_rect; - Get_Display_Rect(window, &display_rect); - display_rect.right -= ODBorderThickness; - display_rect.left += ODBorderThickness; - - Rect content_rect; - content_rect.X = display_rect.left; - client_rect.right -= 2 * ODBorderThickness; - content_rect.Width = client_rect.right - client_rect.left; - display_rect.top += ODBorderThickness; - content_rect.Y = display_rect.top; - client_rect.bottom -= 2 * ODBorderThickness; - display_rect.bottom -= ODBorderThickness; - content_rect.Height = client_rect.bottom - client_rect.top; - - /* - * Keep the attached scrollbar synchronized with the listbox state. This is skipped for - * the few messages that are queried while computing the scroll state (to avoid recursion). - */ - if (message != LB_GETCOUNT && message != LB_GETITEMHEIGHT && message != WM_VSCROLL) { - int count = SendMessage(window, LB_GETCOUNT, 0, 0); - int item_height = SendMessage(window, LB_GETITEMHEIGHT, 0, 0); - if (item_height <= 1) { - item_height = 1; - } - needs_scrollbar = (count * item_height > client_rect.bottom - client_rect.top); - max_position = count - (client_rect.bottom - client_rect.top) / item_height; - if ((uintptr_t)data->attachedWindow > 1) { - SCROLLINFO info; - info.fMask = SIF_RANGE | SIF_POS; - info.nMin = 0; - info.nMax = max_position; - info.nPos = data->ListBox.topIndex; - info.cbSize = sizeof(SCROLLINFO); - SendMessage(data->attachedWindow, SBM_SETSCROLLINFO, 0, (LPARAM)&info); - } - } - - switch (message) { - case WM_ERASEBKGND: - return(0); - - case WM_PAINT: { - call_default = 0; - ODDrawDimmedBackground(content_rect, window); - OD_Draw_Rect(*AlternateSurface, content_rect, 1, 0xFFFFFFFF); - - int fill_color = ODColorToHiColor(ODListBoxColor); - - RECT update_rect; - if (!GetUpdateRect(window, &update_rect, FALSE)) { - break; - } - - int count = SendMessage(window, LB_GETCOUNT, 0, 0); - int index = SendMessage(window, LB_GETTOPINDEX, 0, 0); - while (index < count) { - RECT item_rect; - if (SendMessage(window, LB_GETITEMRECT, index, (LPARAM)&item_rect) != -1) { - if (client_rect.top + item_rect.bottom > client_rect.bottom) { - break; - } - SendMessage(window, LB_GETTEXT, index, (LPARAM)string); - - ArrayList * columns = data->ListBox.columns; - if (columns != NULL) { - if (SendMessage(window, LB_GETSEL, index, 0) > 0) { - Rect fill; - fill.X = item_rect.left + display_rect.left; - fill.Y = item_rect.top + display_rect.top; - fill.Width = item_rect.right - item_rect.left; - fill.Height = item_rect.bottom - item_rect.top; - AlternateSurface->Fill_Rect(fill, fill_color); - } - - HDC dc = GetDC(window); - for (int col = 0; col < columns->length(); col++) { - ColumnData * column = NULL; - columns->getPointer(&column, col); - CellData * cell = NULL; - column->cells.getPointer(&cell, index); - if (cell == NULL || cell->type == CellData::INVALID) { - continue; - } - - if (cell->type == CellData::TEXT || cell->type == CellData::PRIMARY) { - Rect text_rect; - text_rect.X = display_rect.left + item_rect.left + column->xPos; - text_rect.Y = display_rect.top + item_rect.top; - text_rect.Width = 0; - text_rect.Height = 0; - if (cell->type == CellData::TEXT) { - strcpy(string, cell->string.get()); - } else if (cell->type == CellData::PRIMARY) { - SendMessage(window, LB_GETTEXT, index, (LPARAM)string); - } - COLORREF text_color = cell->color; - if (text_color == -1) { - text_color = ODColorText; - } - int max_width = column->width; - if (max_width == 0) { - max_width = 0xFFFF; - } - if (display_rect.right - max_width - column->xPos - item_rect.left - display_rect.left < 0) { - max_width = display_rect.right - column->xPos - item_rect.left - display_rect.left; - } - SendMessage(window, OD_RESTOREDC, 0, (LPARAM)dc); - SIZE ellipsis_size; - GetTextExtentPoint32(dc, "...", strlen("..."), &ellipsis_size); - SIZE text_size; - GetTextExtentPoint32(dc, string, strlen(string), &text_size); - if (text_size.cx > max_width) { - while (true) { - int len = strlen(string); - if (!len) { - break; - } - string[strlen(string) - 1] = '\0'; - GetTextExtentPoint32(dc, string, strlen(string), &text_size); - text_size.cx += ellipsis_size.cx; - if (text_size.cx <= max_width) { - strcat(string, "..."); - break; - } - } - } - OD_Draw_Text(text_color, data->font, text_rect, string, strlen(string), 0, 0, NULL); - } else if (cell->type == CellData::SURFACE) { - Surface * surface = cell->surf; - if (surface != NULL) { - int height = surface->Get_Height(); - int width = surface->Get_Width(); - Rect surface_rect; - surface_rect.Width = width; - surface_rect.X = display_rect.left + item_rect.left + column->xPos; - surface_rect.Height = height; - surface_rect.Y = display_rect.top + item_rect.top + (item_rect.bottom - item_rect.top - height) / 2; - SurfaceCache.DrawTrans(surface_rect, *AlternateSurface, *surface, (255 >> DSurface::RedLeft << DSurface::RedRight) | (255u >> DSurface::BlueLeft << DSurface::BlueRight)); - } - } else { - int ping = cell->pingtime; - Rect ping_rect; - ping_rect.X = display_rect.left + item_rect.left + column->xPos; - ping_rect.Y = display_rect.top + item_rect.top; - ping_rect.Width = 28; - ping_rect.Height = 12; - unsigned color; - if (ping < 300) { - color = DSurface::Build_Hicolor_Pixel(0, 192, 0); - } else if (ping < 500) { - color = DSurface::Build_Hicolor_Pixel(192, 192, 0); - } else { - color = DSurface::Build_Hicolor_Pixel(192, 0, 0); - } - ODDrawGradientRect(ping_rect, *AlternateSurface, color, (ping << 16) / 1000); - } - } - ReleaseDC(window, dc); - } else { - ArrayList * row_colors = data->ListBox.rowColors; - COLORREF text_color; - int * color_ptr = NULL; - if (row_colors == NULL || !row_colors->getPointer(&color_ptr, index) || *color_ptr == -1) { - text_color = ODColorText; - } else { - text_color = *color_ptr; - } - - if (SendMessage(window, LB_GETSEL, index, 0) > 0) { - RECT fill; - fill.left = item_rect.left + display_rect.left; - fill.top = item_rect.top + display_rect.top; - fill.bottom = item_rect.bottom - item_rect.top; - fill.right = item_rect.right - item_rect.left; - AlternateSurface->Fill_Rect(*(Rect *)&fill, fill_color); - } - - Rect text_rect; - int max_width = item_rect.right - item_rect.left; - text_rect.X = item_rect.left + display_rect.left + 2; - text_rect.Y = display_rect.top + item_rect.top; - text_rect.Width = 0; - text_rect.Height = 0; - if (item_rect.right == item_rect.left) { - max_width = 0xFFFF; - } - HDC dc = GetDC(window); - if (data->font != NULL) { - SelectObject(dc, data->font); - } - SIZE ellipsis_size; - GetTextExtentPoint32(dc, "...", strlen("..."), &ellipsis_size); - SIZE text_size; - GetTextExtentPoint32(dc, string, strlen(string), &text_size); - if (text_size.cx > max_width && text_size.cx + ellipsis_size.cx > max_width) { - while (true) { - int len = strlen(string); - if (!len) { - break; - } - string[strlen(string) - 1] = '\0'; - GetTextExtentPoint32(dc, string, strlen(string), &text_size); - if (text_size.cx + ellipsis_size.cx <= max_width) { - strcat(string, "..."); - break; - } - } - } - OD_Draw_Text(text_color, data->font, text_rect, string, strlen(string), 0, 0, NULL); - } - } - index++; - } - ValidateRect(window, &update_rect); - break; - } - - case WM_SIZE: { - if ((uintptr_t)data->attachedWindow > 1) { - RECT parent_display; - Get_Display_Rect(GetParent(window), &parent_display); - Rect win_display; - Get_Display_Rect(window, (LPRECT)&win_display); - MoveWindow(data->attachedWindow, win_display.Width - parent_display.left, win_display.Y - parent_display.top, scrollbar_width, win_display.Height - win_display.Y, TRUE); - } - Surface * surface = data->cachedSurface; - if (surface != NULL) { - if ((unsigned short)lparam != surface->Get_Width() || HIWORD(lparam) != surface->Get_Height()) { - WinData * cache_data = NULL; - ODWinData.getPointer(window, &cache_data); - if (cache_data != NULL && cache_data->cachedSurface != NULL) { - delete cache_data->cachedSurface; - cache_data->cachedSurface = NULL; - _surface_count--; - } - } - } - break; - } - - case WM_SETFONT: { - HDC dc = GetDC(window); - TEXTMETRIC tm; - GetTextMetrics(dc, &tm); - ReleaseDC(window, dc); - SendMessage(window, LB_SETITEMHEIGHT, (WPARAM)-1, (unsigned short)(LOWORD(tm.tmHeight) + 2)); - data->font = (HFONT)wparam; - return(0); - } - - case WM_VSCROLL: { - call_default = 0; - LRESULT position = SendMessage(data->attachedWindow, SBM_GETPOS, 0, 0); - if (position != SendMessage(window, LB_GETTOPINDEX, 0, 0)) { - SendMessage(window, LB_SETTOPINDEX, position, 0); - } - break; - } - - case LB_ADDSTRING: - wparam = (WPARAM)-1; - /// Fall through to insert with an append position. - case LB_INSERTSTRING: { - int position = (int)wparam; - if (position != -1) { - ArrayList * row_colors = data->ListBox.rowColors; - if (row_colors != NULL) { - int color = -1; - row_colors->add(color, position); - } - ArrayList * sel_states = data->ListBox.selStates; - if (sel_states != NULL) { - int selected = 0; - sel_states->add(selected, position); - } - } - if (position < 0) { - position = SendMessage(window, LB_GETCOUNT, 0, 0); - } - - ArrayList * columns = data->ListBox.columns; - CellData cell; - if (columns != NULL) { - for (int col = 0; col < columns->length(); col++) { - - /* - * The first column added implicitly holds the row's listbox string, - * so the new row gets a PRIMARY cell there and INVALID cells elsewhere. - */ - cell.type = (col == 0) ? CellData::PRIMARY : CellData::INVALID; - ColumnData * column = NULL; - columns->getPointer(&column, col); - column->cells.add(cell, position); - } - } - break; - } - - case LB_SETSEL: { - int index = (int)lparam; - if (index < -1) { - return(-1); - } - ArrayList * sel_states = data->ListBox.selStates; - if (sel_states == NULL) { - sel_states = new ArrayList; - data->ListBox.selStates = sel_states; - } - if (index >= SendMessage(window, LB_GETCOUNT, 0, 0) - 1) { - index = SendMessage(window, LB_GETCOUNT, 0, 0) - 1; - } - if (index >= sel_states->length()) { - int unselected = 0; - sel_states->setSize(index + 1, unselected); - } - if (index == -1) { - for (int i = 0; i < sel_states->length(); i++) { - int selected = (int)wparam; - sel_states->replace(selected, i); - } - } else { - int selected = (int)wparam; - sel_states->replace(selected, index); - } - parent = GetParent(window); - SendMessage(parent, WM_COMMAND, (GetWindowLong(window, GWL_ID) & 0xFFFF) | 0x10000, (LPARAM)window); - InvalidateRect(window, NULL, FALSE); - return(0); - } - - case LB_GETSEL: { - ArrayList * sel_states = data->ListBox.selStates; - if (sel_states == NULL) { - return(0); - } - if ((int)wparam >= sel_states->length()) { - return(0); - } - int * selected = NULL; - if (!sel_states->getPointer(&selected, (int)wparam)) { - return(0); - } - return(*selected); - } - - case LB_SETCURSEL: { - int index = (int)wparam; - if ((int)wparam >= -1 && index < SendMessage(window, LB_GETCOUNT, 0, 0)) { - if (data->ListBox.curSel != -1) { - SendMessage(window, LB_SETSEL, 0, data->ListBox.curSel); - } - data->ListBox.curSel = index; - if (index != -1) { - SendMessage(window, LB_SETSEL, TRUE, index); - } - } - parent = GetParent(window); - SendMessage(parent, WM_COMMAND, (GetWindowLong(window, GWL_ID) & 0xFFFF) | 0x10000, (LPARAM)window); - InvalidateRect(window, NULL, FALSE); - return(0); - } - - case LB_GETCURSEL: - return(data->ListBox.curSel); - - case LB_DELETESTRING: { - ArrayList * row_colors = data->ListBox.rowColors; - if (row_colors != NULL && row_colors->length() > (int)wparam) { - row_colors->remove((int)wparam); - } - ArrayList * sel_states = data->ListBox.selStates; - if (sel_states != NULL && sel_states->length() > (int)wparam) { - sel_states->remove((int)wparam); - } - ArrayList * columns = data->ListBox.columns; - if (columns != NULL) { - for (int col = 0; col < columns->length(); col++) { - ColumnData * column = NULL; - columns->getPointer(&column, col); - if (column->cells.length() != 0) { - column->cells.remove((int)wparam); - } - } - } - break; - } - - case WM_NCDESTROY: - case LB_RESETCONTENT: { - ArrayList * row_colors = data->ListBox.rowColors; - if (row_colors != NULL) { - delete row_colors; - } - ArrayList * sel_states = data->ListBox.selStates; - if (sel_states != NULL) { - delete sel_states; - } - data->ListBox.rowColors = 0; - data->ListBox.selStates = 0; - data->ListBox.topIndex = 0; - data->ListBox.curSel = -1; - - ArrayList * columns = data->ListBox.columns; - if (columns != NULL) { - for (int col = 0; col < columns->length(); col++) { - ColumnData * column = NULL; - columns->getPointer(&column, col); - column->cells.clear(); - } - } - - if (message == WM_NCDESTROY) { - if (columns != NULL) { - delete columns; - } - data->ListBox.columns = NULL; - } else { - parent = GetParent(window); - SendMessage(parent, WM_COMMAND, (GetWindowLong(window, GWL_ID) & 0xFFFF) | 0x10000, (LPARAM)window); - } - break; - } - - case LB_GETTOPINDEX: - return(data->ListBox.topIndex); - - case LB_SETTOPINDEX: { - int index = (int)wparam; - int count = SendMessage(window, LB_GETCOUNT, 0, 0); - int item_height = SendMessage(window, LB_GETITEMHEIGHT, 0, 0); - if (!count || !item_height) { - break; - } - int visible = (client_rect.bottom - client_rect.top) / item_height; - if (index < 0) { - index = 0; - } - if (count - visible <= 0) { - index = 0; - } else if (index > count - visible) { - index = count - visible; - } - if (index != data->ListBox.topIndex) { - data->ListBox.topIndex = index; - InvalidateRect(window, NULL, FALSE); - } - return(0); - } - - case LB_SELITEMRANGE: { - int last = HIWORD(lparam); - int first = LOWORD(lparam); - int count = SendMessage(window, LB_GETCOUNT, 0, 0); - if (last < 0 || last < first) { - return(-1); - } - if (last >= count) { - last = count - 1; - } - ArrayList * sel_states = data->ListBox.selStates; - if (sel_states == NULL) { - sel_states = new ArrayList; - data->ListBox.selStates = sel_states; - } - if (last >= sel_states->length()) { - int unselected = 0; - sel_states->setSize(last + 1, unselected); - } - for (int i = first; i <= last; i++) { - int selected = (int)wparam; - sel_states->replace(selected, i); - } - parent = GetParent(window); - SendMessage(parent, WM_COMMAND, (GetWindowLong(window, GWL_ID) & 0xFFFF) | 0x10000, (LPARAM)window); - return(0); - } - - case LB_GETSELCOUNT: { - ArrayList * sel_states = data->ListBox.selStates; - if (sel_states == NULL) { - return(0); - } - int count = sel_states->length(); - int selected = 0; - int total = 0; - for (int i = 0; i < count; i++) { - int * value = NULL; - if (sel_states->getPointer(&value, i)) { - selected = *value; - } - if (selected) { - total++; - } - } - return(total); - } - - case LB_GETSELITEMS: { - ArrayList * sel_states = data->ListBox.selStates; - int total = 0; - if (sel_states != NULL) { - int selected = 0; - if (sel_states->length() > 0) { - int * out = (int *)lparam; - int max = (int)wparam; - for (int i = 0; i < sel_states->length(); i++) { - int * value = NULL; - if (sel_states->getPointer(&value, i)) { - selected = *value; - } - if (selected) { - *out = i; - total++; - out++; - } - if (total >= max) { - break; - } - } - } - } - return(total); - } - - case LB_GETITEMRECT: { - int index = (int)wparam; - if (index < data->ListBox.topIndex) { - return(-1); - } - if (index >= SendMessage(window, LB_GETCOUNT, 0, 0)) { - return(-1); - } - int item_height = SendMessage(window, LB_GETITEMHEIGHT, 0, 0); - int relative = index - data->ListBox.topIndex; - if (relative > (client_rect.bottom - client_rect.top) / item_height) { - return(-1); - } - RECT * out = (RECT *)lparam; - out->top = relative * item_height; - out->bottom = relative * item_height + item_height; - out->left = client_rect.left; - out->right = client_rect.right - client_rect.left; - return(0); - } - - case WM_LBUTTONDOWN: { - int top = SendMessage(window, LB_GETTOPINDEX, 0, 0); - int item_height = SendMessage(window, LB_GETITEMHEIGHT, 0, 0); - int index = top + (int)HIWORD(lparam) / item_height; - LONG style = GetWindowLong(window, GWL_STYLE); - SetFocus(window); - int paint_disabled = SendMessage(window, OD_DISABLEPAINT, 0, 1); - if ((style & LBS_MULTIPLESEL) != 0) { - int select = (SendMessage(window, LB_GETSEL, index, 0) == 0); - Sound_Effect(Rule->GenericClick); - SendMessage(window, LB_SETSEL, select, index); - InvalidateRect(window, NULL, FALSE); - } else if ((style & LBS_NOSEL) == 0) { - Sound_Effect(Rule->GenericClick); - SendMessage(window, LB_SETCURSEL, index, 0); - InvalidateRect(window, NULL, FALSE); - } - SendMessage(window, OD_DISABLEPAINT, 0, paint_disabled); - parent = GetParent(window); - SendMessage(parent, WM_COMMAND, (GetWindowLong(window, GWL_ID) & 0xFFFF) | 0x10000, (LPARAM)window); - return(0); - } - - case WM_LBUTTONDBLCLK: - PostMessage(parent, WM_COMMAND, (GetWindowLong(window, GWL_ID) & 0xFFFF) | 0x20000, (LPARAM)window); - return(0); - - case WM_RBUTTONDOWN: - SendMessage(window, LB_SETSEL, 0, -1); - SendMessage(window, LB_SETCURSEL, (WPARAM)-1, 0); - parent = GetParent(window); - SendMessage(parent, WM_COMMAND, (GetWindowLong(window, GWL_ID) & 0xFFFF) | 0x10000, (LPARAM)window); - InvalidateRect(window, NULL, FALSE); - return(0); - - case OD_GETCELLTIP: { - int count = SendMessage(window, LB_GETCOUNT, 0, 0); - int item_height = SendMessage(window, LB_GETITEMHEIGHT, 0, 0); - ArrayList * columns = data->ListBox.columns; - if (columns != NULL) { - int row = data->ListBox.topIndex + (int)HIWORD(wparam) / item_height; - int best_column = -1; - int best_x = 0; - ColumnData * column = NULL; - for (int col = 0; col < columns->length(); col++) { - columns->getPointer(&column, col); - if (column->xPos <= (int)LOWORD(wparam)) { - if (column->xPos > best_x) { - best_column = col; - best_x = column->xPos; - } - } - } - if (best_column != -1 && row >= 0 && row < count) { - column = NULL; - columns->getPointer(&column, best_column); - if (column != NULL && row < column->cells.length()) { - CellData * cell = NULL; - column->cells.getPointer(&cell, row); - if (cell != NULL && lparam != 0) { - strcpy((char *)lparam, cell->hint.get()); - return(strlen(cell->hint.get()) == 0); - } - } - } - } - return(0); - } - - case OD_ADDCOLUMN: { - ArrayList * columns = data->ListBox.columns; - if (columns == NULL) { - columns = new ArrayList; - data->ListBox.columns = columns; - } - ColumnData * column = NULL; - int col = 0; - while (col < columns->length()) { - columns->getPointer(&column, col); - if (column->xPos == lparam) { - return(lparam); - } - col++; - } - ColumnData new_column; - new_column.xPos = lparam; - new_column.width = wparam; - int length = columns->length(); - columns->add(new_column, length); - return(lparam); - } - - case OD_REMOVECOLUMN: { - ArrayList * columns = data->ListBox.columns; - if (columns != NULL) { - ColumnData * column = NULL; - for (int col = 0; col < columns->length(); col++) { - columns->getPointer(&column, col); - if (column->xPos == lparam) { - columns->remove(col); - return(lparam); - } - } - } - return(-1); - } - - case OD_SETCELL: { - ArrayList * columns = data->ListBox.columns; - if (columns == NULL) { - return(-1); - } - int column_id = LOWORD(wparam); - int row = HIWORD(wparam); - ColumnData * column = NULL; - int found = -1; - for (int col = 0; col < columns->length(); col++) { - columns->getPointer(&column, col); - if (column->xPos == column_id) { - found = col; - break; - } - } - if (found == -1) { - return(-1); - } - if (row < 0 || row >= SendMessage(window, LB_GETCOUNT, 0, 0)) { - return(-1); - } - CellData filler; - if (found == 0) { - filler.type = CellData::PRIMARY; - } - if (row >= column->cells.length()) { - column->cells.setSize(row + 1, filler); - } - column->cells.replace(*(CellData *)lparam, row); - return(column_id); - } - - case OD_SETCOLOR: { - ArrayList * row_colors = data->ListBox.rowColors; - if (row_colors == NULL) { - row_colors = new ArrayList; - data->ListBox.rowColors = row_colors; - } - int index = (int)wparam; - if (index >= row_colors->length()) { - int blank = -1; - row_colors->setSize(index + 1, blank); - } - int color = (int)lparam; - row_colors->replace(color, index); - InvalidateRect(window, NULL, FALSE); - break; - } - - case OD_SUBCLASSED: - data->ListBox.curSel = -1; - data->font = ODListFontPtr; - SendMessage(window, LB_SETITEMHEIGHT, (WPARAM)-1, (unsigned short)(ODListFontSize + 2)); - break; - } - - /* - * Create or destroy the attached scrollbar based on whether the listbox needs one, - * then forward the message to the original Win32 listbox procedure. - */ - if (needs_scrollbar == 1) { - if (data->attachedWindow == NULL) { - data->attachedWindow = (HWND)1; - parent = GetParent(window); - RECT parent_display; - Get_Display_Rect(parent, &parent_display); - RECT win_display; - Get_Display_Rect(window, &win_display); - int x = win_display.left - parent_display.left; - int y = win_display.top - parent_display.top; - data->attachedWindow = CreateWindowEx(0, "Scrollbar", NULL, 0x50010001, - win_display.left - parent_display.left - scrollbar_width + client_rect.right + 1, - client_rect.top + win_display.top - parent_display.top, - scrollbar_width, win_display.bottom - win_display.top, - parent, NULL, ProgramInstance, NULL); - data->scrollBarWidth = scrollbar_width; - InitializeCtrl(data->attachedWindow, 0); - - WinData * sb_data = NULL; - ODWinData.getPointer(data->attachedWindow, &sb_data); - sb_data->ownerWindow = window; - - SCROLLINFO info; - info.nMax = max_position; - info.fMask = SIF_RANGE | SIF_POS; - info.nMin = 0; - info.nPos = data->ListBox.topIndex; - info.cbSize = sizeof(SCROLLINFO); - SendMessage(data->attachedWindow, SBM_SETSCROLLINFO, 0, (LPARAM)&info); - - SetWindowPos(window, NULL, 0, 0, win_display.right - win_display.left - scrollbar_width, win_display.bottom - win_display.top, SWP_NOMOVE); - ShowWindow(data->attachedWindow, SW_SHOW); - BringWindowToTop(data->attachedWindow); - InvalidateRect(data->attachedWindow, NULL, FALSE); - UpdateWindow(data->attachedWindow); - - Rect validate_rect; - validate_rect.X = x; - validate_rect.Y = y; - validate_rect.Width = x + client_rect.right + 1; - validate_rect.Height = client_rect.bottom + y + 1; - ValidateRect(parent, (const RECT *)&validate_rect); - } - } else if (needs_scrollbar == 0 && data->attachedWindow != NULL && !data->paintDisabled) { - DestroyWindow(data->attachedWindow); - HWND scrollbar = data->attachedWindow; - ODRemoveFromDict(scrollbar, 0); - data->attachedWindow = NULL; - data->scrollBarWidth = 0; - SetWindowPos(window, NULL, 0, 0, client_rect.right + ODBorderThickness - client_rect.left + scrollbar_width + 1, client_rect.bottom + 2 * ODBorderThickness - client_rect.top, SWP_NOMOVE); - parent = GetParent(window); - RECT parent_display; - Get_Display_Rect(parent, &parent_display); - Rect validate_rect; - validate_rect.X = display_rect.left - parent_display.left - 1; - validate_rect.Y = display_rect.top - parent_display.top - 1; - validate_rect.Width = scrollbar_width + client_rect.right + display_rect.left - parent_display.left; - validate_rect.Height = display_rect.top - parent_display.top + client_rect.bottom + 1; - ValidateRect(parent, (const RECT *)&validate_rect); - } - - WNDPROC original_proc = NULL; - OriginalWndProcs.getValue(window, original_proc); - if (call_default) { - result = CallWindowProc(original_proc, window, message, wparam, lparam); - } - - if (message == WM_NCDESTROY) { - ArrayList * row_colors = data->ListBox.rowColors; - if (row_colors != NULL) { - delete row_colors; - } - ArrayList * sel_states = data->ListBox.selStates; - if (sel_states != NULL) { - delete sel_states; - } - } - - return(result); -} - - -/// -/// Handles the messages for an owner-drawn scroll bar. -/// The grip is sized against the scroll range and dragged directly with the mouse, while -/// the arrow buttons repeat on a timer for as long as they are held. The owner is told of -/// the new position as it changes, which is what lets a list box scroll under the mouse. -/// -/// Returns with the result of the original window procedure for anything this -/// routine does not handle itself. -LRESULT CALLBACK ScrollBarCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - RECT client_rect; - RECT display_rect; - GetClientRect(window, &client_rect); - Get_Display_Rect(window, &display_rect); - - client_rect.right -= 2 * ODBorderThickness; - client_rect.bottom -= 2 * ODBorderThickness; - - display_rect.left += ODBorderThickness; - display_rect.right -= ODBorderThickness; - display_rect.top += ODBorderThickness; - display_rect.bottom -= ODBorderThickness; - - WinData * data = NULL; - ODWinData.getPointer(window, &data); - - bool keep_parent_capture = false; - if (data->ScrollBar.keepCapture) { - keep_parent_capture = true; - } - int dragging = data->ScrollBar.dragging; - int range = data->ScrollBar.range; - int message_result = data->ScrollBar.result; - int position = data->ScrollBar.position; - int up_pressed = data->ScrollBar.upPressed; - int down_pressed = data->ScrollBar.downPressed; - - if (!range) { - range = 100; - } - - int scroll_code = 0; - int grip_top = 0; - int grip_bottom = 0; - - int width = client_rect.right - client_rect.left; - int travel_height = client_rect.bottom - client_rect.top - 44; - int grip_height = (int)((double)travel_height - log((double)(range + 1)) * (double)travel_height * 0.2); - if (grip_height <= 14) { - grip_height = 14; - } - - int travel = travel_height - grip_height; - if (travel <= 1) { - travel = 1; - } - - if (message < TBM_GETPOS || message == OD_REFRESHNOPAINT) { - if (!dragging) { - grip_top = position * travel / range + client_rect.top + 22; - grip_bottom = grip_top + grip_height; - } else { - POINT cursor; - Get_Logical_Cursor_Pos(window, cursor); - - grip_top = cursor.y - grip_height / 2; - if (grip_top < 22) { - grip_top = 22; - } - - if (client_rect.bottom - grip_height - 22 < grip_top) { - grip_top = client_rect.bottom - grip_height - 22; - } - - grip_bottom = grip_top + grip_height; - scroll_code = SB_THUMBTRACK; - position = range * (grip_top - 22) / travel; - } - } - - switch (message) { - case SBM_GETPOS: - return(position); - - case SBM_SETPOS: - if ((int)wparam <= range && (int)wparam > 0) { - position = (int)wparam; - } - break; - - case SBM_SETRANGE: - range = (int)lparam; - if (position > range) { - position = range; - } - break; - - case SBM_SETSCROLLINFO: { - SCROLLINFO * info = (SCROLLINFO *)lparam; - range = info->nMax; - position = info->nPos; - break; - } - - case WM_NCHITTEST: - case WM_GETDLGCODE: { - WNDPROC proc = NULL; - OriginalWndProcs.getValue(window, proc); - return(CallWindowProc(proc, window, message, wparam, lparam)); - } - - case WM_ERASEBKGND: - return(0); - - case WM_PAINT: { - if (data->paintDisabled) { - return(0); - } - - /* - * The source rect and the blit dest rect get reused for every blit - * below; only the Draw and edge glow calls get their own rects. - */ - Rect src_rect; - Rect full_rect; - full_rect.X = display_rect.left; - full_rect.Y = display_rect.top; - full_rect.Width = client_rect.right; - full_rect.Height = client_rect.bottom; - src_rect.X = 0; - src_rect.Y = 0; - src_rect.Width = client_rect.right; - src_rect.Height = client_rect.bottom; - - HWND parent = GetParent(window); - WinData * parent_data = NULL; - if (parent != NULL) { - ODWinData.getPointer(parent, &parent_data); - } - - RECT parent_display; - Get_Display_Rect(parent, &parent_display); - - Rect source_rect = src_rect; - if (parent_data != NULL && parent_data->cachedSurface != NULL) { - source_rect.X = display_rect.left + source_rect.X - parent_display.left; - source_rect.Y += display_rect.top - parent_display.top; - } - - if (data->cachedSurface != NULL) { - if (data->cachedSurface->Get_Width() != client_rect.right || data->cachedSurface->Get_Height() != client_rect.bottom) { - delete data->cachedSurface; - data->cachedSurface = NULL; - } - } - - if (data->cachedSurface == NULL) { - BSurface * background = new BSurface(client_rect.right, client_rect.bottom, 2); - data->cachedSurface = background; - ++_surface_count; - - if (parent_data != NULL && parent_data->cachedSurface != NULL) { - background->Blit_From(src_rect, *parent_data->cachedSurface, source_rect); - } - - int pixel_count = client_rect.bottom * client_rect.right; - unsigned short * pixels = (unsigned short *)background->Lock(); - unsigned short * ptr = pixels; - for (int i = pixel_count; i > 0; --i) { - *ptr = OD_Blend_Color(*ptr, 0xFFFF, ODColorSteps); - ptr++; - } - if (pixels != NULL) { - background->Unlock(); - } - } - - if (parent_data != NULL && parent_data->cachedSurface != NULL) { - AlternateSurface->Blit_From(full_rect, *parent_data->cachedSurface, source_rect); - } - - OD_Draw_Rect(*AlternateSurface, full_rect, ODBorderThickness, 0xFFFFFFFF); - - Rect grip_rect; - grip_rect.X = display_rect.left; - full_rect.X = display_rect.left; - grip_rect.Y = grip_top + display_rect.top; - grip_rect.Width = client_rect.right; - grip_rect.Height = grip_bottom - grip_top; - full_rect.Y = grip_top + display_rect.top; - full_rect.Width = client_rect.right; - full_rect.Height = grip_bottom - grip_top; - src_rect.Width = client_rect.right; - src_rect.Height = grip_bottom - grip_top; - src_rect.X = 0; - src_rect.Y = grip_top; - - Surface * grip_center = SurfaceCache.GetSurface("sbgripm.pcx", NULL); - if (grip_center != NULL) { - grip_rect.Width = grip_center->Get_Width(); - } - SurfaceCache.Draw(grip_rect, *AlternateSurface, *grip_center, 0, 0); - - Rect grip_src(0, 0, grip_center->Get_Width(), grip_center->Get_Height()); - - Surface * grip_top_surf = SurfaceCache.GetSurface("sbgript.pcx", NULL); - if (grip_top_surf != NULL) { - full_rect.Height = grip_top_surf->Get_Height(); - } - AlternateSurface->Blit_From(full_rect, *grip_top_surf, grip_src); - - Surface * grip_bottom_surf = SurfaceCache.GetSurface("sbgripb.pcx", NULL); - if (grip_bottom_surf != NULL) { - full_rect.Y = display_rect.top + grip_bottom - grip_bottom_surf->Get_Height(); - } - AlternateSurface->Blit_From(full_rect, *grip_bottom_surf, grip_src); - - Rect up_rect; - up_rect.X = display_rect.left; - full_rect.X = display_rect.left; - src_rect.X = display_rect.left; - up_rect.Y = display_rect.top; - full_rect.Y = display_rect.top; - up_rect.Width = client_rect.right; - up_rect.Height = 22; - full_rect.Width = client_rect.right; - full_rect.Height = 22; - src_rect.Width = client_rect.right; - src_rect.Height = 22; - src_rect.X = 0; - src_rect.Y = 0; - AlternateSurface->Blit_From(full_rect, *data->cachedSurface, src_rect); - ODDrawEdgeGlows(*AlternateSurface, up_rect, up_pressed == 0, 2, ODScrollBarAdj, ODScrollBarAdj, ODScrollBarAdj, ODScrollBarAdj); - ODDrawArrowBitmap(*AlternateSurface, up_rect, 1, up_pressed); - - Rect down_rect; - down_rect.X = display_rect.left; - full_rect.X = display_rect.left; - down_rect.Y = display_rect.bottom - 22; - full_rect.Y = display_rect.bottom - 22; - down_rect.Width = client_rect.right; - down_rect.Height = 22; - full_rect.Width = client_rect.right; - full_rect.Height = 22; - src_rect.Width = client_rect.right; - src_rect.Height = 22; - src_rect.X = 0; - src_rect.Y = client_rect.bottom - 22; - AlternateSurface->Blit_From(full_rect, *data->cachedSurface, src_rect); - ODDrawEdgeGlows(*AlternateSurface, down_rect, down_pressed == 0, 2, ODScrollBarAdj, ODScrollBarAdj, ODScrollBarAdj, ODScrollBarAdj); - ODDrawArrowBitmap(*AlternateSurface, down_rect, 0, down_pressed); - - ValidateRect(window, NULL); - break; - } - - case WM_TIMER: { - POINT cursor; - Get_Logical_Cursor_Pos(window, cursor); - - up_pressed = 0; - down_pressed = 0; - - if (message_result && cursor.x > client_rect.right - width) { - if (cursor.y < 22) { - if (!dragging) { - up_pressed = 1; - } - if (position != 0) { - scroll_code = SB_LINEUP; - --position; - } - } else if (cursor.y > client_rect.bottom - 22) { - if (!dragging) { - down_pressed = 1; - } - if (position + 1 <= range) { - scroll_code = SB_LINEDOWN; - ++position; - } - } - } - - SetTimer(window, 0, 0x19, NULL); - break; - } - - case WM_MOUSEMOVE: { - if (dragging) { - RECT rect; - rect.left = client_rect.right - width; - rect.top = client_rect.top; - rect.right = client_rect.right; - rect.bottom = client_rect.bottom; - InvalidateRect(window, &rect, FALSE); - } - - if (wparam & MK_LBUTTON) { - break; - } - } - - case WM_LBUTTONUP: - message_result = 0; - dragging = 0; - if (up_pressed || down_pressed) { - InvalidateRect(window, NULL, FALSE); - } - up_pressed = 0; - down_pressed = 0; - KillTimer(window, 0); - ReleaseCapture(); - if (keep_parent_capture) { - SetCapture(data->ownerWindow); - } - scroll_code = SB_ENDSCROLL; - break; - - case WM_LBUTTONDOWN: - case WM_LBUTTONDBLCLK: { - if (message == WM_LBUTTONDOWN) { - message_result = 1; - SetCapture(window); - SetTimer(window, 0, 0x1F4, NULL); - } else { - message_result = 0; - dragging = 0; - KillTimer(window, 0); - ReleaseCapture(); - if (keep_parent_capture) { - SetCapture(data->ownerWindow); - } - } - - int xpos = (unsigned short)LOWORD(lparam); - int ypos = (unsigned short)HIWORD(lparam); - int repeat = (message == WM_LBUTTONDBLCLK) ? 2 : 1; - - up_pressed = 0; - down_pressed = 0; - - while (repeat > 0) { - if (xpos > (client_rect.right - width)) { - if (ypos < 22 && position) { - up_pressed = 1; - scroll_code = SB_LINEUP; - --position; - } else { - bool skip_thumb_logic = false; - if (ypos > client_rect.bottom - 22) { - if (position + 1 <= range) { - down_pressed = 1; - scroll_code = SB_LINEDOWN; - ++position; - skip_thumb_logic = true; - } - } - - if (!skip_thumb_logic) { - if (ypos < grip_top || ypos >= grip_bottom) { - grip_top = ypos - grip_height / 2; - if (grip_top < 22) { - grip_top = 22; - } - - int max_top = client_rect.bottom - grip_height - 22; - if (max_top < grip_top) { - grip_top = max_top; - } - - grip_bottom = grip_top + grip_height; - scroll_code = SB_THUMBTRACK; - position = range * (grip_top - 22) / travel; - } else if (message == WM_LBUTTONDOWN) { - dragging = 1; - } - } - } - } - --repeat; - } - break; - } - - case OD_SETKEEPCAPTURE: - if (lparam != 0) { - if (data != NULL) { - data->ScrollBar.keepCapture = 1; - } - } else { - if (data != NULL) { - data->ScrollBar.keepCapture = 0; - } - } - break; - } - - int send_notify = 0; - if (position != data->ScrollBar.position || range != data->ScrollBar.range) { - if (data->ownerWindow != NULL) { - send_notify = 1; - } - } - - data->ScrollBar.position = position; - data->ScrollBar.result = message_result; - data->ScrollBar.dragging = dragging; - data->ScrollBar.range = range; - data->ScrollBar.upPressed = up_pressed; - data->ScrollBar.downPressed = down_pressed; - - if (send_notify) { - SendMessage(data->ownerWindow, WM_VSCROLL, MAKEWPARAM(scroll_code, (unsigned short)position), (LPARAM)window); - InvalidateRect(window, NULL, FALSE); - } - - return(0); -} - - -/// -/// Handles the messages for an owner-drawn progress bar. -/// The bar is drawn as a gradient over a cached copy of the dialog background, scaled -/// against the range the dialog set. A position outside that range is clamped rather than -/// refused. -/// -/// Returns with zero; nothing this control is sent needs to reach the original -/// window procedure. -LRESULT CALLBACK ProgressBarCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - RECT rect; - Get_Display_Rect(window, &rect); - - OwnerDraw::WinData * data = NULL; - - ODWinData.getPointer(window, &data); - - switch (message) { - case OD_SUBCLASSED: { - data->ProgressBar.maximum = 100; - break; - } - - case PBM_SETRANGE: { - data->ProgressBar.minimum = LOWORD(lparam); - data->ProgressBar.maximum = HIWORD(lparam); - break; - } - - case PBM_SETPOS: { - int pos = (int)wparam; - if (pos < data->ProgressBar.minimum) { - pos = data->ProgressBar.minimum; - } - if (pos > data->ProgressBar.maximum) { - pos = data->ProgressBar.maximum; - } - data->ProgressBar.position = pos; - - InvalidateRect(window, NULL, FALSE); - break; - } - - case WM_PAINT: { - Rect sourcerect(0, 0, rect.right - rect.left + 1, rect.bottom - rect.top + 1); - Rect destrect(rect.left, rect.top, rect.right - rect.left + 1, rect.bottom - rect.top + 1); - - if (data->cachedSurface == NULL) { - Surface * surf = new BSurface(rect.right - rect.left + 1, rect.bottom - rect.top + 1, 2); - data->cachedSurface = surf; - _surface_count++; - surf->Blit_From(sourcerect, *AlternateSurface, destrect); - } - - AlternateSurface->Blit_From(destrect, *data->cachedSurface, sourcerect); - int pos = (data->ProgressBar.position * 65536) / (data->ProgressBar.maximum - data->ProgressBar.minimum); - int color = ODColorToHiColor(0x000000FF); - ODDrawGradientRect(destrect, *AlternateSurface, color, pos); - ValidateRect(window, NULL); - break; - } - } - - return(0); -} - - -/// -/// Handles the messages for an owner-drawn track bar. -/// The grip is dragged directly with the mouse and snapped to whatever step the dialog -/// asked for, with the current value optionally printed alongside the track. The parent -/// dialog is notified as the value changes, not merely when the drag ends. -/// -/// Returns with the result of the original window procedure for anything this -/// routine does not handle itself. -LRESULT CALLBACK TrackBarCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - bool play_click = true; - - RECT client_rect; - RECT display_rect; - GetClientRect(window, &client_rect); - Get_Display_Rect(window, &display_rect); - int number_width = 50; - - WinData * data = NULL; - ODWinData.getPointer(window, &data); - - int message_result = data->TrackBar.result; - int dragging = data->TrackBar.dragging; - int range = data->TrackBar.range; - int value = data->TrackBar.value; - int minimum = data->TrackBar.minimum; - int maximum; - int thumb_pos = data->TrackBar.thumbPos; - int step = data->TrackBar.step; - int show_numbers = data->TrackBar.showNumbers; - - if (!show_numbers) { - number_width = 0; - } - - int slider_width = client_rect.right - client_rect.left - number_width - 13; - if (slider_width <= 1) { - slider_width = 1; - } - - if (!range) { - WNDPROC proc = NULL; - OriginalWndProcs.getValue(window, proc); - - int range_max = CallWindowProc(proc, window, TBM_GETRANGEMAX, 0, 0); - minimum = CallWindowProc(proc, window, TBM_GETRANGEMIN, 0, 0); - range = range_max - minimum; - value = CallWindowProc(proc, window, TBM_GETPOS, 0, 0) - minimum; - thumb_pos = value * slider_width / range; - if (!range) { - range = 100; - } - - data->TrackBar.thumbPos = thumb_pos; - data->TrackBar.range = range; - data->TrackBar.value = value; - data->TrackBar.minimum = minimum; - data->TrackBar.step = step; - data->TrackBar.showNumbers = show_numbers; - } - - if (!step) { - number_width = 50; - step = 1; - show_numbers = 1; - } - - LONG grip_left; - LONG grip_right; - if (!dragging) { - grip_left = client_rect.left + thumb_pos + 1; - grip_right = grip_left + 12; - } else { - POINT point; - Get_Logical_Cursor_Pos(window, point); - - int xpos = point.x - 6; - if (xpos < 1) { - xpos = 1; - } - - int max_x = client_rect.right - number_width - 12; - if (max_x < xpos) { - xpos = max_x; - } - - int idx = ((range + 1) * (xpos - 1)) / slider_width; - if (idx >= range) { - idx = range; - } - - value = step * ((minimum + idx) / step) - minimum; - thumb_pos = value * slider_width / range; - grip_left = thumb_pos + 1; - grip_right = grip_left + 12; - } - - switch (message) { - case WM_NCHITTEST: - case WM_GETDLGCODE: { - WNDPROC proc = NULL; - OriginalWndProcs.getValue(window, proc); - return(CallWindowProc(proc, window, message, wparam, lparam)); - } - - case WM_ENABLE: - InvalidateRect(window, NULL, FALSE); - break; - - case WM_PAINT: { - Get_Display_Rect(window, &display_rect); - Rect disp_rect(display_rect.left, display_rect.top, display_rect.right - display_rect.left, display_rect.bottom - display_rect.top); - LONG style = GetWindowLong(window, GWL_STYLE); - - if (data->cachedSurface == NULL) { - RECT disp_copy; - RECT client_copy; - Get_Display_Rect(window, &disp_copy); - GetClientRect(window, &client_copy); - - BSurface * surf = new BSurface(client_copy.right + 1, client_copy.bottom + 1, 2); - data->cachedSurface = surf; - _surface_count++; - - Rect src_rect(0, 0, client_copy.right + 1, client_copy.bottom + 1); - Rect dst_rect(disp_copy.left, disp_copy.top, client_copy.right + 1, client_copy.bottom + 1); - surf->Blit_From(src_rect, *AlternateSurface, dst_rect); - ODFillRectTrans(src_rect, *surf, 0, 180); - } - - if (data->cachedSurface != NULL) { - RECT disp_copy; - RECT client_copy; - Get_Display_Rect(window, &disp_copy); - GetClientRect(window, &client_copy); - - Rect src_rect(0, 0, client_copy.right + 1, client_copy.bottom + 1); - Rect dst_rect(disp_copy.left, disp_copy.top, client_copy.right + 1, client_copy.bottom + 1); - AlternateSurface->Blit_From(dst_rect, *data->cachedSurface, src_rect); - } - - if (show_numbers) { - Surface * center = SurfaceCache.GetSurface("trofm.pcx", NULL); - Rect center_rect; - center_rect.Height = center->Get_Height(); - center_rect.Width = number_width; - center_rect.X = disp_rect.X + disp_rect.Width - number_width; - center_rect.Y = disp_rect.Y; - SurfaceCache.Draw(center_rect, *AlternateSurface, *center, 0, 0); - - Surface * left = SurfaceCache.GetSurface("trofl.pcx", NULL); - Rect left_src(0, 0, left->Get_Width(), left->Get_Height()); - Rect left_dst(disp_rect.X + disp_rect.Width - number_width, disp_rect.Y, left_src.Width, left_src.Height); - AlternateSurface->Blit_From(left_dst, *left, left_src); - - Surface * right = SurfaceCache.GetSurface("trofr.pcx", NULL); - Rect right_src(0, 0, right->Get_Width(), right->Get_Height()); - Rect right_dst(disp_rect.X + disp_rect.Width - right_src.Width, disp_rect.Y, right_src.Width, right_src.Height); - AlternateSurface->Blit_From(right_dst, *right, right_src); - } - - Rect grip_rect(grip_left + display_rect.left, display_rect.top, grip_right - grip_left, display_rect.bottom - display_rect.top); - Surface * grip = SurfaceCache.GetSurface("trakgrip.pcx", NULL); - Rect grip_src(0, 0, grip->Get_Width(), grip->Get_Height()); - AlternateSurface->Blit_From(grip_rect, *grip, grip_src); - - int frame_color; - if (ODColorFrame == -1) { - frame_color = -1; - } else { - frame_color = ODColorToHiColor(ODColorFrame); - } - - if (style & WS_DISABLED) { - frame_color = ODColorDisabled; - if (ODColorDisabled != -1) { - frame_color = ODColorToHiColor(ODColorDisabled); - } - } - - Rect frame_rect(disp_rect.X, disp_rect.Y, disp_rect.Width - number_width, disp_rect.Height); - OD_Draw_Rect(*AlternateSurface, frame_rect, 1, frame_color); - - if (style & WS_DISABLED) { - ODFillRectTrans(disp_rect, *AlternateSurface, 0, 128); - } - - if (show_numbers) { - char buffer[16]; - sprintf(buffer, "%d", step * ((value + minimum) / step)); - - COLORREF text_color = ODColorText; - if (style & WS_DISABLED) { - text_color = ODColorDisabled; - } - - RECT text_rect; - text_rect.left = display_rect.right - 49; - text_rect.top = display_rect.top; - text_rect.right = display_rect.right; - text_rect.bottom = display_rect.bottom; - - OD_Draw_Text_Remap(*AlternateSurface, buffer, *(Rect *)&text_rect, "dlgsys", text_color, 5, 0); - } - - ValidateRect(window, NULL); - break; - } - - case WM_ERASEBKGND: - return(0); - - case WM_MOUSEMOVE: - if (dragging) { - RECT rect = client_rect; - InvalidateRect(window, &rect, FALSE); - } - if (wparam & MK_LBUTTON) { - break; - } - - case WM_LBUTTONUP: - message_result = 0; - dragging = 0; - ReleaseCapture(); - break; - - case WM_LBUTTONDOWN: - case WM_LBUTTONDBLCLK: { - if (message == WM_LBUTTONDOWN) { - message_result = 1; - SetCapture(window); - } else { - message_result = 0; - ReleaseCapture(); - } - - int xpos = (unsigned short)LOWORD(lparam); - int ypos = (unsigned short)HIWORD(lparam); - if (ypos > client_rect.bottom - 18) { - if (xpos >= grip_left && xpos < grip_right) { - if (message == WM_LBUTTONDOWN) { - dragging = 1; - } - } else { - int x = xpos - 6; - if (x < 1) { - x = 1; - } - - int max_x = client_rect.right - number_width - 12; - if (max_x < x) { - x = max_x; - } - - int idx = ((range + 1) * (x - 1)) / slider_width; - if (idx >= range) { - idx = range; - } - - value = step * ((minimum + idx) / step) - minimum; - thumb_pos = value * slider_width / range; - } - } - break; - } - - case TBM_GETPOS: - return(step * ((minimum + value) / step)); - - case TBM_SETPOS: - if (lparam - minimum <= range && lparam - minimum >= 0) { - value = lparam - minimum; - } - play_click = false; - thumb_pos = value * slider_width / range; - break; - - case TBM_SETRANGE: - minimum = (unsigned short)LOWORD(lparam); - maximum = (unsigned short)HIWORD(lparam); - range = maximum - minimum; - if (value > range) { - value = range; - } - if (value < minimum) { - value = minimum; - } - play_click = false; - thumb_pos = value * slider_width / range; - break; - - case OD_SETTRACKSTEP: - step = lparam; - break; - - case OD_TRACKNUMBERS: - show_numbers = lparam; - break; - - case OD_TRACKSILENT: - data->TrackBar.clickSuppress = (wparam == 0); - break; - } - - int changed = 0; - if (value != data->TrackBar.value || range != data->TrackBar.range || minimum != data->TrackBar.minimum) { - changed = 1; - } - - data->TrackBar.result = message_result; - data->TrackBar.dragging = dragging; - data->TrackBar.thumbPos = thumb_pos; - data->TrackBar.range = range; - data->TrackBar.value = value; - data->TrackBar.minimum = minimum; - data->TrackBar.step = step; - data->TrackBar.showNumbers = show_numbers; - - if (changed) { - InvalidateRect(window, NULL, FALSE); - - HWND parent = GetParent(window); - SendMessage(parent, WM_HSCROLL, MAKEWPARAM(TB_THUMBTRACK, (unsigned short)(value + minimum)), (LPARAM)window); - - if (play_click == true && !data->TrackBar.clickSuppress) { - Sound_Effect(Rule->GenericClick); - } - } - - return(0); -} - - -/// -/// Handles the messages for an owner-drawn group box. -/// The frame is drawn as four lines with a gap left in the top edge for the caption, which -/// is written through the surface's device context in the dialog font. -/// -/// Returns with the result of the original window procedure for anything this -/// routine does not paint itself. -LRESULT CALLBACK GroupBoxCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - switch (message) { - case WM_PAINT: { - int locks = 0; - while (((DSurface *)AlternateSurface)->Is_Locked()) { - locks++; - AlternateSurface->Unlock(); - } - - HDC hdc = ((DSurface *)AlternateSurface)->GetDC(); - SelectObject(hdc, ODFontPtr); - SetTextColor(hdc, ODColorText); - SetBkMode(hdc, TRANSPARENT); - - char text[256]; - GetWindowText(window, text, sizeof(text)); - - SIZE text_size; - GetTextExtentPoint32(hdc, text, strlen(text), &text_size); - - RECT rect; - Get_Display_Rect(window, &rect); - - int y = rect.top + text_size.cy / 2; - TextOut(hdc, rect.left + 10, rect.top, text, strlen(text)); - - ((DSurface *)AlternateSurface)->ReleaseDC(hdc); - - while (locks > 0) { - ((DSurface *)AlternateSurface)->Lock(); - locks--; - } - - int color = ODColorToHiColor(ODColorFrame); - - AlternateSurface->Draw_Line(Point2D(rect.left, y), Point2D(rect.left + 8, y), color); - - /// With an empty caption the top edge is one full-width line, so the segment - /// starts at rect.left. Do NOT change this to rect.right: it draws a visibly - /// broken, empty top edge. That change has been made and backed out twice. - int spacing = 12; - if (text_size.cx == 0) { - spacing = 0; - } - - AlternateSurface->Draw_Line(Point2D(text_size.cx + rect.left + spacing, y), Point2D(rect.right, y), color); - AlternateSurface->Draw_Line(Point2D(rect.left, y), Point2D(rect.left, rect.bottom), color); - AlternateSurface->Draw_Line(Point2D(rect.left, rect.bottom), Point2D(rect.right, rect.bottom), color); - AlternateSurface->Draw_Line(Point2D(rect.right, y), Point2D(rect.right, rect.bottom), color); - - ValidateRect(window, NULL); - return(0); - } - - case WM_ERASEBKGND: - return(1); - - case WM_NCPAINT: - return(0); - } - - WNDPROC proc = NULL; - OriginalWndProcs.getValue(window, proc); - return(CallWindowProc(proc, window, message, wparam, lparam)); -} - - -/// -/// Handles the messages for an owner-drawn hotkey control. -/// The key the control currently holds is spelled out by Build_Hotkey_String and drawn -/// inside a border, over a cached copy of the dialog background. -/// -/// Returns with the result of the original window procedure for anything this -/// routine does not paint itself. -LRESULT CALLBACK HotkeyCtrlProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - switch (message) { - case WM_PAINT: { - WinData* data = NULL; - RECT rect; - Get_Display_Rect(window, &rect); - Rect alternate_rect(rect.left, rect.top, rect.right - rect.left + 1, rect.bottom - rect.top + 1); - Rect win_rect(0, 0, alternate_rect.Width, alternate_rect.Height); - ODWinData.getPointer(window, &data); - if (data->cachedSurface == NULL) { - BSurface * surf = new BSurface(alternate_rect.Width, alternate_rect.Height, 2); - data->cachedSurface = surf; - _surface_count++; - surf->Blit_From(win_rect, *AlternateSurface, alternate_rect); - } - - char string[64]; - int key = SendMessage(window, HKM_GETHOTKEY, 0, 0); - Build_Hotkey_String((KeyNumType)key, string); - - if (data->cachedSurface != NULL) { - AlternateSurface->Blit_From(alternate_rect, *data->cachedSurface, win_rect); - } - OD_Draw_Rect(*AlternateSurface, alternate_rect, 1, 0xFFFFFFFF); - if (strlen(string)) { - rect.left += 4; - rect.top += 4; - rect.right -= 4; - rect.bottom -= 4; - ODDrawTextBG(*AlternateSurface, string, &rect, ODFontPtr, ODColorText, DT_SINGLELINE|DT_VCENTER); - } - ValidateRect(window, NULL); - return(0); - } - - case WM_ERASEBKGND: - return(1); - - case WM_NCPAINT: - return(0); - } - - WNDPROC proc = NULL; - OriginalWndProcs.getValue(window, proc); - return(CallWindowProc(proc, window, message, wparam, lparam)); -} - - -/// -/// Converts a key code into its printable name. -/// This routine is used by the hotkey control to show a binding the way the player's own -/// keyboard layout names it, with the modifier names spelled out ahead of the key. -/// -/// The key, complete with its modifier bits, to spell out. -/// Buffer to build the name in. -/// Be sure that the buffer is big enough for the modifier names as well. -int Build_Hotkey_String(KeyNumType key, char * buffer) -{ - char key_name[32]; - unsigned char modifier = HIBYTE(key); - - buffer[0] = '\0'; - - UINT lparam; - - /// (p << 16) - places the scan code into bits 16-23. - /// (1 << 0) - purpose unknown; Windows does not document this bit. - /// (1 << 24) - Extended-key bit. Distinguishes some keys on an enhanced keyboard. - /// (1 << 25) - "Don't care" bit. Should not distinguish between left and right ctrl and shift keys. - - if ((modifier & (WWKEY_ALT_BIT >> 8)) != 0) { - lparam = MapVirtualKey(VK_MENU, 0) ; - lparam = (lparam << 16); - lparam |= (1 << 0); - lparam |= (1 << 25); - GetKeyNameText(lparam, key_name, sizeof(key_name)); - strcat(buffer, key_name); - strcat(buffer, "+"); - } - - if ((modifier & (WWKEY_CTRL_BIT >> 8)) != 0) { - lparam = MapVirtualKey(VK_CONTROL, 0); - lparam = (lparam << 16); - lparam |= (1 << 0); - lparam |= (1 << 25); - GetKeyNameText(lparam, key_name, sizeof(key_name)); - strcat(buffer, key_name); - strcat(buffer, "+"); - } - - if ((modifier & (WWKEY_SHIFT_BIT >> 8)) != 0) { - lparam = MapVirtualKey(VK_SHIFT, 0); - lparam = (lparam << 16); - lparam |= (1 << 0); - lparam |= (1 << 25); - GetKeyNameText(lparam, key_name, sizeof(key_name)); - strcat(buffer, key_name); - strcat(buffer, "+"); - } - - lparam = MapVirtualKey(key & 0xFF, 0); - lparam = (lparam << 16); - lparam |= (1 << 0); - lparam |= (1 << 25); - - if ((modifier & (WWKEY_RLS_BIT >> 8)) != 0) { - lparam |= (1 << 24); - } - - GetKeyNameText(lparam, key_name, sizeof(key_name)); - strcat(buffer, key_name); - - return(0); -} - -unsigned short ODRComponentMask; -unsigned short ODGComponentMask; -unsigned short ODBComponentMask; - - -/// -/// Sets up the color component masks used for blending. -/// The masks depend on how the display surface packs its pixels, so this routine cannot -/// run until the video mode is known. -/// -void ODInitMasks(void) -{ - ODRComponentMask = 255; - ODRComponentMask = ODRComponentMask >> DSurface::Get_Red_Left(); - ODRComponentMask <<= DSurface::Get_Red_Right(); - - ODGComponentMask = 255; - ODGComponentMask = ODGComponentMask >> DSurface::Get_Green_Left(); - ODGComponentMask <<= DSurface::Get_Green_Right(); - - ODBComponentMask = 255; - ODBComponentMask = ODBComponentMask >> DSurface::Get_Blue_Left(); - ODBComponentMask <<= DSurface::Get_Blue_Right(); -} - - -/// -/// Loads the artwork the owner-draw controls are built from. -/// Every button, tab, arrow, grip and check box piece is pulled into the surface cache up -/// front, so that no control has to reach for the disk while a dialog is painting. -/// -void ODCacheImages(void) -{ - SurfaceCache.CachePCX("dbak6440.pcx"); - SurfaceCache.CachePCX("gdii.pcx"); - SurfaceCache.CachePCX("nodi.pcx"); - SurfaceCache.CachePCX("arrow_uu.pcx"); - SurfaceCache.CachePCX("arrow_ud.pcx"); - SurfaceCache.CachePCX("arrow_du.pcx"); - SurfaceCache.CachePCX("arrow_dd.pcx"); - SurfaceCache.CachePCX("leftbar.pcx"); - SurfaceCache.CachePCX("rightbar.pcx"); - SurfaceCache.CachePCX("trakgrip.pcx"); - SurfaceCache.CachePCX("sbgript.pcx"); - SurfaceCache.CachePCX("sbgripm.pcx"); - SurfaceCache.CachePCX("sbgripb.pcx"); - SurfaceCache.CachePCX("bar_ll.pcx"); - SurfaceCache.CachePCX("bar_lr.pcx"); - SurfaceCache.CachePCX("bar_ul.pcx"); - SurfaceCache.CachePCX("bar_ur.pcx"); - SurfaceCache.CachePCX("dlgsysi.pcx", 1); - SurfaceCache.CachePalettedPCX("dlgsysa.pcx"); - SurfaceCache.CachePCX("wouban.pcx"); - SurfaceCache.CachePCX("wodban.pcx"); - SurfaceCache.CachePCX("wouleave.pcx"); - SurfaceCache.CachePCX("wodleave.pcx"); - SurfaceCache.CachePCX("wousqlch.pcx"); - SurfaceCache.CachePCX("wodsqlch.pcx"); - SurfaceCache.CachePCX("woudcon.pcx"); - SurfaceCache.CachePCX("woddcon.pcx"); - SurfaceCache.CachePCX("woukick.pcx"); - SurfaceCache.CachePCX("wodkick.pcx"); - SurfaceCache.CachePCX("wouhelp.pcx"); - SurfaceCache.CachePCX("wodhelp.pcx"); - SurfaceCache.CachePCX("woufind.pcx"); - SurfaceCache.CachePCX("wodfind.pcx"); - SurfaceCache.CachePCX("wouopt.pcx"); - SurfaceCache.CachePCX("wodopt.pcx"); - SurfaceCache.CachePCX("woutrny.pcx"); - SurfaceCache.CachePCX("wodtrny.pcx"); - SurfaceCache.CachePCX("wouclan.pcx"); - SurfaceCache.CachePCX("wodclan.pcx"); - SurfaceCache.CachePCX("woufgame.pcx"); - SurfaceCache.CachePCX("wodfgame.pcx"); - SurfaceCache.CachePCX("wouact.pcx"); - SurfaceCache.CachePCX("wodact.pcx"); - SurfaceCache.CachePCX("wouref.pcx"); - SurfaceCache.CachePCX("wodref.pcx"); - SurfaceCache.CachePCX("tab_tlu.pcx"); - SurfaceCache.CachePCX("tab_tmu.pcx"); - SurfaceCache.CachePCX("tab_tru.pcx"); - SurfaceCache.CachePCX("tab_tld.pcx"); - SurfaceCache.CachePCX("tab_tmd.pcx"); - SurfaceCache.CachePCX("tab_trd.pcx"); - SurfaceCache.CachePCX("tab_ftl.pcx"); - SurfaceCache.CachePCX("tab_ftr.pcx"); - SurfaceCache.CachePCX("tab_ftm.pcx"); - SurfaceCache.CachePCX("tab_fbr.pcx"); - SurfaceCache.CachePCX("tab_fbl.pcx"); - SurfaceCache.CachePCX("tab_fbm.pcx"); - SurfaceCache.CachePCX("tab_fmr.pcx"); - SurfaceCache.CachePCX("tab_fml.pcx"); - SurfaceCache.CachePCX("woloper.pcx"); - SurfaceCache.CachePCX("wolsqlch.pcx"); - SurfaceCache.CachePCX("woltrny.pcx"); - SurfaceCache.CachePCX("woluser.pcx"); - SurfaceCache.CachePCX("wolvoice.pcx"); - SurfaceCache.CachePCX("wolpriv.pcx"); - SurfaceCache.CachePCX("wolacpt.pcx"); - SurfaceCache.CachePCX("wolhost.pcx"); - SurfaceCache.CachePCX("wolclan.pcx"); - SurfaceCache.CachePCX("dnarrowp.pcx"); - SurfaceCache.CachePCX("uparrowp.pcx"); - SurfaceCache.CachePCX("dnarrowr.pcx"); - SurfaceCache.CachePCX("uparrowr.pcx"); - SurfaceCache.CachePCX("trofl.pcx"); - SurfaceCache.CachePCX("trofm.pcx"); - SurfaceCache.CachePCX("trofr.pcx"); - SurfaceCache.CachePCX("sb_psh_u.pcx"); - SurfaceCache.CachePCX("sb_psh_d.pcx"); - SurfaceCache.CachePCX("sb_rel_u.pcx"); - SurfaceCache.CachePCX("sb_rel_d.pcx"); - SurfaceCache.CachePCX("bst_chkd.pcx"); - SurfaceCache.CachePCX("bst_uchk.pcx"); - SurfaceCache.CachePCX("bst_chkg.pcx"); - SurfaceCache.CachePCX("bst_uckg.pcx"); - SurfaceCache.CachePCX("ccd_i.pcx"); - SurfaceCache.CachePCX("cce_i.pcx"); - SurfaceCache.CachePCX("cud_i.pcx"); - SurfaceCache.CachePCX("cue_i.pcx"); - SurfaceCache.CachePCX("bue_li30.pcx"); - SurfaceCache.CachePCX("bue_mi30.pcx"); - SurfaceCache.CachePCX("bue_ri30.pcx"); - SurfaceCache.CachePCX("bde_li30.pcx"); - SurfaceCache.CachePCX("bde_mi30.pcx"); - SurfaceCache.CachePCX("bde_ri30.pcx"); - SurfaceCache.CachePCX("bud_li30.pcx"); - SurfaceCache.CachePCX("bud_mi30.pcx"); - SurfaceCache.CachePCX("bud_ri30.pcx"); - SurfaceCache.CachePCX("bue_li24.pcx"); - SurfaceCache.CachePCX("bue_mi24.pcx"); - SurfaceCache.CachePCX("bue_ri24.pcx"); - SurfaceCache.CachePCX("bde_li24.pcx"); - SurfaceCache.CachePCX("bde_mi24.pcx"); - SurfaceCache.CachePCX("bde_ri24.pcx"); - SurfaceCache.CachePCX("bud_li24.pcx"); - SurfaceCache.CachePCX("bud_mi24.pcx"); - SurfaceCache.CachePCX("bud_ri24.pcx"); -} - - -/// -/// Draws a single blended line. -/// This is the low level routine behind the frames the owner-draw controls are built from. -/// Every pixel along the line is blended toward the color rather than replaced, so the -/// dialog artwork still shows through the frame. -/// -/// The surface to draw upon. -/// One end of the line. -/// The other end of the line. -/// The raw color value to blend toward. -/// The blend strength, 0 through 255. -/// bool; Was the line drawn? -bool ODDrawEdgeGlow(Surface & surf, Point2D const & start, Point2D const & end, int color, unsigned char steps) -{ - Point2D startpoint = start; - Point2D endpoint = end; - - if (startpoint.X > endpoint.X) { - std::swap(startpoint, endpoint); - } - - int bpp = surf.Bytes_Per_Pixel(); - void * buffer = surf.Lock(startpoint); - if (buffer != NULL) { - unsigned short blend_color = (unsigned short)color; - - if (startpoint.Y == endpoint.Y) { - /* - * Simplest of the blits, straight horizontal line. - */ - if (bpp == 1) { - memset(buffer, color, endpoint.X - startpoint.X + 1); - } else { - for (int i = 0; i <= endpoint.X - startpoint.X; i++) { - *((unsigned short *)buffer + i) = OD_Blend_Color(*((unsigned short *)buffer + i), blend_color, steps); - } - } - } else if (startpoint.X == endpoint.X) { - int pitch = startpoint.Y > endpoint.Y ? -surf.Stride() : surf.Stride(); - - /* - * Straight vertical line. - */ - int dy = abs(endpoint.Y - startpoint.Y); - for (int i = 0; i <= dy; i++) { - if (bpp == 1) { - *(unsigned char *)buffer = color; - } else { - *(unsigned short *)buffer = OD_Blend_Color(*(unsigned short *)buffer, blend_color, steps); - } - buffer = (unsigned char *)buffer + pitch; - } - } else { - /* - * Distances to x and y. - */ - int dx = endpoint.X - startpoint.X; - int dy = endpoint.Y - startpoint.Y; - /* - * The line isn't straight so we need to do some maths. - */ - int pitch = surf.Stride(); - if (dy < 0) { - pitch = -pitch; - } - - dy = abs(dy); - int dx2 = 2 * dx; - int dy2 = 2 * dy; - - if (dx > dy) { - /* - * The slope is not steep. - */ - int delta = dy2 - dx; - - /* - * Plot low line. - */ - for (int i = 0; i < dx; i++) { - if (bpp == 1) { - *((unsigned char *)buffer + i) = color; - } else { - *((unsigned short *)buffer + i) = OD_Blend_Color(*((unsigned short *)buffer + i), blend_color, steps); - } - - if (delta > 0) { - buffer = (unsigned char *)buffer + pitch; - delta -= dx2; - } - - delta += dy2; - } - } else { - /* - * The slope is steep. - */ - int delta = dx2 - dy; - int k = 0; - - /* - * Plot high line. - */ - for (int i = 0; i < dy; i++) { - if (bpp == 1) { - *((unsigned char *)buffer + k) = color; - } else { - *((unsigned short *)buffer + k) = OD_Blend_Color(*((unsigned short *)buffer + k), blend_color, steps); - } - - if (delta > 0) { - k++; - delta -= dy2; - } - - delta += dx2; - buffer = (unsigned char *)buffer + pitch; - } - } - } - - surf.Unlock(); - return(true); - } - return(false); -} - - -/// -/// Draws a blended frame around a rectangle. -/// Each of the four edges is blended with its own strength, which is what gives a control -/// its raised or sunken look. Every corner pixel is left to a single edge so that no pixel -/// is blended twice and shows up darker than its neighbors. -/// -/// The surface to draw upon. -/// The rectangle to frame. -/// Should the frame appear raised rather than sunken? -/// How many nested frames to draw, working inward. -/// The blend strength for the left edge. -/// The blend strength for the top edge. -/// The blend strength for the right edge. -/// The blend strength for the bottom edge. -void ODDrawEdgeGlows(Surface & surface, Rect const & rect, BOOL raised, int count, int left_alpha, int top_alpha, int right_alpha, int bottom_alpha) -{ - /* - * The function flips these when the frame is sunken, which is how callers - * choose between a raised vs. sunken style (i.e., highlight/shadow swapped). - */ - - int color2 = 0xFFFF; // used on top & left edges (default: lighter) - int color1 = 0; // used on bottom & right edges (default: darker) - - /* - * When the frame is sunken, invert highlight/shadow: - * - top/left become darker - * - bottom/right become lighter - */ - if (!raised) { - color1 = 0xFFFF; - color2 = 0; - } - - for (int i = 0; i < count; i++) { - - Point2D end1; - Point2D start1; - Point2D end2; - Point2D start2; - Point2D end3; - Point2D start3; - Point2D end4; - Point2D start4; - - /* - * Top edge (left -> right). The "-2" keeps this top line from touching the - * top-right corner pixel, avoiding overdraw with the right edge drawn below. - */ - end1.X = rect.Width - i + rect.X - 2; - start1.X = i + rect.X; - end1.Y = i + rect.Y; - start1.Y = i + rect.Y; - ODDrawEdgeGlow(surface, start1, end1, color2, top_alpha); - - /* - * Bottom edge (left -> right). - */ - end2.X = rect.Width - i + rect.X - 1; - start2.X = i + rect.X; - end2.Y = rect.Y - i + rect.Height - 1; - start2.Y = end2.Y; - ODDrawEdgeGlow(surface, start2, end2, color1, bottom_alpha); - - /* - * Left edge (top -> bottom). The "+1" on the top avoids double-hitting the - * top-left corner pixel (the top edge already handled it). - */ - end3.X = rect.X + i; - start3.X = end3.X; - start3.Y = rect.Y + i + 1; - end3.Y = rect.Y - i + rect.Height - 1; - ODDrawEdgeGlow(surface, start3, end3, color2, left_alpha); - - /* - * Right edge (top -> bottom). The "-2" on the bottom avoids double-hitting - * the bottom-right corner pixel (the bottom edge already handled it). - */ - end4.X = rect.X - i + rect.Width - 1; - start4.X = end4.X; - start4.Y = i + rect.Y; - end4.Y = rect.Y - i + rect.Height - 2; - ODDrawEdgeGlow(surface, start4, end4, color1, right_alpha); - } -} - - -/// -/// Draws a scroll arrow bitmap. -/// The image is picked out of the surface cache by direction and press state, and drawn at -/// its own size from the top left corner of the rectangle given. -/// -/// The surface to draw upon. -/// The area whose top left corner the arrow is drawn from. -/// Should the upward pointing arrow be used? -/// Is the arrow button currently held down? -void ODDrawArrowBitmap(Surface & surface, Rect const & rect, BOOL upward, BOOL pressed) -{ - char fname[32]; - - char state = 'r'; - if (pressed) { - state = 'p'; - } - - if (upward) { - sprintf(fname, "uparrow%c.pcx", state); - } else { - sprintf(fname, "dnarrow%c.pcx", state); - } - - Surface * arrowSurface = SurfaceCache.GetSurface(fname); - - Rect destRect = rect; - destRect.Width = arrowSurface->Get_Width(); - destRect.Height = arrowSurface->Get_Height(); - - Rect srcRect; - srcRect.Y = 0; - srcRect.X = 0; - srcRect.Width = arrowSurface->Get_Width(); - srcRect.Height = arrowSurface->Get_Height(); - - surface.Blit_From(destRect, *arrowSurface, srcRect); -} - - -/// -/// Converts a Windows color reference into a display pixel. -/// The dialog colors are all written as RGB() values, so they have to be packed into the -/// pixel layout of the display surface before anything can be drawn with them. -/// -/// Returns with the packed pixel value. An all-ones color is passed through -/// unchanged. -int ODColorToHiColor(COLORREF color) -{ - if (color == 0xFFFFFFFF) { - return(0xFFFFFFFF); - } - /// Do not replace the union with direct byte extraction. It improves several callers and - /// breaks ProgressBarCtrlProc, which is otherwise exact -- and an exact caller outranks the - /// partial ones. - union { - struct { - unsigned int red : 8; - unsigned int green : 8; - unsigned int blue : 8; - unsigned int a : 8; - }; - int v; - } c; - - c.v = color; - - return(DSurface::Build_Hicolor_Pixel(c.red, c.green, c.blue)); -} - - -/// -/// Draws a rectangular outline around an area. -/// The outline is drawn outside the rectangle given, thickening outward as the offset -/// grows. Use this routine for the plain frames the owner-draw controls sit inside. -/// -/// How far beyond the rectangle, in pixels, the outline reaches. -/// The raw color to draw with, or -1 for the common frame color. -void OD_Draw_Rect(Surface & surf, Rect const & rect, int offset, int color) -{ - if (color == -1) { - color = ODColorToHiColor(ODColorFrame); - } - - Rect work; - work.X = rect.X - offset; - work.Y = rect.Y - offset; - work.Width = 2 * offset + rect.Width; - work.Height = 2 * offset + rect.Height; - - for (int i = 0; i < offset; i++) { - - Point2D end1; - Point2D start1; - Point2D end2; - Point2D start2; - Point2D end3; - Point2D start3; - Point2D end4; - Point2D start4; - - /* - * The same eight-point ring that ODDrawEdgeGlows walks. - */ - end1.X = work.Width - i + work.X - 2; - start1.X = i + work.X; - end1.Y = i + work.Y; - start1.Y = i + work.Y; - surf.Draw_Line(start1, end1, color); - - end2.X = work.Width - i + work.X - 1; - start2.X = i + work.X; - end2.Y = work.Y - i + work.Height - 1; - start2.Y = end2.Y; - surf.Draw_Line(start2, end2, color); - - end3.X = work.X + i; - start3.X = end3.X; - start3.Y = work.Y + i + 1; - end3.Y = work.Y - i + work.Height - 1; - surf.Draw_Line(start3, end3, color); - - end4.X = work.X - i + work.Width - 1; - start4.X = end4.X; - start4.Y = i + work.Y; - end4.Y = work.Y - i + work.Height - 2; - surf.Draw_Line(start4, end4, color); - } -} - - -/// -/// Draws the bevelled border of an owner-draw button. -/// Each of the four edges is drawn in its own shade of green, so that the border reads as -/// a raised frame rather than a flat outline. The frame is drawn outside the rectangle. -/// -/// How far beyond the rectangle, in pixels, the border reaches. -void ODDrawButtonRect(Surface & surf, Rect const & rect, int offset) -{ - Rect work = rect; - - int color1 = DSurface::Build_Hicolor_Pixel(39, 248, 116); - int color2 = DSurface::Build_Hicolor_Pixel(19, 123, 57); - int color3 = DSurface::Build_Hicolor_Pixel(30, 186, 87); - int color4 = DSurface::Build_Hicolor_Pixel(24, 153, 71); - - work.X += -1 - offset; - work.Y += -1 - offset; - work.Width += 2 * offset + 2; - int y2 = 2 * offset + 2 + work.Height; - - for (int i = 0; i < offset; i++) { - - Point2D end1; - Point2D start1; - Point2D end2; - Point2D start2; - Point2D end3; - Point2D start3; - Point2D end4; - Point2D start4; - - /* - * The same eight-point ring as OD_Draw_Rect / ODDrawEdgeGlows, but each - * edge gets its own shade so the border reads as a bevelled button. - * r.Height is never written back - y2 carries the grown height. - */ - end1.X = work.Width - i + work.X - 2; - start1.X = i + work.X; - end1.Y = i + work.Y; - start1.Y = i + work.Y; - surf.Draw_Line(start1, end1, color1); - - end2.X = work.Width - i + work.X - 1; - start2.X = i + work.X; - end2.Y = work.Y - i + y2 - 1; - start2.Y = end2.Y; - surf.Draw_Line(start2, end2, color2); - - end3.X = work.X + i; - start3.X = end3.X; - start3.Y = work.Y + i + 1; - end3.Y = work.Y - i + y2 - 1; - surf.Draw_Line(start3, end3, color3); - - end4.X = work.X - i + work.Width - 1; - start4.X = end4.X; - start4.Y = i + work.Y; - end4.Y = work.Y - i + y2 - 2; - surf.Draw_Line(start4, end4, color4); - } -} - - -/// -/// Draws text onto a surface with the Windows text formatter. -/// This routine borrows a device context from the surface, unlocking it as often as it -/// must beforehand, and lets Windows lay the string out within the rectangle given. -/// -/// The DrawText formatting flags to lay the string out with. -/// Returns with the pixel width of the string. -int ODDrawTextBG(Surface & surface, LPCSTR string, LPRECT rect, HGDIOBJ font, COLORREF color, UINT format) -{ - int locks = 0; - - while (((DSurface &)surface).Is_Locked()) { - locks++; - surface.Unlock(); - } - - HDC hdc = ((DSurface &)surface).GetDC(); - - SelectObject(hdc, font); - SetTextColor(hdc, color); - SetBkMode(hdc, TRANSPARENT); - - SIZE char_size; - GetTextExtentPoint32(hdc, string, strlen(string), &char_size); - DrawText(hdc, string, strlen(string), rect, format); - - ((DSurface &)surface).ReleaseDC(hdc); - - while (locks > 0) { - ((DSurface &)surface).Lock(); - locks--; - } - - return(char_size.cx); -} - - -/// -/// Draws word wrapped text with a remapped bitmap font. -/// This routine breaks the text into lines that will fit the rectangle -- honoring the -/// newlines already in it and breaking at a space wherever one can be found -- and hands -/// each line in turn to ODDrawCharRemap. -/// -/// The base name of the font sheets to draw with. -/// The OD_DRAW_CHAR alignment flags to lay each line out with. -/// The extra spacing to insert between characters. -int OD_Draw_Text_Remap(Surface & surface, const char * text, Rect const & rect, char const * name, COLORREF color, int flags, int char_spacing) -{ - int line_len = strlen(text); - char const * line_ptr = text; - Rect draw_rect = rect; - - FontMetrics data; - if (!ODGetFontMetrics(name, &data)) { - return(0); - } - - while (line_len) { - if (line_ptr) { - char const * nl_ptr = strchr(line_ptr, '\n'); - if (nl_ptr) { - int nl_len = (int)(nl_ptr - line_ptr) + 1; - if (line_len >= nl_len) { - line_len = nl_len; - } - } - } - - if ((unsigned char)*line_ptr <= ' ') { - ++line_ptr; - if (--line_len == 0) { - return(0); - } - } - - int text_width = 0; - for (char const * cursor = text; cursor - text < line_len; ) { - text_width += char_spacing + data.charWidths[OD_Glyph(UTF8::Decode(cursor))]; - } - - if (text_width > draw_rect.Width - draw_rect.X) { - int fallback = (int)UTF8::Boundary_Before(line_ptr, line_len - 1); - int cut = line_len - 1; - - flags &= ~4; - - while (cut > 0) { - if ((unsigned char)line_ptr[cut] <= ' ') { - break; - } - --cut; - } - if (cut > 0) { - line_len = cut; - if (cut != -1) { - continue; - } - } - - line_len = fallback; - } else { - ODDrawCharRemap(surface, line_ptr, line_len, draw_rect, name, color, (char)flags, char_spacing); - line_ptr += line_len; - draw_rect.Y += data.glyphHeight; - line_len = strlen(line_ptr); - } - } - - return(0); -} - - -/// -/// Determines how strongly a hue should be remapped. -/// The font remapper uses this to pull its hue shift back around the primary colors, so -/// that text tinted near one of them does not swing away from the color asked for. -/// -/// The hue to compute the factor for. -/// Returns with the scale factor; the nearer the hue sits to a primary, the smaller -/// it gets. -float ODCalcTextRemapFactor(int hue) -{ - float val = 1.0f; - - int arr[3]; - arr[0] = 43; - arr[1] = 128; - arr[2] = 213; - - for (int i = 0; i < 3; i++) { - int value = arr[i]; - - if (hue > value - 16 && hue <= value) { - val = float(value - hue); - val *= (1.0f / 16); - val *= (60.0f / 100); - val += (40.0f / 100); - } else if (hue > value && hue <= value + 16) { - val = float(hue - value); - val *= (1.0f / 16); - val *= (60.0f / 100); - val += (40.0f / 100); - } - } - return(val); -} - - -/// -/// Draws a line of text with a remapped bitmap font. -/// This routine builds a table that shifts the font's own palette toward the color asked -/// for and then alpha blends each character onto the destination surface. It is the low -/// level draw that all of the owner-draw remapped text ends up going through. -/// -/// The maximum number of characters of the text to draw. -/// The rectangle to align the text within. -/// The base name of the font sheets to draw with. -/// The OD_DRAW_CHAR alignment flags to lay the text out with. -/// The extra spacing to insert between characters. -void ODDrawCharRemap(Surface & dst_surf, const char *text, int max_chars, Rect const & rect, char const *font_name, COLORREF color, char flags, int char_spacing) -{ - int i; - Rect draw_rect = rect; - - char name_i[64]; - strcpy(name_i, font_name); - strcat(name_i, "i.pcx"); - - char palette[768]; - Surface *sheet_i = SurfaceCache.GetSurface(name_i, palette); - if (sheet_i == NULL) { - return; - } - - char name_a[64]; - strcpy(name_a, font_name); - strcat(name_a, "a.pcx"); - - Surface *sheet_a = SurfaceCache.GetSurface(name_a, NULL); - if (sheet_a == NULL) { - return; - } - - RGBClass remap_rgb((unsigned char)color, (unsigned char)(color >> 8), (unsigned char)(color >> 16)); - HSVClass remap_hsv = remap_rgb; - RGBClass pal_rgb; - HSVClass out_hsv; - - int hue = remap_hsv.Get_Hue(); - - int end = int(hue + 15.0); - float min_factor = 1.0f; - for (i = int(hue - 15.0); i <= end; ++i) { - float factor = ODCalcTextRemapFactor(i); - if (factor < min_factor) { - min_factor = factor; - } - } - - unsigned char sat = (unsigned char)remap_hsv.Get_Saturation(); - unsigned char val = (unsigned char)remap_hsv.Get_Value(); - - unsigned short remap_table[256]; - float hue_float = (float)hue; - unsigned char *pal = (unsigned char *)&palette; - for (i = 0; i < 256; ++i) { - pal_rgb.Set_Red(pal[0]); - pal_rgb.Set_Green(pal[1]); - pal_rgb.Set_Blue(pal[2]); - HSVClass pal_hsv = pal_rgb; - - /* - * Start from the palette entry's HSV and adjust each channel. The - * wholesale copy is fully overwritten below. - */ - out_hsv = pal_hsv; - out_hsv.Set_Hue((unsigned char)(int)(hue_float - (int)(68.0f - pal_hsv.Get_Hue()) * min_factor)); - out_hsv.Set_Saturation((unsigned char)((sat * out_hsv.Get_Saturation()) >> 8)); - out_hsv.Set_Value((unsigned char)((val * out_hsv.Get_Value()) >> 8)); - - RGBClass out_rgb = out_hsv; - pal_rgb = out_rgb; - - int packed = (((out_rgb.Get_Blue() << 8) | out_rgb.Get_Green()) << 8) | out_rgb.Get_Red(); - remap_table[i] = (unsigned short)ODColorToHiColor(packed); - pal += 3; - } - - FontMetrics font_data; - if (!ODGetFontMetrics(font_name, &font_data)) { - return; - } - - if ((int)strlen(text) < max_chars) { - max_chars = strlen(text); - } - - int total_width = 0; - for (char const * cursor = text; cursor - text < max_chars; ) { - total_width += font_data.charWidths[OD_Glyph(UTF8::Decode(cursor))] + char_spacing; - } - - if ((flags & OD_DRAW_CHAR_FLAG_HORIZONTAL_CENTER) != 0) { - draw_rect.X += (draw_rect.Width - draw_rect.X - total_width) / 2; - } else if ((flags & OD_DRAW_CHAR_ALIGN_FLAG_RIGHT) != 0) { - draw_rect.X = draw_rect.Width - total_width - 1; - } - - if ((flags & OD_DRAW_CHAR_FLAG_VERTICAL_CENTER) != 0) { - draw_rect.Y = draw_rect.Y + (draw_rect.Height - font_data.glyphHeight - draw_rect.Y) / 2; - } - - draw_rect.Y -= font_data.topMargin; - --draw_rect.X; - - unsigned char *src_i = (unsigned char *)sheet_i->Lock(); - unsigned char *src_a = (unsigned char *)sheet_a->Lock(); - unsigned char *dst = (unsigned char *)dst_surf.Lock(); - - if (src_i != NULL && src_a != NULL && dst != NULL) { - int cell_w = font_data.glyphWidth + font_data.leftMargin; - int cell_h = font_data.glyphHeight + font_data.topMargin; - int chars_per_row = sheet_i->Get_Width() / (font_data.glyphWidth + font_data.leftMargin); - int dst_stride = dst_surf.Stride() / 2; - int src_stride = sheet_i->Stride(); - - int x = draw_rect.X; - for (char const * cursor = text; cursor - text < max_chars; ) { - - unsigned char index = OD_Glyph(UTF8::Decode(cursor)); - if (index <= ' ') { - x += font_data.charWidths[index] + char_spacing; - } else { - int glyph = index + 1; - int src_x = (glyph % chars_per_row) * cell_w; - int src_y = (glyph / chars_per_row) * cell_h; - - int src_y_end = src_y + cell_h; - int src_delta = src_i - src_a; - unsigned char *alpha_col = src_a + (src_y * src_stride + src_x); - unsigned char *dst_col = dst + 2 * (dst_stride * draw_rect.Y + x); - - for (int sx = src_x; sx < src_x + cell_w; ++sx) { - if (src_y < src_y_end) { - unsigned short *dst_px = (unsigned short *)dst_col; - unsigned char *alpha_px = alpha_col; - - int sy = src_y_end - src_y; - do { - unsigned char alpha = *alpha_px; - if (alpha != 0) { - unsigned char index = alpha_px[src_delta]; - *dst_px = OD_Blend_Color(*dst_px, remap_table[index], alpha); - } - - dst_px += dst_stride; - alpha_px += src_stride; - --sy; - } while (sy != 0); - } - - ++alpha_col; - dst_col += 2; - } - - x += font_data.charWidths[index] + char_spacing; - } - } - } - - if (&dst_surf != NULL) { - dst_surf.Unlock(); - } - sheet_a->Unlock(); - sheet_i->Unlock(); -} - - -/// -/// Fetches the metrics of a remappable bitmap font. -/// This routine measures the font's sheet -- the margins, the size of a character cell and -/// the inked width of every character -- so that the remap text routines know how to lay -/// characters out. Measuring is expensive, so the result is kept by font name. -/// -/// The base name of the font, without the sheet suffix. -/// Buffer to fill in with the measurements. -/// bool; Were the metrics available? -bool ODGetFontMetrics(char const * font_name, FontMetrics * metrics) -{ - static Dictionary metricsDict(Wstring_Hash); - - char buf[64]; - strcpy(buf, font_name); - strcat(buf, "a.pcx"); - - Wstring name; - name = (char *)font_name; - name.toLower(); - - FontMetrics * found = NULL; - if (metricsDict.getPointer(name, &found)) { - if (metrics != NULL) { - *metrics = *found; - return(true); - } - } - - DebugString("TS: Computing font metrics....\n"); - - FontMetrics temp; - memset(&temp, 0, sizeof(temp)); - - char palette[768]; - Surface * surf = SurfaceCache.GetSurface(buf, palette); - if (surf == NULL) { - return(false); - } - - char * basePtr = (char *)surf->Lock(); - int stride = surf->Stride(); - - /* - * ---------------------------------------------------------------- - * Vertical metrics: topMargin = blank rows above the glyph row, - * glyphHeight = inked rows (probed at column 4). - * ---------------------------------------------------------------- - */ - temp.topMargin = 0; - while (temp.topMargin < surf->Get_Height()) { - if (basePtr[stride * temp.topMargin + 4] != 0) break; - ++temp.topMargin; - } - int y = temp.topMargin; - while (y < surf->Get_Height()) { - if (basePtr[stride * y + 4] == 0) break; - ++y; - ++temp.glyphHeight; - } - - /* - * ---------------------------------------------------------------- - * Horizontal metrics: leftMargin = blank columns before the glyphs, - * glyphWidth = inked columns (probed along row 'top'). - * ---------------------------------------------------------------- - */ - temp.leftMargin = 0; - while (temp.leftMargin < surf->Get_Width()) { - if (basePtr[stride * temp.topMargin + temp.leftMargin] != 0) break; - ++temp.leftMargin; - } - int left = temp.leftMargin; - - int x; - x = left; - while (x < surf->Get_Width()) { - if (basePtr[stride * temp.topMargin + x] == 0) break; - ++x; - ++temp.glyphWidth; - } - - /* - * ---------------------------------------------------------------- - * Compute per-character metrics - * ---------------------------------------------------------------- - */ - int width = surf->Get_Width(); - int charsPerRow = width / (left + temp.glyphWidth); - for (int ch = 0; ch < 256; ++ch) { - - int left = temp.leftMargin; - int fontHeight = temp.glyphHeight; - int top = temp.topMargin; - int fontWidth = temp.glyphWidth; - - int glyphY = top + (fontHeight + top) * ((ch + 1) / charsPerRow); - int glyphX = left + (left + fontWidth) * ((ch + 1) % charsPerRow); - - int first = -1; - int last = 0; - - for (int x = glyphX; x < glyphX + fontWidth; ++x) { - int nonEmpty = 0; - for (int y = glyphY; y < glyphY + fontHeight; ++y) { - if (basePtr[stride * y + x] != 0) ++nonEmpty; - } - if (nonEmpty) { - last = x; - if (first == -1) first = x; - } - } - - if (first != -1) { - temp.charWidths[ch] = (last - first + 1); - } else { - temp.charWidths[ch] = (fontWidth / 3 + 1); - } - } - - surf->Unlock(); - - /* - * ---------------------------------------------------------------- - * Store result in caller's buffer - * ---------------------------------------------------------------- - */ - memcpy(metrics, &temp, sizeof(FontMetrics)); - - metricsDict.add(name, temp); - - return(true); -} - - -/// -/// Draws a line of text onto a surface. -/// This routine borrows a device context from the surface, unlocking it as often as it -/// must beforehand, and lets Windows put the text out aligned within the rectangle given. -/// Nothing is drawn while the game does not hold the focus. -/// -/// The number of characters of the text to draw. -/// The surface to draw upon, or NULL to draw on the alternate -/// surface. -/// Returns with the pixel width of the text. -int OD_Draw_Text(COLORREF color, HFONT font, Rect const & rect, const char * text, int len, int x_alignment, int y_alignment, Surface * surface) -{ - if (!GameInFocus && !WindowedMode) { - return(0); - } - - DSurface *destsurf = (DSurface *)surface; - if (!surface) { - destsurf = (DSurface *)AlternateSurface; - } - - int lock_count = 0; - while (destsurf->Is_Locked()) { - lock_count++; - destsurf->Unlock(); - } - - SIZE text_size; - - HDC hDC = destsurf->GetDC(); - if (hDC) { - - if (font) { - SelectObject(hDC, font); - } - - SetTextColor(hDC, color); - SetBkMode(hDC, TRANSPARENT); - - GetTextExtentPoint32(hDC, text, len, &text_size); - - int x_offset = rect.X; - int y_offset = rect.Y; - - if (x_alignment == OD_TEXT_ALIGN_MIN) { - x_offset += (rect.Width - text_size.cx + 1) / 2; - } else if (x_alignment == OD_TEXT_ALIGN_CENTER) { - x_offset += (text_size.cx + 1) / -2; - } else if (x_alignment == OD_TEXT_ALIGN_MAX) { - x_offset += -1 - text_size.cx; - } - - if (y_alignment == OD_TEXT_ALIGN_MIN) { - y_offset += (rect.Height - text_size.cy + 1) / 2; - } else if (y_alignment == OD_TEXT_ALIGN_CENTER) { - y_offset += (text_size.cy + 1) / -2; - } else if (y_alignment == OD_TEXT_ALIGN_MAX) { - y_offset += -1 - text_size.cy; - } - - TextOut(hDC, x_offset, y_offset, text, len); - destsurf->ReleaseDC(hDC); - } else { - text_size.cx = 0; - } - - while (lock_count) { - destsurf->Lock(); - lock_count--; - } - - return(text_size.cx); -} - - -/// -/// Handles the owner-draw item message for a control. -/// The controls paint themselves through the game's own surfaces rather than through a -/// device context, so this routine only records the item state that Windows handed over -/// and then forces the control to repaint. -/// -void OwnerDraw::Draw_Item(LPDRAWITEMSTRUCT drawit) -{ - if (VisibleSurface != NULL && AlternateSurface != NULL) { - RECT rect1; - Get_Display_Rect(drawit->hwndItem, &rect1); - RECT rect2; - GetClientRect(drawit->hwndItem, &rect2); - - if (GetWindowLong(drawit->hwndItem, GWL_STYLE) & WS_BORDER) { - int x = GetSystemMetrics(SM_CXBORDER); - int y = GetSystemMetrics(SM_CYBORDER); - rect1.left += x; - rect1.right -= x; - rect1.top += y; - rect1.bottom -= y; - } - - if (drawit->CtlType == ODT_BUTTON) { - WinData * data = NULL; - HWND window = drawit->hwndItem; - - ODWinData.getPointer(window, &data); - - data->DrawItem.itemState = drawit->itemState; - - InvalidateRect(window, &drawit->rcItem, FALSE); - UpdateWindow(window); - } - } -} - - -/// -/// Draws a dimmed copy of the parent's background behind a control. -/// This routine takes the piece of the parent window that the control covers, darkens it -/// and keeps the result on a surface cached against the control, giving the control a -/// smoked glass look. Later calls simply blit the cached copy. -/// -void ODDrawDimmedBackground(Rect const & rect, HWND hWnd) -{ - WinData * winData = NULL; - WinData * parentWinData = NULL; - - ODWinData.getPointer(hWnd, &winData); - - RECT rcClient; - GetClientRect(hWnd, &rcClient); - - RECT dispChild; - Get_Display_Rect(hWnd, &dispChild); - - Surface * surface = winData->cachedSurface; - if (surface == NULL) { - surface = new BSurface(rcClient.right + 1, rcClient.bottom + 1, 2); - winData->cachedSurface = surface; - ++_surface_count; - - Rect dstFull(0, 0, rcClient.right + 1, rcClient.bottom + 1); - - HWND parent = GetParent(hWnd); - ODWinData.getPointer(parent, &parentWinData); - - Surface * parentSurf = parentWinData->cachedSurface; - if (parentSurf) { - RECT dispParent; - Get_Display_Rect(parent, &dispParent); - - Rect srcRel; - srcRel.X = dispChild.left - dispParent.left; - srcRel.Y = dispChild.top - dispParent.top; - srcRel.Width = RECT_WIDTH(dispChild) + 1; - srcRel.Height = RECT_HEIGHT(dispChild) + 1; - - surface->Blit_From(dstFull, *parentSurf, srcRel); - } else { - Surface *srcSurf = VisibleSurface; - - Rect srcAbs; - srcAbs.X = dispChild.left; - srcAbs.Y = dispChild.top; - srcAbs.Width = RECT_WIDTH(dispChild) + 1; - srcAbs.Height = RECT_HEIGHT(dispChild) + 1; - if (srcSurf) { - surface->Blit_From(dstFull, *srcSurf, srcAbs); - } - } - - unsigned short* surfptr = (unsigned short*)surface->Lock(); - if (surfptr) { - for (int i = 0; i < surface->Get_Width() * surface->Get_Height(); ++i) { - surfptr[i] = OD_Blend_Color(surfptr[i], 0, 180); - } - - surface->Unlock(); - } - } - - Rect src; - src.Width = rect.Width; - src.Height = rect.Height; - src.Y = rect.Y - dispChild.top; - src.X = rect.X - dispChild.left; - - AlternateSurface->Blit_From(rect, *surface, src); -} - - -/// -/// Draws a rectangle that fades in from left to right. -/// The fill runs from the left edge as far as the progress value asks for, blending into -/// whatever is already on the surface instead of overwriting it. This is what gives the -/// progress bar its soft leading edge. -/// -/// The fill position, as a 16.16 fraction of the rectangle width. -void ODDrawGradientRect(Rect const & rect, Surface & surface, int color, int progress) -{ - int fade = 1; - int run = (rect.Width * progress) >> 16; - - if (run < 0) return; - if (run == 0) run = 1; - - unsigned short * surfptr = (unsigned short*)surface.Lock(); - if (!surfptr) return; - - for (int row = 0; row < rect.Height; row++) { - int idx = rect.X + (rect.Y + row) * (surface.Stride() / 2); - - int q_div4 = rect.Height / 4; /// a quarter of the height, rounded down - if (row == (q_div4 * 3)) { - fade = 1; - } - if (row == q_div4) { - fade = 0; - } - - if (run > 0) { - unsigned short * pixptr = &surfptr[idx]; - for (int i = 0; i < run; i++) { - if (fade) { - pixptr[i] = OD_Blend_Color(pixptr[i], color, (255 * (i + 1)) / rect.Width); - } else { - pixptr[i] = color; - } - } - } - } - - surface.Unlock(); -} - - -/// -/// Darkens the left and top edges of a rectangle. -/// This routine halves the intensity of every pixel it covers, which is what gives the -/// tooltip frame its shadowed bevel. -/// -/// The width of the darkened band down the left edge. -/// The height of the darkened band across the top edge. -void ODDrawBevelDarken(Rect const & rect, Surface & surface, int xpos, int ypos) -{ - unsigned short * surfptr = (unsigned short *)surface.Lock(); - - if (surfptr != NULL) { - - int stride = surface.Stride() / 2; - - /* - * The half-intensity mask is respelled at each store with per-component - * drift: the green term reuses one load of its mask, the red term's - * second read carries a cast (defeating the load reuse), and the blue - * term is missing the "& mask" entirely (harmless, since blue is the - * low bit field). - * NOTE: this is NOT OD_Blend_Color(px, 0, ...) - the blend helper goes - * through the alpha statics with multiplies and a >>8 (visible at the - * real blend-to-black sites); the binary here has only the shift/mask - * arithmetic, and the per-component drift proves it was hand-written. - */ - int y; - for (y = rect.Y; y < rect.Y + rect.Height; ++y) { - int index = y * stride + rect.X; - for (int x = rect.X; x < rect.X + xpos; ++x) { - unsigned short px = surfptr[index]; - - surfptr[index] = (px >> 1) & (((ODRComponentMask >> 1) & (unsigned int)ODRComponentMask) | ((ODGComponentMask >> 1) & ODGComponentMask) | (ODBComponentMask >> 1)); - index++; - } - } - - for (y = rect.Y; y < rect.Y + ypos; ++y) { - int index = y * stride + rect.X + xpos; - for (int x = rect.X + xpos; x < rect.Width + rect.X; ++x) { - unsigned short px = surfptr[index]; - - surfptr[index] = (px >> 1) & (((ODRComponentMask >> 1) & (unsigned int)ODRComponentMask) | ((ODGComponentMask >> 1) & ODGComponentMask) | (ODBComponentMask >> 1)); - index++; - } - } - - surface.Unlock(); - } -} - - -/// -/// Fills a rectangle with a translucent color. -/// Each pixel of the area is blended toward the color given rather than replaced by it, -/// so whatever was already drawn there still shows through. -/// -/// The strength of the blend, from 0 for invisible to 255 for solid. -void ODFillRectTrans(Rect const & rect, Surface & surf, int color, int trans) -{ - unsigned short * surfptr = (unsigned short *)surf.Lock(); - - if (surfptr != NULL) { - - int strideWords = surf.Stride() / 2; - - for (int y = rect.Y; y < rect.Height + rect.Y; y++) { - int rowIndex = strideWords * y; - - for (int x = rect.X; x < rect.X + rect.Width; x++) { - surfptr[rowIndex + x] = OD_Blend_Color(surfptr[rowIndex + x], color, trans); - } - - rowIndex += strideWords; - } - - surf.Unlock(); - } -} - - -/// -/// Draws the decorative bit strand along a rectangle. -/// This routine scatters the small "bits" graphics along the top and bottom edges of the -/// area, picking a random variant for each one so that no two dialogs look quite alike. -/// -/// The surface to draw the strand upon. -void ODDrawBitsStrand(Rect const & rect, Surface & surface) -{ - unsigned char paldata[768]; - Surface *img = SurfaceCache.GetSurface("bits_i.pcx", paldata); - Surface *mask = SurfaceCache.GetSurface("bits_a.pcx", NULL); - int index = 0; - SurfaceCacheConvertPalette(paldata); - Rect work = rect; - int rnd; - - for (int x = 0; x < rect.Width; x += 10) { - - rnd = rand() % 8; - work.Set(rect.X+x, rect.Y+1, 10, 12); - SurfaceCache.DrawMasked(work, surface, *img, *mask, paldata, 0, 10 * rnd, 0); - - rnd = rand() % 8; - work.Set(rect.X+x, rect.Y+rect.Height-16, 10, 12); - SurfaceCache.DrawMasked(work, surface, *img, *mask, paldata, 0, 10 * rnd, 0); - - index++; - - if ((index % 6) == 0) { - x += 10; - } - } -} - - -/// -/// Draws the backdrop of an owner-draw dialog. -/// The first call composes the whole backdrop -- wallpaper, side bars, corner pieces and -/// the glowing border -- onto a surface cached on the dialog. Every later call simply -/// blits that cached surface, which is what makes repainting a dialog cheap. -/// -void OwnerDraw::Draw_Dialog_Back(HWND window) -{ - WinData * entry = NULL; - PaletteClass rgb; - - /// Find dictionary entry for this dialog (must exist by invariant set during WM_INITDIALOG). - if (ODWinData.getEntries() != 0) { - ODWinData.getPointer(window, &entry); - } - - /* - * Compute client rect and display rect; expand to max of each dimension. - */ - RECT rcClient; - ::GetClientRect(window, &rcClient); - - RECT rcDisp; - Get_Display_Rect(window, &rcDisp); - - Rect rFull; - rFull.Set(rcDisp.left, rcDisp.top, rcDisp.right - rcDisp.left, rcDisp.bottom - rcDisp.top); - - if (rFull.Width <= rcClient.right) { - rFull.Width = rcClient.right; - } - if (rFull.Height <= rcClient.bottom) { - rFull.Height = rcClient.bottom; - } - - Surface * surf = entry->cachedSurface; - if (surf == NULL) { - surf = new BSurface(rcClient.right, rcClient.bottom, 2); - entry->cachedSurface = surf; - ++_surface_count; - - // The art is 640x400 and centered on the screen; a dialog reaching past it keeps black there. - surf->Fill(0); - - Surface * back = SurfaceCache.GetSurface("dbak6440.pcx"); - - Rect dst = rFull; - Rect src = rFull; - - dst -= Point2D(rcDisp.left, rcDisp.top); - - if (VideoModeWidth > back->Get_Width()) { - src.X += (VideoModeWidth - back->Get_Width()) / -2; - } - if (VideoModeHeight > back->Get_Height()) { - src.Y += (VideoModeHeight - back->Get_Height()) / -2; - } - - surf->Blit_From(dst, *back, src); - - /// Side bars - Surface * leftbar = SurfaceCache.GetSurface("leftbar.pcx"); - Surface * rightbar = SurfaceCache.GetSurface("rightbar.pcx"); - - Rect work; - if (leftbar != 0) { - work = rFull; - work.Width = leftbar->Get_Width(); - work.X = 0; work.Y = 0; - SurfaceCache.Draw(work, *surf, *leftbar); - } - if (rightbar != 0) { - work = rFull; - work.X = work.Width - rightbar->Get_Width(); - work.Y = 0; - work.Width = rightbar->Get_Width(); - SurfaceCache.Draw(work, *surf, *rightbar); - } - - Surface * bar_ul = SurfaceCache.GetSurface("bar_ul.pcx"); - Rect srcCorner; - srcCorner.Y = 0; - srcCorner.X = 0; - srcCorner.Width = bar_ul->Get_Width(); - srcCorner.Height = bar_ul->Get_Height(); - - /// dst rect used for each corner - Rect dstCorner; - dstCorner.X = 0; - dstCorner.Y = 0; - dstCorner.Width = srcCorner.Width; - dstCorner.Height = srcCorner.Height; - - surf->Blit_From(dstCorner, *bar_ul, srcCorner); - - Surface * bar_ll = SurfaceCache.GetSurface("bar_ll.pcx"); - dstCorner.Y = rFull.Height - srcCorner.Height; - surf->Blit_From(dstCorner, *bar_ll, srcCorner); - - Surface * bar_ur = SurfaceCache.GetSurface("bar_ur.pcx"); - dstCorner.Y = 0; - dstCorner.X = rFull.Width - srcCorner.Width; - surf->Blit_From(dstCorner, *bar_ur, srcCorner); - - Surface * bar_lr = SurfaceCache.GetSurface("bar_lr.pcx"); - dstCorner.Y = rFull.Height - srcCorner.Height; - surf->Blit_From(dstCorner, *bar_lr, srcCorner); - - // Edge glow loops (layers 0..15) - for (int layer = 0; layer < 16; ++layer) { - - /* - * Top edge between left and right bars. The bar widths are re-fetched - * at every use - Get_Width is virtual, so the four calls per pass - * cannot be a cached local. - */ - Point2D p2(layer + leftbar->Get_Width(), layer); - Point2D p3(rFull.Width - layer - rightbar->Get_Width() - 1, layer); - - /// The alpha fades from 96 down to 6 as the layers move inward. The - /// parameter is an unsigned char, so the whole thing is computed in - /// 8-bit arithmetic and homed as a byte. - ODDrawEdgeGlow(*surf, p2, p3, 0xFFFF, 96 - 6 * layer); - - // Bottom edge - p3.Y = rFull.Height - layer - 1; - p2.Y = p3.Y; - ODDrawEdgeGlow(*surf, p2, p3, 0xFFFF, 96 - 6 * layer); - - /// Left vertical - p2.X = layer + leftbar->Get_Width(); - p3.X = p2.X; - p2.Y = layer + 1; - p3.Y = rFull.Height - layer - 2; - ODDrawEdgeGlow(*surf, p2, p3, 0xFFFF, 96 - 6 * layer); - - /// Right vertical - p2.X = rFull.Width - layer - rightbar->Get_Width() - 1; - p3.X = p2.X; - ODDrawEdgeGlow(*surf, p2, p3, 0xFFFF, 96 - 6 * layer); - } - } - - /// Final blit to AlternateSurface unless flagged as "captured" (UserData2 != 0) - if (surf != NULL) { - if (entry->paintDisabled == 0) { - Rect srcOnXs = rFull; - srcOnXs -= Point2D(rcDisp.left, rcDisp.top); - AlternateSurface->Blit_From(rFull, *surf, srcOnXs); - } - } -} - - -/// -/// Frees the cached background of a window that is going away. -/// Owner-draw windows keep the picture of whatever sits behind them on a cached surface. -/// This routine hands that surface back as the window is destroyed. -/// -void On_WM_NCDESTROY(HWND window) -{ - WinData *data; - if (ODWinData.getPointer(window, &data)) { - if (data != NULL && data->cachedSurface != NULL) { - delete data->cachedSurface; - data->cachedSurface = NULL; - _surface_count--; - } - } -} - - -/// -/// Repaints part of a window right away. -/// The area is marked as needing painting and the paint is forced through, rather than -/// waiting for Windows to get round to it. -/// -/// The area of the window to repaint, or NULL for the whole window. -int WINAPI ODUpdateWindowRect(HWND window, RECT *rect) -{ - InvalidateRect(window, rect, FALSE); - UpdateWindow(window); - return(1); -} - - -/// -/// Adds a window to a list of windows. -/// This routine is the enumeration callback used when a caller needs a snapshot of the -/// child windows of a dialog. -/// -/// The list to append the window to. -BOOL CALLBACK ODAddWindowToList(HWND window, ArrayList * list) -{ - if (list != NULL) { - list->add(window, list->length()); - } - return(TRUE); -} - - -/// -/// Takes the mouse away from the game so that a dialog may use it. -/// The game cursor gives up its capture, leaving Windows free to drive the dialog and its -/// controls. -/// -/// Returns with the number of captures now outstanding. -/// Each call must be matched by a call to Release_Mouse. -int OwnerDraw::Capture_Mouse(void) -{ - if (MouseCursor != NULL) { - if (MouseCursor->Is_Captured() == true) { - MouseCursor->Release_Mouse(); - } - } - _mouse_counter++; - return(_mouse_counter); -} - - -/// -/// Gives the mouse back to the game. -/// This routine undoes one Capture_Mouse. Only when the last dialog has finished with the -/// mouse does the game cursor take it back. -/// -/// Returns with the number of captures still outstanding. -int OwnerDraw::Release_Mouse(void) -{ - if (_mouse_counter > 0) { - _mouse_counter--; - } - if (_mouse_counter == 0) { - if (MouseCursor != NULL) { - if (!MouseCursor->Is_Captured()) { - MouseCursor->Capture_Mouse(); - } - } - } - return(_mouse_counter); -} - - -/// -/// Creates a modeless dialog from a resource template. -/// This routine fetches the dialog template out of the game resources, creates the dialog -/// and makes it the top window of the dialog stack. The mouse is captured for it. -/// -/// The resource ID of the dialog template to create. -/// The dialog procedure that will drive the dialog. -/// Returns with the handle of the new dialog, or NULL if it could not be -/// created. -/// Every dialog begun with this routine must be finished with End_Dialog. -HWND OwnerDraw::Begin_Dialog(int id, DLGPROC proc) -{ - LPCDLGTEMPLATE templ = (LPCDLGTEMPLATE)Fetch_Resource(MAKEINTRESOURCE(id), (LPCSTR)RT_DIALOG); - if (templ == NULL) { - return(NULL); - } - - int idx = g_DialogCount; - g_Dialogs[idx].handle = NULL; - g_Dialogs[idx].id = 0; - g_DialogCount++; - - HWND handle = CreateDialogIndirectParam(ProgramInstance, templ, MainWindow, proc, 0); - if (handle == NULL) { - g_DialogCount--; - return(NULL); - } - - g_Dialogs[idx].handle = handle; - g_Dialogs[idx].id = LOWORD(id); - - Capture_Mouse(); - - Add_Modeless_Dialog(handle); - - g_TopWindow = handle; - g_TopWindowID = LOWORD(id); - - return(handle); -} - - -/// -/// Shuts down a dialog and forgets about it. -/// This routine destroys the window and takes it off the stack of open dialogs, handing -/// the focus back to whichever dialog was underneath it -- or to the main game window once -/// the last dialog has gone. The mouse capture taken by Begin_Dialog is given back here. -/// -void OwnerDraw::End_Dialog(HWND window) -{ - Keyboard->Clear(); - - if (window == Wait_Box_Handle()) { - UI_Wait_Box_Close(); - return; - } - - DestroyWindow(window); - - for (int index = 0; index < g_DialogCount; index++) { - if (g_Dialogs[index].handle == window) { - memmove(&g_Dialogs[index], &g_Dialogs[index + 1], sizeof(WSDialogStruct) * (g_DialogCount - (index + 1))); - - g_Dialogs[g_DialogCount - 1].handle = NULL; - g_Dialogs[g_DialogCount - 1].id = 0; - - --g_DialogCount; - - if (g_DialogCount > 0) { - g_TopWindow = g_Dialogs[g_DialogCount - 1].handle; - g_TopWindowID = g_Dialogs[g_DialogCount - 1].id; - - SetForegroundWindow(g_TopWindow); - SetFocus(g_TopWindow); - } else { - g_TopWindow = NULL; - g_TopWindowID = 0; - - SetForegroundWindow(MainWindow); - SetFocus(MainWindow); - } - break; - } - } - - UpdateWindow(MainWindow); - Release_Mouse(); -} - - -/// -/// Displays a dialog that has already been created. -/// This routine makes the dialog visible, brings it to the front and flushes the game -/// keyboard so that no stale keystrokes leak into it. -/// -void OwnerDraw::Display_Dialog(HWND window) -{ - if (window == Wait_Box_Handle()) { - Keyboard->Clear(); - return; - } - - ShowWindow(window, SW_SHOWNORMAL); - SetForegroundWindow(window); - Keyboard->Clear(); -} - - -/// -/// Handles the messages common to every owner-draw dialog. -/// A dialog procedure hands its messages to this routine first and deals with them itself -/// only when they come back unclaimed. This is where the subclassing, centering, -/// background painting and control coloring that all dialogs share is performed. -/// -/// Returns with the message result, or zero if the caller should handle it. -INT_PTR OwnerDraw::Default_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - switch (message) { - case WM_DRAWITEM: - Draw_Item((DRAWITEMSTRUCT*)lparam); - return(1); - - case WM_DESTROY: - Remove_Modeless_Dialog(window); - --_dialog_count; - SetFocus(MainWindow); - return(0); - - case WM_PAINT: - Draw_Dialog_Back(window); - ValidateRect(window, 0); - return(0); - - case WM_ERASEBKGND: - return(1); - - case WM_INITDIALOG: - ++_dialog_count; - Subclass_Dialog(window, 0); - Resize_Dialogs(window); - Center_Window_Within_Window(window); - SetFocus(window); - return(0); - - case WM_CTLCOLORMSGBOX: - case WM_CTLCOLOREDIT: - case WM_CTLCOLORLISTBOX: - case WM_CTLCOLORBTN: - case WM_CTLCOLORDLG: - case WM_CTLCOLORSCROLLBAR: - case WM_CTLCOLORSTATIC: - return((INT_PTR)GetStockObject(BLACK_BRUSH)); - - case OD_SUBCLASSED: - SendMessage(window, OD_SETTOP, (WPARAM)window, 1); - return(0); - - default: - return(0); - } -} - - -/// -/// Services the game while a dialog is up. -/// A dialog's own message pump calls this routine every pass. It dispatches the pending -/// Windows messages and then either runs the game logic loop -- so that a multiplayer -/// game keeps up while the dialog is showing -- or just the maintenance callback. -/// -/// bool; Has the game ended, so that the dialog should be shut down? -bool OwnerDraw::Dialog_Message_Handler(void) -{ - static bool inmainloop = false; - - Windows_Message_Handler(); - - if (Session.Type != GAME_NORMAL && Session.Type != GAME_SKIRMISH && !Session.NetOpen && !Session.Suspended) { - if (!inmainloop) { - inmainloop = true; - bool end = Main_Loop(); - inmainloop = false; - if (end) { - return(true); - } - } - } else { - Call_Back(); - } - - return(false); -} - - -/// -/// Moves a dialog to the position specified. -/// The position is relative to the client area of the main game window rather than to the -/// desktop, and the dialog keeps its current size. -/// -/// The horizontal position to move to, or -1 to leave it where it is. -/// The vertical position to move to, or -1 to leave it where it is. -/// Returns with non-zero if the dialog was moved. -int OwnerDraw::Move_Dialog(HWND window, int x, int y) -{ - int xpos; - int ypos; - - RECT rect1; - rect1.left = 0; - rect1.top = 0; - rect1.right = VideoModeWidth; - rect1.bottom = VideoModeHeight; - - ClientToScreen(MainWindow, (LPPOINT)&rect1); - ClientToScreen(MainWindow, (LPPOINT)&rect1.right); - - RECT rect2; - GetWindowRect(window, &rect2); - - rect2.right -= rect2.left; - rect2.bottom -= rect2.top; - - if (x == -1) { - xpos = rect2.left - rect1.left; - } else { - xpos = x; - } - rect2.left = xpos; - - if (y == -1) { - ypos = rect2.top - rect1.top; - } else { - ypos = y; - } - rect2.top = ypos; - - return(MoveWindow(window, rect2.left, rect2.top, rect2.right, rect2.bottom, FALSE)); -} - - -/// -/// Creates the custom message box dialog. -/// This routine brings the modeless message box up, captures the mouse and makes the box -/// the top window. The second button stays hidden unless a caption is supplied for it. -/// -/// The message text to show in the box. -/// The caption for the cancel button, or NULL to leave it hidden. -/// Flag to be raised if the player cancels the box. -/// Returns with the handle of the message box, or NULL if it could not be -/// created. -HWND OwnerDraw::Custom_Message_Box(const char *btn1txt, const char *btn2txt, bool * cancelled) -{ - if (UI_Use_Rml() && UI_Wait_Box_Open(btn1txt, btn2txt, cancelled)) { - return(Wait_Box_Handle()); - } - - HWND dlg = OwnerDraw::Begin_Dialog(IDD_MSGBOX_1, Custom_Message_Box_Proc); - - SetWindowLongPtr(dlg, DWLP_USER, (LONG_PTR)cancelled); - SetDlgItemText(dlg, IDC_MSGBOX_TEXT, btn1txt); - - if (btn2txt) { - HWND handle = GetDlgItem(dlg, IDCANCEL); - SetWindowText(handle, btn2txt); - EnableWindow(handle, TRUE); - ShowWindow(handle, SW_SHOW); - } - - return(dlg); -} - - -/// -/// Handles the messages for a custom message box. -/// This routine lets the default dialog handler have first refusal of the message and -/// then watches for the cancel button. Cancelling feeds an escape key to the game -/// keyboard and raises the flag the box was opened with. -/// -/// Returns with the message result, or zero when the message was consumed here. -INT_PTR CALLBACK Custom_Message_Box_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - INT_PTR res = Default_Dialog_Proc(window, message, wparam, lparam); - - if (res == 0) { - if (message == WM_COMMAND && wparam == IDCANCEL) { - bool * cancelled = (bool *)GetWindowLongPtr(window, DWLP_USER); - if (cancelled) { - Keyboard->Put(KN_ESC); - *cancelled = true; - } - } - return(0); - } - return(res); -} - - -/// -/// Sets the text displayed by a custom message box. -/// Use this routine to change the prompt of a message box that is already on the screen. -/// The box is repainted before the routine returns. -/// -void OwnerDraw::Set_Custom_Message_Box_Text(HWND window, LPCSTR text) -{ - if (window == Wait_Box_Handle()) { - UI_Wait_Box_Set_Text(text); - return; - } - - SetDlgItemText(window, IDC_MSGBOX_TEXT, text); - UpdateWindow(window); -} diff --git a/code/ownrdraw.h b/code/ownrdraw.h deleted file mode 100644 index 5d6b10ccc..000000000 --- a/code/ownrdraw.h +++ /dev/null @@ -1,509 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#pragma once - -#include "arraylist.h" -#include "keyboard.h" -#include "surface.h" -#include "win.h" -#include "winfix.h" -#include "wstring.h" - - -namespace OwnerDraw { - - struct CellData { - CellData(void); - CellData(CellData const & that) : type(that.type), string(that.string), hint(that.hint), color(that.color), surf(that.surf), pingtime(that.pingtime) {} - CellData const & operator=(CellData & that) - { - type = that.type; - string = that.string; - hint = that.hint; - color = that.color; - surf = that.surf; - pingtime = that.pingtime; - return(*this); - } - - enum DataType { - INVALID, - TEXT, - SURFACE, - PING, - PRIMARY, - }; - - /* - * This specifies what kind of content the cell holds, and hence how it is drawn. - * A cell constructs as INVALID, and an INVALID cell contributes nothing to the row. - */ - DataType type; - - /* - * This is the text drawn in a TEXT cell. A PRIMARY cell ignores it and takes its - * text from the list box item itself instead. - */ - Wstring string; - - /* - * This is the tooltip text for the cell, shown when the mouse rests over this - * column of the row. If empty, then the cell has no tooltip. - */ - Wstring hint; - - /* - * This is the color the cell's text is drawn in. If -1, then the default list text - * color is used instead. - */ - int color; - - /* - * Pointer to the image drawn in a SURFACE cell, centered vertically in the row. The - * cell does not own the surface. - */ - Surface * surf; - - /* - * This is the round trip time in milliseconds, drawn as a bar in a PING cell. The - * bar fills in proportion to one second, and turns yellow at 300 and red at 500. - */ - int pingtime; - }; - - struct ColumnData { - ColumnData(void) : xPos(0), width(0), cells() {} - ColumnData(ColumnData & that) : xPos(that.xPos), width(that.width), cells(that.cells) {} - - /* - * This is the offset of the column from the left edge of a row, expressed in pixels. - */ - int xPos; - - /* - * This is the width available to the column, expressed in pixels. Text wider than - * this is truncated with an ellipsis. If zero, then the width is unlimited. - */ - int width; - - /* - * These are the column's cells, one for each row of the list box and indexed by row - * number. A row with no cell here draws nothing in this column. - */ - ArrayList cells; - }; - - struct Tooltip { - - /* - * This is the area of the screen the tooltip covers. - */ - Rect bounds; - - /* - * Pointer to a saved copy of the screen underneath the tooltip. Hiding the tooltip - * blits this back, so the dialog beneath never has to repaint itself. - */ - Surface * background; - - /* - * This is the text displayed in the tooltip. If the owning control supplies none, - * then the placeholder "Tool Tip" is used. - */ - char text[128]; - - /* - * If a tooltip currently exists, then this flag will be true. It stays true while - * the tooltip is hidden, and is only cleared when the tooltip is taken down. - */ - int isActive; - - /* - * If the tooltip is active but has been erased from the screen, then this flag will - * be true. A hidden tooltip can be put back without saving the screen again. - */ - int isHidden; - - /* - * This is the control the tooltip belongs to. The tooltip is taken down when that - * control is destroyed, hidden, or loses the keyboard focus. - */ - HWND window; - - Tooltip(void); - }; - - /* - * Key of the dictionary CtrlProc keeps of the messages it is already in the - * middle of handling, so that a control cannot re-enter its own handler. - */ - struct CtrlMsgData { - /* - * These two together identify one message in flight -- the message code and the - * control it was sent to. - */ - UINT message; - HWND window; - - bool operator ==(CtrlMsgData const & that) const; - }; - - /* - * Measurements of one of the remap fonts, cached by ODGetFontMetrics. - */ - struct FontMetrics { - int charWidths[256]; /// inked width of each character, indexed by character code - int glyphWidth; /// width of the inked part of a glyph cell - int glyphHeight; /// height of the inked part of a glyph cell - int topMargin; /// blank rows above each row of glyphs - int leftMargin; /// blank columns before each glyph - }; - - /* - * Per-control data block stored by value in ODWinData, keyed by the control's HWND. - * A common header and footer are shared by the framework (CtrlProc) and every control; - * the middle is a union overlaid differently by each control type. - */ - struct WinData { - - /* - * These fields precede the per-control union and are common to every control type. - * They are maintained by the framework rather than by any one control's handler. - */ - LPARAM userData; /// value handed in when the control was subclassed; nothing reads it back - int scrollBarWidth; /// width of the attached scrollbar, or zero if there is none - HWND ownerWindow; /// in a scrollbar's own record, the control that created it - HWND attachedWindow; /// attached scrollbar or dropdown; 1 while one is being created - Surface *cachedSurface; /// cached copy of the control's backdrop, rebuilt when it resizes - Surface *image; /// image the control draws (OD_SETIMAGE) - Surface *altImage; /// image drawn instead while the control is pressed (OD_SETALTIMAGE) - int itemHeightSet; /// combo box: the item height has already been set up once - int paintDisabled; /// suppress painting, for this control and its attached window (OD_DISABLEPAINT) - int toolTipsEnabled; /// show a tooltip while the mouse rests over the control (OD_TOOLTIPS) - - /* - * Per-control state. The same bytes mean different things per control type. - */ - union { - struct { /// list box -- columns, colors and selection - ArrayList *rowColors; /// per-row background color, -1 for the default (OD_SETCOLOR) - ArrayList *selStates; /// per-row selected flag, indexed alongside rowColors - int topIndex; /// the row drawn at the top of the visible area - int curSel; /// the currently selected row, or -1 if there is none - ArrayList *columns; /// the columns the rows are divided into (OD_ADDCOLUMN) - } ListBox; - - struct { /// scroll bar -- range, thumb position and arrows - int result; /// the left button is being held down on the scrollbar - int dragging; /// the grip is being dragged with the mouse - int range; /// number of scroll positions, defaulting to 100 - int position; /// the current scroll position within the range - int upPressed; /// the up arrow is held, and is drawn depressed - int downPressed; /// the down arrow is held, and is drawn depressed - int keepCapture; /// hand the mouse back to the owner on release (OD_SETKEEPCAPTURE) - } ScrollBar; - - struct { /// track bar (slider) -- range, value and thumb - int result; /// the left button is being held down on the track bar - int dragging; /// the thumb is being dragged with the mouse - int range; /// span of the value, the maximum less the minimum - int value; /// the current value, measured up from the minimum - int minimum; /// the low end of the value range - int thumbPos; /// how far along the slider the thumb is drawn, in pixels - int step; /// the increment a click applies; reported values snap to it - int showNumbers; /// draw the value as a number beside the slider (OD_TRACKNUMBERS) - int clickSuppress; /// stay silent rather than click when the value changes (OD_TRACKSILENT) - } TrackBar; - - struct { /// combo box -- the closed control; see ComboDrop - int selIndex; /// Unused - int reserved2C; /// Unused - int reserved30; /// Unused - HWND dropdown; /// the open dropdown window, or NULL while the list is closed - int reserved38[6]; /// Unused - COLORREF itemColors[50]; /// per-item text color, -1 for the default (OD_SETCOLOR) - } ComboBox; - - struct { /// static text -- the owner-drawn caption - char *text; /// the control's own copy of its caption, freed when it is destroyed - COLORREF textColor; /// the color the caption is drawn in (OD_SETCOLOR) - } Static; - - struct { /// owner-draw button - int state; /// item state from WM_DRAWITEM; ODS_SELECTED draws altImage - } Button; - - struct { /// auto-checkbox -- toggles itself when clicked - int checkState; /// whether the box is checked (BM_GETCHECK / BM_SETCHECK) - } CheckBox; - - struct { /// edit box -- focus / tab-stop state during dialog reveal - int reserved28; /// Unused - int reserved2C; /// Unused - int focusPending; /// focus arrived before the reveal finished, so OD_ACTIVATE must apply it - int focusEnabled; /// clear until OD_ACTIVATE; while clear, focus is deflected to MainWindow - int hadTabStop; /// WS_TABSTOP was stripped when subclassing, and OD_ACTIVATE restores it - } Edit; - - struct { /// combo box dropdown window - int selection; /// the highlighted item, seeded from the owning combo box - int reserved2C; /// Unused - int scrollTop; /// the first item visible in the dropped-down list - } ComboDrop; - - struct { /// the item state any owner-draw control was last asked to draw - int itemState; /// item state from WM_DRAWITEM; the same field as Button::state - } DrawItem; - - struct { /// progress bar -- the filled proportion of the bar - int minimum; /// the value at which the bar reads as empty - int maximum; /// the value at which the bar reads as full, 100 by default - int position; /// the current value, clamped to the range - } ProgressBar; - - unsigned char _size[0x100]; /// pads the union out so that the footer lands at the right offset - }; - - /* - * These fields follow the per-control union and are common to every control type. - * They hold the device context state CtrlProc saves and restores around a paint. - */ - HFONT font; /// the control's font, or the object OD_SAVEDC displaced out of the device context - int bkMode; /// the background mode saved by OD_SAVEDC - COLORREF bkColor; /// the background color saved by OD_SAVEDC - COLORREF textColor; /// the text color saved by OD_SAVEDC - int field_138; /// Unused - int animState; /// reveal state of the owning dialog -- 0 hidden, 1 awaiting the animated reveal, 2 shown - }; - - void Initialize(void); - void Prepare_Resources(HWND window); - void Register_Control_Classes(void); - bool Subclass_Dialog(HWND window, LPARAM lParam); - - void Draw_Item(LPDRAWITEMSTRUCT drawit); - void Draw_Dialog_Back(HWND window); - - int Capture_Mouse(void); - int Release_Mouse(void); - - HWND Begin_Dialog(int id, DLGPROC proc); - void Display_Dialog(HWND window); - int Move_Dialog(HWND window, int x, int y); - void End_Dialog(HWND window); - - INT_PTR Default_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); - bool Dialog_Message_Handler(void); - - bool Start_Tooltip(Rect const & rect, char const * text, HWND window); - bool Show_Tooltip(bool save_background); - bool Hide_Tooltip(void); - bool End_Tooltip(void); - - HWND Custom_Message_Box(const char * btn1txt, const char * btn2txt = NULL, bool * cancelled = NULL); - void Set_Custom_Message_Box_Text(HWND window, LPCSTR text); -}; - - -#define OD_TEXT_ALIGN_MIN 1 -#define OD_TEXT_ALIGN_CENTER 2 -#define OD_TEXT_ALIGN_MAX 3 - -int OD_Draw_Text_Remap(Surface & surface, const char * string, Rect const & rect, char const * name, COLORREF color, int flags, int char_spacing); -int OD_Draw_Text(COLORREF color, HFONT font, Rect const & rect, const char * text, int len, int x_alignment, int y_alignment, Surface * surface); -void OD_Draw_Rect(Surface & surf, Rect const & rect, int offset, int color); - -void On_WM_NCDESTROY(HWND window); - -int Build_Hotkey_String(KeyNumType key, char * buffer); - -extern COLORREF ODColorText; -extern COLORREF ODColorTextDim; -extern COLORREF ODColorDisabled; -extern COLORREF ODColorFrame; -extern COLORREF ODListBoxColor; -extern COLORREF ODColorUnused1; - - -/// Flags for ODDrawCharRemap. -#define OD_DRAW_CHAR_FLAG_HORIZONTAL_CENTER 1 -#define OD_DRAW_CHAR_ALIGN_FLAG_RIGHT 2 -#define OD_DRAW_CHAR_FLAG_VERTICAL_CENTER 4 - - -extern unsigned short ODRComponentMask; -extern unsigned short ODGComponentMask; -extern unsigned short ODBComponentMask; - -inline unsigned short OD_Blend_Color(unsigned short pixel, unsigned short color, unsigned char alpha) -{ - static unsigned blend_color_alpha; - static unsigned blend_pixel_alpha; - - blend_color_alpha = alpha; - blend_pixel_alpha = 255 - alpha; - - unsigned short r = ((((pixel & ODRComponentMask) * blend_pixel_alpha) + ((color & ODRComponentMask) * blend_color_alpha)) >> 8) & ODRComponentMask; - unsigned short g = ((((pixel & ODGComponentMask) * blend_pixel_alpha) + ((color & ODGComponentMask) * blend_color_alpha)) >> 8) & ODGComponentMask; - unsigned short b = (((pixel & ODBComponentMask) * blend_pixel_alpha) + ((color & ODBComponentMask) * blend_color_alpha)) >> 8; - return((unsigned short)(r | g | b)); -} - - -/* - * Owner-draw control messages, handled by CtrlProc and the per-control - * *CtrlProc functions in ownrdraw.cpp. Each is WM_USER + offset. The comment - * above each says what it does, which control(s) handle it, and its - * wParam / lParam / return contract. - */ - -/* - * Initialize a freshly subclassed control's WinData. Per-control: listbox sets item height/font, - * combobox sets item height and clears item colors, static captures its caption, progress bar sets - * max=100, edit strips WS_TABSTOP and deflects focus. - * Used by: all controls. Input: none. Output: none. - */ -#define OD_SUBCLASSED (WM_USER + 151) - -/* - * Set a per-element color. listbox = per-row background (wParam = row); combobox = per-item text - * color (wParam = item 0..50); static = text color (wParam ignored, lParam == -1 resets to default). - * Used by: listbox, combobox, static. Input: wParam = element index, lParam = COLORREF. Output: none. - */ -#define OD_SETCOLOR (WM_USER + 152) - -/* - * Enable or disable hover tooltips for the control. - * Used by: all controls. Input: lParam = BOOL. Output: previous enabled state. - */ -#define OD_TOOLTIPS (WM_USER + 154) - -/* - * Ask the parent dialog for a control's tooltip text; the dialog copies it into the lParam buffer. - * Used by: sent by the framework to the parent dialog. Input: wParam = control ID, lParam = char[]. Output: none. - */ -#define OD_GETTIPTEXT (WM_USER + 155) - -/* - * Set the control's primary user image (WinData::Image). - * Used by: all controls. Input: lParam = Surface*. Output: previous Image. - */ -#define OD_SETIMAGE (WM_USER + 156) - -/* - * Suppress painting for the control and its attached child window. - * Used by: all controls. Input: lParam = BOOL. Output: previous state. - */ -#define OD_DISABLEPAINT (WM_USER + 157) - -/* - * Restore the font / bk-mode / bk-color / text-color into the HDC that were saved by OD_SAVEDC. - * Used by: all controls. Input: lParam = HDC. Output: 1. - */ -#define OD_RESTOREDC (WM_USER + 158) - -/* - * Save the HDC's current font / bk-mode / bk-color / text-color into WinData for OD_RESTOREDC. - * Used by: all controls. Input: lParam = HDC. Output: 1. - */ -#define OD_SAVEDC (WM_USER + 159) - -/* - * Query whether the control has an attached child window (scrollbar / dropdown). - * Used by: all controls. Input: none. Output: BOOL. - */ -#define OD_HASATTACHED (WM_USER + 160) - -/* - * Non-painting refresh tick: sent in place of WM_PAINT while painting is disabled, so the control can - * update grip/scroll state without drawing. - * Used by: scrollbar, listbox (sent by CtrlProc). Input: forwarded wParam/lParam. Output: none. - */ -#define OD_REFRESHNOPAINT (WM_USER + 165) - -/* - * Add a column to the multi-column listbox. - * Used by: listbox. Input: wParam = column width, lParam = column x-position (also the id). Output: the x-position (existing one if a column is already there). - */ -#define OD_ADDCOLUMN (WM_USER + 166) - -/* - * Remove the column whose x-position matches lParam. - * Used by: listbox. Input: lParam = column x-position/id. Output: the id, or -1 if not found. - */ -#define OD_REMOVECOLUMN (WM_USER + 167) - -/* - * Set the contents of one listbox cell. - * Used by: listbox. Input: wParam = MAKEWPARAM(columnId, row), lParam = CellData*. Output: column id, or -1 on failure. - */ -#define OD_SETCELL (WM_USER + 168) - -/* - * Push a window onto the modal z-order stack and pin it above its siblings. - * Used by: all controls (CtrlProc). Input: wParam = target HWND (0 = self), lParam = BOOL (1 = add and raise, 0 = remove). Output: previous top window. - */ -#define OD_SETTOP (WM_USER + 169) - -/* - * Set the control's alternate image (WinData::AltImage, e.g. the pressed/hover button surface). - * Used by: all controls. Input: lParam = Surface*. Output: previous AltImage. - */ -#define OD_SETALTIMAGE (WM_USER + 170) - -/* - * Set the trackbar step (the increment applied per click). - * Used by: trackbar. Input: lParam = INT step. Output: none. - */ -#define OD_SETTRACKSTEP (WM_USER + 171) - -/* - * Show or hide the numeric value drawn beside the trackbar. - * Used by: trackbar. Input: lParam = BOOL. Output: none. - */ -#define OD_TRACKNUMBERS (WM_USER + 172) - -/* - * Hit-test a listbox cell at a client point and copy its text into the buffer (for the per-cell tooltip). - * Used by: listbox. Input: wParam = MAKELPARAM(x, y), lParam = char[]. Output: non-zero when the cell text is empty. - */ -#define OD_GETCELLTIP (WM_USER + 173) - -/* - * Suppress the click sound on trackbar value changes. - * Used by: trackbar. Input: wParam = BOOL (0 = silent). Output: none. - */ -#define OD_TRACKSILENT (WM_USER + 174) - -/* - * Sent to a dialog's children once the animated reveal finishes; edit controls re-enable focus/tab-stop - * and apply any focus that arrived during the animation. - * Used by: edit controls. Input: none. Output: none. - */ -#define OD_ACTIVATE (WM_USER + 175) - -/* - * No dedicated handler: re-enters the control's WndProc so the edit-box focus-deflection at function - * entry runs again (used after focus changes). - * Used by: edit controls. Input: none. Output: none. - */ -#define OD_REFOCUS (WM_USER + 176) - -/* - * Set the scrollbar's "keep parent capture" flag. - * Used by: scrollbar. Input: lParam = BOOL. Output: none. - */ -#define OD_SETKEEPCAPTURE (WM_USER + 177) - -/* - * Initialize a combo-box dropdown window; seeds its highlighted selection from the owner combo. - * Used by: combo-box dropdown (ComboDropWinCtrlProc). Input: none. Output: none. - */ -#define OD_DROPSUBCLASSED (WM_USER + 1000) diff --git a/code/preview.cpp b/code/preview.cpp index c5070b013..0fc2e9638 100644 --- a/code/preview.cpp +++ b/code/preview.cpp @@ -23,13 +23,12 @@ #include "lzopipe.h" #include "lzostraw.h" #include "overtype.h" -#include "ownrdraw.h" +#include "drawhelp.h" #include "pcx.h" #include "scenario.h" #include "surface.h" #include "tactical.h" #include "terrain.h" -#include "windlg.h" #include "xpipe.h" #include "xstraw.h" diff --git a/code/progress.cpp b/code/progress.cpp index dc15cd603..70ae4e649 100644 --- a/code/progress.cpp +++ b/code/progress.cpp @@ -23,7 +23,6 @@ #include "language/language.h" #include "lightcon.h" #include "mixfile.h" -#include "ownrdraw.h" #include "scheme.h" #include "session.h" #include "shapeset.h" @@ -31,7 +30,6 @@ #include "ui/uiprogress.h" #include "ui/uishell.h" #include "voc.h" -#include "windlg.h" #include @@ -156,9 +154,6 @@ void ProgressScreenClass::Set_Graphic_Data(const char * progbar, const char * ba } rect.Width = rect.Width + 2; rect.Height = rect.Height + 2; - if (PlayerCount == 1 && Dialog != 0) { - HiddenSurface->Draw_Rect(rect, NormalDrawer->Convert_Pixel(15)); - } if (PlayerCount != 1) { pt.X = rect.X - 80; pt.Y = rect.Y; @@ -271,25 +266,14 @@ void ProgressScreenClass::Display_Progress(Point2D xpt) if (IsActive) { Point2D pt = xpt; - Surface *surface; - if (Dialog == 0) { - surface = HiddenSurface; - } else { - surface = AlternateSurface; - } + Surface *surface = HiddenSurface; ConvertClass * drawer = NormalDrawer; for (int i = 0; i < PlayerCount; i++) { if (Shape != NULL) { if (pt == Point2D(-1,-1)) { if (PlayerCount == 1) { - if (Dialog) { - RECT crect; - Get_Display_Rect(GetDlgItem(Dialog, IDC_PROGRESS_BAR_FRAME), &crect); - pt = Point2D(crect.left + (crect.right - crect.left) / 2, crect.top + (crect.bottom - crect.top) / 2); - } else { - return; - } + return; } else { pt = Point2D(Pos.X, Pos.Y + (10 * i)); drawer = ColorSchemes[Session.Color_Index_To_Scheme(Session.Players[i]->Player.Color)]->Converter; @@ -390,8 +374,6 @@ void ProgressScreenClass::Progress_Changed(Point2D pt) if (IsOverlay) { UI_Progress_Wait_Set_Progress(Get_Current_Progress(0)); - } else if (Dialog != NULL) { - SendMessage(Dialog, WM_PAINT, 0, 0); } else { Display_Progress(pt); } @@ -399,63 +381,24 @@ void ProgressScreenClass::Progress_Changed(Point2D pt) /// -/// Creates the progress dialog. -/// This routine brings up the owner draw progress dialog and gives it its first -/// paint. Initialize() calls it when the caller asks for the dialog presentation +/// Opens the progress screen. +/// Initialize() calls this routine when the caller asks for the windowed presentation /// rather than the full screen one. /// void ProgressScreenClass::Begin_Dialog(void) { - if (UI_Use_Rml() && UI_Progress_Wait_Open()) { - IsOverlay = true; - return; - } - - Dialog = OwnerDraw::Begin_Dialog(IDD_PROGRESS_WAIT, ProgressScreenClass::Dialog_Proc); - if (Dialog != NULL) { - SetWindowLongPtr(Dialog, DWLP_USER, (LONG_PTR)this); - OwnerDraw::Display_Dialog(Dialog); - SendMessage(Dialog, WM_PAINT, 0, 0); - } + IsOverlay = UI_Progress_Wait_Open(); } /// -/// Takes down the progress dialog. -/// This routine is used when the progress screen is finished with the dialog -/// presentation. It is harmless to call when no dialog was ever created. +/// Takes down the progress screen. +/// It is harmless to call this routine when no screen was ever opened. /// void ProgressScreenClass::End_Dialog(void) { if (IsOverlay) { UI_Progress_Wait_Close(); IsOverlay = false; - return; - } - - if (Dialog != NULL) { - OwnerDraw::End_Dialog(Dialog); - Dialog = NULL; - } -} - - -/// -/// Handles the messages sent to the progress dialog. -/// This routine gives the owner draw default dialog procedure first refusal on every -/// message, and repaints the progress display itself when a paint request comes back -/// unhandled. -/// -/// Returns with the dialog result, zero if the message was left unhandled. -INT_PTR CALLBACK ProgressScreenClass::Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - INT_PTR res = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - if (res == 0) { - if (message == WM_PAINT) { - ProgressScreenClass *screen = (ProgressScreenClass *)GetWindowLongPtr(window, DWLP_USER); - screen->Display_Progress(); - } - res = 0; } - return(res); } diff --git a/code/progress.h b/code/progress.h index cb8cb7e9c..f69f60b1c 100644 --- a/code/progress.h +++ b/code/progress.h @@ -50,10 +50,9 @@ class ProgressScreenClass void End_Dialog(void); // Is the dialog presentation up, whichever of the two it is? - bool Has_Dialog(void) const { return(Dialog != NULL || IsOverlay); } + bool Has_Dialog(void) const { return(IsOverlay); } private: - static INT_PTR CALLBACK Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); public: /* @@ -104,16 +103,9 @@ class ProgressScreenClass char PlayerCount; /* - * Handle of the progress dialog, or NULL when the progress is presented on the full - * screen instead. The dialog is used where the game must keep a window up while it - * works rather than take the screen over. - */ - HWND Dialog; - - /* - * If the dialog presentation is a document rather than a window, then this flag will - * be true and Dialog stays NULL. The document draws its own frame and bar, so the - * routines that paint into the game's surfaces stand aside for it. + * If the progress is presented as a screen of its own rather than on the full + * screen, then this flag will be true. That screen draws its own frame and bar, so + * the routines that paint into the game's surfaces stand aside for it. */ bool IsOverlay; diff --git a/code/queue.cpp b/code/queue.cpp index ee653419d..83fabec35 100644 --- a/code/queue.cpp +++ b/code/queue.cpp @@ -133,7 +133,6 @@ #include "opents_build.h" #include "overlay.h" #include "overtype.h" -#include "ownrdraw.h" #include "particle.h" #include "partsys.h" #include "psystype.h" @@ -171,7 +170,6 @@ #include "warhead.h" #include "waypoint.h" #include "weapon.h" -#include "windlg.h" #include "winstub.h" #include "wsproto.h" @@ -324,8 +322,6 @@ static int Process_Reconnect_Dialog(CDTimerClass *timeout_time BasicTimerClass *timer); static int Handle_Timeout(ConnManClass *net, FrameSyncStruct *their); static void Stop_Game(bool=false); -INT_PTR CALLBACK Reconnect_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); -static void Refill_Message_List(HWND window, UIReconnectPresenterClass const & screen); static void Close_Reconnect_Dialog(void); void Kick_Player_Now(ConnManClass *net, int kickee, FrameSyncStruct * their, bool error); bool Cast_Kick_Vote(int kicker, int kickee); @@ -2299,13 +2295,12 @@ static int Process_Reconnect_Dialog(CDTimerClass *timeout_time { static int displayed_time = 0; // time value currently displayed - static HWND disconnect_dialog; /// the disconnect/kick dialog, when no document was shown int new_time; int i; //------------------------------------------------------------------------ - /// Update the frame-sync progress info for Draw_Sync_Bars. + /// Update the frame-sync progress info the screen's bars are drawn from. //------------------------------------------------------------------------ SyncWaitElapsed = *timer; for (i = 0; i < num_conn; i++) { @@ -2313,12 +2308,10 @@ static int Process_Reconnect_Dialog(CDTimerClass *timeout_time } //------------------------------------------------------------------------ - /// The first time through, open the screen. A build whose document will not - /// prepare gets the dialog instead, running against the same presenter. + /// The first time through, open the screen. //------------------------------------------------------------------------ if (fresh) { TacticalActive = false; - disconnect_dialog = NULL; int frames[ARRAY_SIZE(SyncBarFrameSync)]; int reported = 0; @@ -2326,16 +2319,7 @@ static int Process_Reconnect_Dialog(CDTimerClass *timeout_time frames[reported++] = their[i].frame; } - if (!UI_Reconnect_Open(reconn != 0, frames, reported)) { - disconnect_dialog = WS_Create_Dialog(ProgramInstance, IDD_MPLAYER_DISCONNECT, MainWindow, Reconnect_Dialog_Proc, true); - Center_Window_Within_Window(disconnect_dialog); - if (disconnect_dialog) { - MouseCursor->Hide_Mouse(); - ShowWindow(disconnect_dialog, SW_SHOWNORMAL); - UpdateWindow(disconnect_dialog); - MouseCursor->Show_Mouse(); - } - } + UI_Reconnect_Open(reconn != 0, frames, reported); } UIReconnectPresenterClass * const screen = UI_Reconnect_Screen(); @@ -2364,37 +2348,10 @@ static int Process_Reconnect_Dialog(CDTimerClass *timeout_time UI_Reconnect_Service(); - //------------------------------------------------------------------------ - /// Put the model on the dialog's controls, for the build that has one. - //------------------------------------------------------------------------ - if (disconnect_dialog) { - if (screen->TimeChanged) { - HWND item = GetDlgItem(disconnect_dialog, IDC_DISCONNECT_TIME_REMAINING); - if (item) { - SendMessage(item, WM_SETTEXT, 0, (LPARAM)screen->TimeText.c_str()); - } - if (!(displayed_time & 1)) { - PostMessage(disconnect_dialog, WM_PAINT, 0, 0); - } - screen->TimeChanged = false; - } - - if (screen->MessagesChanged) { - Refill_Message_List(disconnect_dialog, *screen); - screen->MessagesChanged = false; - } - - screen->Drain(); - } - //------------------------------------------------------------------------ /// If the user gave up, bail out of the game. //------------------------------------------------------------------------ if (screen->Cancelled) { - if (disconnect_dialog) { - WS_Destroy_Dialog(disconnect_dialog, false); - disconnect_dialog = NULL; - } UI_Reconnect_Close(); TacticalActive = true; Map.Flag_To_Redraw(GS_REDRAW_ALL); @@ -2405,27 +2362,6 @@ static int Process_Reconnect_Dialog(CDTimerClass *timeout_time } // end of Process_Reconnect_Dialog -static int SyncNameButtonControlsIDs[MAX_PLAYERS] = { - IDC_DISCONNECT_PLAYER1, - IDC_DISCONNECT_PLAYER2, - IDC_DISCONNECT_PLAYER3, - IDC_DISCONNECT_PLAYER4, - IDC_DISCONNECT_PLAYER5, - IDC_DISCONNECT_PLAYER6, - IDC_DISCONNECT_PLAYER7, - IDC_DISCONNECT_PLAYER8 -}; -static int SyncBarControlIDs[MAX_PLAYERS] = { - IDC_DISCONNECT_PLAYER1_BOX, - IDC_DISCONNECT_PLAYER2_BOX, - IDC_DISCONNECT_PLAYER3_BOX, - IDC_DISCONNECT_PLAYER4_BOX, - IDC_DISCONNECT_PLAYER5_BOX, - IDC_DISCONNECT_PLAYER6_BOX, - IDC_DISCONNECT_PLAYER7_BOX, - IDC_DISCONNECT_PLAYER8_BOX -}; - /// /// Fetches the connection index for a player. @@ -2436,50 +2372,6 @@ static int Connection_Index(int player) { return(Ipx.Connection_Index(player)); } - - -/// -/// Draws the frame sync bars on the reconnect dialog. -/// Every player in the game gets a bar that shrinks and changes color as the wait on that -/// player drags on, so the humans can see who the game is actually stalled on. -/// -/// The reconnect dialog that owns the bar controls. -void Draw_Sync_Bars(HWND window) -{ - for (int i = 0; i < Session.Players.Count(); i++) { - RECT bar_winrect; - Get_Display_Rect(GetDlgItem(window, SyncBarControlIDs[i]), &bar_winrect); - - Rect bar_rect; - bar_rect.X = bar_winrect.left; - bar_rect.Y = bar_winrect.top; - bar_rect.Width = bar_winrect.right - bar_winrect.left; - bar_rect.Height = bar_winrect.bottom - bar_winrect.top; - - int playerid = Connection_Index(Session.Players[i]->Player.ID); - - unsigned progress; - if (i == 0) { - progress = 0; - } else { - progress = SyncWaitElapsed - SyncBarFrameSync[playerid].timing; - } - - unsigned short color = DSurface::Build_Hicolor_Pixel(0, 200, 0); - if (progress > 240) { - color = DSurface::Build_Hicolor_Pixel(200, 200, 0); - if (progress > 480) { - color = DSurface::Build_Hicolor_Pixel(200, 0, 0); - } - } - - int w = std::max(100 - (int)(100 * progress / 1200), 0) * bar_rect.Width; - bar_rect.Width = std::max(6, w / 100); - - AlternateSurface->Fill_Rect(AlternateSurface->Get_Rect(), bar_rect, color); - } -} - bool Cast_Kick_Vote(int kicker, int kickee); @@ -2591,27 +2483,6 @@ void Forget_Kick_Player(int player) } } -/// -/// Puts the screen's message list on the dialog's list box and scrolls it to the end. -/// The model holds the lines and the control shows them, so a presentation that is not a -/// window keeps the same backlog. -/// -/// The reconnect dialog holding the list box. -/// The screen whose messages are shown. -static void Refill_Message_List(HWND window, UIReconnectPresenterClass const & screen) -{ - HWND listbox = GetDlgItem(window, IDC_DISCONNECT_MESSAGES); - if (listbox == NULL) { - return; - } - - ListBox_ResetContent(listbox); - for (std::string const & line : screen.Messages) { - ListBox_AddString(listbox, line.c_str()); - } - ListBox_SetTopIndex(listbox, ListBox_GetCount(listbox) - 1); -} - /// /// Handles a kick proposal arriving from another player. @@ -2699,92 +2570,6 @@ bool Cast_Kick_Vote(int kicker, int kickee) } -/// -/// Handles the messages for the reconnect dialog. -/// This is the dialog that appears when the game stalls waiting on somebody. It paints the -/// per-player sync bars and offers a kick button for each player in the game. -/// -INT_PTR CALLBACK Reconnect_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - UIReconnectPresenterClass * const screen = UI_Reconnect_Screen(); - - switch (message) { - case IDCANCEL: - Remove_Modeless_Dialog(window); - break; - - case WM_DRAWITEM: - OwnerDraw::Draw_Item((DRAWITEMSTRUCT *)lparam); - return(TRUE); - - case WM_PAINT: - OwnerDraw::Draw_Dialog_Back(window); - Draw_Sync_Bars(window); - ValidateRect(window, NULL); - break; - - case WM_INITDIALOG: { - OwnerDraw::Subclass_Dialog(window, 0); - Center_Window_Within_Window(window); - Add_Modeless_Dialog(window); - - int i; - for (i = 0; i < MAX_PLAYERS; i++) { - HWND button = GetDlgItem(window, SyncNameButtonControlsIDs[i]); - EnableWindow(button, FALSE); - HWND bar = GetDlgItem(window, SyncBarControlIDs[i]); - EnableWindow(bar, FALSE); - } - - for (i = 0; i < MAX_PLAYERS; i++) { - HWND button = GetDlgItem(window, SyncNameButtonControlsIDs[i]); - HWND bar = GetDlgItem(window, SyncBarControlIDs[i]); - if (i < Session.Players.Count()) { - SendMessage(button, WM_SETTEXT, 0, (LPARAM)Session.Players[i]->Name); - EnableWindow(button, TRUE); - EnableWindow(bar, TRUE); - } else { - DestroyWindow(button); - DestroyWindow(bar); - } - } - break; - } - - case WM_MOVING: - return(On_WM_MOVING(window, wparam, lparam)); - - case WM_CTLCOLORMSGBOX: - case WM_CTLCOLOREDIT: - case WM_CTLCOLORLISTBOX: - case WM_CTLCOLORBTN: - case WM_CTLCOLORDLG: - case WM_CTLCOLORSCROLLBAR: - case WM_CTLCOLORSTATIC: - return((INT_PTR)GetStockObject(BLACK_BRUSH)); - - case WM_ERASEBKGND: - return(TRUE); - - case WM_COMMAND: - if (screen == NULL) { - break; - } - - for (int seat = 0; seat < MAX_PLAYERS; seat++) { - if (LOWORD(wparam) == (WPARAM)SyncNameButtonControlsIDs[seat]) { - screen->Queue(UIIntent{UI_RECONNECT_KICK, "", seat}); - } - } - - if (LOWORD(wparam) == IDCANCEL) { - screen->Queue(UIIntent{UI_RECONNECT_CANCEL, "", 0}); - } - break; - } - - return(FALSE); -} /// The name comes from the TS demo build, which ships this routine with symbols. @@ -2799,15 +2584,9 @@ static void Close_Reconnect_Dialog(void) //------------------------------------------------------------------------ // If the reconnect screen was shown, force the map to redraw. //------------------------------------------------------------------------ - bool shown = UI_Reconnect_Has_View(); + bool const shown = UI_Reconnect_Has_View(); UI_Reconnect_Close(); - HWND dialog = WS_Find_Dialog(IDD_MPLAYER_DISCONNECT); - if (dialog) { - WS_Destroy_Dialog(dialog, false); - shown = true; - } - if (shown) { TacticalActive = true; Map.Flag_To_Redraw(GS_REDRAW_ALL); diff --git a/code/restate.cpp b/code/restate.cpp index 752485b16..4df578c3b 100644 --- a/code/restate.cpp +++ b/code/restate.cpp @@ -29,7 +29,7 @@ #include "msanim.h" #include "msengine.h" #include "msfont.h" -#include "ownrdraw.h" +#include "drawhelp.h" #include "rules.h" #include "scenario.h" #include "srfcache.h" diff --git a/code/scenario.cpp b/code/scenario.cpp index 93a676079..01d507432 100644 --- a/code/scenario.cpp +++ b/code/scenario.cpp @@ -127,7 +127,6 @@ #include "newmenu.h" #include "overlay.h" #include "overtype.h" -#include "ownrdraw.h" #include "partsys.h" #include "pcx.h" #include "preview.h" @@ -397,10 +396,6 @@ bool Start_Scenario(char const * name, bool briefing, CampaignType campaign) if (briefing && Session.Type == GAME_NORMAL && !has_briefing_movie) { - // No dialog has been put up in a game a client launched, so the artwork it draws with - // is not built yet. - OwnerDraw::Prepare_Resources(MainWindow); - if (Scen->TransitTheme != THEME_NONE) { Theme.Play_Song(Scen->TransitTheme); transit_playing = true; @@ -734,7 +729,7 @@ bool Read_Scenario(char const * fname) read_ok = RandomMapGen.SeedData.Load(name); if (read_ok) { - RandomMapGen.Generate_Random_Map(false, NULL); + RandomMapGen.Generate_Random_Map(false); Multiplayer_Last_Minute_Fixups(); } strcpy(Scen->ScenarioName, name); diff --git a/code/score.cpp b/code/score.cpp index e7c48104f..f505e0b41 100644 --- a/code/score.cpp +++ b/code/score.cpp @@ -73,7 +73,6 @@ #include "surface.h" #include "theme.h" #include "utf8.h" -#include "windlg.h" #include "winstub.h" #include @@ -126,10 +125,6 @@ void ScoreClass::Presentation(void) CCFileClass file; struct Fame hallfame[NUMFAMENAMES]; - while (WS_Destroy_Dialog(NULL, NULL)) { - ; - } - XPos = (HiddenSurface->Get_Width() - 640) / 2; YPos = (HiddenSurface->Get_Height() - 400) / 2; diff --git a/code/sidebar.cpp b/code/sidebar.cpp index f204c7c5b..297e83edf 100644 --- a/code/sidebar.cpp +++ b/code/sidebar.cpp @@ -837,10 +837,6 @@ bool SidebarClass::Add(RTTIType type, int id) *=============================================================================================*/ bool SidebarClass::Scroll(bool up, int column) { - if (_dialog_count != 0) { - return(false); - } - if (column == -1) { bool scr = false; if (Column[0].Scroll(up)) { diff --git a/code/srfcache.cpp b/code/srfcache.cpp index 36d0c09c6..21bdb5bea 100644 --- a/code/srfcache.cpp +++ b/code/srfcache.cpp @@ -17,7 +17,7 @@ #include "ccfile.h" #include "dsurface.h" -#include "ownrdraw.h" +#include "drawhelp.h" #include "pcx.h" #include @@ -103,6 +103,9 @@ static unsigned int SurfaceCache_Wstring_Hash(Wstring & string) } +SurfaceCacheClass SurfaceCache; + + /// /// Constructs the cache as a Wstring-keyed dictionary using the surface /// cache hash function. diff --git a/code/ui/uikeyboard.cpp b/code/ui/uikeyboard.cpp index e300d0fff..a10f9efd1 100644 --- a/code/ui/uikeyboard.cpp +++ b/code/ui/uikeyboard.cpp @@ -43,7 +43,7 @@ #include "init.h" #include "language/language.h" #include "msgbox.h" -#include "ownrdraw.h" +#include "keyboard.h" #include "vector.h" #include "keyboard.h" diff --git a/code/ui/uimapgen.cpp b/code/ui/uimapgen.cpp index 7e185d0f1..67d36abe7 100644 --- a/code/ui/uimapgen.cpp +++ b/code/ui/uimapgen.cpp @@ -178,12 +178,12 @@ void UIMapGenPresenterClass::Execute(UIIntent const & intent) Apply(); if (Debug_Map) { - RandomMapGen.Generate_Random_Map(false, NULL); + RandomMapGen.Generate_Random_Map(false); Scen->Set_Scenario_Name(Fetch_String(TXT_RANDOM_MAP_DESCRIPTION)); Write_Scenario_INI("RandMap.Map", true); } else if (RandomMapGen.MapPreview == NULL || RandomMapGen.MapPreview->Get_Preview_Surface() == NULL) { // A map the player never previewed has to be built before it can be accepted. - RandomMapGen.Generate_Random_Map(true, NULL); + RandomMapGen.Generate_Random_Map(true); } Answer(ANSWER_ACCEPTED); @@ -499,7 +499,7 @@ void UIMapGenPresenterClass::Answer(int answer) /// void UIMapGenPresenterClass::Generate_Preview(void) { - RandomMapGen.Generate_Random_Map(true, NULL); + RandomMapGen.Generate_Random_Map(true); RandomMapGen.MapPreview->Create_Preview(); delete RandomMapGen.MapSeeder; diff --git a/code/ui/uireconnect.cpp b/code/ui/uireconnect.cpp index 6edda8c68..dc5230095 100644 --- a/code/ui/uireconnect.cpp +++ b/code/ui/uireconnect.cpp @@ -396,12 +396,6 @@ bool UI_Reconnect_Open(bool reconnect, int const * frames, int connections) _Presenter = new UIReconnectPresenterClass; _Presenter->Open(reconnect, frames, connections); - // The presentation is latched here, at screen entry. A document that will not prepare - // drops the screen back to the legacy dialog, which runs against the same presenter. - if (!UI_Use_Rml()) { - return(false); - } - ReconnectViewClass * const view = new ReconnectViewClass(*_Presenter); // The wait loop stops servicing the map's input while this screen is up, so the screen diff --git a/code/ui/uishell.cpp b/code/ui/uishell.cpp index 4fbd90721..6bf82aa72 100644 --- a/code/ui/uishell.cpp +++ b/code/ui/uishell.cpp @@ -18,6 +18,8 @@ #include "uishell.h" +#include "drawhelp.h" + #include "uiinternal.h" #include "uirmlview.h" @@ -61,7 +63,6 @@ static int _ModalDepth = 0; // How many shown documents have handed the mouse pointer to the host. A legacy dialog gave // the pointer back to Windows for as long as it was up, which is what drew an arrow over it; // the game's own pointer is a shape it only has while a scenario is running. -static int _PointerDepth = 0; // How many modal runners are on the stack. A runner owns the context between its own // passes, so the tick that Main_Loop and Call_Back make from inside one is dropped rather @@ -530,24 +531,6 @@ void UI_Paint_Now(bool immediate) } -bool UI_Document_Is_Visible(void) -{ - return(_Initialized && _Context != nullptr && _Context->GetNumDocuments() > 0); -} - - -/// -/// Should a migrated screen use its RmlUi view rather than its legacy one? -/// The answer is latched at screen entry, never mid-gesture, and LegacyDialogs in SUN.INI -/// returns every migrated screen to the view it replaced for as long as one exists. The key -/// and this function both go when OwnerDraw does. -/// -bool UI_Use_Rml(void) -{ - return(_Initialized && _Context != nullptr && !Options.LegacyDialogs); -} - - #ifndef NDEBUG /// /// Shows or hides the document that proves the shell renders, clips and takes input. @@ -609,37 +592,6 @@ static bool Handle_Developer_Key(WPARAM key) #endif -/// -/// Hands the mouse pointer to the host while a document is shown. -/// OwnerDraw::Capture_Mouse did this for every legacy dialog: with the game's mouse -/// released, WM_SETCURSOR falls through to the window class and Windows draws an arrow. -/// A front end has no game pointer of its own, so without this a document shows none. -/// -static void Release_Pointer_To_Host(void) -{ - if (MouseCursor != nullptr && MouseCursor->Is_Captured()) { - MouseCursor->Release_Mouse(); - } - - _PointerDepth++; -} - - -/// -/// Takes the pointer back once the last document has gone. -/// -static void Recapture_Pointer(void) -{ - if (_PointerDepth > 0) { - _PointerDepth--; - } - - if (_PointerDepth == 0 && MouseCursor != nullptr && !MouseCursor->Is_Captured()) { - MouseCursor->Capture_Mouse(); - } -} - - /// /// Opens an exclusive input scope for a modal document. /// The keyboard queue is cleared so a key pressed before the screen opened cannot be read diff --git a/code/ui/uishell.h b/code/ui/uishell.h index 706be3eb0..9b534384f 100644 --- a/code/ui/uishell.h +++ b/code/ui/uishell.h @@ -43,8 +43,6 @@ bool UI_Overlay_Is_Dirty(void); // Is an overlay document on screen? The coexistence rule in docs/UI_DESIGN.md forbids // showing a legacy dialog while one is. -bool UI_Document_Is_Visible(void); // Should a migrated screen use its RmlUi view rather than its legacy one? No screen has // migrated yet, so this answers false until one has. -bool UI_Use_Rml(void); diff --git a/code/video.cpp b/code/video.cpp index 9c0492bc9..28048612d 100644 --- a/code/video.cpp +++ b/code/video.cpp @@ -15,6 +15,8 @@ #include "video.h" +#include "drawhelp.h" + #include "_surface.h" #include "bgfxbackend.h" #include "dbgprint.h" @@ -217,6 +219,10 @@ bool Video_Set_Mode(int width, int height) VideoModeWidth = width; VideoModeHeight = height; + // The blend masks follow how the display surface packs its pixels, so they are built + // where that becomes known. OwnerDraw's first subclassed control used to do this. + Prepare_Draw_Resources(); + Update_Scale_Info(); Win_Cursor_Refresh(); UI_On_Resize(); diff --git a/code/wdtprops.cpp b/code/wdtprops.cpp index d4d5c0c9f..d4489b5c0 100644 --- a/code/wdtprops.cpp +++ b/code/wdtprops.cpp @@ -11,7 +11,6 @@ #include "data.h" #include "language/language.h" -#include "ownrdraw.h" #include "wdtnet.h" diff --git a/code/wdtsel.cpp b/code/wdtsel.cpp index b6f362eac..5a73d5a52 100644 --- a/code/wdtsel.cpp +++ b/code/wdtsel.cpp @@ -22,7 +22,7 @@ #include "msfont.h" #include "newmenu.h" #include "netshare.h" -#include "ownrdraw.h" +#include "drawhelp.h" #include "pcx.h" #include "session.h" #include "theme.h" @@ -1114,7 +1114,7 @@ bool WDT_Select_Campaign(Campaign * campaign, bool vq_anim) /// void Selection::Start(void) { - OwnerDraw::Capture_Mouse(); + Release_Pointer_To_Host(); if (ThemeName != NULL) { Theme.Play_Song(Theme.From_Name(ThemeName)); Theme.Set_Repeat(true); @@ -1144,7 +1144,7 @@ void Selection::End(void) AlternateSurface->Fill(0); Draw_Menu_Background(); Show_Mouse(); - OwnerDraw::Release_Mouse(); + Recapture_Pointer(); } diff --git a/code/windlg.cpp b/code/windlg.cpp deleted file mode 100644 index 93ba040cb..000000000 --- a/code/windlg.cpp +++ /dev/null @@ -1,755 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#include "always.h" - -#include "windlg.h" - -#include "arraylist.h" -#include "data.h" -#include "globals.h" -#include "init.h" -#include "msgloop.h" -#include "ownrdraw.h" -#include "video.h" -#include "win.h" - -#include -#include - - -BOOL CALLBACK Resize_Dialog(HWND window, LPARAM lParam); -BOOL CALLBACK Save_Control_Value_Enum_Proc(HWND window, LPARAM lParam); -void WS_Save_Dialog_Values(HWND window); -void WS_Save_Control_Value(int control_id, unsigned char * data, int size); - - -HWND g_TopWindow; -int g_TopWindowID; -int g_LastResponse; - -WSDialogStruct g_Dialogs[64]; -int g_DialogCount; - - -/// -/// Fetches a window's rectangle relative to the main game window. -/// The dialog layout code works in the main window's client space rather than in screen -/// coordinates, so it uses this routine in place of GetWindowRect. -/// -/// Receives the window rectangle, offset into the main window's -/// client area. -/// bool; Was the window rectangle available? -BOOL Get_Display_Rect(HWND window, LPRECT rect) -{ - RECT client; - BOOL res = GetWindowRect(window, rect); - if (!res) { - return(res); - } - GetClientRect(MainWindow, &client); - ClientToScreen(MainWindow, (LPPOINT)&client); - rect->left -= client.left; - rect->right -= client.left; - rect->top -= client.top; - rect->bottom -= client.top; - return(res); -} - - -/// -/// Finds the stack slot a dialog window occupies. -/// The dialog bookkeeping routines use this routine to turn a window handle back into a -/// position in the dialog stack. -/// -/// Returns with the index of the dialog, or -1 if the window is not a tracked -/// dialog. -inline int WS_Dialog_Index(HWND window) -{ - for (int i = 0; i < g_DialogCount; i++) { - if (g_Dialogs[i].handle == window) { - return(i); - } - } - return(-1); -} - - -/// -/// Creates a dialog and pushes it onto the dialog stack. -/// This routine builds the dialog from its resource template, registers it with the -/// message loop so its keystrokes are routed properly, rescales it to the presentation -/// layout, and leaves it as the topmost dialog with the focus. -/// -/// The module instance to load the dialog template from. -/// The resource identifier of the dialog template. -/// The window the dialog is to be parented to. -/// The dialog procedure that messages are routed to. -/// Should the dialog be made visible straight away? -/// Returns with the window handle of the new dialog. If the template is -/// missing or the dialog could not be created, NULL is returned. -HWND WS_Create_Dialog(HINSTANCE instance, int id, HWND parent, DLGPROC proc, BOOL force_show) -{ - WSDialogStruct *slot = &g_Dialogs[g_DialogCount]; - g_Dialogs[g_DialogCount].handle = 0; - g_Dialogs[g_DialogCount].id = 0; - - LPCDLGTEMPLATE templ = (LPCDLGTEMPLATE)Fetch_Resource(MAKEINTRESOURCE(id), (LPCSTR)RT_DIALOG); - - if (templ == NULL) { - return(NULL); - } - - g_DialogCount++; - - HWND window = CreateDialogIndirectParam(instance, (LPCDLGTEMPLATE)templ, parent, proc, 0); - - if (window == NULL) { - g_DialogCount--; - return(NULL); - } - - _dialog_count++; - - Add_Modeless_Dialog(window); - - EnumChildWindows(window, Resize_Dialog, TRUE); - - Resize_Dialog(window, 0); - - OwnerDraw::Capture_Mouse(); - - slot->handle = window; - slot->id = id; - - if (force_show) { - ShowWindow(window, SW_SHOWNORMAL); - } - - SetForegroundWindow(window); - SetFocus(window); - g_TopWindow = window; - g_TopWindowID = id; - return(window); -} - - -/// -/// Closes a dialog along with everything stacked on top of it. -/// This routine records the dialog's control values before it goes, so they can still -/// be read back with WS_Get_Saved_Value, then unregisters and destroys it. Whichever dialog is -/// left underneath becomes the topmost one again and is given back the focus. -/// -/// The dialog to close. If this is NULL, the topmost dialog is -/// closed. -/// The response to report to whoever is waiting on this dialog. -/// bool; Was a dialog found and closed? -bool WS_Destroy_Dialog(HWND window, int id) -{ - if (window == NULL) { - if (g_DialogCount != 0) { - window = g_Dialogs[g_DialogCount - 1].handle; - if (window == NULL) { - return(false); - } - } else { - return(false); - } - } - - int index = WS_Dialog_Index(window); - if (index == -1) { - return(false); - } - - WS_Save_Dialog_Values(window); - - int last = g_DialogCount - 1; - if (g_DialogCount - 1 >= index) { - WSDialogStruct *dlg = &g_Dialogs[last]; - int count = last - index + 1; - do { - Remove_Modeless_Dialog(dlg->handle); - DestroyWindow(dlg->handle); - _dialog_count--; - OwnerDraw::Release_Mouse(); - dlg--; - count--; - } while (count); - } - - g_DialogCount = index; - - if (g_DialogCount != 0) { - HWND hwnd = g_Dialogs[g_DialogCount - 1].handle; - int id = g_Dialogs[g_DialogCount - 1].id; - SetForegroundWindow(hwnd); - InvalidateRect(hwnd, NULL, FALSE); - UpdateWindow(hwnd); - g_TopWindow = hwnd; - g_TopWindowID = id; - SetFocus(hwnd); - SendMessage(g_TopWindow, OD_REFOCUS, 0, 0); - } else { - g_TopWindow = 0; - g_TopWindowID = 0; - SetFocus(MainWindow); - } - g_LastResponse = id; - - return(true); -} - - -/// -/// Is this window one of the dialogs still open? -/// The wait loop uses this routine to tell when the dialog it is watching over has -/// finally been destroyed. -/// -/// bool; Is the window still on the dialog stack? -BOOL WS_Has_Dialog(HWND window) -{ - for (int i = 0; i < g_DialogCount; i++) { - if (g_Dialogs[i].handle == window) { - return(true); - } - } - return(false); -} - - -/// -/// Finds an open dialog by its resource identifier. -/// Should the same dialog happen to be open more than once, the one nearest the top of -/// the stack is the one found. -/// -/// The dialog resource identifier to search for. -/// Returns with the window handle of the dialog, or NULL if no such dialog is -/// open. -HWND WS_Find_Dialog(int id) -{ - for (int i = g_DialogCount - 1; i >= 0; i--) { - if (id == g_Dialogs[i].id) { - return(g_Dialogs[i].handle); - } - } - return(NULL); -} - - -/// -/// Fetches the dialog sitting above the one given. -/// -/// Returns with the window handle of the next dialog up the stack, or NULL if -/// the dialog given is the topmost one or is not tracked at all. -HWND WS_Next_Upper_Dialog(HWND window) -{ - for (int i = 0; i < g_DialogCount; i++) { - if (window == g_Dialogs[i].handle && i < g_DialogCount - 1) { - return(g_Dialogs[i + 1].handle); - } - } - return(NULL); -} - - -/// -/// Fetches the dialog sitting below the one given. -/// -/// Returns with the window handle of the next dialog down the stack, or NULL -/// if the dialog given is the bottom one or is not tracked at all. -HWND WS_Next_Lower_Dialog(HWND window) -{ - for (int i = g_DialogCount - 1; i >= 0; i--) { - if (window == g_Dialogs[i].handle && i > 0) { - return(g_Dialogs[i - 1].handle); - } - } - return(NULL); -} - - -/// -/// Waits until a dialog has been dismissed. -/// Use this routine to make one of these modeless dialogs behave as a modal one. It -/// pumps the message queue on the dialog's behalf, keeps the title screen refreshed, -/// and polls the abort callback, returning only once the dialog is gone. -/// -/// Optional routine polled on every pass. Should it return true, -/// the dialog is cancelled. May be NULL. -/// Should the dialog be forced to the front for the duration -/// of the wait? -/// Returns with the response the dialog was closed with. -int WS_Wait_Dialog(HWND window, bool (*callback)(void), bool, bool place_on_top) -{ - MSG msg; - - if (place_on_top) { - SetForegroundWindow(window); - SendMessage(window, OD_SETTOP, 0, TRUE); - } - - while (true) { - if (!WS_Has_Dialog(window)) { - break; - } - if (callback != NULL) { - if (callback() == TRUE) { - WS_Destroy_Dialog(window, IDCANCEL); - } - } - Title_Screen_Restore(false); - - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { - TranslateMessage(&msg); - DispatchMessage(&msg); - if (!WS_Has_Dialog(window)) { - break; - } - } - - /* - * This loop pumps messages itself rather than going through the game's handler, - * so anything the dialog drew reaches the screen from here. - */ - Video_Present_If_Dirty(); - - Sleep(0); - } - - if (place_on_top) { - SendMessage(window, OD_SETTOP, 0, FALSE); - } - - return(g_LastResponse); -} - - -/// -/// Fetches the dialog currently on top of the stack. -/// -/// Returns with the window handle of the topmost dialog, or NULL if no dialog -/// is up. -HWND WS_Top_Window(void) -{ - return(g_TopWindow); -} - - -/// -/// Fetches the resource identifier of the topmost dialog. -/// -/// Returns with the dialog identifier, or zero if no dialog is up. -int WS_Top_Window_ID(void) -{ - return(g_TopWindowID); -} - - -/// -/// Fetches the response the last dialog was closed with. -/// -/// Returns with the identifier handed to WS_Destroy_Dialog when the most -/// recently closed dialog went away. -WPARAM WS_Last_Response(void) -{ - return(g_LastResponse); -} - - -/// -/// Records the values of every control in a dialog. -/// This routine is called as a dialog is being closed, so that the caller can still -/// interrogate its controls afterwards with WS_Get_Saved_Value. Whatever was recorded for a -/// previous dialog is discarded first. -/// -void WS_Save_Dialog_Values(HWND window) -{ - WS_Clear_Saved_Values(); - EnumChildWindows(window, Save_Control_Value_Enum_Proc, NULL); -} - - -/// -/// Records the value of one dialog control. -/// This routine is handed to EnumChildWindows by WS_Save_Dialog_Values. An edit box contributes -/// its text, while a button, slider or combo box contributes its current setting. -/// Anything else is passed over without comment. -/// -/// Always TRUE, so that child window enumeration carries on. -BOOL CALLBACK Save_Control_Value_Enum_Proc(HWND window, LPARAM lParam) -{ - char class_name[128]; - - GetClassName(window, class_name, sizeof(class_name)); - unsigned int id = GetWindowLong(window, GWL_ID); - - if (!strcmp(class_name, WC_EDIT)) { - int size = SendMessage(window, WM_GETTEXTLENGTH, 0, 0) + 1; - if (size > 257) { - size = 257; - } - - unsigned char *buf = new unsigned char[size]; - SendMessage(window, WM_GETTEXT, size, (LPARAM)buf); - buf[size - 1] = '\0'; - WS_Save_Control_Value(id, buf, size); - return(TRUE); - } - - if (!strcmp(class_name, WC_BUTTON)) { - int *i = new int; - *i = Button_GetCheck(window); - WS_Save_Control_Value(id, (unsigned char *)i, sizeof(*i)); - return(TRUE); - } - - if (!strcmp(class_name, TRACKBAR_CLASS)) { - int *i = new int; - *i = Slider_GetPos(window); - WS_Save_Control_Value(id, (unsigned char *)i, sizeof(*i)); - return(TRUE); - } - - if (!strcmp(class_name, WC_COMBOBOX)) { - int *i = new int; - *i = ComboBox_GetCurSel(window); - WS_Save_Control_Value(id, (unsigned char *)i, sizeof(*i)); - return(TRUE); - } - - return(TRUE); -} - - -ArrayList g_SavedValueIDs; -ArrayList g_SavedValueSizes; -ArrayList g_SavedValues; - - -/// -/// Records the value of a single dialog control. -/// This routine is the low level record keeper behind WS_Save_Dialog_Values. The value -/// survives the dialog it came from and is handed back later by WS_Get_Saved_Value. -/// -/// The control identifier the value belongs to. -/// Pointer to the value data. -/// The length of the value data, in bytes. -/// The value block is adopted by the record and is freed by -/// WS_Clear_Saved_Values. It must be allocated, never a temporary buffer. -void WS_Save_Control_Value(int control_id, unsigned char * data, int size) -{ - g_SavedValueIDs.addTail(control_id); - g_SavedValues.addTail(data); - g_SavedValueSizes.addTail(size); -} - - -/// -/// Fetches the recorded value of a dialog control. -/// Use this routine after a dialog has been closed, when the caller still needs to know -/// what the user left behind in one of its controls. -/// -/// The control identifier whose value is wanted. -/// Buffer that the recorded value is copied into. -/// The size of the destination buffer, in bytes. -/// Returns with the number of bytes copied. If no value was recorded for that -/// control, -1 is returned. -int WS_Get_Saved_Value(int control_id, unsigned char * dest, int dest_size) -{ - int entry_id = 0; - unsigned char *saved = NULL; - int saved_size = 0; - - for (int index = 0; index < g_SavedValueIDs.length(); index++) { - g_SavedValueIDs.get(entry_id, index); - if (entry_id == control_id) { - if (index >= 0) { - g_SavedValues.get(saved, index); - g_SavedValueSizes.get(saved_size, index); - } - if (saved_size < dest_size) { - dest_size = saved_size; - } - memcpy(dest, saved, dest_size); - return(dest_size); - } - } - return(-1); -} - - -/// -/// Discards every recorded dialog control value. -/// This routine frees the value blocks captured by WS_Save_Dialog_Values and empties the -/// record, leaving it ready for the next dialog that gets torn down. -/// -void WS_Clear_Saved_Values(void) -{ - unsigned char *data = NULL; - - for (int index = 0; index < g_SavedValueIDs.length(); index++) { - g_SavedValues.get(data, index); - delete data; - } - g_SavedValueIDs.clear(); - g_SavedValueSizes.clear(); - g_SavedValues.clear(); -} - - -/// -/// Handles messages for the layout reference dialog. -/// This routine handles nothing whatsoever. The reference dialog is created only to be -/// measured and is destroyed again immediately, so every message is left to the default -/// handling. -/// -INT_PTR CALLBACK Resize_Dialog_Proc(HWND window, UINT message, WPARAM wParam, LPARAM lParam) -{ - // nothing - return(FALSE); -} - - -/// -/// Fetches the client dimensions a dialog template was laid out at. -/// This routine creates the dialog just long enough to measure it and then throws it -/// away. The layout scaling code uses it to discover the resolution a template was -/// designed against. -/// -/// The resource identifier of the dialog template to -/// measure. -/// Receives the width and height of the dialog's client area. -BOOL Get_Dialog_Resolution(unsigned short template_id, DLGPROC dialog_proc, int, POINT &pt) -{ - HWND window; - tagRECT rcl; - - window = CreateDialogParam(ProgramInstance, MAKEINTRESOURCE(template_id), 0, dialog_proc, 0); - GetClientRect(window, &rcl); - DestroyWindow(window); - pt.x = rcl.right; - pt.y = rcl.bottom; - return(1); -} - - -/// -/// Rescales a dialog and every control it holds. -/// Use this routine after a dialog has been created, or whenever its layout has to be -/// rebuilt for the presentation size currently in force. -/// -void Resize_Dialogs(HWND window) -{ - EnumChildWindows(window, Resize_Dialog, 1); - Resize_Dialog(window, 0); -} - - -/// -/// Rescales a dialog or one of its controls to the presentation layout. -/// This routine is handed to EnumChildWindows by the dialog creation code and by -/// Resize_Dialogs, so that every control is carried from the coordinate space its -/// template was designed in over to the one dialogs are actually presented in. -/// -/// Should the window rectangle be taken relative to its parent? -/// This is set when enumerating child controls, and clear for the dialog itself. -/// Always TRUE, so that child window enumeration carries on. -BOOL CALLBACK Resize_Dialog(HWND window, LPARAM lParam) -{ - static int resize_dialog_width; - static int resize_dialog_height; - static int resize_dialog_scale_x = 300; - static int resize_dialog_scale_y = 163; - - LONG w; - LONG wheight; - RECT rcl; - RECT wrcl; - - char class_name[128]; - GetWindowRect(window, &rcl); - GetClassName(window, class_name, sizeof(class_name)); - - if (strcmp(class_name, WC_COMBOBOX) == 0) { - ComboBox_GetDroppedControlRect(window, &rcl); - } - - if (lParam) { - HWND win = (HWND)GetWindowLongPtr(window, GWLP_HWNDPARENT); - GetWindowRect(win, &wrcl); - rcl.left -= wrcl.left; - rcl.right -= wrcl.left; - rcl.top -= wrcl.top; - rcl.bottom -= wrcl.top; - } - - if (resize_dialog_width == 0) { - HWND win = CreateDialogParam(ProgramInstance, MAKEINTRESOURCE(198), NULL, Resize_Dialog_Proc, NULL); - GetClientRect(win, &wrcl); - DestroyWindow(win); - w = wrcl.right; - wheight = wrcl.bottom; - resize_dialog_width = w; - resize_dialog_height = wheight; - } else { - w = resize_dialog_width; - wheight = resize_dialog_height; - } - - int width = resize_dialog_scale_x * (rcl.right - rcl.left + 1) / w; - int height = resize_dialog_scale_y * (rcl.bottom - rcl.top + 1) / wheight; - - int x = resize_dialog_scale_x * rcl.left; - int y = resize_dialog_scale_y * rcl.top; - - rcl.left = x / w; - rcl.top = y / wheight; - rcl.right = width + rcl.left - 1; - rcl.bottom = height + rcl.top - 1; - - MoveWindow(window, rcl.left, rcl.top, width, height, TRUE); - - return(TRUE); -} - - -struct EzFont { - char FaceName[128]; - int DeciPtWidth; - int DeciPtHeight; - int Attributes; - HFONT FontHandle; -}; - -ArrayList g_EzFonts; - - -/// derived from MSDN "Moving Your Game to Windows, Part III" ttfont.cpp - -#define EZ_ATTR_BOLD 1 -#define EZ_ATTR_ITALIC 2 -#define EZ_ATTR_UNDERLINE 4 -#define EZ_ATTR_STRIKEOUT 8 -HFONT Ez_Create_Font (HDC hdc, const char * face_name, int decipt_width, int decipt_height, int attributes); - - -/// -/// Fetches a font of the typeface and point size requested. -/// This routine keeps every font it has built, so repeated requests for the same -/// description hand back the same handle rather than burning another GDI object. -/// The dialog drawing code calls this routine wherever it needs a font. -/// -/// The device context to build the font for. If this is NULL, the -/// font is only looked up and never created. -/// The character width in tenths of a point. -/// The character height in tenths of a point. -/// Bit flags of the EZ_ATTR_ style attributes to apply. -/// Returns with a handle to the font, or NULL if it was neither cached nor -/// able to be created. -/// The returned handle stays owned by the font cache. Do not delete it. -HFONT WS_Get_Font(HDC hdc, const char * face_name, int decipt_width, int decipt_height, int attributes) -{ - EzFont font; - - for (int index = 0; index < g_EzFonts.length(); index++) { - g_EzFonts.get(font, index); - if (!strcmp(font.FaceName, face_name) && font.DeciPtWidth == decipt_width && font.DeciPtHeight == decipt_height && font.Attributes == attributes) { - return(font.FontHandle); - } - } - - if (hdc == NULL) { - return(NULL); - } - - HFONT hFont = Ez_Create_Font(hdc, face_name, decipt_width, decipt_height, attributes); - - if (hFont == NULL) { - return(NULL); - } - - strcpy(font.FaceName, face_name); - font.DeciPtWidth = decipt_width; - font.DeciPtHeight = decipt_height; - font.Attributes = attributes; - font.FontHandle = hFont; - - if (g_EzFonts.addTail(font)) { - return(hFont); - } - - return(NULL); -} - - -/// -/// Creates a font of the typeface and point size requested. -/// This routine maps the requested decipoint dimensions through the device context's -/// current transform, so the font it builds matches the coordinate space the caller -/// draws in. Use WS_Get_Font in preference to this routine -- that one caches its fonts. -/// -/// The device context the font is to be built for. -/// The character width in tenths of a point. Zero lets the -/// typeface choose its own aspect. -/// The character height in tenths of a point. -/// Bit flags of the EZ_ATTR_ style attributes to apply. -/// Returns with a handle to the font created, or NULL if it could not be -/// created. -/// The caller takes ownership of the font handle. -HFONT Ez_Create_Font (HDC hdc, const char * face_name, int decipt_width, - int decipt_height, int attributes) -{ - HFONT hFont ; - LOGFONT lf ; - POINT pt ; - TEXTMETRIC tm ; - - SaveDC (hdc) ; - - SetGraphicsMode (hdc, GM_ADVANCED) ; - ModifyWorldTransform (hdc, NULL, MWT_IDENTITY) ; - SetViewportOrgEx (hdc, 0, 0, NULL) ; - SetWindowOrgEx (hdc, 0, 0, NULL) ; - - pt.x = decipt_width ; - pt.y = decipt_height ; - - DPtoLP (hdc, &pt, 1) ; - - lf.lfHeight = -pt.y ; - lf.lfWidth = 0 ; - lf.lfEscapement = 0 ; - lf.lfOrientation = 0 ; - lf.lfWeight = attributes & EZ_ATTR_BOLD ? 700 : 0 ; - lf.lfItalic = attributes & EZ_ATTR_ITALIC ? 1 : 0 ; - lf.lfUnderline = attributes & EZ_ATTR_UNDERLINE ? 1 : 0 ; - lf.lfStrikeOut = attributes & EZ_ATTR_STRIKEOUT ? 1 : 0 ; - lf.lfCharSet = ANSI_CHARSET ; - lf.lfOutPrecision = 0 ; - lf.lfClipPrecision = 0 ; - lf.lfQuality = 0 ; - lf.lfPitchAndFamily = 0 ; - - strcpy (lf.lfFaceName, face_name) ; - - hFont = CreateFontIndirect (&lf) ; - - if (decipt_width != 0) { - hFont = (HFONT) SelectObject (hdc, hFont) ; - GetTextMetrics (hdc, &tm) ; - DeleteObject (SelectObject (hdc, hFont)) ; - lf.lfWidth = (int) (tm.tmAveCharWidth * - fabs (pt.x) / fabs (pt.y) + 0.5); - hFont = CreateFontIndirect (&lf) ; - } - - RestoreDC (hdc, -1); - return(hFont); -} diff --git a/code/windlg.h b/code/windlg.h deleted file mode 100644 index 3caf8291c..000000000 --- a/code/windlg.h +++ /dev/null @@ -1,57 +0,0 @@ -/******************************************************************************* - * O P E N T S - ******************************************************************************* - * SPDX-License-Identifier: GPL-3.0-or-later - * Copyright 2026 OpenTS contributors - * - * See LICENSE.md for applicable additional terms and warranty disclaimers. - ******************************************************************************/ - -#pragma once - -#include "win.h" - - -BOOL Get_Display_Rect(HWND window, LPRECT rect); - -HWND WS_Create_Dialog(HINSTANCE instance, int id, HWND parent, DLGPROC proc, BOOL force_show); -bool WS_Destroy_Dialog(HWND window, int id); - -HWND WS_Find_Dialog(int id); -BOOL WS_Has_Dialog(HWND window); - -int WS_Wait_Dialog(HWND window, bool (*callback)(void), bool=false, bool place_on_top=true); - -HWND WS_Top_Window(void); -int WS_Top_Window_ID(void); -extern HWND g_TopWindow; - -int WS_Get_Saved_Value(int control_id, unsigned char * dest, int dest_size); -void WS_Clear_Saved_Values(void); - -HWND WS_Next_Upper_Dialog(HWND window); -HWND WS_Next_Lower_Dialog(HWND window); - -void Resize_Dialogs(HWND window); - -HFONT WS_Get_Font(HDC hdc, const char * face_name, int decipt_width, int decipt_height, int attributes); - -struct WSDialogStruct { - /* - * This is the window handle of the dialog occupying this slot. It stays zero until the - * dialog has actually been created, so a template that failed to load claims no slot. - */ - HWND handle; - - /* - * This is the resource identifier of the template the dialog was built from. It is what - * lets a dialog be found again by name rather than by handle. - */ - int id; -}; - -extern WSDialogStruct g_Dialogs[64]; -extern int g_DialogCount; -extern HWND g_TopWindow; -extern int g_TopWindowID; -extern int g_LastResponse; diff --git a/code/winfix.cpp b/code/winfix.cpp index c23c43d49..21edec860 100644 --- a/code/winfix.cpp +++ b/code/winfix.cpp @@ -33,11 +33,12 @@ #include "always.h" +#include "_xmouse.h" + #include "winfix.h" #include "ini.h" #include "misc.h" -#include "ownrdraw.h" #include "trim.h" #include diff --git a/code/winstub.cpp b/code/winstub.cpp index 90f9a44c1..17cbc0c8d 100644 --- a/code/winstub.cpp +++ b/code/winstub.cpp @@ -72,7 +72,6 @@ #include "video.h" #include "win.h" #include "wincursor.h" -#include "windlg.h" #include "winfix.h" #include "wwmouse.h" #include "mainopt.h" @@ -152,14 +151,9 @@ void Focus_Restore(void) if (MouseCursor && _MouseCaptured == true && !Debug_Map) { MouseCursor->Capture_Mouse(); } - Heal_Dialog_Controls(); Map.Flag_To_Redraw(GS_REDRAW_ALL); InvalidateRect(MainWindow, 0, 0); Pause_Ingame_Movie(false); - if (WS_Top_Window()) { - SetActiveWindow(WS_Top_Window()); - SetFocus(WS_Top_Window()); - } } diff --git a/code/worlddom.cpp b/code/worlddom.cpp index b560a11b3..966f50915 100644 --- a/code/worlddom.cpp +++ b/code/worlddom.cpp @@ -18,7 +18,6 @@ #include "language/language.h" #include "mapgen.h" #include "mixfile.h" -#include "ownrdraw.h" #include "wdtnet.h" @@ -39,51 +38,6 @@ extern WDTPointer g_WDTResumedCampaign; -/// -/// Handles the dialog messages for the tour side choice menu. -/// This routine gives the owner draw default handler first refusal and, for a button it -/// does not consume, records the player's choice in the dialog result. -/// -/// -/// Returns with the result of the owner draw handler, or FALSE if it left the message alone. -/// -INT_PTR CALLBACK WDT_Faction_Choice_Menu_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - int* retval; - - INT_PTR rc = OwnerDraw::Default_Dialog_Proc(window, message, wparam, lparam); - - if (rc == 0) { - - switch (message) { - case WM_COMMAND: { - retval = (int *)GetWindowLongPtr(window, DWLP_USER); - switch (LOWORD(wparam)) { - case IDC_PICKCLAN_JOIN: - *retval = 1; - break; - - case IDC_PICKCLAN_GDI: - *retval = 2; - break; - - case IDC_PICKCLAN_NOD: - *retval = 3; - break; - - case IDC_CANCEL: - *retval = 4; - break; - } - break; - } - } - return(FALSE); - } - return(rc); -} - - /// /// Asks the player which side to fight for in the tour. /// This routine runs the graphic menu that offers the two sides and tidies it away again. From 65fc399bd00ce63fc032269c4002b48665db4e72 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 13:22:37 +0100 Subject: [PATCH 158/179] refactor(ui): drop the dialog templates from the resource script Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/language/language.rc | 1493 ------------------------------------- 1 file changed, 1493 deletions(-) diff --git a/code/language/language.rc b/code/language/language.rc index be570491f..744c9d538 100644 --- a/code/language/language.rc +++ b/code/language/language.rc @@ -115,1499 +115,6 @@ END #endif // APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Dialog -// - -IDD_MISSION_ABORT DIALOG DISCARDABLE 0, 0, 256, 63 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,174,37,60,14 - CTEXT "Do you want to abort the mission?",-1,22,12,212,19, - SS_CENTERIMAGE | NOT WS_GROUP - CONTROL "Abort",IDC_ABORT_MISSION,"Button",BS_OWNERDRAW,22,37,60, - 14 - CONTROL "Restart",IDC_RESTART_MISSION,"Button",BS_OWNERDRAW,98, - 37,60,14 -END - -IDD_OPT_CONFIRM_MODE DIALOG DISCARDABLE 0, 0, 239, 70 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "OK",IDOK,"Button",BS_OWNERDRAW,111,44,50,14 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,167,44,50,14 - LTEXT "Click OK to keep this display mode or wait and your old display settings will be restored.", - IDC_CONFIRM_MODE_TEXT,22,12,195,26,NOT WS_GROUP -END - -IDD_OPT_KEYBOARD DIALOGEX 0, 0, 336, 208 -STYLE WS_CHILD -FONT 8, "MS Sans Serif", 0, 0, 0x1 -BEGIN - LTEXT "Category:",-1,22,27,146,9,SS_CENTERIMAGE | NOT WS_GROUP - COMBOBOX IDC_KEY_CATEGORY,22,42,138,146,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_SORT | CBS_HASSTRINGS - LTEXT "Commands:",-1,168,27,146,8,SS_CENTERIMAGE | NOT - WS_GROUP - LISTBOX IDC_KEY_COMMANDS,168,41,146,104,LBS_SORT | - LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | - LBS_NOINTEGRALHEIGHT | NOT WS_BORDER - LTEXT "Press new shortcut key:",-1,22,119,128,9,SS_CENTERIMAGE | - NOT WS_GROUP - GROUPBOX "Description:",-1,22,57,138,57 - LTEXT "",IDC_KEY_DESCRIPTION,29,68,127,42,NOT WS_GROUP, - WS_EX_TRANSPARENT - CONTROL "HotKey1",IDC_KEY_HOTKEY,"msctls_hotkey32",WS_BORDER,22, - 131,85,14 - CONTROL "Assign",IDC_KEY_ASSIGN,"Button",BS_OWNERDRAW,110,132,50, - 14 - CONTROL "OK",IDOK,"Button",BS_OWNERDRAW,187,181,50,14 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,264,181,50,14 - CTEXT "Customize Keyboard",-1,22,12,292,11,SS_CENTERIMAGE | - NOT WS_GROUP - LTEXT "Currently assigned to:",-1,22,151,146,11,SS_CENTERIMAGE | - NOT WS_GROUP - LTEXT "Current shortcut:",-1,168,151,146,11,SS_CENTERIMAGE | - NOT WS_GROUP - LTEXT "",IDC_KEY_ASSIGNED_TO,22,166,146,10,SS_CENTERIMAGE | - NOT WS_GROUP - LTEXT "",IDC_KEY_CURRENT_SHORTCUT,168,166,146,10, - SS_CENTERIMAGE | NOT WS_GROUP - CONTROL "Reset All",IDC_KEY_RESET_ALL,"Button",BS_OWNERDRAW,22, - 181,54,14 -END - -IDD_OPT_DISPLAY DIALOG DISCARDABLE 0, 0, 229, 196 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "OK",IDOK,"Button",BS_OWNERDRAW,22,170,62,14 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,145,170,62,14 - LISTBOX IDC_DISPLAY_RESLIST,22,37,185,110,LBS_OWNERDRAWFIXED | - LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | NOT WS_BORDER - CTEXT "Resolution Modes",IDC_DISPLAY_RESLABEL,22,25,185,10, - SS_CENTERIMAGE | NOT WS_GROUP - CTEXT "Display Options:",-1,22,12,185,9,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Stretch movies to fit resolution",IDC_STRETCH_MOVIES, - "Button",BS_AUTOCHECKBOX | BS_FLAT,22,153,185,10 -END - -IDD_DROPSHIP_LIMITS DIALOGEX 0, 0, 230, 140 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Dropship Loadout Limits" -FONT 8, "MS Sans Serif", 0, 0, 0x1 -BEGIN - DEFPUSHBUTTON "OK",IDC_DROPSHIP_OK,101,114,50,14 - PUSHBUTTON "Cancel",IDC_CANCEL,158,113,50,14 - CONTROL "List1",IDC_DROPSHIP_LIST,"SysListView32",LVS_REPORT | - LVS_SORTASCENDING | LVS_EDITLABELS | WS_BORDER | - WS_TABSTOP,7,7,202,98,0,HIDC_DROPSHIP_LIST -END - -IDD_DROPSHIP_LIMIT DIALOG DISCARDABLE 0, 0, 203, 71 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Dropship Unit Limit" -FONT 8, "MS Sans Serif" -BEGIN - DEFPUSHBUTTON "OK",IDC_DROPSHIP_OK,78,48,50,14 - PUSHBUTTON "Cancel",IDC_CANCEL,139,48,50,14 - LTEXT "Unit Name",IDC_DROPSHIP_UNITNAME_LABEL,19,7,55,10 - EDITTEXT IDC_DROPSHIP_LIMIT_EDIT,19,24,49,14,ES_AUTOHSCROLL - LTEXT "(Use -1 to signify an unlimited supply)",-1,73,25,123, - 12 -END - -IDD_OPT_CTRL_GAME_MP DIALOG DISCARDABLE 0, 0, 292, 175 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Options Menu",1,"Button",BS_OWNERDRAW,196,149,77,14 - CONTROL "Keyboard",IDC_OPT_KEYBOARD_BTN,"Button",BS_OWNERDRAW, - 109,149,77,14 - CONTROL "Sound",IDC_OPT_SOUND_BTN,"Button",BS_OWNERDRAW,22,149, - 77,14 - CONTROL "Slider1",IDC_GAME_SPEED_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,90,12,128,15 - RTEXT "Game Speed:",-1,22,12,63,15,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Slider2",IDC_SCROLL_SPEED_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,90,43,128,15 - RTEXT "Scroll Rate:",-1,22,43,63,15,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Slider3",IDC_DETAIL_LEVEL_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,90,74,128,15 - RTEXT "Visual Details:",-1,22,74,63,15,SS_CENTERIMAGE | NOT - WS_GROUP - LTEXT "Higher",IDC_DETAIL_LEVEL_LABEL,224,74,50,15, - SS_CENTERIMAGE | NOT WS_GROUP - LTEXT "Faster",IDC_SCROLL_SPEED_LABEL,224,43,50,15, - SS_CENTERIMAGE | NOT WS_GROUP - LTEXT "Faster",IDC_GAME_SPEED_LABEL,224,12,50,15, - SS_CENTERIMAGE | NOT WS_GROUP - CONTROL "Sidebar Text",IDC_SIDEBAR_TEXT,"Button",BS_AUTOCHECKBOX | - BS_FLAT,22,94,119,10 - CONTROL "Target Lines",IDC_TARGET_LINES,"Button",BS_AUTOCHECKBOX | - BS_FLAT,22,112,119,10 - CONTROL "Tooltips",IDC_TOOLTIPS,"Button",BS_AUTOCHECKBOX | - BS_FLAT,147,94,127,10 - CONTROL "Scroll Coasting",IDC_SCROLL_COASTING,"Button", - BS_AUTOCHECKBOX | BS_FLAT,147,112,127,10 - CONTROL "Edge Scrolling",IDC_EDGE_SCROLL,"Button", - BS_AUTOCHECKBOX | BS_FLAT,22,130,119,10 -END - -IDD_OPT_CTRL_GAME_SP DIALOG DISCARDABLE 0, 0, 292, 179 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Main Menu",1,"Button",BS_OWNERDRAW,81,153,130,14 - CONTROL "Slider1",IDC_GAME_SPEED_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,80,12,148,13 - LTEXT "Game Speed",-1,22,12,58,13,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Slider2",IDC_SCROLL_SPEED_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,80,34,148,13 - LTEXT "Scroll Rate",-1,22,34,58,13,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Slider3",IDC_DETAIL_LEVEL_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,80,56,148,13 - LTEXT "Visual Details",-1,22,56,58,13,SS_CENTERIMAGE | NOT - WS_GROUP - RTEXT "Higher",IDC_DETAIL_LEVEL_LABEL,229,56,45,13, - SS_CENTERIMAGE | NOT WS_GROUP - RTEXT "Faster",IDC_SCROLL_SPEED_LABEL,229,34,45,13, - SS_CENTERIMAGE | NOT WS_GROUP - RTEXT "Faster",IDC_GAME_SPEED_LABEL,229,12,45,13, - SS_CENTERIMAGE | NOT WS_GROUP - CONTROL "Slider3",IDC_DIFFICULTY_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,80,78,148,13 - LTEXT "Difficulty",-1,22,78,58,13,SS_CENTERIMAGE | NOT - WS_GROUP - RTEXT "Harder",IDC_DIFFICULTY_LABEL,229,78,45,13, - SS_CENTERIMAGE | NOT WS_GROUP - CONTROL "Cameo Text",IDC_SIDEBAR_TEXT,"Button",BS_AUTOCHECKBOX | - BS_FLAT,22,103,124,10 - CONTROL "Target Lines",IDC_TARGET_LINES,"Button",BS_AUTOCHECKBOX | - BS_FLAT,22,119,124,10 - CONTROL "Tooltips",IDC_TOOLTIPS,"Button",BS_AUTOCHECKBOX | - BS_FLAT,146,103,128,10 - CONTROL "Scroll Coasting",IDC_SCROLL_COASTING,"Button", - BS_AUTOCHECKBOX | BS_FLAT,146,119,128,10 - CONTROL "Edge Scrolling",IDC_EDGE_SCROLL,"Button", - BS_AUTOCHECKBOX | BS_FLAT,22,135,124,10 -END - -IDD_GAME_SETTINGS_FLAGS DIALOG DISCARDABLE 0, 0, 220, 77 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Bases",IDC_BASES,"Button",BS_AUTOCHECKBOX,5,18,102,10 - CONTROL "Harvester Truce",IDC_HARVTRUCE,"Button",BS_AUTOCHECKBOX, - 5,61,203,10 - CONTROL "Allies allowed",IDC_ALLIES,"Button",BS_AUTOCHECKBOX,5,4, - 103,10 - LTEXT "Time Limit",-1,0,67,50,10,NOT WS_VISIBLE | WS_DISABLED - LTEXT "Kill Limit",-1,112,67,50,10,NOT WS_VISIBLE | - WS_DISABLED - CONTROL "Slider1",IDC_TIMELIMIT,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS | NOT WS_VISIBLE | WS_DISABLED,43,64,64,13 - CONTROL "Slider2",IDC_KILLLIMIT,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS | NOT WS_VISIBLE | WS_DISABLED,149,64,64,13 - CONTROL "Bridges Destroyable",IDC_BRIDGE_DESTROY,"Button", - BS_AUTOCHECKBOX,5,47,204,10 - CONTROL "Re-Deployable MCV",IDC_REDEPLOY_MCV,"Button", - BS_AUTOCHECKBOX | WS_TABSTOP,5,32,206,11 - CONTROL "Fog Of War",IDC_FOG_OF_WAR,"Button",BS_AUTOCHECKBOX,118, - 4,93,10 - CONTROL "Crates",IDC_CRATES,"Button",BS_AUTOCHECKBOX,118,18,95, - 10 -END - -IDD_GAME_SETTINGS_SLIDERS DIALOG DISCARDABLE 0, 0, 220, 77 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Slider1",IDC_UNITCOUNT,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS,105,33,70,13 - LTEXT "Unit Count",-1,45,35,60,10 - CONTROL "Slider2",IDC_TECHLEVEL,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS,105,48,70,13 - CONTROL "Slider3",IDC_CREDITS,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS,105,3,70,13 - LTEXT "Tech Level",-1,45,50,60,10 - LTEXT "Credits",-1,45,5,60,10 - LTEXT "AI Players",-1,45,65,60,10 - CONTROL "Slider3",IDC_AIPLAYERS,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS,105,63,70,13 - LTEXT "Difficulty",-1,45,20,60,10 - CONTROL "Slider3",IDC_GAMESET_DIFFICULTY,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,105,18,70,13 -END - -IDD_OPT_MAIN DIALOG DISCARDABLE 0, 0, 200, 148 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Main Menu",IDC_OPTMAIN_MAINMENU,"Button",BS_OWNERDRAW | - BS_CENTER,37,120,126,18 - CONTROL "Display",IDC_OPTMAIN_DISPLAY,"Button",BS_OWNERDRAW | - BS_CENTER,37,32,126,18 - CONTROL "Keyboard",IDC_OPTMAIN_KEYBOARD,"Button",BS_OWNERDRAW | - BS_CENTER,37,76,126,18 - CONTROL "Sound",IDC_OPTMAIN_SOUND,"Button",BS_OWNERDRAW | - BS_CENTER,37,54,126,18 - CONTROL "Game Settings",IDC_OPTMAIN_GAME_SETTINGS,"Button", - BS_OWNERDRAW | BS_CENTER,37,10,126,18 -END - -IDD_MAIN_MENU DIALOG DISCARDABLE 0, 0, 204, 147 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Exit Game",IDC_EXIT_GAME,"Button",BS_OWNERDRAW,37,117, - 130,18 - CONTROL "New Campaign",IDC_NEWCAMPAIGN,"Button",BS_OWNERDRAW,37, - 12,130,18 - CONTROL "Load Mission",IDC_LOAD_MISSION,"Button",BS_OWNERDRAW,37, - 33,130,18 - CONTROL "Multiplayer Game",IDC_MULTIPLAYER_GAME,"Button", - BS_OWNERDRAW,37,54,130,18 - CONTROL "Intro / Sneak Peek",IDC_INTRO,"Button",BS_OWNERDRAW,37, - 75,130,18 - CONTROL "Options",IDC_OPTIONS,"Button",BS_OWNERDRAW,37,96,130,18 -END - -IDD_MAPGEN DIALOG DISCARDABLE 0, 0, 424, 242 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "OK",IDOK,"Button",BS_OWNERDRAW,265,220,65,14 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,337,220,65,14 - COMBOBOX IDC_MAPGEN_ENVIRONMENT,95,8,100,103,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_SORT | CBS_HASSTRINGS - CONTROL "Slider1",IDC_MAPGEN_HILLS,"msctls_trackbar32",TBS_TOP, - 95,104,100,14 - LTEXT "Hills:",-1,22,104,74,14,SS_CENTERIMAGE | NOT WS_GROUP - CONTROL "Slider1",IDC_MAPGEN_WATER,"msctls_trackbar32",TBS_TOP, - 95,161,100,14 - LTEXT "Players:",-1,22,47,74,14,SS_CENTERIMAGE | NOT WS_GROUP - LTEXT "Map Width:",-1,206,8,65,12,SS_CENTERIMAGE | NOT - WS_GROUP - LTEXT "Map Height:",-1,205,25,65,12,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Slider1",IDC_MAPGEN_VEGETATION,"msctls_trackbar32", - TBS_TOP,95,180,100,14 - CONTROL "Slider1",IDC_MAPGEN_CITIES,"msctls_trackbar32",TBS_TOP, - 95,199,100,14 - CONTROL "Slider1",IDC_MAPGEN_TIBERIUM_FIELDS,"msctls_trackbar32", - TBS_TOP,95,142,100,14 - LTEXT "Tiberium Fields:",-1,22,142,74,14,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Slider1",IDC_MAPGEN_ACCESSIBILITY,"msctls_trackbar32", - TBS_TOP,95,85,100,14 - LTEXT "Environment:",-1,22,8,66,14,SS_CENTERIMAGE | NOT - WS_GROUP - EDITTEXT IDC_MAPGEN_DIMENSION_EDIT,345,25,57,12,ES_NUMBER | NOT - WS_VISIBLE | WS_DISABLED | NOT WS_BORDER | NOT - WS_TABSTOP - CONTROL "Load Map",IDC_MAPGEN_LOAD_MAP,"Button",BS_OWNERDRAW,100, - 220,65,14 - CONTROL "Save Map",IDC_MAPGEN_SAVE_MAP,"Button",BS_OWNERDRAW,22, - 220,65,14 - COMBOBOX IDC_MAPGEN_TIME_OF_DAY,95,25,100,101,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_SORT | CBS_HASSTRINGS - LTEXT "Water:",-1,22,161,74,14,SS_CENTERIMAGE | NOT WS_GROUP - LTEXT "Time of Day:",-1,22,27,66,14,SS_CENTERIMAGE | NOT - WS_GROUP - LTEXT "Vegetation:",-1,22,180,74,14,SS_CENTERIMAGE | NOT - WS_GROUP - LTEXT "Cities:",-1,22,199,74,14,SS_CENTERIMAGE | NOT WS_GROUP - LTEXT "Accessability:",-1,22,85,74,14,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Preview Map",IDC_MAPGEN_PREVIEW,"Button",BS_OWNERDRAW, - 314,193,88,14 - GROUPBOX "",IDC_PREVIEW_FRAME,205,47,197,134 - CTEXT "Preview",-1,208,101,194,16,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Surprise Me",IDC_MAPGEN_SURPRISE,"Button",BS_OWNERDRAW, - 205,193,88,14 - CONTROL "Delete Map",IDC_MAPGEN_DELETE_MAP,"Button",BS_OWNERDRAW, - 178,220,65,14 - CONTROL "Slider1",IDC_MAPGEN_CLIFFS,"msctls_trackbar32",TBS_TOP, - 95,66,100,14 - LTEXT "Cliffs:",-1,22,66,74,14,SS_CENTERIMAGE | NOT WS_GROUP - CONTROL "Slider1",IDC_MAPGEN_PLAYERS,"msctls_trackbar32",TBS_TOP, - 95,47,100,14 - CONTROL "Slider1",IDC_MAPGEN_TIBERIUM_AMOUNT,"msctls_trackbar32", - TBS_TOP,95,123,100,14 - LTEXT "Tiberium Amount:",-1,22,123,74,14,SS_CENTERIMAGE | NOT - WS_GROUP - COMBOBOX IDC_MAPGEN_MAP_WIDTH,277,8,63,103,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS - COMBOBOX IDC_MAPGEN_MAP_HEIGHT,277,25,63,103,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS -END - -IDD_MSGBOX_1 DIALOG DISCARDABLE 0, 0, 218, 64 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW | NOT WS_VISIBLE, - 83,38,50,14 - CTEXT "",IDC_MSGBOX_TEXT,22,12,174,23,SS_CENTERIMAGE | NOT - WS_GROUP -END - -IDD_MSGBOX_3 DIALOG DISCARDABLE 0, 0, 260, 84 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,178,58,60,14 - CTEXT "Do you want to abort the mission?",IDC_MSGBOX_TEXT,22, - 12,216,38,SS_CENTERIMAGE | NOT WS_GROUP - CONTROL "OK",IDC_MSGBOX_OK,"Button",BS_OWNERDRAW,22,58,60,14 - CONTROL "Button 3",IDC_MSGBOX_BTN3,"Button",BS_OWNERDRAW,100,58, - 60,14 -END - -IDD_MISSION_DELETE DIALOG DISCARDABLE 0, 0, 302, 198 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Delete",1,"Button",BS_OWNERDRAW | WS_TABSTOP,173,172,50, - 14 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW | WS_TABSTOP,230, - 172,50,14 - LISTBOX IDC_MISSION_DELETE_LIST,22,42,258,122,LBS_OWNERDRAWFIXED | - LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | NOT WS_BORDER | - WS_TABSTOP - CTEXT "DELETE",-1,22,12,258,8,SS_CENTERIMAGE | NOT WS_GROUP - LTEXT "Description",-1,22,26,121,8,SS_CENTERIMAGE | NOT - WS_GROUP - CTEXT "Mission",-1,149,26,33,8,SS_CENTERIMAGE | NOT WS_VISIBLE | - WS_DISABLED | NOT WS_GROUP - CTEXT "Time Stamp",-1,196,26,71,8,SS_CENTERIMAGE | NOT - WS_GROUP -END - -IDD_MISSION_LOAD DIALOG DISCARDABLE 0, 0, 302, 198 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Load",1,"Button",BS_OWNERDRAW | WS_TABSTOP,171,171,50, - 14 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW | WS_TABSTOP,230, - 171,50,14 - LISTBOX IDC_MISSION_LOAD_LIST,22,40,258,124,LBS_OWNERDRAWFIXED | - LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | NOT WS_BORDER - CTEXT "LOAD",-1,22,12,258,8,SS_CENTERIMAGE | NOT WS_GROUP - LTEXT "Description",-1,22,26,121,8,SS_CENTERIMAGE | NOT - WS_GROUP - CTEXT "Mission",-1,149,26,39,8,SS_CENTERIMAGE | NOT WS_VISIBLE | - WS_DISABLED | NOT WS_GROUP - CTEXT "Time Stamp",-1,196,26,72,8,SS_CENTERIMAGE | NOT - WS_GROUP -END - -IDD_MISSION_SAVE DIALOG DISCARDABLE 0, 0, 302, 198 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Save",1,"Button",BS_OWNERDRAW | WS_TABSTOP,174,172,50, - 14 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW | WS_TABSTOP,230, - 172,50,14 - LISTBOX IDC_MISSION_SAVE_LIST,22,42,258,105,LBS_OWNERDRAWFIXED | - LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | NOT WS_BORDER | - WS_TABSTOP - CTEXT "SAVE",-1,22,12,258,8,SS_CENTERIMAGE | NOT WS_GROUP - EDITTEXT IDC_MISSION_SAVE_DESC,22,154,258,14,NOT WS_BORDER | NOT - WS_TABSTOP - LTEXT "Description",-1,22,26,121,8,SS_CENTERIMAGE | NOT - WS_GROUP - CTEXT "Mission",-1,149,26,33,8,SS_CENTERIMAGE | NOT WS_VISIBLE | - WS_DISABLED | NOT WS_GROUP - CTEXT "Time Stamp",-1,199,26,66,8,SS_CENTERIMAGE | NOT - WS_GROUP -END - -IDD_MODEM_GUEST DIALOG DISCARDABLE 0, 0, 426, 238 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - EDITTEXT IDC_INPUT,15,218,268,12,ES_MULTILINE | ES_AUTOHSCROLL | - ES_WANTRETURN | NOT WS_BORDER | NOT WS_TABSTOP - LTEXT "Name:",-1,15,7,49,12,SS_CENTERIMAGE | NOT WS_GROUP - COMBOBOX IDC_MODEM_YOURSIDE,68,24,83,90,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS - COMBOBOX IDC_MODEM_YOURCOLOR,68,41,83,146,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS - LTEXT "Side:",-1,15,24,49,12,SS_CENTERIMAGE | NOT WS_GROUP - LTEXT "Color:",-1,15,41,49,12,SS_CENTERIMAGE | NOT WS_GROUP - CONTROL "Accept",1,"Button",BS_OWNERDRAW | WS_DISABLED,289,218, - 58,14 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,353,218,58,14 - LISTBOX IDC_PMESSAGES,15,130,268,84,NOT LBS_NOTIFY | - LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | - LBS_NOINTEGRALHEIGHT | LBS_NOSEL | NOT WS_BORDER - LTEXT "None",IDC_SCENARIONAME,35,112,241,12,SS_CENTERIMAGE | - NOT WS_GROUP - LTEXT "Map:",-1,15,112,23,12,SS_CENTERIMAGE | NOT WS_GROUP - EDITTEXT IDC_NAME,68,7,83,12,ES_MULTILINE | ES_AUTOHSCROLL | - ES_WANTRETURN | NOT WS_BORDER | NOT WS_TABSTOP - LTEXT "Opponent:",-1,15,61,122,12,SS_CENTERIMAGE | NOT - WS_GROUP - LTEXT "????",IDC_MODEM_OPPONENT,15,78,125,12,SS_CENTERIMAGE | - NOT WS_GROUP - LTEXT "Unit Count:",-1,157,39,49,12,SS_CENTERIMAGE | - WS_DISABLED | NOT WS_GROUP - CONTROL "Slider1",IDC_MODEM_UNITCOUNT,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS | WS_DISABLED,210,39,83,12 - LTEXT "Tech Level:",-1,157,7,49,12,SS_CENTERIMAGE | - WS_DISABLED | NOT WS_GROUP - CONTROL "Slider2",IDC_TECHLEVEL,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS | WS_DISABLED,210,7,83,12 - LTEXT "Credits:",-1,157,23,49,12,SS_CENTERIMAGE | WS_DISABLED | - NOT WS_GROUP - CONTROL "Slider4",IDC_CREDITS,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS | WS_DISABLED,210,23,83,12 - CONTROL "Slider4",IDC_AIPLAYERS,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS | WS_DISABLED,210,55,83,12 - LTEXT "AI Players:",-1,157,55,49,12,SS_CENTERIMAGE | - WS_DISABLED | NOT WS_GROUP - GROUPBOX "",IDC_PREVIEW_FRAME,289,127,122,80 - CONTROL "Slider4",IDC_AILEVEL_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS | WS_DISABLED,210,71,83,12 - LTEXT "AI Level:",-1,157,71,49,12,SS_CENTERIMAGE | WS_DISABLED | - NOT WS_GROUP - CONTROL "Slider4",IDC_GAME_SPEED_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS | WS_DISABLED,210,87,83,12 - LTEXT "Game Speed",-1,157,87,49,12,SS_CENTERIMAGE | - WS_DISABLED | NOT WS_GROUP - CONTROL "Bases",IDC_MODEM_BASES,"Button",BS_AUTOCHECKBOX | - BS_FLAT | WS_DISABLED,301,19,110,12 - CONTROL "Crates",IDC_GOODIES,"Button",BS_AUTOCHECKBOX | BS_FLAT | - WS_DISABLED,301,31,110,12 - CONTROL "Fog Of War",IDC_MODEM_FOG,"Button",BS_AUTOCHECKBOX | - BS_FLAT | WS_DISABLED,301,55,110,12 - CONTROL "Bridges Destroyable",IDC_BRIDGE_DESTROY,"Button", - BS_AUTOCHECKBOX | BS_FLAT | WS_DISABLED,301,43,110,12 - CONTROL "Harvester Truce",IDC_HARVTRUCE,"Button",BS_AUTOCHECKBOX | - BS_FLAT | WS_DISABLED,301,67,110,12 - CONTROL "Re-Deployable MCV",IDC_MODEM_REDEPLOY_MCV,"Button", - BS_AUTOCHECKBOX | BS_FLAT | WS_DISABLED,301,7,110,12 - CONTROL "Short Game",IDC_SHORT_GAME,"Button",BS_AUTOCHECKBOX | - BS_FLAT | WS_DISABLED,301,79,110,12 - CONTROL "Multi Engineer",IDC_MULTI_ENGINEER,"Button", - BS_AUTOCHECKBOX | BS_FLAT | WS_DISABLED,301,91,110,12 -END - -IDD_MODEM_HOST DIALOG DISCARDABLE 0, 0, 426, 243 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - EDITTEXT IDC_INPUT,15,223,268,12,ES_MULTILINE | ES_AUTOHSCROLL | - ES_WANTRETURN | NOT WS_BORDER | NOT WS_TABSTOP - LTEXT "Name:",-1,15,7,49,12,SS_CENTERIMAGE | NOT WS_GROUP - COMBOBOX IDC_MODEM_YOURSIDE,68,24,83,90,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS - COMBOBOX IDC_MODEM_YOURCOLOR,68,41,83,146,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS - LTEXT "Side:",-1,15,24,49,12,SS_CENTERIMAGE | NOT WS_GROUP - LTEXT "Color:",-1,15,41,49,12,SS_CENTERIMAGE | NOT WS_GROUP - CONTROL "Go!",1,"Button",BS_OWNERDRAW | WS_DISABLED,289,223,58, - 14 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,353,223,58,14 - LISTBOX IDC_PMESSAGES,15,128,268,91,NOT LBS_NOTIFY | - LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | - LBS_NOINTEGRALHEIGHT | LBS_NOSEL | NOT WS_BORDER - LTEXT "None",IDC_SCENARIONAME,36,110,242,12,SS_CENTERIMAGE | - NOT WS_GROUP - CONTROL "Multiplayer Map",IDC_MULTIMAP,"Button",BS_OWNERDRAW,299, - 204,100,14 - LTEXT "Map:",-1,15,110,32,12,SS_CENTERIMAGE | NOT WS_GROUP - EDITTEXT IDC_NAME,68,7,83,12,ES_MULTILINE | ES_WANTRETURN | NOT - WS_BORDER | NOT WS_TABSTOP - LTEXT "Opponent:",-1,15,63,122,12,SS_CENTERIMAGE | NOT - WS_GROUP - LTEXT "????",IDC_MODEM_OPPONENT,15,80,129,12,SS_CENTERIMAGE | - NOT WS_GROUP - CONTROL "Bases",IDC_MODEM_BASES,"Button",BS_AUTOCHECKBOX | - BS_FLAT,301,19,110,12 - CONTROL "Crates",IDC_GOODIES,"Button",BS_AUTOCHECKBOX | BS_FLAT, - 301,31,110,12 - CONTROL "Fog Of War",IDC_MODEM_FOG,"Button",BS_AUTOCHECKBOX | - BS_FLAT,301,55,110,12 - CONTROL "Bridges Destroyable",IDC_BRIDGE_DESTROY,"Button", - BS_AUTOCHECKBOX | BS_FLAT,301,43,110,12 - GROUPBOX "",IDC_PREVIEW_FRAME,289,123,122,75 - LTEXT "Unit Count:",-1,157,39,49,12,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Slider1",IDC_MODEM_UNITCOUNT,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,210,39,83,12 - LTEXT "Tech Level:",-1,157,7,49,12,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Slider2",IDC_TECHLEVEL,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS,210,7,83,12 - LTEXT "Credits:",-1,157,23,49,12,SS_CENTERIMAGE | NOT WS_GROUP - CONTROL "Slider4",IDC_CREDITS,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS,210,23,83,12 - CONTROL "Slider4",IDC_AIPLAYERS,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS,210,55,83,12 - LTEXT "AI Players:",-1,157,55,49,12,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Slider4",IDC_AILEVEL_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,210,71,83,12 - LTEXT "AI Level:",-1,157,71,49,12,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Harvester Truce",IDC_HARVTRUCE,"Button",BS_AUTOCHECKBOX | - BS_FLAT,301,67,110,12 - CONTROL "Re-Deployable MCV",IDC_MODEM_REDEPLOY_MCV,"Button", - BS_AUTOCHECKBOX | BS_FLAT,301,7,110,12 - CONTROL "Short Game",IDC_SHORT_GAME,"Button",BS_AUTOCHECKBOX | - BS_FLAT,301,79,110,12 - LTEXT "Game Speed",-1,157,88,49,12,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Slider4",IDC_GAME_SPEED_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,210,87,83,12 - CONTROL "Multi Engineer",IDC_MULTI_ENGINEER,"Button", - BS_AUTOCHECKBOX | BS_FLAT,301,91,94,12 -END - -IDD_MPLAYER_SELECT_GAME DIALOG DISCARDABLE 0, 0, 204, 144 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Main Menu",2,"Button",BS_OWNERDRAW | WS_TABSTOP,37,112, - 130,18 - CTEXT "Select Multiplayer Game",-1,37,12,130,12,SS_CENTERIMAGE - CONTROL "Internet",IDC_INTERNET,"Button",BS_OWNERDRAW | - WS_TABSTOP,37,28,130,18 - CONTROL "Modem / Serial",IDC_MODEMSERIAL,"Button",BS_OWNERDRAW | - WS_TABSTOP,37,49,130,18 - CONTROL "Network",IDC_NETWORK,"Button",BS_OWNERDRAW | WS_TABSTOP, - 37,70,130,18 - CONTROL "Skirmish",IDC_SKIRMISH,"Button",BS_OWNERDRAW | - WS_TABSTOP,37,91,130,18 -END - -IDD_MPLAYER_HOST DIALOGEX 0, 0, 426, 240 -STYLE WS_CHILD -FONT 8, "MS Sans Serif", 0, 0, 0x1 -BEGIN - EDITTEXT IDC_INPUT,18,199,256,12,ES_MULTILINE | ES_WANTRETURN | - NOT WS_BORDER - LTEXT "Players:",-1,18,41,57,12,SS_CENTERIMAGE | NOT WS_GROUP - CONTROL "Kick a user from your game",IDC_KICK,"Button", - BS_OWNERDRAW,18,215,20,18 - COMBOBOX IDC_YOURSIDE,73,6,76,90,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS - COMBOBOX IDC_YOURCOLOR,73,22,76,146,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS - LTEXT "Your Side:",-1,18,8,50,12,SS_CENTERIMAGE | NOT WS_GROUP - LTEXT "Your Color:",-1,18,23,52,12,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Go!",IDC_GO,"Button",BS_OWNERDRAW,354,219,54,14 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,291,219,53,14 - LISTBOX IDC_USERS,18,56,150,67,LBS_MULTIPLESEL | - LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | - LBS_NOINTEGRALHEIGHT | NOT WS_BORDER - LISTBOX IDC_PMESSAGES,18,130,256,65,NOT LBS_NOTIFY | - LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | - LBS_NOINTEGRALHEIGHT | LBS_NOSEL | NOT WS_BORDER - GROUPBOX "",IDC_PREVIEW_FRAME,281,138,126,73,0,0, - HIDC_PREVIEW_FRAME - LTEXT "None",IDC_SCENARIONAME,255,8,153,10 - CONTROL "Multiplayer Map",IDC_MULTIMAP,"Button",BS_OWNERDRAW,160, - 5,90,14 - CONTROL "Bases",IDC_BASES,"Button",BS_AUTOCHECKBOX,300,49,108,10 - CONTROL "Harvester Truce",IDC_HARVTRUCE,"Button",BS_AUTOCHECKBOX, - 300,37,108,10 - CONTROL "Allies allowed",IDC_ALLIES,"Button",BS_AUTOCHECKBOX,300, - 25,105,10 - RTEXT "AI Players:",-1,166,44,58,10 - CONTROL "Slider3",IDC_AIPLAYERS,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS,227,43,66,12 - CONTROL "Slider1",IDC_UNITCOUNT,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS,227,75,66,12 - RTEXT "Unit Count:",-1,166,76,58,10 - CONTROL "Slider2",IDC_TECHLEVEL,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS,227,91,66,12 - CONTROL "Slider3",IDC_CREDITS,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS,227,107,66,12 - RTEXT "Tech Level:",-1,166,92,58,10 - RTEXT "Credits:",-1,166,108,58,10 - CONTROL "Fog Of War",IDC_FOG_OF_WAR,"Button",BS_AUTOCHECKBOX,300, - 73,108,10 - CONTROL "Bridges Destroyable",IDC_BRIDGE_DESTROY,"Button", - BS_AUTOCHECKBOX,300,85,108,10 - CONTROL "Crates",IDC_CRATES,"Button",BS_AUTOCHECKBOX,300,97,108, - 10 - RTEXT "AI Level:",-1,166,60,58,10 - CONTROL "Slider3",IDC_AILEVEL_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,227,59,66,12 - CONTROL "Re-Deployable MCV",IDC_REDEPLOY_MCV,"Button", - BS_AUTOCHECKBOX | WS_TABSTOP,300,61,108,10 - CONTROL "Short Game",IDC_SHORT_GAME,"Button",BS_AUTOCHECKBOX,300, - 109,108,10 - CONTROL "Slider1",IDC_GAME_SPEED_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,227,27,66,12 - RTEXT "Game Speed:",-1,166,28,58,10 - CONTROL "Multi Engineer",IDC_MULTI_ENGINEER,"Button", - BS_AUTOCHECKBOX,300,121,108,10 -END - -IDD_MPLAYER_GUEST DIALOGEX 0, 0, 426, 240 -STYLE WS_CHILD -FONT 8, "MS Sans Serif", 0, 0, 0x1 -BEGIN - LTEXT "Map:",-1,149,7,26,10,SS_CENTERIMAGE | NOT WS_GROUP - LTEXT "Players:",-1,18,42,50,12,SS_CENTERIMAGE | NOT WS_GROUP - COMBOBOX IDC_YOURSIDE,66,7,76,101,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS - COMBOBOX IDC_YOURCOLOR,66,24,76,145,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS - LTEXT "Your Side:",-1,18,7,50,12,SS_CENTERIMAGE | NOT WS_GROUP - LTEXT "Your Color:",-1,18,24,50,12,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Accept",IDC_ACCEPT,"Button",BS_OWNERDRAW,350,215,58,18 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,279,215,58,18 - LTEXT "None",IDC_SCENARIONAME,176,7,232,10,SS_CENTERIMAGE | - NOT WS_GROUP - LISTBOX IDC_USERS,18,56,150,68,LBS_MULTIPLESEL | - LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | - LBS_NOINTEGRALHEIGHT | NOT WS_BORDER - CONTROL "Bases",IDC_BASES,"Button",BS_AUTOCHECKBOX | WS_DISABLED, - 300,49,108,10 - CONTROL "Harvester Truce",IDC_HARVTRUCE,"Button",BS_AUTOCHECKBOX | - WS_DISABLED,300,37,108,10 - CONTROL "Allies allowed",IDC_ALLIES,"Button",BS_AUTOCHECKBOX | - WS_DISABLED,300,25,105,10 - RTEXT "AI Players",-1,162,44,57,10,WS_DISABLED - CONTROL "Slider3",IDC_AIPLAYERS,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS | WS_DISABLED,227,43,66,12 - CONTROL "Slider1",IDC_UNITCOUNT,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS | WS_DISABLED,227,75,66,12 - RTEXT "Unit Count",-1,162,76,57,10,WS_DISABLED - CONTROL "Slider2",IDC_TECHLEVEL,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS | WS_DISABLED,227,91,66,12 - CONTROL "Slider3",IDC_CREDITS,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS | WS_DISABLED,227,107,66,12 - RTEXT "Tech Level",-1,162,92,57,10,WS_DISABLED - RTEXT "Credits",-1,162,108,57,10,WS_DISABLED - CONTROL "Fog Of War",IDC_FOG_OF_WAR,"Button",BS_AUTOCHECKBOX | - WS_DISABLED,300,73,108,10 - CONTROL "Bridges Destroyable",IDC_BRIDGE_DESTROY,"Button", - BS_AUTOCHECKBOX | WS_DISABLED,300,85,108,10 - CONTROL "Crates",IDC_CRATES,"Button",BS_AUTOCHECKBOX | - WS_DISABLED,300,97,108,10 - RTEXT "AI Level",-1,162,60,57,10,WS_DISABLED - CONTROL "Slider3",IDC_AILEVEL_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS | WS_DISABLED,227,59,66,12 - CONTROL "Re-Deployable MCV",IDC_REDEPLOY_MCV,"Button", - BS_AUTOCHECKBOX | WS_DISABLED | WS_TABSTOP,300,61,108,10 - EDITTEXT IDC_INPUT,18,199,256,12,ES_MULTILINE | ES_WANTRETURN | - NOT WS_BORDER - LISTBOX IDC_PMESSAGES,18,130,256,65,NOT LBS_NOTIFY | - LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | - LBS_NOINTEGRALHEIGHT | LBS_NOSEL | NOT WS_BORDER - GROUPBOX "",IDC_PREVIEW_FRAME,281,138,126,73,0,0, - HIDC_PREVIEW_FRAME - CONTROL "Short Game",IDC_SHORT_GAME,"Button",BS_AUTOCHECKBOX | - WS_DISABLED,300,109,84,10 - CONTROL "Slider3",IDC_GAME_SPEED_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS | WS_DISABLED,227,27,66,12 - RTEXT "Game Speed",-1,162,28,57,10,WS_DISABLED - CONTROL "Multi Engineer",IDC_MULTI_ENGINEER,"Button", - BS_AUTOCHECKBOX | WS_DISABLED,300,121,108,10 -END - -IDD_MPLAYER_GAME_LIST DIALOG DISCARDABLE 0, 0, 426, 240 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Join",IDC_GAMELIST_JOIN,"Button",BS_OWNERDRAW,264,214, - 62,18 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,181,214,62,18 - CONTROL "New",IDC_GAMELIST_NEW,"Button",BS_OWNERDRAW,345,214,62, - 18 - LISTBOX IDC_GAMELIST,294,27,113,68,LBS_OWNERDRAWFIXED | - LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | NOT WS_BORDER - LISTBOX IDC_USERS,294,111,113,100,LBS_OWNERDRAWFIXED | - LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | NOT WS_BORDER - LISTBOX IDC_PMESSAGES,19,27,266,166,LBS_OWNERDRAWFIXED | - LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | LBS_NOSEL | NOT - WS_BORDER - EDITTEXT IDC_INPUT,19,198,266,12,ES_MULTILINE | ES_WANTRETURN | - NOT WS_BORDER - EDITTEXT IDC_YOURNAME,89,8,68,12,NOT WS_BORDER - LTEXT "Games:",-1,296,14,50,10 - LTEXT "Players:",-1,296,99,50,10 - LTEXT "Your Name:",-1,24,9,62,10 -END - -IDD_OPT_CTRL_SP DIALOG DISCARDABLE 0, 0, 209, 140 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Resume Mission",IDC_RESUME_MISSION,"Button", - BS_OWNERDRAW,47,114,115,14 - CONTROL "Restate Briefing",IDC_BRIEFING,"Button",BS_OWNERDRAW,47, - 29,115,14 - CONTROL "Load Game",IDC_LOAD_GAME,"Button",BS_OWNERDRAW,47,46, - 115,14 - CONTROL "Save Game",IDC_SAVE_GAME,"Button",BS_OWNERDRAW,47,63, - 115,14 - CONTROL "Delete Game",IDC_DELETE_GAME,"Button",BS_OWNERDRAW,47, - 80,115,14 - CONTROL "Game Controls",IDC_GAME_CONTROLS,"Button",BS_OWNERDRAW, - 47,12,115,14 - CONTROL "Abort Mission",IDC_ABORT_MISSION,"Button",BS_OWNERDRAW, - 47,97,115,14 -END - -IDD_OPT_CTRL_MP DIALOG DISCARDABLE 0, 0, 209, 75 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Resume Mission",IDC_RESUME_MISSION,"Button", - BS_OWNERDRAW,55,48,99,14 - CONTROL "Game Controls",IDC_GAME_CONTROLS,"Button",BS_OWNERDRAW, - 55,12,99,14 - CONTROL "Abort Mission",IDC_ABORT_MISSION,"Button",BS_OWNERDRAW, - 55,30,99,14 -END - -IDD_SERIAL_PHONE_LIST DIALOG DISCARDABLE 0, 0, 344, 167 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Dial",1,"Button",BS_OWNERDRAW,209,141,50,14 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,272,141,50,14 - CTEXT "Phone List",-1,22,12,300,11,SS_CENTERIMAGE | NOT - WS_GROUP - LISTBOX IDC_PHONE_LIST,96,28,226,84,LBS_OWNERDRAWFIXED | - LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | NOT WS_BORDER - EDITTEXT IDC_PHONE_NAME,96,120,226,14,ES_MULTILINE | - ES_AUTOHSCROLL | ES_WANTRETURN | NOT WS_BORDER | NOT - WS_TABSTOP - CONTROL "Add",IDC_SERIAL_ADD,"Button",BS_OWNERDRAW,22,28,69,14 - CONTROL "Delete",IDC_PHONE_DELETE,"Button",BS_OWNERDRAW,22,70,69, - 14 - CONTROL "Edit",IDC_PHONE_EDIT,"Button",BS_OWNERDRAW,22,49,69,14 - LTEXT "Phone Number:",-1,22,120,67,14,SS_CENTERIMAGE | NOT - WS_GROUP -END - -IDD_SERIAL_PHONE_ENTRY DIALOG DISCARDABLE 0, 0, 247, 136 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Save",1,"Button",BS_OWNERDRAW,117,110,50,14 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,175,110,50,14 - CTEXT "Phonebook Entry",-1,22,12,203,12,SS_CENTERIMAGE | NOT - WS_GROUP - EDITTEXT IDC_PHONE_NAME,76,35,149,14,ES_MULTILINE | - ES_AUTOHSCROLL | ES_WANTRETURN | NOT WS_BORDER | NOT - WS_TABSTOP - RTEXT "Name:",-1,22,35,47,14,SS_CENTERIMAGE | NOT WS_GROUP - RTEXT "Number:",-1,22,56,47,14,SS_CENTERIMAGE | NOT WS_GROUP - EDITTEXT IDC_PHONE_NUMBER,76,56,149,14,ES_MULTILINE | - ES_AUTOHSCROLL | ES_WANTRETURN | NOT WS_BORDER | NOT - WS_TABSTOP - CONTROL "Default",IDC_PHONE_DEFAULT,"Button",BS_AUTOCHECKBOX | - BS_FLAT,28,84,95,12 - CONTROL "Custom",IDC_PHONE_CUSTOM,"Button",BS_AUTOCHECKBOX | - BS_FLAT,123,84,96,12 - GROUPBOX "Settings:",-1,22,73,203,30 -END - -IDD_PROGRESS_WAIT DIALOGEX 0, 0, 192, 53 -STYLE WS_CHILD -FONT 8, "MS Sans Serif", 0, 0, 0x1 -BEGIN - GROUPBOX "",IDC_PROGRESS_BAR_FRAME,46,26,100,15,0,0, - HIDC_PROGRESS_BAR_FRAME - CTEXT "Working - Please Wait",IDC_PROGRESS_TEXT,22,12,148,11, - SS_CENTERIMAGE | NOT WS_GROUP -END - -IDD_MPLAYER_DISCONNECT DIALOG DISCARDABLE 0, 0, 339, 220 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW | WS_TABSTOP,22, - 194,295,14 - LISTBOX IDC_DISCONNECT_MESSAGES,22,103,295,84,NOT LBS_NOTIFY | - LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | - LBS_NOINTEGRALHEIGHT | LBS_NOSEL | NOT WS_BORDER - CONTROL "Player 1",IDC_DISCONNECT_PLAYER1,"Button",BS_OWNERDRAW | - WS_TABSTOP,22,12,90,14 - CONTROL "Player 3",IDC_DISCONNECT_PLAYER3,"Button",BS_OWNERDRAW | - WS_TABSTOP,22,48,90,14 - CONTROL "Player 2",IDC_DISCONNECT_PLAYER2,"Button",BS_OWNERDRAW | - WS_TABSTOP,22,30,90,14 - CONTROL "Player 4",IDC_DISCONNECT_PLAYER4,"Button",BS_OWNERDRAW | - WS_TABSTOP,22,66,90,14 - CONTROL "Player 5",IDC_DISCONNECT_PLAYER5,"Button",BS_OWNERDRAW | - WS_TABSTOP,175,12,90,14 - CONTROL "Player 6",IDC_DISCONNECT_PLAYER6,"Button",BS_OWNERDRAW | - WS_TABSTOP,175,30,90,14 - CONTROL "Player 7",IDC_DISCONNECT_PLAYER7,"Button",BS_OWNERDRAW | - WS_TABSTOP,175,48,90,14 - CONTROL "Player 8",IDC_DISCONNECT_PLAYER8,"Button",BS_OWNERDRAW | - WS_TABSTOP,175,66,90,14 - GROUPBOX "",IDC_DISCONNECT_PLAYER1_BOX,118,12,40,10 - GROUPBOX "",IDC_DISCONNECT_PLAYER2_BOX,118,30,40,10 - GROUPBOX "",IDC_DISCONNECT_PLAYER3_BOX,118,48,40,10 - GROUPBOX "",IDC_DISCONNECT_PLAYER4_BOX,118,66,40,10 - GROUPBOX "",IDC_DISCONNECT_PLAYER5_BOX,271,12,40,10 - GROUPBOX "",IDC_DISCONNECT_PLAYER6_BOX,271,30,40,10 - GROUPBOX "",IDC_DISCONNECT_PLAYER7_BOX,271,48,40,10 - GROUPBOX "",IDC_DISCONNECT_PLAYER8_BOX,271,66,40,10 - LTEXT "Time Remaining:",IDC_DISCONNECT_TIME_REMAINING,29,88, - 279,8,NOT WS_GROUP -END - -IDD_DESYNC_HOST DIALOG DISCARDABLE 0, 0, 360, 264 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CTEXT "Synchronization Error",IDC_DESYNC_HEADER,30,10,280,10, - NOT WS_GROUP - LTEXT "Players:",-1,30,23,100,10,NOT WS_GROUP - LISTBOX IDC_DESYNC_PLAYER_LIST,30,35,120,105,NOT LBS_NOTIFY | - LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | - LBS_NOINTEGRALHEIGHT | LBS_NOSEL | NOT WS_BORDER - LTEXT "The game has gone out of sync.\r\n\r\nPress ""Load Game"" to load a saved game from this session, re-syncing the game for all players.\r\n\r\nPress ""Continue"" to continue playing without the desynced players. They will continue in a separate game session.", - -1,160,25,180,82,NOT WS_GROUP - LTEXT "Press ""Quit"" to exit the game.",-1,160,127,180,22, - NOT WS_GROUP - LISTBOX IDC_DESYNC_CHAT_LIST,30,145,300,60,NOT LBS_NOTIFY | - LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | - LBS_NOINTEGRALHEIGHT | LBS_NOSEL | NOT WS_BORDER - EDITTEXT IDC_DESYNC_CHAT_EDIT,30,207,299,12,ES_AUTOHSCROLL | - NOT WS_BORDER - LTEXT "Loading in 5 seconds...", - IDC_DESYNC_COUNTDOWN_TEXT,30,223,140,10,NOT WS_GROUP | - NOT WS_VISIBLE - GROUPBOX "",IDC_DESYNC_COUNTDOWN_BAR,180,222,149,12,NOT WS_VISIBLE - CONTROL "Load Game",IDC_DESYNC_LOAD,"Button",BS_OWNERDRAW | - WS_TABSTOP,30,239,70,12 - CONTROL "Continue",IDC_DESYNC_CONTINUE,"Button",BS_OWNERDRAW | - WS_TABSTOP,145,239,70,12 - CONTROL "Quit",IDC_DESYNC_QUIT,"Button",BS_OWNERDRAW | - WS_TABSTOP,260,239,70,12 -END - -IDD_DESYNC_WAIT DIALOG DISCARDABLE 0, 0, 360, 264 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CTEXT "Synchronization Error",IDC_DESYNC_HEADER,30,10,280,10, - NOT WS_GROUP - LTEXT "Players:",-1,30,23,100,10,NOT WS_GROUP - LISTBOX IDC_DESYNC_PLAYER_LIST,30,35,120,105,NOT LBS_NOTIFY | - LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | - LBS_NOINTEGRALHEIGHT | LBS_NOSEL | NOT WS_BORDER - LTEXT "The game has gone out of sync.\r\n\r\nIf there are saves available from this session, the game host can attempt to load a save to re-sync the game.\r\n\r\nAlternatively, the host can choose for the desynced players to continue playing in separate game sessions.", - -1,160,25,180,82,NOT WS_GROUP - LTEXT "Please wait while the host is making a decision.",-1,160, - 127,180,22, - NOT WS_GROUP - LISTBOX IDC_DESYNC_CHAT_LIST,30,145,300,60,NOT LBS_NOTIFY | - LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | - LBS_NOINTEGRALHEIGHT | LBS_NOSEL | NOT WS_BORDER - EDITTEXT IDC_DESYNC_CHAT_EDIT,30,207,299,12,ES_AUTOHSCROLL | - NOT WS_BORDER - LTEXT "Loading in 5 seconds...", - IDC_DESYNC_COUNTDOWN_TEXT,30,223,140,10,NOT WS_GROUP | - NOT WS_VISIBLE - GROUPBOX "",IDC_DESYNC_COUNTDOWN_BAR,180,222,149,12,NOT WS_VISIBLE - CONTROL "Quit",IDC_DESYNC_QUIT,"Button",BS_OWNERDRAW | - WS_TABSTOP | WS_DISABLED,145,239,70,12 -END - -IDD_SELECT_SERIAL DIALOG DISCARDABLE 0, 0, 191, 147 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,37,115,117,18 - CONTROL "Dial",IDC_SERIAL_DIAL,"Button",BS_OWNERDRAW,37,27,117, - 18 - CONTROL "Answer",IDC_SERIAL_ANSWER,"Button",BS_OWNERDRAW,37,49, - 117,18 - CONTROL "Null Modem",IDC_SERIAL_NULLMODEM,"Button",BS_OWNERDRAW, - 37,71,117,18 - CONTROL "Settings",IDC_SERIAL_SETTINGS_BTN,"Button",BS_OWNERDRAW, - 37,93,117,18 - CTEXT "Modem / Serial",-1,37,12,117,8,SS_CENTERIMAGE | NOT - WS_GROUP -END - -IDD_SERIAL_SETTINGS DIALOG DISCARDABLE 0, 0, 361, 225 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,289,199,50,14 - CTEXT "Serial Settings",-1,22,12,317,11,SS_CENTERIMAGE | NOT - WS_GROUP - COMBOBOX IDC_SERIAL_PORT,22,38,144,30,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS - LTEXT "Port:",-1,22,25,144,12,SS_CENTERIMAGE | NOT WS_GROUP - COMBOBOX IDC_SERIAL_BAUD,22,70,144,30,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS - LTEXT "Baud:",-1,22,54,63,12,SS_CENTERIMAGE | NOT WS_GROUP - COMBOBOX IDC_SERIAL_CALLWAITING,180,38,144,30,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS - EDITTEXT IDC_SERIAL_CALLWAITING_EDIT,180,38,129,12,ES_MULTILINE | - ES_WANTRETURN | NOT WS_VISIBLE | NOT WS_BORDER | NOT - WS_TABSTOP - CONTROL "Pulse Dial",IDC_SERIAL_PULSE,"Button",BS_AUTOCHECKBOX | - BS_NOTIFY | BS_FLAT,180,55,68,10 - CONTROL "Tone Dial",IDC_SERIAL_TONE,"Button",BS_AUTOCHECKBOX | - BS_NOTIFY | BS_FLAT,180,69,69,10 - CONTROL "Save",1,"Button",BS_OWNERDRAW,226,199,50,14 - EDITTEXT IDC_SERIAL_INITSTRING,88,122,245,12,ES_MULTILINE | - ES_WANTRETURN | NOT WS_BORDER | NOT WS_TABSTOP - LISTBOX IDC_SERIAL_INITLIST,88,138,245,52,LBS_OWNERDRAWFIXED | - LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | NOT WS_BORDER - EDITTEXT IDC_SERIAL_PORT_EDIT,22,39,129,12,ES_MULTILINE | - ES_WANTRETURN | NOT WS_VISIBLE | NOT WS_BORDER | NOT - WS_TABSTOP - LTEXT "Call Waiting:",-1,180,25,144,12,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Delete",IDC_SERIAL_INIT_DELETE,"Button",BS_OWNERDRAW,28, - 142,55,14 - CONTROL "Add",IDC_SERIAL_ADD,"Button",BS_OWNERDRAW,28,122,55,14 - CONTROL "Error Correction",IDC_SERIAL_ERROR_CORRECTION,"Button", - BS_AUTOCHECKBOX | BS_NOTIFY | BS_FLAT,180,83,144,10 - CONTROL "Data Compression",IDC_SERIAL_DATA_COMPRESSION,"Button", - BS_AUTOCHECKBOX | BS_NOTIFY | BS_FLAT,180,97,145,10 - GROUPBOX "Init String",-1,22,108,317,86 -END - -IDD_SKIRMISH DIALOG DISCARDABLE 0, 0, 426, 240 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - LTEXT "Name:",-1,22,12,78,8,NOT WS_GROUP - EDITTEXT IDC_SKIRMISH_NAME,22,24,78,12,NOT WS_BORDER | NOT - WS_TABSTOP - LTEXT "Side:",-1,22,42,78,8,NOT WS_GROUP - COMBOBOX IDC_SKIRMISH_SIDE,22,52,78,74,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS - LTEXT "Color:",-1,22,71,78,8,NOT WS_GROUP - COMBOBOX IDC_SKIRMISH_COLOR,22,82,78,73,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS | WS_VSCROLL - LTEXT "Unit Count:",IDC_SKIRMISH_UNITCOUNT_LABEL,298,18,100,8, - NOT WS_GROUP - CONTROL "Slider1",IDC_SKIRMISH_UNITCOUNT,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,298,30,100,14 - LTEXT "Credits:",IDC_SKIRMISH_CREDITS_LABEL,298,47,100,8,NOT - WS_GROUP - CONTROL "Slider3",IDC_SKIRMISH_CREDITS,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,298,59,100,14 - LTEXT "Tech Level:",IDC_SKIRMISH_TECHLEVEL_LABEL,298,76,100,8, - NOT WS_GROUP - CONTROL "Slider2",IDC_SKIRMISH_TECHLEVEL,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,298,88,100,14 - LTEXT "AI Level:",IDC_SKIRMISH_AILEVEL_LABEL,298,105,100,8,NOT - WS_GROUP - CONTROL "Slider4",IDC_DIFFICULTY_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,298,117,100,14 - CONTROL "OK",IDOK,"Button",BS_OWNERDRAW,294,214,50,14 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,354,214,50,14 - CONTROL "Slider4",IDC_SKIRMISH_AIPLAYERS,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,298,146,100,14 - LTEXT "AI Players:",IDC_SKIRMISH_AIPLAYERS_LABEL,298,134,100,8, - NOT WS_GROUP - CONTROL "Multiplay Map",IDC_MULTIMAP,"Button",BS_OWNERDRAW,294, - 197,110,14 - GROUPBOX "",IDC_PREVIEW_FRAME,22,122,215,106 - LTEXT "Map:",-1,22,97,36,10,NOT WS_GROUP - LTEXT "None",IDC_SCENARIONAME,22,111,268,10,NOT WS_GROUP - CTEXT "Preview",-1,22,165,215,13,SS_CENTERIMAGE | NOT WS_GROUP - GROUPBOX "",-1,294,12,110,181 - CONTROL "Bases",IDC_SKIRMISH_BASES,"Button",BS_AUTOCHECKBOX,123, - 21,157,10 - CONTROL "Crates",IDC_SKIRMISH_CRATES,"Button",BS_AUTOCHECKBOX, - 123,33,157,10 - CONTROL "Fog Of War",IDC_SKIRMISH_FOG,"Button",BS_AUTOCHECKBOX, - 123,45,157,10 - CONTROL "Bridges Destroyable",IDC_SKIRMISH_BRIDGES,"Button", - BS_AUTOCHECKBOX,123,57,157,10 - GROUPBOX "",-1,117,12,167,96 - CONTROL "Re-Deployable MCV",IDC_REDEPLOY_MCV,"Button", - BS_AUTOCHECKBOX,123,69,157,10 - CONTROL "Short Game",IDC_SHORT_GAME,"Button",BS_AUTOCHECKBOX,123, - 81,157,10 - CONTROL "Slider4",IDC_GAME_SPEED_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,298,175,100,14 - LTEXT "Game Speed",IDC_SKIRMISH_GAMESPEED_LABEL,298,163,100,8, - NOT WS_GROUP - CONTROL "Multi Engineer",IDC_MULTI_ENGINEER,"Button", - BS_AUTOCHECKBOX,123,93,108,10 -END - -IDD_SOUND_OPTIONS_DIALOG DIALOG DISCARDABLE 0, 0, 294, 215 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "OK",IDOK,"Button",BS_OWNERDRAW | WS_TABSTOP,210,189,62, - 14 - CONTROL "Slider1",IDC_MUSIC_VOLUME,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS | WS_TABSTOP,97,12,175,15 - CONTROL "Slider2",IDC_SOUND_VOLUME,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS | WS_TABSTOP,97,34,175,15 - RTEXT "Music Volume:",-1,22,12,70,15,SS_CENTERIMAGE | NOT - WS_GROUP - RTEXT "Sound Volume:",-1,22,34,70,15,SS_CENTERIMAGE - LISTBOX IDC_SOUND_TRACKLIST,97,82,175,99,LBS_OWNERDRAWFIXED | - LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | NOT WS_BORDER | - WS_TABSTOP - CONTROL "Play",IDC_SOUND_PLAY,"Button",BS_OWNERDRAW | WS_TABSTOP, - 22,86,70,14 - CONTROL "Stop",IDC_SOUND_STOP,"Button",BS_OWNERDRAW | WS_TABSTOP, - 22,113,70,14 - RTEXT "Voice Volume:",-1,22,56,70,15,SS_CENTERIMAGE - CONTROL "Slider2",IDC_VOICE_VOLUME,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS | WS_TABSTOP,97,56,175,15 - CONTROL "Shuffle",IDC_SOUND_SHUFFLE,"Button",BS_AUTOCHECKBOX | - BS_FLAT,22,140,70,14 - CONTROL "Repeat",IDC_SOUND_REPEAT,"Button",BS_AUTOCHECKBOX | - BS_FLAT,22,167,70,14 -END - -IDD_SOUND_OPTIONS_DIALOG_LITE DIALOG DISCARDABLE 0, 0, 294, 112 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - RTEXT "Music Volume:",-1,22,17,70,15,SS_CENTERIMAGE - CONTROL "Slider1",IDC_MUSIC_VOLUME,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS | WS_TABSTOP,99,17,173,15 - RTEXT "Sound Volume:",-1,22,40,71,15,SS_CENTERIMAGE - CONTROL "Slider2",IDC_SOUND_VOLUME,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS | WS_TABSTOP,99,40,173,15 - CONTROL "OK",IDOK,"Button",BS_OWNERDRAW | BS_CENTER | WS_TABSTOP, - 115,86,62,14 - RTEXT "Voice Volume:",-1,22,62,71,15,SS_CENTERIMAGE - CONTROL "Slider2",IDC_VOICE_VOLUME,"msctls_trackbar32",TBS_BOTH | - TBS_NOTICKS | WS_TABSTOP,99,62,173,15 -END - -IDD_VERSION DIALOG DISCARDABLE 0, 0, 272, 106 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "OK",IDOK,"Button",BS_OWNERDRAW | BS_CENTER | WS_TABSTOP, - 111,80,50,14 - LISTBOX IDC_VERSION_INFO,22,12,228,55,LBS_OWNERDRAWFIXED | - LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | LBS_NOSEL | NOT - WS_BORDER | WS_TABSTOP -END - -IDD_MSGBOX_3_SMALL DIALOG DISCARDABLE 0, 0, 290, 90 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "OK",IDOK,"Button",BS_OWNERDRAW | NOT WS_VISIBLE,120,64, - 50,14 - CTEXT "Msg",IDC_MSGBOX_TEXT,22,12,246,40 - CONTROL "No",7,"Button",BS_OWNERDRAW | NOT WS_VISIBLE,70,64,50, - 14 - CONTROL "Yes",6,"Button",BS_OWNERDRAW | NOT WS_VISIBLE,169,64,50, - 14 -END - -IDD_CAMPAIGN DIALOG DISCARDABLE 0, 0, 246, 149 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CTEXT "Select Campaign:",-1,22,12,206,10,SS_CENTERIMAGE | NOT - WS_GROUP - LISTBOX IDC_LIST,22,28,206,51,LBS_OWNERDRAWFIXED | - LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | NOT WS_BORDER - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,178,121,50,16 - CONTROL "OK",IDOK,"Button",BS_OWNERDRAW,121,121,50,16 - CONTROL "Slider3",IDC_DIFFICULTY_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,22,98,206,15 - CONTROL "Difficulty",-1,"Static",SS_LEFTNOWORDWRAP | - SS_CENTERIMAGE,22,84,136,15 - RTEXT "Harder",IDC_DIFFICULTY_LABEL,162,84,66,15, - SS_CENTERIMAGE | NOT WS_GROUP -END - -IDD_EXCEPTION DIALOG DISCARDABLE 0, 0, 294, 231 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION -CAPTION "Tiberian Sun Has Encountered Difficulty" -FONT 8, "MS Sans Serif" -BEGIN - PUSHBUTTON "Debug",IDC_EXCEPT_DEBUG,115,200,60,14,BS_CENTER - PUSHBUTTON "Main Menu",IDC_EXCEPT_MAINMENU,217,200,60,14 - CTEXT "Tiberian Sun has encountered a problem.\nSee the file DEBUG.TXT for details.", - -1,7,7,280,26,NOT WS_GROUP - DEFPUSHBUTTON "Quit",IDC_EXCEPT_QUIT,19,200,60,14 - EDITTEXT IDC_EXCEPT_DETAILS,17,53,261,140,ES_MULTILINE | - ES_NOHIDESEL | ES_READONLY | ES_WANTRETURN | WS_VSCROLL | - WS_HSCROLL - LTEXT "Details:",IDC_EXCEPT_DETAILS_LABEL,19,41,45,8 -END - -IDD_EXCEPTION_SIMPLE DIALOG DISCARDABLE 0, 0, 252, 82 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION -CAPTION "Tiberian Sun" -FONT 8, "MS Sans Serif" -BEGIN - CTEXT "Tiberian Sun has encountered an internal error",-1,7,11, - 238,8,NOT WS_GROUP - CTEXT "and is unable to continue normally.", - IDC_EXCEPT_DETAILS_LABEL,7,19,238,8,NOT WS_GROUP - CTEXT "Please visit our website at http://www.westwood.com", - IDC_EXCEPT_WEBSITE,7,36,238,8,NOT WS_GROUP - PUSHBUTTON "OK",IDC_EXCEPT_QUIT,83,61,79,14 - CTEXT "for the latest updates and technical support.",-1,7,44, - 238,8,NOT WS_GROUP -END - -IDD_MPLAYER_SELECT_GAME_FS DIALOG DISCARDABLE 0, 0, 197, 146 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CTEXT "Select Multiplayer Game",-1,30,8,130,12,SS_CENTERIMAGE | - NOT WS_GROUP - CONTROL "Main Menu",2,"Button",BS_OWNERDRAW,30,118,130,18 - CONTROL "Internet",IDC_INTERNET,"Button",BS_OWNERDRAW,30,22,130, - 18 - CONTROL "Modem / Serial",IDC_MODEMSERIAL,"Button",BS_OWNERDRAW, - 30,60,130,18 - CONTROL "Network",IDC_NETWORK,"Button",BS_OWNERDRAW,30,79,130,18 - CONTROL "Skirmish",IDC_SKIRMISH,"Button",BS_OWNERDRAW,30,98,130, - 18 - CONTROL "World Domination! (Internet)",IDC_WORLDDOM,"Button", - BS_OWNERDRAW,30,41,130,18 -END - -IDD_SELECT_GAME_TYPE DIALOG DISCARDABLE 0, 0, 228, 108 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Main Menu",2,"Button",BS_OWNERDRAW,62,82,104,14 - CTEXT "Select Game Type",-1,22,12,184,19,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Tiberian Sun (Original)",IDC_GAMETYPE_ORIGINAL,"Button", - BS_OWNERDRAW,62,42,104,14 - CONTROL "Firestorm",IDC_GAMETYPE_FIRESTORM,"Button",BS_OWNERDRAW, - 62,62,104,14 -END - -IDD_MAPGEN_FS DIALOG DISCARDABLE 0, 0, 426, 242 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "OK",IDOK,"Button",BS_OWNERDRAW,265,220,65,14 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,337,220,65,14 - COMBOBOX IDC_MAPGEN_ENVIRONMENT,79,8,100,103,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_SORT | CBS_HASSTRINGS - CONTROL "Slider1",IDC_MAPGEN_HILLS,"msctls_trackbar32",TBS_TOP, - 95,92,100,14 - LTEXT "Hills:",-1,22,92,74,14,SS_CENTERIMAGE | NOT WS_GROUP - CONTROL "Slider1",IDC_MAPGEN_WATER,"msctls_trackbar32",TBS_TOP, - 95,143,100,14 - LTEXT "Players:",-1,22,41,74,14,SS_CENTERIMAGE | NOT WS_GROUP - LTEXT "Map Width:",-1,187,8,53,12,SS_CENTERIMAGE | NOT - WS_GROUP - LTEXT "Map Height:",-1,186,25,53,12,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Slider1",IDC_MAPGEN_VEGETATION,"msctls_trackbar32", - TBS_TOP,95,160,100,14 - CONTROL "Slider1",IDC_MAPGEN_CITIES,"msctls_trackbar32",TBS_TOP, - 95,177,100,14 - CONTROL "Slider1",IDC_MAPGEN_TIBERIUM_FIELDS,"msctls_trackbar32", - TBS_TOP,95,126,100,14 - LTEXT "Tiberium Fields:",-1,22,126,74,14,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Slider1",IDC_MAPGEN_ACCESSIBILITY,"msctls_trackbar32", - TBS_TOP,95,75,100,14 - LTEXT "Environment:",-1,22,7,53,14,SS_CENTERIMAGE | NOT - WS_GROUP - EDITTEXT IDC_MAPGEN_DIMENSION_EDIT,22,206,57,12,ES_NUMBER | NOT - WS_VISIBLE | WS_DISABLED | NOT WS_BORDER | NOT - WS_TABSTOP - CONTROL "Load Map",IDC_MAPGEN_LOAD_MAP,"Button",BS_OWNERDRAW,100, - 220,65,14 - CONTROL "Save Map",IDC_MAPGEN_SAVE_MAP,"Button",BS_OWNERDRAW,22, - 220,65,14 - COMBOBOX IDC_MAPGEN_TIME_OF_DAY,79,25,100,101,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_SORT | CBS_HASSTRINGS - LTEXT "Water:",-1,22,143,74,14,SS_CENTERIMAGE | NOT WS_GROUP - LTEXT "Time of Day:",-1,22,24,53,14,SS_CENTERIMAGE | NOT - WS_GROUP - LTEXT "Vegetation:",-1,22,160,74,14,SS_CENTERIMAGE | NOT - WS_GROUP - LTEXT "Cities:",-1,22,177,74,14,SS_CENTERIMAGE | NOT WS_GROUP - LTEXT "Accessability:",-1,22,75,74,14,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Preview Map",IDC_MAPGEN_PREVIEW,"Button",BS_OWNERDRAW, - 316,200,88,14 - GROUPBOX "",IDC_PREVIEW_FRAME,207,57,197,134 - CTEXT "Preview",-1,210,123,194,16,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Surprise Me",IDC_MAPGEN_SURPRISE,"Button",BS_OWNERDRAW, - 205,200,88,14 - CONTROL "Delete Map",IDC_MAPGEN_DELETE_MAP,"Button",BS_OWNERDRAW, - 178,220,65,14 - CONTROL "Slider1",IDC_MAPGEN_CLIFFS,"msctls_trackbar32",TBS_TOP, - 95,58,100,14 - LTEXT "Cliffs:",-1,22,58,74,14,SS_CENTERIMAGE | NOT WS_GROUP - CONTROL "Slider1",IDC_MAPGEN_PLAYERS,"msctls_trackbar32",TBS_TOP, - 95,41,100,14 - CONTROL "Slider1",IDC_MAPGEN_TIBERIUM_AMOUNT,"msctls_trackbar32", - TBS_TOP,95,109,100,14 - LTEXT "Tiberium Amount:",-1,22,109,74,14,SS_CENTERIMAGE | NOT - WS_GROUP - COMBOBOX IDC_MAPGEN_MAP_WIDTH,248,8,63,103,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS - COMBOBOX IDC_MAPGEN_MAP_HEIGHT,248,26,63,103,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS - CONTROL "Transitions",IDC_MAPGEN_TRANSITIONS,"Button", - BS_AUTOCHECKBOX,320,26,84,10 - CONTROL "Ion Storms",IDC_MAPGEN_ION_STORMS,"Button", - BS_AUTOCHECKBOX,320,8,84,10 - CONTROL "Slider1",IDC_MAPGEN_VEINHOLES,"msctls_trackbar32", - TBS_TOP,95,194,100,14 - LTEXT "Veinholes:",-1,22,194,74,14,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Lifeforms",IDC_MAPGEN_LIFEFORMS,"Button", - BS_AUTOCHECKBOX,320,44,84,10 -END - -IDD_WDT_PICK_CLAN DIALOG DISCARDABLE 0, 0, 206, 163 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CTEXT "World Domination Tour games use your Battle Clan affiliation to determine which side you are fighting for. If you do not wish to join a Battle Clan, select which side you wish to fight for.", - -1,15,11,180,48,NOT WS_GROUP - CONTROL "Join Battle Clan",IDC_PICKCLAN_JOIN,"Button", - BS_OWNERDRAW,27,64,150,19 - CONTROL "GDI",IDC_PICKCLAN_GDI,"Button",BS_OWNERDRAW,27,87,150, - 19 - CONTROL "Nod",IDC_PICKCLAN_NOD,"Button",BS_OWNERDRAW,27,110,150, - 19 - CONTROL "Cancel",IDC_CANCEL,"Button",BS_OWNERDRAW,27, - 133,150,19 -END - -IDD_MAPGEN_WDT DIALOG DISCARDABLE 0, 0, 426, 242 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "OK",IDOK,"Button",BS_OWNERDRAW,265,220,65,14 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,337,220,65,14 - COMBOBOX IDC_MAPGEN_ENVIRONMENT,79,8,100,103,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_SORT | CBS_HASSTRINGS - CONTROL "Slider1",IDC_MAPGEN_HILLS,"msctls_trackbar32",TBS_TOP, - 95,92,100,14 - LTEXT "Hills:",-1,22,92,74,14,SS_CENTERIMAGE | NOT WS_GROUP - CONTROL "Slider1",IDC_MAPGEN_WATER,"msctls_trackbar32",TBS_TOP, - 95,143,100,14 - LTEXT "Map Width:",-1,187,8,53,12,SS_CENTERIMAGE | NOT - WS_GROUP - LTEXT "Map Height:",-1,186,25,53,12,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Slider1",IDC_MAPGEN_VEGETATION,"msctls_trackbar32", - TBS_TOP,95,160,100,14 - CONTROL "Slider1",IDC_MAPGEN_CITIES,"msctls_trackbar32",TBS_TOP, - 95,177,100,14 - CONTROL "Slider1",IDC_MAPGEN_TIBERIUM_FIELDS,"msctls_trackbar32", - TBS_TOP,95,126,100,14 - LTEXT "Tiberium Fields:",-1,22,126,74,14,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Slider1",IDC_MAPGEN_ACCESSIBILITY,"msctls_trackbar32", - TBS_TOP,95,75,100,14 - LTEXT "Environment:",-1,22,7,53,14,SS_CENTERIMAGE | NOT - WS_GROUP - EDITTEXT IDC_MAPGEN_DIMENSION_EDIT,22,206,57,12,ES_NUMBER | NOT - WS_VISIBLE | WS_DISABLED | NOT WS_BORDER | NOT - WS_TABSTOP - CONTROL "Load Map",IDC_MAPGEN_LOAD_MAP,"Button",BS_OWNERDRAW,100, - 220,65,14 - CONTROL "Save Map",IDC_MAPGEN_SAVE_MAP,"Button",BS_OWNERDRAW,22, - 220,65,14 - COMBOBOX IDC_MAPGEN_TIME_OF_DAY,79,25,100,101,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_SORT | CBS_HASSTRINGS - LTEXT "Water:",-1,22,143,74,14,SS_CENTERIMAGE | NOT WS_GROUP - LTEXT "Time of Day:",-1,22,24,53,14,SS_CENTERIMAGE | NOT - WS_GROUP - LTEXT "Vegetation:",-1,22,160,74,14,SS_CENTERIMAGE | NOT - WS_GROUP - LTEXT "Cities:",-1,22,177,74,14,SS_CENTERIMAGE | NOT WS_GROUP - LTEXT "Accessability:",-1,22,75,74,14,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Preview Map",IDC_MAPGEN_PREVIEW,"Button",BS_OWNERDRAW, - 316,198,88,14 - GROUPBOX "",IDC_PREVIEW_FRAME,207,56,197,134 - CTEXT "Preview",-1,210,122,194,16,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Surprise Me",IDC_MAPGEN_SURPRISE,"Button",BS_OWNERDRAW, - 205,198,88,14 - CONTROL "Delete Map",IDC_MAPGEN_DELETE_MAP,"Button",BS_OWNERDRAW, - 178,220,65,14 - CONTROL "Slider1",IDC_MAPGEN_CLIFFS,"msctls_trackbar32",TBS_TOP, - 95,58,100,14 - LTEXT "Cliffs:",-1,22,58,74,14,SS_CENTERIMAGE | NOT WS_GROUP - CONTROL "Slider1",IDC_MAPGEN_TIBERIUM_AMOUNT,"msctls_trackbar32", - TBS_TOP,95,109,100,14 - LTEXT "Tiberium Amount:",-1,22,109,74,14,SS_CENTERIMAGE | NOT - WS_GROUP - COMBOBOX IDC_MAPGEN_MAP_WIDTH,248,8,63,103,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS - COMBOBOX IDC_MAPGEN_MAP_HEIGHT,248,26,63,103,CBS_DROPDOWNLIST | - CBS_OWNERDRAWFIXED | CBS_HASSTRINGS - CONTROL "Transitions",IDC_MAPGEN_TRANSITIONS,"Button", - BS_AUTOCHECKBOX,320,25,84,10 - CONTROL "Ion Storms",IDC_MAPGEN_ION_STORMS,"Button", - BS_AUTOCHECKBOX,320,8,84,10 - CONTROL "Slider1",IDC_MAPGEN_VEINHOLES,"msctls_trackbar32", - TBS_TOP,96,194,100,14 - LTEXT "Veinholes:",-1,22,194,74,14,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "1 on 1",IDC_WDT_1ON1,"Button",BS_AUTOCHECKBOX,22,44,73, - 10 - CONTROL "2 on 2",IDC_WDT_2ON2,"Button",BS_AUTOCHECKBOX,102,44,73, - 10 - CONTROL "Lifeforms",IDC_MAPGEN_LIFEFORMS,"Button", - BS_AUTOCHECKBOX,320,42,84,10 -END - -IDD_OPT_CTRL_WOL DIALOG DISCARDABLE 0, 0, 340, 185 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Resume Mission",IDC_RESUME_MISSION,"Button", - BS_OWNERDRAW,120,76,99,14 - CONTROL "Game Controls",IDC_GAME_CONTROLS,"Button",BS_OWNERDRAW, - 120,8,99,14 - CONTROL "Load Game",IDC_LOAD_GAME,"Button",BS_OWNERDRAW, - 120,25,99,14 - CONTROL "Save Game",IDC_SAVE_GAME,"Button",BS_OWNERDRAW, - 120,42,99,14 - CONTROL "Abort Mission",IDC_ABORT_MISSION,"Button",BS_OWNERDRAW, - 120,59,99,14 - CONTROL "Slider1",IDC_GAME_SPEED_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,95,132,148,13 - LTEXT "Game Speed",-1,39,132,58,13,SS_CENTERIMAGE | NOT - WS_GROUP - RTEXT "Faster",IDC_GAME_SPEED_LABEL,247,132,45,13, - SS_CENTERIMAGE | NOT WS_GROUP - CONTROL "Slider1",IDC_CTRLWOL_CONNECTION,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,95,110,148,13 - LTEXT "Connection",-1,39,110,58,13,SS_CENTERIMAGE | NOT - WS_GROUP - RTEXT "Better",IDC_SCROLL_SPEED_LABEL,247,110,45,13, - SS_CENTERIMAGE | NOT WS_GROUP - GROUPBOX "Internet Game Controls",-1,28,92,283,68 -END - -IDD_MPLAYER_SELECT_MAP DIALOGEX 0, 0, 360, 200 -STYLE WS_CHILD -FONT 8, "MS Sans Serif", 0, 0, 0x1 -BEGIN - CONTROL "OK",IDOK,"Button",BS_OWNERDRAW | WS_TABSTOP,22,174,50, - 14 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW | WS_TABSTOP,288, - 174,50,14 - LISTBOX IDC_SELECTMAP_LIST,22,26,175,142,LBS_OWNERDRAWFIXED | - LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | NOT WS_BORDER | - WS_TABSTOP - CTEXT "Select Multiplayer Map",-1,22,12,316,12,SS_CENTERIMAGE | - NOT WS_GROUP - GROUPBOX "",IDC_PREVIEW_FRAME,209,59,129,80,0,0, - HIDC_PREVIEW_FRAME - CONTROL "Create Random Map",IDC_CREATE_RANDOM_MAP,"Button", - BS_OWNERDRAW | WS_TABSTOP,132,174,106,14 -END - -IDD_MPLAYER_SELECT_MAP_SIMPLE DIALOG DISCARDABLE 0, 0, 219, 200 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "OK",IDOK,"Button",BS_OWNERDRAW | WS_TABSTOP,22,174,50, - 14 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW | WS_TABSTOP,147, - 174,50,14 - LISTBOX IDC_SELECTMAP_LIST,22,26,175,143,LBS_OWNERDRAWFIXED | - LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | NOT WS_BORDER | - WS_TABSTOP - CTEXT "Select Multiplayer Map",-1,22,12,175,12,SS_CENTERIMAGE | - NOT WS_GROUP -END - -IDD_MSGBOX_2 DIALOG DISCARDABLE 0, 0, 290, 90 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "OK",IDOK,"Button",BS_OWNERDRAW,22,64,50,14 - CTEXT "Msg",IDC_MSGBOX_TEXT,22,12,246,40 - CONTROL "Cancel",IDCANCEL,"Button",BS_OWNERDRAW,218,64,50,14 -END - -IDD_MSGBOX_3_LARGE DIALOG DISCARDABLE 0, 0, 360, 160 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "OK",IDOK,"Button",BS_OWNERDRAW | NOT WS_VISIBLE,155,134, - 50,14 - CTEXT "Msg",IDC_MSGBOX_TEXT,22,12,316,112 - CONTROL "No",7,"Button",BS_OWNERDRAW | NOT WS_VISIBLE,105,134,50, - 14 - CONTROL "Yes",6,"Button",BS_OWNERDRAW | NOT WS_VISIBLE,204,134, - 50,14 -END - -IDD_OPT_CTRL_GAME_WOL DIALOG DISCARDABLE 0, 0, 294, 144 -STYLE WS_CHILD -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "Options Menu",1,"Button",BS_OWNERDRAW,196,118,77,14 - CONTROL "Keyboard",IDC_OPT_KEYBOARD_BTN,"Button",BS_OWNERDRAW, - 109,118,77,14 - CONTROL "Sound",IDC_OPT_SOUND_BTN,"Button",BS_OWNERDRAW,22,118, - 77,14 - CONTROL "Slider2",IDC_SCROLL_SPEED_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,90,12,128,15 - RTEXT "Scroll Rate:",-1,22,12,63,15,SS_CENTERIMAGE | NOT - WS_GROUP - CONTROL "Slider3",IDC_DETAIL_LEVEL_SLIDER,"msctls_trackbar32", - TBS_BOTH | TBS_NOTICKS,90,43,128,15 - RTEXT "Visual Details:",-1,22,43,63,15,SS_CENTERIMAGE | NOT - WS_GROUP - LTEXT "Higher",IDC_DETAIL_LEVEL_LABEL,226,43,50,15, - SS_CENTERIMAGE | NOT WS_GROUP - LTEXT "Faster",IDC_SCROLL_SPEED_LABEL,226,12,50,15, - SS_CENTERIMAGE | NOT WS_GROUP - CONTROL "Sidebar Text",IDC_SIDEBAR_TEXT,"Button",BS_AUTOCHECKBOX | - BS_FLAT,22,63,119,10 - CONTROL "Target Lines",IDC_TARGET_LINES,"Button",BS_AUTOCHECKBOX | - BS_FLAT,22,81,119,10 - CONTROL "Tooltips",IDC_TOOLTIPS,"Button",BS_AUTOCHECKBOX | - BS_FLAT,147,63,129,10 - CONTROL "Scroll Coasting",IDC_SCROLL_COASTING,"Button", - BS_AUTOCHECKBOX | BS_FLAT,147,81,129,10 - CONTROL "Edge Scrolling",IDC_EDGE_SCROLL,"Button", - BS_AUTOCHECKBOX | BS_FLAT,22,99,119,10 -END - - -///////////////////////////////////////////////////////////////////////////// -// -// Dialog Info -// - ///////////////////////////////////////////////////////////////////////////// // // String Table From 441b9b2ed25f945336856b65f30dd797658122e4 Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 13:22:53 +0100 Subject: [PATCH 159/179] refactor(ui): remove the LegacyDialogs key Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/options.cpp | 5 ----- code/options.h | 7 ------- manual/content/keys/legacydialogs.md | 13 ------------- manual/data/ini-keys.yaml | 18 ------------------ 4 files changed, 43 deletions(-) delete mode 100644 manual/content/keys/legacydialogs.md diff --git a/code/options.cpp b/code/options.cpp index 1db5ea329..426f74ff4 100644 --- a/code/options.cpp +++ b/code/options.cpp @@ -123,7 +123,6 @@ OptionsClass::OptionsClass(void) : SidebarSorting(true), ActionLines(true), ToolTips(true), - LegacyDialogs(false), TextBackgroundColor(12), AutoSaveInterval(10800), ScreenWidth(-1), @@ -405,9 +404,6 @@ void OptionsClass::Load_Settings(void) AutoSaveInterval = ConfigINI.Get_Int("Options", "AutoSaveInterval", AutoSaveInterval); DebugString("AutoSaveInterval = %d\n", AutoSaveInterval); - LegacyDialogs = ConfigINI.Get_Bool("Options", "LegacyDialogs", LegacyDialogs); - DebugString("LegacyDialogs are %s\n", LegacyDialogs == true ? "ON" : "OFF"); - ScreenWidth = ConfigINI.Get_Int("Video", "ScreenWidth", ScreenWidth); ScreenHeight = ConfigINI.Get_Int("Video", "ScreenHeight", ScreenHeight); DebugString("Resolution = %d X %d\n", ScreenWidth, ScreenHeight); @@ -479,7 +475,6 @@ void OptionsClass::Save_Settings (void) ConfigINI.Put_Bool("Options", "ToolTips", ToolTips); ConfigINI.Put_Int("Options", "TextBackgroundColor", TextBackgroundColor); ConfigINI.Put_Int("Options", "AutoSaveInterval", AutoSaveInterval); - ConfigINI.Put_Bool("Options", "LegacyDialogs", LegacyDialogs); ConfigINI.Put_Int("Video", "ScreenWidth", ScreenWidth); ConfigINI.Put_Int("Video", "ScreenHeight", ScreenHeight); ConfigINI.Put_Bool("Video", "StretchMovies", StretchMovies); diff --git a/code/options.h b/code/options.h index 459899b8a..5910b1a37 100644 --- a/code/options.h +++ b/code/options.h @@ -127,13 +127,6 @@ class OptionsClass { */ bool ToolTips; - /* - * Should a screen that has been migrated to the new user interface open the Win32 - * dialog it replaced instead? This is transitional: it exists while both views of a - * screen do, and goes when the last legacy dialog does. - */ - bool LegacyDialogs; - /* * The palette index drawn behind each glyph of the in-game message list, or zero for * none. Twelve, black, is the value the CnCNet client's chat background option writes. diff --git a/manual/content/keys/legacydialogs.md b/manual/content/keys/legacydialogs.md deleted file mode 100644 index 5ee1c4f9f..000000000 --- a/manual/content/keys/legacydialogs.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -key: LegacyDialogs -summary: Returns the rebuilt screens to the dialogs they replaced. -when_omitted: - kind: value - value: "no" ---- - -The game's dialogs are being rebuilt one screen at a time. A screen that has been rebuilt keeps the dialog it replaced alongside it, and `LegacyDialogs=yes` is what selects the old one. Set it when a rebuilt screen misbehaves, so that the screen can still be reached while the fault is reported. - -The choice is read once per screen, as the screen opens, so a running screen is never swapped for the other one. Screens that have not been rebuilt are unaffected either way, and a rebuilt screen whose files cannot be loaded falls back to its dialog on its own without the key being set. - -The key exists only while both halves do. It is written back to `sun.ini` with the rest of `[Options]`, and it goes when the last dialog does. diff --git a/manual/data/ini-keys.yaml b/manual/data/ini-keys.yaml index 1435e9718..dd6af0039 100644 --- a/manual/data/ini-keys.yaml +++ b/manual/data/ini-keys.yaml @@ -12774,24 +12774,6 @@ LastTilesInSet: source: code/isotype.cpp guard: null level: IsometricTileTypeClass -LegacyDialogs: - key: LegacyDialogs - scopes: - - applies_to: - - client settings - file: sun.ini - section: - kind: literal - name: Options - value_type: boolean - status: generated - _provenance: - default_candidate: 'no' - declared_in: OptionsClass - member: LegacyDialogs - source: code/options.cpp - guard: null - level: OptionsClass LegalTarget: key: LegalTarget scopes: From bdaa3ca5048a243efbb6621cd9601a93ef78378f Mon Sep 17 00:00:00 2001 From: OpenTS contributor Date: Wed, 9 Sep 2026 13:45:33 +0100 Subject: [PATCH 160/179] docs: record the retirement of OwnerDraw Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- docs/UI_DESIGN.md | 58 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index 36a794617..bbab3bbe2 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -1,7 +1,7 @@ # UI system design -Status: in progress. Steps 1 to 12 of the migration plan have landed; nothing -from step 13 onward is implemented. +Status: in progress. Steps 1 to 13 of the migration plan have landed. OwnerDraw +is gone; step 14, the sidebar, is the only step left. Everything outside the migration plan remains a proposal informed by source inspection and upstream documentation. This page owns the UI architecture and migration; [Building @@ -83,17 +83,18 @@ the presenter closing and a marked presenter drains nothing. ## Where the UI stands today -OpenTS has four UI systems plus a few bespoke screens. They share the software -frame and the keyboard queue but nothing else. +OpenTS has three UI systems plus a few bespoke screens. They share the software +frame and the keyboard queue but nothing else. OwnerDraw was the fourth and step +13 deleted it. What follows describes what it was, because the screens that +replaced it were converted from its templates and inherit its geometry. | System | Files | Used by | Draws into | | --- | --- | --- | --- | -| OwnerDraw | `ownrdraw.cpp` (7,009 lines), `windlg.cpp`, `msgloop.cpp`, 53 templates in `language.rc` | main menu, options, skirmish, load and save, lobbies, desync, map generator, WDT, message boxes, progress wait | `AlternateSurface`, then `VisibleSurface` | | GadgetClass | `gadget.cpp`, `control.cpp`, `toggle.cpp`, `list.cpp`, `edit.cpp`, `slider.cpp`, ... | sidebar, radar, tactical buttons, message list, checklist, mission restate | `LogicalSurface` (`SidebarSurface`, `HiddenSurface`) | | MSEngine | `msengine.cpp`, `msanim.cpp`, `grphmenu.cpp` | graphic menu, map select, score screens, WDT screens, credits | `AlternateSurface`, `HiddenSurface` | | Bespoke | `progress.cpp`, `score.cpp`, `movies.cpp` | loading screen, score, movies | `HiddenSurface` | -OwnerDraw is the largest and the least portable. Each dialog is a real Win32 +OwnerDraw was the largest and the least portable. Each dialog was a real Win32 child window of `MainWindow`, created from a resource template by `CreateDialogIndirectParam`. Every control is subclassed; its window procedure paints into `AlternateSurface` and blits the result into `VisibleSurface` @@ -640,15 +641,11 @@ strings, is inserted as text, never as markup. ## Configuration -One transitional key in `SUN.INI`, `LegacyDialogs` under `[Options]`, returns -every migrated screen to its legacy view while that view exists. Step 3 named -it and `UI_Use_Rml` reads it. -Defaults are decided per screen family in code, so a family switches to RmlUi -by default when its evidence is in without a key per family. The key is -deleted with OwnerDraw. There is no build option: RmlUi and ImGui are always -compiled and linked, so one configuration matrix carries the evidence. -`Options` reads and writes the key where it handles `[Video]` today, and the -key has its manual page. A sidebar view key follows the sidebar view. +`LegacyDialogs` under `[Options]` in `SUN.INI` returned every migrated screen to +its legacy view while both existed. Step 3 named it; step 13 deleted it with +OwnerDraw, along with `UI_Use_Rml` and its manual page. There is no build +option: RmlUi and ImGui are always compiled and linked, so one configuration +matrix carries the evidence. A sidebar view key follows the sidebar view. ## Dear ImGui @@ -1034,9 +1031,34 @@ text beyond an ASCII test document. own blitter stretches, and a `UISurfaceBufferClass` is not one. `MapPreviewSurfaceClass` had relied on that blit since step 10, where the two sizes were close enough to hide it. -13. **Retire OwnerDraw** (M). Delete `ownrdraw.cpp`, `windlg.cpp`, the - modeless dialog list, the dialog templates, the kill switch, and the - coexistence assertions. String tables stay. +13. **Retire OwnerDraw** (M). Landed. `ownrdraw.cpp`, `ownrdraw.h`, + `windlg.cpp` and `windlg.h` are gone, with the legacy view behind every + migrated screen, the modeless dialog list in `msgloop.cpp`, the 53 dialog + templates in `language.rc`, the `LegacyDialogs` key and `UI_Use_Rml`. + `Language.dat` is byte-identical across the template deletion, as it was + across step 4's name table. `UI_Document_Is_Visible` went with the kill + switch: it was the coexistence check and it never had a caller, because a + legacy dialog cannot open on this fork at all. + + Six things in those files had nothing to do with dialogs and are still + wanted by unmigrated MSEngine and bespoke screens, so they moved to + `code/drawhelp.{h,cpp}`: the remapped bitmap text drawing + (`OD_Draw_Text_Remap` and the font metrics behind it), `OD_Draw_Text`, + `OD_Blend_Color` with its component masks, `WS_Get_Font` and its font cache, + `Get_Display_Rect`, and the counted pointer-capture pair. The `OD_` and `WS_` + names are kept because their callers spell them, and the header says why. + `Build_Hotkey_String` went to `keyboard.cpp` instead: it spells a key, not a + control. + + The pointer-capture pair is load-bearing and there is now exactly one + counter. `OwnerDraw::Capture_Mouse` released the game's mouse to the host so + that `WM_SETCURSOR` would fall through and the window class arrow would be + drawn, and the shell had grown a second counter of its own for documents. + Both now call the pair in `drawhelp.cpp`, so a graphic menu and a document + cannot disagree about who holds the pointer. + + `_dialog_count` became dead and took `Heal_Dialog_Controls` and + `SidebarClass::Scroll`'s guard against scrolling under a dialog with it. 14. **Sidebar** (M, then L). The model and view split with the gadget view; later the RmlUi view over the whole column and its selection key. From b6e3b78dc2ea0c357ed0e5360cb38bd6012066cb Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 14:06:50 +0100 Subject: [PATCH 161/179] fix(engine): hold the frame loop to 60 fps at game speed zero Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/mainloop.cpp | 7 ++++++- manual/content/keys/gamespeed.md | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/code/mainloop.cpp b/code/mainloop.cpp index 93cf13697..90c0b7e9a 100644 --- a/code/mainloop.cpp +++ b/code/mainloop.cpp @@ -294,7 +294,12 @@ bool Main_Loop(void) } } } else { - FrameTimer = Options.GameSpeed; + /* + * A game speed of zero is the "fastest" setting, which asks for no delay at all. The + * period display paced the loop on its own; a modern one does not, so hold the same + * 60 frames a second the network pacing in Queue_AI already reads that setting as. + */ + FrameTimer = std::max(1, Options.GameSpeed); } /* diff --git a/manual/content/keys/gamespeed.md b/manual/content/keys/gamespeed.md index 1bb84e16f..1903bd9bd 100644 --- a/manual/content/keys/gamespeed.md +++ b/manual/content/keys/gamespeed.md @@ -7,7 +7,7 @@ when_omitted: value: "3" --- -A frame is not begun until the delay has run out, so a larger figure gives a slower game: `0` runs as fast as the machine manages and `3` holds the game to twenty frames a second at most. The delay governs a single player mission, a skirmish, and a network game still using the older command protocol; a network game on the current protocol turns the figure into a frame rate instead — 60 at `0`, 45 at `1`, and sixty divided by the figure above that — and runs at whichever is lower, that or the rate the machines can sustain. +A frame is not begun until the delay has run out, so a larger figure gives a slower game: `0` holds the game to sixty frames a second and `3` to twenty at most. The delay governs a single player mission, a skirmish, and a network game still using the older command protocol; a network game on the current protocol turns the figure into a frame rate instead — 60 at `0`, 45 at `1`, and sixty divided by the figure above that — and runs at whichever is lower, that or the rate the machines can sustain. `0` names no delay at all, so the pace used to be whatever the display imposed; a machine that draws faster than the display once did ran the simulation away from the player, and the figure is now floored at one sixtieth of a second on the delay path as well. The same figure rescales the delays that have to keep their real-world timing whatever the frame rate is — building animations, infantry sequences and the pauses between EVA reminders — so that lowering it speeds the game up without speeding those up in proportion. From 8861e08f1ed69d767533dec30b5419e19a98437a Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 14:16:41 +0100 Subject: [PATCH 162/179] feat(video): put the window into real fullscreen and offer it in display options Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/mainopt.cpp | 9 +++++ code/ui/uidisplayoptions.cpp | 17 +++++++-- code/ui/uidisplayoptions.h | 2 ++ code/winstub.cpp | 55 +++++++++++++++++++++++++++++ code/winstub.h | 2 ++ manual/content/keys/fullscreen.md | 2 +- platform/win32compat/src/window.cpp | 49 ++++++++++++++++++++++++- ui/display.rcss | 14 +++++--- ui/display.rml | 1 + 9 files changed, 142 insertions(+), 9 deletions(-) diff --git a/code/mainopt.cpp b/code/mainopt.cpp index 566444409..8b4aceee3 100644 --- a/code/mainopt.cpp +++ b/code/mainopt.cpp @@ -33,6 +33,7 @@ #include "sounddlg.h" #include "stimer.h" #include "surface.h" +#include "winstub.h" #include "wwmouse.h" #include "ui/uidisplayconfirm.h" #include "ui/uidisplayoptions.h" @@ -100,6 +101,14 @@ void Display_Options_Dialog(void) if (screen.Choice != UIDisplayOptionsPresenterClass::CHOICE_ACCEPT) { break; } + + // The window mode is not staged and is not offered as a trial: the player can see + // at once whether the screen is covered, and the frame is unchanged either way. + if (screen.Fullscreen != Options.Fullscreen) { + Options.Fullscreen = screen.Fullscreen; + Set_Window_Fullscreen(Options.Fullscreen); + } + if (!screen.Wants_Mode_Change()) { break; } diff --git a/code/ui/uidisplayoptions.cpp b/code/ui/uidisplayoptions.cpp index 2a9e19893..9a14fc59e 100644 --- a/code/ui/uidisplayoptions.cpp +++ b/code/ui/uidisplayoptions.cpp @@ -11,8 +11,8 @@ // mainopt.cpp. // // What the extraction fixes in place: the resolution is staged and only a trial the player -// confirms writes it to the settings, while the movie stretching preference is written -// straight to the settings at accept and left alone at cancel; and the staged resolution +// confirms writes it to the settings, while the movie stretching and full screen +// preferences are written straight to the settings at accept and left alone at cancel; and the staged resolution // moves only when the player leaves the screen on a row other than the one it opened on, so // re-picking the row already in force stages nothing and skips the trial. // @@ -48,6 +48,7 @@ void UIDisplayOptionsPresenterClass::Refresh(void) StagedWidth = Options.ScreenWidth; StagedHeight = Options.ScreenHeight; StretchMovies = Options.StretchMovies; + Fullscreen = Options.Fullscreen; int * const modes = EnumDisplayModes(MIN_WIDTH, MIN_HEIGHT, MAX_WIDTH, MAX_HEIGHT); if (modes != NULL) { @@ -109,6 +110,11 @@ void UIDisplayOptionsPresenterClass::Execute(UIIntent const & intent) return; } + if (intent.Action == UI_DISPLAY_FULLSCREEN) { + Fullscreen = (intent.Value != 0); + return; + } + UIResult result; if (intent.Action == UI_DISPLAY_ACCEPT) { @@ -170,6 +176,7 @@ void DisplayOptionsViewClass::Bind(Rml::DataModelConstructor & model) model.Bind("modes", &Screen.Modes); model.Bind("selected", &Screen.Selected); model.Bind("stretch", &Screen.StretchMovies); + model.Bind("fullscreen", &Screen.Fullscreen); model.BindEventCallback("pick", [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { @@ -182,6 +189,11 @@ void DisplayOptionsViewClass::Bind(Rml::DataModelConstructor & model) Screen.Queue(UIIntent{UI_DISPLAY_STRETCH, "", Screen.StretchMovies ? 0 : 1}); }); + model.BindEventCallback("togglefull", + [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const &) { + Screen.Queue(UIIntent{UI_DISPLAY_FULLSCREEN, "", Screen.Fullscreen ? 0 : 1}); + }); + model.BindEventCallback("press", [this](Rml::DataModelHandle, Rml::Event &, Rml::VariantList const & arguments) { if (arguments.empty()) return; @@ -208,6 +220,7 @@ void DisplayOptionsViewClass::Sync(void) Model.DirtyVariable("selected"); Model.DirtyVariable("stretch"); + Model.DirtyVariable("fullscreen"); } diff --git a/code/ui/uidisplayoptions.h b/code/ui/uidisplayoptions.h index 676f3d617..73b82e310 100644 --- a/code/ui/uidisplayoptions.h +++ b/code/ui/uidisplayoptions.h @@ -24,6 +24,7 @@ inline constexpr char const * UI_DISPLAY_SELECT = "select"; // Value: row inline constexpr char const * UI_DISPLAY_STRETCH = "stretch"; // Value: check state +inline constexpr char const * UI_DISPLAY_FULLSCREEN = "fullscreen"; // Value: check state inline constexpr char const * UI_DISPLAY_ACCEPT = "accept"; inline constexpr char const * UI_DISPLAY_CANCEL = "cancel"; @@ -69,6 +70,7 @@ class UIDisplayOptionsPresenterClass : public UIPresenterClass std::vector Modes; int Selected = -1; bool StretchMovies = false; + bool Fullscreen = false; // The resolution the screen is staging. It starts at the one in force and moves // only when the player accepts a row other than the one the screen opened on, which diff --git a/code/winstub.cpp b/code/winstub.cpp index 17cbc0c8d..fb89680d0 100644 --- a/code/winstub.cpp +++ b/code/winstub.cpp @@ -376,6 +376,7 @@ LRESULT CALLBACK /*_export*/ Windows_Procedure(HWND hwnd, UINT message, WPARAM w // it created for it. Windows presents into the window handle itself. extern "C" void * Win32Compat_Native_Window_Handle(HWND window); extern "C" int Win32Compat_Window_Refresh_Rate(HWND window); +extern "C" BOOL Win32Compat_Set_Window_Fullscreen(HWND window, BOOL fullscreen); #endif @@ -403,6 +404,21 @@ bool Win_Window_Drawable_Size(HWND window, int & width, int & height) } +// A borderless window covering the desktop is all a full screen presentation is on +// Windows, so there is nothing further to ask for there. A host that keeps its own +// furniture above an ordinary window has to be told, or it draws over the game. +bool Win_Set_Window_Fullscreen(HWND window, bool fullscreen) +{ +#ifdef _WIN32 + (void)window; + (void)fullscreen; + return(true); +#else + return(Win32Compat_Set_Window_Fullscreen(window, fullscreen ? TRUE : FALSE) != FALSE); +#endif +} + + int Win_Window_Refresh_Rate(HWND window) { #ifndef _WIN32 @@ -536,6 +552,8 @@ void Create_Main_Window ( HINSTANCE instance , int command_show , int width , in NULL, instance, NULL ); + + Win_Set_Window_Fullscreen(MainWindow, true); } ShowWindow (MainWindow, SW_NORMAL); @@ -553,6 +571,43 @@ void Create_Main_Window ( HINSTANCE instance , int command_show , int width , in } +/// +/// Moves the main window between a full screen presentation and a window. +/// The frame is not resized: it keeps the resolution the display options settled on and is +/// scaled into whichever the window now is, which is what the two creation paths already do. +/// +/// Should the window cover the screen? +/// The resize the host reports rebuilds the presentation, so nothing else needs telling. +void Set_Window_Fullscreen(bool fullscreen) +{ + if (MainWindow == NULL) { + return; + } + + WindowedMode = !fullscreen; + SetWindowLong(MainWindow, GWL_STYLE, fullscreen ? WS_POPUP : WS_OVERLAPPEDWINDOW); + Win_Set_Window_Fullscreen(MainWindow, fullscreen); + + if (fullscreen) { + return; + } + + int clientwidth = (Options.WindowWidth > 0) ? Options.WindowWidth : Options.ScreenWidth; + int clientheight = (Options.WindowHeight > 0) ? Options.WindowHeight : Options.ScreenHeight; + + RECT rect; + SetRect(&rect, 0, 0, clientwidth, clientheight); + AdjustWindowRectEx(&rect, GetWindowLong(MainWindow, GWL_STYLE), FALSE, GetWindowLong(MainWindow, GWL_EXSTYLE)); + + int windowwidth = rect.right - rect.left; + int windowheight = rect.bottom - rect.top; + int x = (GetSystemMetrics(SM_CXSCREEN) - windowwidth) / 2; + int y = (GetSystemMetrics(SM_CYSCREEN) - windowheight) / 2; + + MoveWindow(MainWindow, std::max(x, 0), std::max(y, 0), windowwidth, windowheight, 1); +} + + /// /// Loads a title screen picture and centers it on the surface. /// This routine is used by the startup and scenario loading sequences to put some diff --git a/code/winstub.h b/code/winstub.h index ea3ef4518..86798d28c 100644 --- a/code/winstub.h +++ b/code/winstub.h @@ -23,6 +23,8 @@ void Create_Main_Window ( HINSTANCE instance , int command_show , int width , in NativeWindow Win_Native_Window(HWND window); bool Win_Window_Drawable_Size(HWND window, int & width, int & height); int Win_Window_Refresh_Rate(HWND window); +bool Win_Set_Window_Fullscreen(HWND window, bool fullscreen); +void Set_Window_Fullscreen(bool fullscreen); void Load_Title_Screen(char const * name, Surface * surface, PaletteClass * palette); diff --git a/manual/content/keys/fullscreen.md b/manual/content/keys/fullscreen.md index a28138e2e..b339328f2 100644 --- a/manual/content/keys/fullscreen.md +++ b/manual/content/keys/fullscreen.md @@ -8,7 +8,7 @@ when_omitted: A full-screen game opens a borderless window the size of the desktop. A windowed game opens an ordinary framed window that can be moved, resized, and maximized. Neither one changes the desktop's own resolution: the game always renders at [`ScreenWidth`](/keys/screenwidth/) by [`ScreenHeight`](/keys/screenheight/) and that picture is scaled into whichever window it has, so alt-tabbing away and back does not disturb the rest of the desktop. -This setting is read before the window is created, well before the rest of `SUN.INI`, and it is written back whenever the game saves its options. +This setting is read before the window is created, well before the rest of `SUN.INI`, and it is written back whenever the game saves its options. The display options screen offers it as a check box beside the resolution list and moves the window as soon as the screen is accepted, without the trial and rollback a resolution gets: the frame keeps the resolution it had and the player can see straight away whether the screen is covered. The [`-WIN`](/using/command-line/windowed/) command line option asks for a window regardless of what this setting says. It applies to that run only and is never written back, so a launcher can offer a window without disturbing the player's own preference. diff --git a/platform/win32compat/src/window.cpp b/platform/win32compat/src/window.cpp index 12144fe16..9eba77731 100644 --- a/platform/win32compat/src/window.cpp +++ b/platform/win32compat/src/window.cpp @@ -421,7 +421,14 @@ extern "C" LONG_PTR SetWindowLongPtr(HWND handle, int index, LONG_PTR value) LONG_PTR const previous = GetWindowLongPtr(handle, index); switch (index) { - case GWL_STYLE: window->Style = (DWORD)value; break; + case GWL_STYLE: + window->Style = (DWORD)value; + // Windows leaves the frame alone until a SWP_FRAMECHANGED asks for it. The host + // has no such second step, so the border follows the style as it is set. + if (window->Handle != NULL) { + SDL_SetWindowBordered(window->Handle, (window->Style & WS_POPUP) == 0); + } + break; case GWL_EXSTYLE: window->ExStyle = (DWORD)value; break; case GWLP_WNDPROC: window->Procedure = (WNDPROC)value; break; default: break; @@ -834,3 +841,43 @@ extern "C" int Win32Compat_Window_Refresh_Rate(HWND handle) SDL_DisplayMode const * mode = SDL_GetCurrentDisplayMode(SDL_GetDisplayForWindow(window->Handle)); return(mode != NULL ? (int)(mode->refresh_rate + 0.5f) : 0); } + + +// The engine expresses a full screen presentation as a borderless window covering the +// desktop, which is what that amounts to on the platform this shim stands in for. Here it +// has to be asked for, or the host's own furniture stays on top of the window. The +// desktop's own mode is kept and the frame is scaled into it, which is what the engine +// already does with the display mode it renders at. +extern "C" BOOL Win32Compat_Set_Window_Fullscreen(HWND handle, BOOL fullscreen) +{ + Win32Window * window = Win32_Lookup(handle); + + if (window == NULL || window->Handle == NULL) { + return(FALSE); + } + + if (!SDL_SetWindowFullscreenMode(window->Handle, NULL)) { + return(FALSE); + } + + if (!SDL_SetWindowFullscreen(window->Handle, fullscreen != FALSE)) { + return(FALSE); + } + + // The size the window reports is read back straight away by the caller, so the change + // has to have landed rather than be waiting in the event queue. + SDL_SyncWindow(window->Handle); + return(TRUE); +} + + +extern "C" BOOL Win32Compat_Window_Is_Fullscreen(HWND handle) +{ + Win32Window * window = Win32_Lookup(handle); + + if (window == NULL || window->Handle == NULL) { + return(FALSE); + } + + return((SDL_GetWindowFlags(window->Handle) & SDL_WINDOW_FULLSCREEN) != 0 ? TRUE : FALSE); +} diff --git a/ui/display.rcss b/ui/display.rcss index 6a95b0266..ca5387e31 100644 --- a/ui/display.rcss +++ b/ui/display.rcss @@ -26,28 +26,32 @@ #title { top: 17.5dp; height: 14.625dp; line-height: 14.625dp; } #reslabel { top: 38.625dp; height: 16.25dp; line-height: 16.25dp; } -/* The resolution list, 185 x 110 dialog units at 22, 37. */ +/* The resolution list, 185 x 110 dialog units at 22, 37, one row shorter to make room for + the full screen check the template has no control for. */ #reslist { left: 31dp; top: 58.125dp; width: 277.5dp; - height: 178.75dp; + height: 162.5dp; } /* A row spans the list less its scrollbar. */ #reslist .row { width: 265.5dp; } -/* The movie stretching check box, 185 x 10 dialog units at 22, 153. */ -#stretch +/* The movie stretching check box, 185 x 10 dialog units at 22, 153, moved up one row, and + the full screen check in the place it used to hold. */ +#stretch, #fullscreen { left: 31dp; - top: 246.625dp; width: 277.5dp; height: 16.25dp; line-height: 16.25dp; } +#stretch { top: 230.375dp; } +#fullscreen { top: 246.625dp; } + /* OK and Cancel, both 62 x 14 dialog units on the same row. */ .button { diff --git a/ui/display.rml b/ui/display.rml index c3e213ed0..1c1649a44 100644 --- a/ui/display.rml +++ b/ui/display.rml @@ -12,6 +12,7 @@
{{ mode.label }}
Stretch movies to fit resolution
+
Full screen
[[TXT_OK]]
[[TXT_CANCEL]]
From ca90b5b5b4f556b19d27969b201e51d8c6a7e01b Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 14:30:53 +0100 Subject: [PATCH 163/179] fix(skirmish): start the session speed at the saved game speed Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/init.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/code/init.cpp b/code/init.cpp index de21f0c68..0f550bd03 100644 --- a/code/init.cpp +++ b/code/init.cpp @@ -416,6 +416,11 @@ int Init_Game(int , char * []) Options.Load_Settings(); SaveManager.Autosave.Set_Interval(Options.AutoSaveInterval); + // The session speed starts at the player's own saved setting rather than at zero, which + // is the "fastest" end of the scale. The skirmish screen seeds its slider from it and so + // opened every match at that end, whatever the player had settled on. + Session.Options.GameSpeed = Options.GameSpeed; + /* ** Initialize the animation system. */ From 8cb3a5424f3035833a3408ae6e99513934c4e17e Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 14:31:04 +0100 Subject: [PATCH 164/179] fix(vqa): keep the frame loader's result in a type that holds VQAERR_NONE Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/mixfile.cpp | 5 ++++- code/vqalib/loader.cpp | 6 ++++-- manual/content/formats/vqa.md | 4 +--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/code/mixfile.cpp b/code/mixfile.cpp index f650c0de9..e9c2fa55c 100644 --- a/code/mixfile.cpp +++ b/code/mixfile.cpp @@ -563,7 +563,10 @@ bool MixFileClass::Offset(char const * filename, void ** realptr, MixFileClass * if (realptr != NULL && ptr->Data != NULL) { *realptr = (char *)ptr->Data + block->Offset; } - if (ptr->Data == NULL && offset != NULL) { + // The block's own offset is measured from the data section. The offset reported + // here is measured from the start of the archive file, cached or not, because + // that is the only thing a caller can seek to in the file it then opens. + if (offset != NULL) { *offset += ptr->DataStart; } return(true); diff --git a/code/vqalib/loader.cpp b/code/vqalib/loader.cpp index 6c29890fb..8bc8ac659 100644 --- a/code/vqalib/loader.cpp +++ b/code/vqalib/loader.cpp @@ -186,7 +186,9 @@ long VQA_LoadFrame(VQAHandleP *vqap, long flags) int scan_frame; int tocache; VQA_H_FUNC handler; - unsigned int rc; + // VQAERR_NONE is -1, so a narrower or unsigned holder loses it: it comes back as + // 0xFFFFFFFF from a long return and matches no error code the caller knows. + long rc; int val4; int fsize; VQA_H_FUNC oldhandler; @@ -1270,7 +1272,7 @@ long VQA_SeekGroup(VQAHandleP *vqap, long framenum, long groupsize, VQABool prel int bytes_per_frame; int preload_frames; int covered_bytes; - unsigned int rc; + long rc; bool bool2; int loadflags; long group_end; diff --git a/manual/content/formats/vqa.md b/manual/content/formats/vqa.md index 0ad066db4..8328222d8 100644 --- a/manual/content/formats/vqa.md +++ b/manual/content/formats/vqa.md @@ -90,9 +90,7 @@ The record also carries the block dimensions, a single-color count, the codebook Two entries in that list decide nothing. The drawing position is read only where a movie is placed by offset instead of being centered or given a destination, and nothing asks for that. The second audio track's three fields are read only where that track is selected in place of the first, and nothing selects it, so a movie carrying two tracks plays its first one. -:::caution[Movies in a cached archive do not play] -The offset the archive reader seeks to is measured from the start of the archive file when the archive is not cached, and from the start of the archive's data section when it is. Only the first is a position within the file it then opens, so a movie inside an archive that was cached at startup is read from the wrong place, fails the container check, and is passed over in silence. Among the numbered expansion archives, the `ECACHE` set is cached and the `EXPAND` set is not, and of the two patch archives `PCACHE.MIX` is cached while `PATCH.MIX` is not. [MIX archives](/formats/mix/) covers what caching does. -::: +A movie is read from the archive file itself rather than from a cached copy, so the position the reader seeks to is measured from the start of that file whether or not the archive was cached. [MIX archives](/formats/mix/) covers what caching does. :::danger[A long movie name overruns the buffer the filename is built in] The filename is assembled in a fixed twenty-byte buffer. `.VQA` takes four of those bytes and the string terminator a fifth, so a registered name of fifteen characters fills the buffer exactly and a sixteenth character writes one byte past its end. The registry accepts names of up to thirty-one characters, and playing a movie registered at that length writes sixteen bytes over whatever follows the buffer. The names the game ships with are all eight characters or fewer. From 301c94039619c5962cc61962b23919bc227b7c1a Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 14:31:11 +0100 Subject: [PATCH 165/179] fix(movie): size the movie filename buffer for the names the registry accepts Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SMFrbHFBcGcMut3cddJ8S3 --- code/movie.cpp | 10 ++++------ code/movie.h | 4 ++++ manual/content/formats/vqa.md | 4 +--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/code/movie.cpp b/code/movie.cpp index 8876d902c..3c8d1dc40 100644 --- a/code/movie.cpp +++ b/code/movie.cpp @@ -184,10 +184,9 @@ void _Play_Movie(char const * name, ThemeType theme) /// void Play_Movie(VQType vq, ThemeType theme, bool clrscrn, bool stretch) { - static char _buf[20]; + static char _buf[MOVIE_FILENAME_SIZE]; if (vq != VQ_NONE) { - strcpy(_buf, Movies[vq]); - strcpy(_buf + strlen(Movies[vq]), ".VQA"); + snprintf(_buf, sizeof(_buf), "%s.VQA", Movies[vq]); Play_Movie(_buf, theme, clrscrn, stretch, true); } } @@ -219,10 +218,9 @@ void Play_Ingame_Movie(const char * name) /// void Play_Ingame_Movie(VQType vq) { - static char _buf[20]; + static char _buf[MOVIE_FILENAME_SIZE]; if (vq != VQ_NONE) { - strcpy(_buf, Movies[vq]); - strcpy(_buf + strlen(Movies[vq]), ".VQA"); + snprintf(_buf, sizeof(_buf), "%s.VQA", Movies[vq]); Play_Ingame_Movie(_buf); } } diff --git a/code/movie.h b/code/movie.h index 9a067300c..1bfa77abb 100644 --- a/code/movie.h +++ b/code/movie.h @@ -20,6 +20,10 @@ template class DynamicVectorClass; extern DynamicVectorClass Movies; +// Room for the longest name the movie registry accepts, its ".VQA" and the terminator. +// RulesClass::Do_Movies reads a name into a 32 byte buffer, so 31 characters can arrive. +inline constexpr int MOVIE_FILENAME_SIZE = 36; + void Play_Movie(char const * name, ThemeType theme=THEME_NONE, bool clrscrn_after=true, bool stretch=true, bool clrscrn_before=true); void Play_Movie(VQType vq, ThemeType theme=THEME_NONE, bool clrscrn=true, bool stretch=true); void Play_Ingame_Movie(VQType vq); diff --git a/manual/content/formats/vqa.md b/manual/content/formats/vqa.md index 8328222d8..8572824de 100644 --- a/manual/content/formats/vqa.md +++ b/manual/content/formats/vqa.md @@ -92,9 +92,7 @@ Two entries in that list decide nothing. The drawing position is read only where A movie is read from the archive file itself rather than from a cached copy, so the position the reader seeks to is measured from the start of that file whether or not the archive was cached. [MIX archives](/formats/mix/) covers what caching does. -:::danger[A long movie name overruns the buffer the filename is built in] -The filename is assembled in a fixed twenty-byte buffer. `.VQA` takes four of those bytes and the string terminator a fifth, so a registered name of fifteen characters fills the buffer exactly and a sixteenth character writes one byte past its end. The registry accepts names of up to thirty-one characters, and playing a movie registered at that length writes sixteen bytes over whatever follows the buffer. The names the game ships with are all eight characters or fewer. -::: +The filename is built from the registered name and `.VQA` into a buffer sized for the longest name the registry accepts, so a name of any length the registry admits is truncated rather than written past the end. The names the game ships with are all eight characters or fewer. ## Sound and picture From e2330c4dc2dc91bc87d5efec1216a1fe4a2bbc8f Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 15:20:45 +0100 Subject: [PATCH 166/179] feat(ui): read the game's PCX pictures and SHP frames for documents --- code/shapeset.h | 9 ++ code/ui/uitexture.cpp | 275 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 273 insertions(+), 11 deletions(-) diff --git a/code/shapeset.h b/code/shapeset.h index b41c8d065..2e58df3b0 100644 --- a/code/shapeset.h +++ b/code/shapeset.h @@ -38,6 +38,8 @@ #include "rect.h" #include "rgb.h" +#include + /* ** This is the header that appears at the beginning of the ShapeSet file. The header @@ -168,6 +170,13 @@ class ShapeSet void Set_Size(short size) {Size = size;} }; + // A shape file is cast straight onto this class, so the frame records that follow the + // header keep their file widths and offsets. + static_assert(sizeof(ShapeRecord) == 24, "Shape frame record layout changed"); + static_assert(offsetof(ShapeRecord, Width) == 4, "Shape frame record layout changed"); + static_assert(offsetof(ShapeRecord, Color) == 12, "Shape frame record layout changed"); + static_assert(offsetof(ShapeRecord, Data) == 20, "Shape frame record layout changed"); + bool Is_Shape_Index_Valid(int index) const {return(unsigned(index) < unsigned(Count));} ShapeRecord const * Fetch_Record_Pointer(int shape) const diff --git a/code/ui/uitexture.cpp b/code/ui/uitexture.cpp index f7f6aceae..5ac571281 100644 --- a/code/ui/uitexture.cpp +++ b/code/ui/uitexture.cpp @@ -12,8 +12,18 @@ // exactly as a document does. // // Images resolve by extension. PNG and TGA decode through bimg, which bgfx already carries. -// The engine's own PCX and SHP artwork, and the surfaces the engine draws at run time, are -// not read here yet; the first screen that shows game art adds them. +// PCX goes through Read_PCX_File and SHP through ShapeSet, which is how the game's own +// artwork reaches a document. +// +// The source string is a file name followed by up to two '#' arguments: +// +// dbak6440.pcx the picture, through the palette it carries +// dbak6440.pcx#mousepal.pal the picture, through a named palette instead +// mouse.shp#0 one frame, through the game palette +// mouse.shp#0#mousepal.pal one frame, through a named palette +// +// A .pal file holds the six-bit guns the video hardware wanted, so its values are scaled +// the way init.cpp scales the palettes it loads. Shape index zero is transparent. // // docs/UI_DESIGN.md, "Assets and strings", owns the routing. @@ -21,15 +31,21 @@ #include "uiinternal.h" +#include "_palette.h" #include "ccfile.h" #include "dbgprint.h" +#include "dsurface.h" +#include "pcx.h" +#include "shapeset.h" #include #include #include +#include #include #include +#include static bx::DefaultAllocator _Allocator; @@ -85,21 +101,210 @@ static bool Read_Whole_File(char const * name, std::vector & byte /// -/// Reads an image a document referenced and hands back its pixels. +/// Reads one of the game's palette files. /// -/// The image source string as the document wrote it. -/// Receives premultiplied RGBA8 pixels, top row first. -/// bool; Was the image decoded? -bool UI_Decode_Image(char const * source, UIImageData & image) +/// bool; Was a full 256 colour palette read? +static bool Read_Palette_File(char const * name, PaletteClass & palette) { - std::string const name = Base_Name(source); - std::string const extension = Extension_Of(name.c_str()); + std::vector bytes; - if (extension != "png" && extension != "tga") { - DebugString("[UI] Image %s has no reader; only PNG and TGA are read so far.\n", name.c_str()); + if (!Read_Whole_File(name, bytes) || bytes.size() < 256 * 3) { return(false); } + for (int index = 0; index < 256; index++) { + palette[index] = RGBClass( + (unsigned char)(bytes[index * 3 + 0] << 2), + (unsigned char)(bytes[index * 3 + 1] << 2), + (unsigned char)(bytes[index * 3 + 2] << 2)); + } + + return(true); +} + + +static void Store_Opaque_Pixel(UIImageData & image, std::size_t offset, RGBClass const & color) +{ + image.Pixels[offset + 0] = (unsigned char)color.Get_Red(); + image.Pixels[offset + 1] = (unsigned char)color.Get_Green(); + image.Pixels[offset + 2] = (unsigned char)color.Get_Blue(); + image.Pixels[offset + 3] = 255; +} + + +/// +/// Decodes one of the game's PCX pictures. +/// +/// The file name, resolved through the game's file system. +/// A palette to use instead of the one the file carries, or an +/// empty string for the file's own. +/// bool; Was the picture decoded? +static bool Decode_PCX(std::string const & name, std::string const & palette_name, UIImageData & image) +{ + CCFileClass file(name.c_str()); + PaletteClass palette; + + Surface * picture = Read_PCX_File(file, &palette); + if (picture == NULL) { + return(false); + } + + if (!palette_name.empty() && !Read_Palette_File(palette_name.c_str(), palette)) { + DebugString("[UI] Palette %s could not be read for %s.\n", palette_name.c_str(), name.c_str()); + } + + image.Width = picture->Get_Width(); + image.Height = picture->Get_Height(); + + if (image.Width <= 0 || image.Height <= 0) { + delete picture; + return(false); + } + + image.Pixels.assign((std::size_t)image.Width * image.Height * 4, 0); + + int const stride = picture->Stride(); + unsigned char const * bits = (unsigned char const *)picture->Lock(); + + if (bits == NULL) { + delete picture; + return(false); + } + + if (picture->Bytes_Per_Pixel() == 1) { + for (int y = 0; y < image.Height; y++) { + unsigned char const * row = bits + (std::size_t)y * stride; + for (int x = 0; x < image.Width; x++) { + Store_Opaque_Pixel(image, ((std::size_t)y * image.Width + x) * 4, palette[row[x]]); + } + } + } else { + + // A three plane PCX is decoded straight into the primary's own packing, so the + // component masks the video mode established are what take it apart again. + for (int y = 0; y < image.Height; y++) { + unsigned short const * row = (unsigned short const *)(bits + (std::size_t)y * stride); + for (int x = 0; x < image.Width; x++) { + unsigned short const pixel = row[x]; + RGBClass const color( + (unsigned char)((pixel >> DSurface::RedRight) << DSurface::RedLeft), + (unsigned char)((pixel >> DSurface::GreenRight) << DSurface::GreenLeft), + (unsigned char)((pixel >> DSurface::BlueRight) << DSurface::BlueLeft)); + Store_Opaque_Pixel(image, ((std::size_t)y * image.Width + x) * 4, color); + } + } + } + + picture->Unlock(); + delete picture; + + return(true); +} + + +/// +/// Decodes one frame of a shape file into the shape's logical rectangle. +/// Index zero is transparent, so a frame keeps the position its sub-rectangle gives it and +/// the pixels around it stay clear. +/// +/// bool; Was the frame decoded? +static bool Decode_SHP(std::string const & name, int frame, std::string const & palette_name, UIImageData & image) +{ + std::vector bytes; + + if (!Read_Whole_File(name.c_str(), bytes) || bytes.size() < sizeof(ShapeSet)) { + return(false); + } + + PaletteClass palette = GamePalette; + if (!palette_name.empty() && !Read_Palette_File(palette_name.c_str(), palette)) { + DebugString("[UI] Palette %s could not be read for %s.\n", palette_name.c_str(), name.c_str()); + } + + ShapeSet const * shape = (ShapeSet const *)bytes.data(); + + image.Width = shape->Get_Width(); + image.Height = shape->Get_Height(); + + if (image.Width <= 0 || image.Height <= 0 || frame < 0 || frame >= shape->Get_Count()) { + return(false); + } + + image.Pixels.assign((std::size_t)image.Width * image.Height * 4, 0); + + Rect const rect = shape->Get_Rect(frame); + unsigned char const * data = (unsigned char const *)shape->Get_Data(frame); + + if (data == NULL || rect.Width <= 0 || rect.Height <= 0) { + return(true); + } + + if (rect.X < 0 || rect.Y < 0 + || rect.X + rect.Width > image.Width || rect.Y + rect.Height > image.Height) { + DebugString("[UI] Shape %s frame %d claims a rectangle outside its own bounds.\n", name.c_str(), frame); + return(false); + } + + bool const compressed = shape->Is_RLE_Compressed(frame); + unsigned char const * const end = bytes.data() + bytes.size(); + unsigned char const * line = data; + + for (int y = 0; y < rect.Height; y++) { + + // A compressed line starts with its own byte length and then runs of pixels, where a + // zero introduces a count of transparent ones. + unsigned char const * source = compressed ? line + sizeof(unsigned short) : data + (std::size_t)y * rect.Width; + int x = 0; + + while (x < rect.Width) { + + if (source >= end) { + DebugString("[UI] Shape %s frame %d runs past the end of the file.\n", name.c_str(), frame); + return(false); + } + + unsigned char const index = *source++; + + if (index == 0) { + if (compressed) { + if (source >= end) { + return(false); + } + unsigned char const run = *source++; + + // A zero length run would leave the line where it is, so it counts as one + // pixel rather than as a reason to stop moving. + x += run != 0 ? run : 1; + } else { + x++; + } + continue; + } + + std::size_t const offset = ((std::size_t)(rect.Y + y) * image.Width + (rect.X + x)) * 4; + Store_Opaque_Pixel(image, offset, palette[index]); + x++; + } + + if (compressed) { + if (line + sizeof(unsigned short) > end) { + return(false); + } + unsigned short length; + std::memcpy(&length, line, sizeof(length)); + if (length == 0) { + return(false); + } + line += length; + } + } + + return(true); +} + + +static bool Decode_Through_Bimg(std::string const & name, UIImageData & image) +{ std::vector bytes; if (!Read_Whole_File(name.c_str(), bytes)) { return(false); @@ -128,5 +333,53 @@ bool UI_Decode_Image(char const * source, UIImageData & image) image.Pixels[pixel + 2] = (unsigned char)((image.Pixels[pixel + 2] * alpha + 127) / 255); } + return(true); +} + + +/// +/// Reads an image a document referenced and hands back its pixels. +/// +/// The image source string as the document wrote it. +/// Receives premultiplied RGBA8 pixels, top row first. +/// bool; Was the image decoded? +bool UI_Decode_Image(char const * source, UIImageData & image) +{ + std::string const request = Base_Name(source); + + std::string name = request; + std::string first; + std::string second; + + std::size_t mark = name.find('#'); + if (mark != std::string::npos) { + first = name.substr(mark + 1); + name = name.substr(0, mark); + + mark = first.find('#'); + if (mark != std::string::npos) { + second = first.substr(mark + 1); + first = first.substr(0, mark); + } + } + + std::string const extension = Extension_Of(name.c_str()); + bool decoded = false; + + if (extension == "pcx") { + decoded = Decode_PCX(name, first, image); + } else if (extension == "shp") { + decoded = Decode_SHP(name, std::atoi(first.c_str()), second, image); + } else if (extension == "png" || extension == "tga") { + decoded = Decode_Through_Bimg(name, image); + } else { + DebugString("[UI] Image %s has no reader.\n", request.c_str()); + return(false); + } + + if (!decoded) { + return(false); + } + return(image.Width > 0 && image.Height > 0); } From a0c30a945d13705807140254939b1fd9993e6036 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 15:42:50 +0100 Subject: [PATCH 167/179] feat(ui): blit the original dialog artwork in the RmlUi screens --- docs/UI_DESIGN.md | 35 +++++--- ui/campaign.rcss | 1 - ui/campaign.rml | 2 +- ui/desyncbase.rcss | 13 ++- ui/gamecontrols.rcss | 1 - ui/gamecontrolsmp.rcss | 1 - ui/gamecontrolswol.rcss | 1 - ui/gameoptionswol.rcss | 8 +- ui/keyboard.rcss | 63 ++++---------- ui/lobbybase.rcss | 67 +++++---------- ui/mainmenu.rml | 2 +- ui/mapgenbase.rcss | 57 +++++------- ui/messagebox.rcss | 34 +------- ui/messagebox.rml | 3 +- ui/mpguest.rcss | 4 +- ui/mpselect.rml | 2 +- ui/mpselectfs.rml | 2 +- ui/options.rml | 2 +- ui/optionsbase.rcss | 186 +++++++++++++++++++++++----------------- ui/progresswait.rcss | 14 +-- ui/progresswait.rml | 3 +- ui/reconnect.rcss | 9 +- ui/savebrowser.rcss | 13 ++- ui/selectmap.rcss | 7 +- ui/skirmish.rcss | 66 ++++---------- ui/sound.rcss | 138 ++++++----------------------- ui/sound.rml | 3 +- ui/soundlite.rcss | 76 +--------------- ui/soundlite.rml | 3 +- ui/version.rcss | 40 +-------- ui/version.rml | 5 +- ui/waitbox.rcss | 29 +------ ui/waitbox.rml | 5 +- 33 files changed, 281 insertions(+), 614 deletions(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index bbab3bbe2..230cc1e55 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -13,9 +13,9 @@ geometry's indices in a static buffer but streams its vertices through a transient one, because the program the overlays share is bgfx's embedded imgui shader, whose vertex stage multiplies by `u_viewProj` alone and so ignores the per-draw model transform; a program with a model transform restores the static -vertex buffer the renderer table describes. `uitexture.cpp` reads PNG and -TGA only, so PCX and SHP files wait for the first screen that shows game art, -and the cursor and clipboard requests are recorded rather than acted on. Step 6 +vertex buffer the renderer table describes. `uitexture.cpp` read PNG and TGA +only until the dialog artwork needed PCX and SHP, and the cursor and clipboard +requests are recorded rather than acted on. Step 6 brought the `` element, which is the other route to game art: pixels the engine draws rather than a file a document names. @@ -26,9 +26,9 @@ legacy dialog, and the keyboard queue is cleared as the scope opens and closes. The version screen needed no name table, because it composes its own text and takes its one string-table entry through `Fetch_String`, which already yields UTF-8 on a build whose active code page is 65001; a document that writes -`[[TXT_OK]]` waited for the name table step 4 brought. The screen draws its -panel rather than blitting `dbak6440.pcx`, which waits for PCX decoding with the rest of the -game art. +`[[TXT_OK]]` waited for the name table step 4 brought. The screen drew its +panel rather than blitting `dbak6440.pcx` until PCX decoding arrived with the rest of the +game art; it blits it now. Step 4 put the runner under load. `UI_Run_Modal` runs the game as well as a screen: in a network session it steps `Main_Loop` between passes and reports a @@ -98,10 +98,14 @@ OwnerDraw was the largest and the least portable. Each dialog was a real Win32 child window of `MainWindow`, created from a resource template by `CreateDialogIndirectParam`. Every control is subclassed; its window procedure paints into `AlternateSurface` and blits the result into `VisibleSurface` -itself. `Draw_Dialog_Back` composes `dbak6440.pcx`, the side bars, and sixteen -glow passes into a cached surface and assumes 640x400 art centered on the -screen. Text is GDI "MS Sans Serif" at 14 and 12 pixels through `WS_Get_Font`, -plus the `dlgsys` remap sheets for list text. Tooltips save and restore the +itself. `Draw_Dialog_Back` composes `dbak6440.pcx`, the two side bars, the four +`bar_` corner pieces and sixteen glow passes into a cached surface and assumes +640x400 art centered on the screen. Buttons, check boxes, static captions, tabs +and combo boxes draw their text from the `dlgsys` remap sheets; list boxes, +tooltips and the hotkey control use GDI "MS Sans Serif" at 14 and 12 pixels +through `WS_Get_Font`. Text is `RGB(112,255,0)` and `RGB(144,144,144)` when +disabled; a control that stands over the wallpaper shows it blended 180/255 +toward black. Tooltips save and restore the pixels under them. `Heal_Dialog_Controls` forces every child window to repaint after each `Update_Visible_Surface`, so a dialog repaints once per game frame. The templates hold 322 `CONTROL` entries: 103 owner-draw buttons, 40 track @@ -587,9 +591,14 @@ required document, style, or font fails preparation with the name reported. Images resolve by extension. PNG and TGA decode through `bimg_decode`, which is already vendored and needs only linking. PCX goes through `Read_PCX_File` -with the palette named in the source string. SHP frames use a -`name.shp#frame` form with an optional palette, decoded to RGBA with index -zero transparent. Surfaces the engine draws at runtime (the map preview, the +and uses the palette the file carries, or the one a `name.pcx#palette.pal` +source names instead. SHP frames use a `name.shp#frame` form, with +`name.shp#frame#palette.pal` naming a palette and `GamePalette` standing in +when none is named, decoded to RGBA with index zero transparent. A `.pal` file +holds six-bit guns, so its values are scaled the way `init.cpp` scales the +palettes it loads. A source whose file cannot be read leaves the element with +whatever its background and border draw, which is why the dialog panel and +button in `ui/optionsbase.rcss` keep a flat colour under their artwork. Surfaces the engine draws at runtime (the map preview, the desync host icons, a progress bar) reach a document through a `` custom element bound to a named provider; the shell re-uploads the texture when the provider marks it dirty. A document writes `` diff --git a/ui/campaign.rcss b/ui/campaign.rcss index 91dab2e0f..16c569509 100644 --- a/ui/campaign.rcss +++ b/ui/campaign.rcss @@ -64,7 +64,6 @@ height: 24.375dp; } -.slider slidertrack { margin-top: 9.1875dp; } /* OK and Cancel, both 50 x 16 dialog units at y 121. */ .button diff --git a/ui/campaign.rml b/ui/campaign.rml index c42d6bf3e..4581486ad 100644 --- a/ui/campaign.rml +++ b/ui/campaign.rml @@ -5,7 +5,7 @@ -
+
Select Campaign:
diff --git a/ui/desyncbase.rcss b/ui/desyncbase.rcss index 45ddad102..11ba3a4fd 100644 --- a/ui/desyncbase.rcss +++ b/ui/desyncbase.rcss @@ -27,7 +27,7 @@ height: 16.25dp; line-height: 16.25dp; text-align: center; - color: #e4e6da; + color: #70ff00; } /* "Players:" at 30, 23 and the 120 x 105 seat list at 30, 35. */ @@ -54,7 +54,7 @@ top: 0dp; width: 16dp; text-align: center; - color: #e4e6da; + color: #70ff00; } #players .name @@ -88,7 +88,7 @@ box-sizing: border-box; left: 240dp; width: 270dp; - color: #b9bcae; + color: #70ff00; line-height: 16dp; } @@ -122,12 +122,9 @@ width: 223.5dp; height: 19.5dp; - background-color: #14160f; + background-color: #000000b4; border-width: 1dp; - border-top-color: #10120e; - border-left-color: #10120e; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + border-color: #ffffff; } /* The bar shrinks as the load nears, which is what Draw_Countdown_Bar drew straight into diff --git a/ui/gamecontrols.rcss b/ui/gamecontrols.rcss index faffa6bb3..cfd458e64 100644 --- a/ui/gamecontrols.rcss +++ b/ui/gamecontrols.rcss @@ -23,7 +23,6 @@ height: 21.125dp; } -.slider slidertrack { margin-top: 7.5dp; } #speed { top: 17.5dp; } #scroll { top: 53.25dp; } diff --git a/ui/gamecontrolsmp.rcss b/ui/gamecontrolsmp.rcss index c37fe478a..23e3f7e30 100644 --- a/ui/gamecontrolsmp.rcss +++ b/ui/gamecontrolsmp.rcss @@ -21,7 +21,6 @@ height: 24.375dp; } -.slider slidertrack { margin-top: 9.2dp; } #speed { top: 17.5dp; } #scroll { top: 67.875dp; } diff --git a/ui/gamecontrolswol.rcss b/ui/gamecontrolswol.rcss index 7d10bc4d0..0dad687cf 100644 --- a/ui/gamecontrolswol.rcss +++ b/ui/gamecontrolswol.rcss @@ -23,7 +23,6 @@ height: 24.375dp; } -.slider slidertrack { margin-top: 9.2dp; } #scroll { top: 17.5dp; } #detail { top: 67.875dp; } diff --git a/ui/gameoptionswol.rcss b/ui/gameoptionswol.rcss index a525de1be..3a647cc12 100644 --- a/ui/gameoptionswol.rcss +++ b/ui/gameoptionswol.rcss @@ -43,12 +43,9 @@ line-height: 14dp; padding-left: 8dp; - color: #b9bcae; + color: #70ff00; border-width: 1dp; - border-top-color: #10120e; - border-left-color: #10120e; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + border-color: #ffffff; } /* Two track bars, 148 x 13 dialog units at x 95. */ @@ -59,7 +56,6 @@ height: 21.125dp; } -.slider slidertrack { margin-top: 7.5dp; } #connection { top: 176.75dp; } #speed { top: 212.5dp; } diff --git a/ui/keyboard.rcss b/ui/keyboard.rcss index 58953f7bf..a31749fce 100644 --- a/ui/keyboard.rcss +++ b/ui/keyboard.rcss @@ -49,13 +49,10 @@ width: 207dp; height: 20dp; - color: #b9bcae; - background-color: #2b2f25; - border-width: 2dp; - border-top-color: #14160f; - border-left-color: #14160f; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + color: #70ff00; + background-color: #000000b4; + border-width: 1dp; + border-color: #ffffff; } #category selectvalue @@ -74,20 +71,7 @@ width: 16dp; height: 16dp; - background-color: #33372c; - border-width: 2dp; - border-top-color: #767c68; - border-left-color: #767c68; - border-right-color: #14160f; - border-bottom-color: #14160f; -} - -#category selectarrow:active -{ - border-top-color: #14160f; - border-left-color: #14160f; - border-right-color: #767c68; - border-bottom-color: #767c68; + decorator: image(dnarrowr.pcx); } /* The dropped list, capped at the 146 dialog units the template gives it. */ @@ -97,12 +81,9 @@ max-height: 237.25dp; overflow-y: auto; - background-color: #14160f; + background-color: #000000b4; border-width: 1dp; - border-top-color: #6e7360; - border-left-color: #6e7360; - border-right-color: #10120e; - border-bottom-color: #10120e; + border-color: #ffffff; } #category selectbox option @@ -112,14 +93,14 @@ line-height: 20dp; padding: 0dp 4dp; white-space: nowrap; - color: #b9bcae; + color: #70ff00; } -#category selectbox option:hover { color: #e4e6da; } +#category selectbox option:hover { color: #70ff00; } #category selectbox option:checked { - background-color: #3f4536; - color: #e4e6da; + background-color: #225061; + color: #70ff00; } /* The command list, 146 x 104 dialog units at 168, 41. */ @@ -149,12 +130,9 @@ line-height: 14dp; padding-left: 8dp; - color: #b9bcae; + color: #70ff00; border-width: 1dp; - border-top-color: #10120e; - border-left-color: #10120e; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + border-color: #ffffff; } #description @@ -167,7 +145,7 @@ width: 190.5dp; height: 68.25dp; overflow: hidden; - color: #d6d8cc; + color: #70ff00; } /* The capture control, 85 x 14 dialog units at 22, 131. It stands where msctls_hotkey32 @@ -187,16 +165,13 @@ white-space: nowrap; overflow: hidden; - color: #e4e6da; - background-color: #14160f; - border-width: 2dp; - border-top-color: #10120e; - border-left-color: #10120e; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + color: #70ff00; + background-color: #000000b4; + border-width: 1dp; + border-color: #ffffff; } -#capture:focus { background-color: #1d2017; } +#capture:focus { background-color: #000000b4; } /* Assign, Reset All, OK and Cancel. */ .button { height: 22.75dp; line-height: 22.75dp; } diff --git a/ui/lobbybase.rcss b/ui/lobbybase.rcss index 12758c3f0..e595f6803 100644 --- a/ui/lobbybase.rcss +++ b/ui/lobbybase.rcss @@ -19,7 +19,7 @@ position: absolute; box-sizing: border-box; - background-color: #14160f; + background-color: #000000b4; overflow-y: auto; overflow-x: hidden; } @@ -30,7 +30,7 @@ box-sizing: border-box; padding: 0dp 2dp; line-height: 14dp; - color: #b9bcae; + color: #70ff00; } /* The player list's own columns. The two setup dialogs register them with OD_ADDCOLUMN at @@ -63,7 +63,7 @@ width: 37dp; white-space: nowrap; overflow: hidden; - color: #949a84; + color: #70ff00; } /* The host and accepted markers, which the list drew as the wolhost.pcx and wolacpt.pcx @@ -77,7 +77,7 @@ top: 0dp; width: 20dp; text-align: center; - color: #e4e6da; + color: #70ff00; } /* The chat entry, where the templates put an EDITTEXT. It states a width, because a field @@ -93,16 +93,13 @@ padding: 0dp 4dp; font-family: LatoLatin; - color: #e4e6da; - background-color: #14160f; - border-width: 2dp; - border-top-color: #10120e; - border-left-color: #10120e; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + color: #70ff00; + background-color: #000000b4; + border-width: 1dp; + border-color: #ffffff; } -.field:focus { background-color: #1d2017; } +.field:focus { background-color: #000000b4; } /* A CBS_DROPDOWNLIST combo, sized the way ownrdraw.cpp sizes one: the item height, which is the 14 pixel dialog font plus two, inside a two pixel border. The template's own height is @@ -114,13 +111,10 @@ box-sizing: border-box; height: 20dp; - color: #b9bcae; - background-color: #2b2f25; - border-width: 2dp; - border-top-color: #14160f; - border-left-color: #14160f; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + color: #70ff00; + background-color: #000000b4; + border-width: 1dp; + border-color: #ffffff; } .combo selectvalue @@ -139,20 +133,7 @@ width: 16dp; height: 16dp; - background-color: #33372c; - border-width: 2dp; - border-top-color: #767c68; - border-left-color: #767c68; - border-right-color: #14160f; - border-bottom-color: #14160f; -} - -.combo selectarrow:active -{ - border-top-color: #14160f; - border-left-color: #14160f; - border-right-color: #767c68; - border-bottom-color: #767c68; + decorator: image(dnarrowr.pcx); } .combo selectbox @@ -160,12 +141,9 @@ width: 114dp; overflow-y: auto; - background-color: #14160f; + background-color: #000000b4; border-width: 1dp; - border-top-color: #6e7360; - border-left-color: #6e7360; - border-right-color: #10120e; - border-bottom-color: #10120e; + border-color: #ffffff; } .combo selectbox option @@ -175,11 +153,11 @@ line-height: 18dp; padding: 0dp 4dp; white-space: nowrap; - color: #b9bcae; + color: #70ff00; } -.combo selectbox option:hover { color: #e4e6da; } -.combo selectbox option:checked { background-color: #3f4536; } +.combo selectbox option:hover { color: #70ff00; } +.combo selectbox option:checked { background-color: #225061; } /* The preview frame, a GROUPBOX 126 x 73 dialog units at 281, 138 on both setup templates. The picture inside it is drawn by the screen and reaches the document through the @@ -194,12 +172,9 @@ width: 189dp; height: 118.625dp; - background-color: #14160f; + background-color: #000000b4; border-width: 1dp; - border-top-color: #10120e; - border-left-color: #10120e; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + border-color: #ffffff; } .previewframe surface diff --git a/ui/mainmenu.rml b/ui/mainmenu.rml index 8d5e89561..5b6156f80 100644 --- a/ui/mainmenu.rml +++ b/ui/mainmenu.rml @@ -5,7 +5,7 @@ -
+
New Campaign
Load Mission
Multiplayer Game
diff --git a/ui/mapgenbase.rcss b/ui/mapgenbase.rcss index ef808da8a..6493fa446 100644 --- a/ui/mapgenbase.rcss +++ b/ui/mapgenbase.rcss @@ -9,7 +9,7 @@ /* A caption the templates give SS_CENTERIMAGE, so its text sits in the middle of the box the template declares rather than at the top of it. */ -.caption { color: #b9bcae; } +.caption { color: #70ff00; } /* The preview frame, a GROUPBOX 197 x 134 dialog units on all three templates. The picture inside it is drawn by the screen and reaches the document through the element, @@ -22,12 +22,9 @@ width: 295.5dp; height: 217.75dp; - background-color: #14160f; + background-color: #000000b4; border-width: 1dp; - border-top-color: #10120e; - border-left-color: #10120e; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + border-color: #ffffff; } #previewframe surface @@ -46,7 +43,7 @@ left: 0dp; width: 100%; text-align: center; - color: #6e7360; + color: #909090; } /* The seed field, an ES_NUMBER edit all three templates declare NOT WS_VISIBLE and @@ -66,22 +63,19 @@ padding: 0dp 4dp; font-family: LatoLatin; - color: #e4e6da; - background-color: #14160f; - border-width: 2dp; - border-top-color: #10120e; - border-left-color: #10120e; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + color: #70ff00; + background-color: #000000b4; + border-width: 1dp; + border-color: #ffffff; } -#seed:focus { background-color: #1d2017; } +#seed:focus { background-color: #000000b4; } /* A control the tournament territory has fixed. It is shown rather than hidden, so the screen keeps its shape whatever the territory allows. */ .locked { - color: #6b6e63; + color: #909090; pointer-events: none; } @@ -98,13 +92,10 @@ box-sizing: border-box; height: 20dp; - color: #b9bcae; - background-color: #2b2f25; - border-width: 2dp; - border-top-color: #14160f; - border-left-color: #14160f; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + color: #70ff00; + background-color: #000000b4; + border-width: 1dp; + border-color: #ffffff; } .combo selectvalue @@ -123,12 +114,7 @@ width: 16dp; height: 16dp; - background-color: #33372c; - border-width: 2dp; - border-top-color: #767c68; - border-left-color: #767c68; - border-right-color: #14160f; - border-bottom-color: #14160f; + decorator: image(dnarrowr.pcx); } .combo selectbox @@ -136,12 +122,9 @@ width: 100%; overflow-y: auto; - background-color: #14160f; + background-color: #000000b4; border-width: 1dp; - border-top-color: #6e7360; - border-left-color: #6e7360; - border-right-color: #10120e; - border-bottom-color: #10120e; + border-color: #ffffff; } .combo selectbox option @@ -151,11 +134,11 @@ line-height: 18dp; padding: 0dp 4dp; white-space: nowrap; - color: #b9bcae; + color: #70ff00; } -.combo selectbox option:hover { color: #e4e6da; } -.combo selectbox option:checked { background-color: #3f4536; } +.combo selectbox option:hover { color: #70ff00; } +.combo selectbox option:checked { background-color: #225061; } /* The five buttons across the bottom row, 65 x 14 dialog units at y 220, and the two above the preview, 88 x 14. */ diff --git a/ui/messagebox.rcss b/ui/messagebox.rcss index 29ce1fe0c..4ddd6608d 100644 --- a/ui/messagebox.rcss +++ b/ui/messagebox.rcss @@ -3,16 +3,13 @@ child's offset taken from the panel's content box and a bordered element's declared size taken inside its own border. - The panel is drawn rather than blitted because the dialog's own background is a PCX, and - PCX decoding arrives with the first screen that shows game art. Everything here stays - inside the styling profile docs/UI_DESIGN.md declares: text, ordinary layout, borders and - basic decorators, with no filter, layer, shader or transform. */ + The dialog look, including the panel artwork, comes from ui/optionsbase.rcss. */ body { font-family: LatoLatin; font-size: 12dp; - color: #d6d8cc; + color: #70ff00; width: 100%; height: 100%; @@ -31,13 +28,6 @@ body width: 386dp; height: 132.5dp; - - background-color: #23261fF2; - border-width: 2dp; - border-top-color: #6e7360; - border-left-color: #6e7360; - border-right-color: #10120e; - border-bottom-color: #10120e; } /* The message: 22, 12, 216 x 38 dialog units, centred both ways as the template's CTEXT with @@ -67,14 +57,6 @@ body height: 18.75dp; line-height: 18.75dp; text-align: center; - - color: #e4e6da; - background-color: #33372c; - border-width: 2dp; - border-top-color: #767c68; - border-left-color: #767c68; - border-right-color: #14160f; - border-bottom-color: #14160f; } .first { left: 31dp; } @@ -84,16 +66,4 @@ body /* A lone button takes the middle slot, as the dialog moved it there. */ .first.centred { left: 148dp; } -.button:hover -{ - background-color: #474d3d; -} -.button:active -{ - background-color: #2a2e24; - border-top-color: #14160f; - border-left-color: #14160f; - border-right-color: #767c68; - border-bottom-color: #767c68; -} diff --git a/ui/messagebox.rml b/ui/messagebox.rml index 5162f8bd1..ebea98c90 100644 --- a/ui/messagebox.rml +++ b/ui/messagebox.rml @@ -1,10 +1,11 @@ Message + -
+
{{ message }}
{{ button1 }}
{{ button3 }}
diff --git a/ui/mpguest.rcss b/ui/mpguest.rcss index ce6d0938b..640d88495 100644 --- a/ui/mpguest.rcss +++ b/ui/mpguest.rcss @@ -92,11 +92,11 @@ /* What WS_DISABLED looks like: shown, dimmed and out of reach. */ .off { - color: #6b6e63; + color: #909090; pointer-events: none; } -.off sliderbar { background-color: #3f4336; border-top-color: #5c6152; border-left-color: #5c6152; } +.off sliderbar { background-color: #000000b4; border-color: #ffffff; } /* Cancel and Accept, both 58 x 18 dialog units at 279, 215 and 350, 215. */ .button { height: 29.25dp; line-height: 29.25dp; width: 87dp; } diff --git a/ui/mpselect.rml b/ui/mpselect.rml index 1351fc499..2149d1e87 100644 --- a/ui/mpselect.rml +++ b/ui/mpselect.rml @@ -5,7 +5,7 @@ -
+
Select Multiplayer Game
Internet
Modem / Serial
diff --git a/ui/mpselectfs.rml b/ui/mpselectfs.rml index 0e9ea001b..d1891e4e3 100644 --- a/ui/mpselectfs.rml +++ b/ui/mpselectfs.rml @@ -5,7 +5,7 @@ -
+
Select Multiplayer Game
Internet
World Domination! (Internet)
diff --git a/ui/options.rml b/ui/options.rml index d3dbb77c0..b43a3c72e 100644 --- a/ui/options.rml +++ b/ui/options.rml @@ -5,7 +5,7 @@ -
+
Game Settings
Display
Sound
diff --git a/ui/optionsbase.rcss b/ui/optionsbase.rcss index 255876966..81733cc87 100644 --- a/ui/optionsbase.rcss +++ b/ui/optionsbase.rcss @@ -1,35 +1,72 @@ /* What every options family document looks like. Only the look lives here; each document's own stylesheet carries its geometry, converted from its dialog template's units. - The palette and the raised and sunken borders are the ones ui/sound.rcss established for - the same family of dialogs. The panel is drawn rather than blitted because the dialogs' - own background is a PCX, and PCX decoding arrives with the first screen that shows game - art. Everything here stays inside the styling profile docs/UI_DESIGN.md declares: text, - ordinary layout, borders and basic decorators, with no filter, layer, shader or - transform. */ + The artwork is the game's own. A dialog backdrop is the slice of dbak6440.pcx that lies + under the dialog, with leftbar.pcx and rightbar.pcx tiled down its edges and the four + bar_ corner pieces over them, which is what Draw_Dialog_Back composed. A push button is + bue_li24, bue_mi24 and bue_ri24 in a row, and bde_ the same while it is held down. A + check box is cue_i.pcx, or cce_i.pcx when it is ticked, beside its caption. The colours + are the ones OwnerDraw::Initialize set: text is RGB(112,255,0), disabled text is + RGB(144,144,144), a track bar's frame is RGB(78,182,220) and a picked list row is + RGB(34,80,97). A control that stood over the wallpaper -- a list, a field, a combo box, + a track bar -- showed it blended 180/255 toward black. + + Two things the original did that are not reproduced. The sixteen inward glow passes + around a dialog edge need a fade over sixteen pixels, which no decorator in the profile + docs/UI_DESIGN.md declares can express. A disabled button is the enabled artwork under + image-color rather than under a half-black rectangle; the result is the same product. + + Everything here stays inside that profile: text, images, ordinary layout, borders and + basic decorators, with no filter, layer, shader or transform. */ body { font-family: LatoLatin; font-size: 12dp; - color: #d6d8cc; + color: #70ff00; width: 100%; height: 100%; } -/* A dialog panel. A document states its own size and where it sits. */ +/* A dialog panel. A document states its own size and where it sits. The border keeps the + width the templates were converted against; the artwork is painted over all of it. */ .panel { display: block; position: absolute; - background-color: #23261fF2; border-width: 2dp; - border-top-color: #6e7360; - border-left-color: #6e7360; - border-right-color: #10120e; - border-bottom-color: #10120e; + border-color: transparent; + + /* The colour is what the panel is left with if the artwork cannot be read, which is what + a document shown before the mix files are mounted would see. A background paints under + the decorators, so it is invisible whenever they load. */ + background-color: #23261fF2; + + decorator: + image(bar_ul.pcx scale-none left top) border-box, + image(bar_ur.pcx scale-none right top) border-box, + image(bar_ll.pcx scale-none left bottom) border-box, + image(bar_lr.pcx scale-none right bottom) border-box, + image(leftbar.pcx repeat-y left top) border-box, + image(rightbar.pcx repeat-y right top) border-box, + image(dbak6440.pcx scale-none 50% 50%) border-box; +} + +/* A panel the main menu family raises above the middle of the frame. Its wallpaper slice + is taken 147dp above its own top edge, which is where the frame's centred artwork sits + under a panel whose top is 53dp above the middle. */ +.panel.raised +{ + decorator: + image(bar_ul.pcx scale-none left top) border-box, + image(bar_ur.pcx scale-none right top) border-box, + image(bar_ll.pcx scale-none left bottom) border-box, + image(bar_lr.pcx scale-none right bottom) border-box, + image(leftbar.pcx repeat-y left top) border-box, + image(rightbar.pcx repeat-y right top) border-box, + image(dbak6440.pcx scale-none 50% 91%) border-box; } /* An owner-draw push button. */ @@ -45,63 +82,51 @@ body white-space: nowrap; overflow: hidden; - color: #e4e6da; + color: #70ff00; background-color: #33372c; - border-width: 2dp; - border-top-color: #767c68; - border-left-color: #767c68; - border-right-color: #14160f; - border-bottom-color: #14160f; + decorator: tiled-horizontal(bue_li24.pcx, bue_mi24.pcx, bue_ri24.pcx); } -.button:hover { background-color: #474d3d; } - .button:active { - background-color: #2a2e24; - border-top-color: #14160f; - border-left-color: #14160f; - border-right-color: #767c68; - border-bottom-color: #767c68; + decorator: tiled-horizontal(bde_li24.pcx, bde_mi24.pcx, bde_ri24.pcx); } .button.disabled, .disabled .button { - color: #6b6e63; - background-color: #2a2d24; + color: #909090; + image-color: #808080; pointer-events: none; } -/* A BS_FLAT check box, which reads as pressed in rather than as a tick beside a caption. */ +/* A BS_AUTOCHECKBOX with BS_FLAT: an eighteen pixel box at the left edge of the control + with the caption beside it, which is what CheckBoxCtrlProc drew. */ .check { display: block; position: absolute; box-sizing: border-box; - text-align: center; + text-align: left; + padding-left: 26dp; white-space: nowrap; overflow: hidden; - color: #b9bcae; - background-color: #2b2f25; - border-width: 2dp; - border-top-color: #5c6152; - border-left-color: #5c6152; - border-right-color: #14160f; - border-bottom-color: #14160f; + color: #70ff00; + decorator: image(cue_i.pcx contain left center); } -.check:hover { background-color: #3b4032; } - .check.ticked { - color: #e4e6da; - background-color: #4a5140; - border-top-color: #14160f; - border-left-color: #14160f; - border-right-color: #767c68; - border-bottom-color: #767c68; + decorator: image(cce_i.pcx contain left center); +} + +.check.disabled, +.disabled .check +{ + color: #909090; + image-color: #808080; + pointer-events: none; } /* A static caption. A document says where it sits and how it is aligned. */ @@ -113,11 +138,17 @@ body overflow: hidden; } -/* A TBS_NOTICKS track bar: a plain groove with a thumb. */ +/* A TBS_NOTICKS track bar. The original drew no groove: the control is the wallpaper + dimmed inside a one pixel frame, with trakgrip.pcx spanning its whole height. */ .slider { display: block; position: absolute; + box-sizing: border-box; + + background-color: #000000b4; + border-width: 1dp; + border-color: #4eb6dc; } .slider slider @@ -128,31 +159,16 @@ body .slider sliderbar { - width: 14dp; + width: 12dp; height: 100%; - background-color: #5c6152; - border-width: 2dp; - border-top-color: #949a84; - border-left-color: #949a84; - border-right-color: #14160f; - border-bottom-color: #14160f; + decorator: image(trakgrip.pcx); } -.slider sliderbar:hover { background-color: #6f7563; } -.slider sliderbar:active { background-color: #4a4e41; } - .slider slidertrack { width: 100%; - height: 6dp; - - background-color: #14160f; - border-width: 1dp; - border-top-color: #10120e; - border-left-color: #10120e; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + height: 100%; } .slider sliderarrowdec, @@ -168,8 +184,11 @@ body { display: block; position: absolute; + box-sizing: border-box; - background-color: #14160f; + background-color: #000000b4; + border-width: 1dp; + border-color: #ffffff; overflow-y: auto; overflow-x: hidden; } @@ -180,39 +199,44 @@ body box-sizing: border-box; padding: 1dp 4dp; white-space: nowrap; - color: #b9bcae; + color: #70ff00; } -.list .row:hover { color: #e4e6da; } - .list .row.picked { - background-color: #3f4536; - color: #e4e6da; + background-color: #225061; } .list scrollbarvertical { - width: 12dp; - background-color: #23261f; + width: 18dp; + background-color: #000000b4; } .list scrollbarvertical slidertrack { - width: 12dp; + width: 18dp; margin-top: 0dp; - background-color: #14160f; + background-color: transparent; border-width: 0dp; } .list scrollbarvertical sliderbar { - width: 12dp; + width: 18dp; min-height: 20dp; - background-color: #5c6152; - border-width: 2dp; - border-top-color: #949a84; - border-left-color: #949a84; - border-right-color: #14160f; - border-bottom-color: #14160f; + + decorator: tiled-vertical(sbgript.pcx, sbgripm.pcx, sbgripb.pcx); } + +.list scrollbarvertical sliderarrowdec, +.list scrollbarvertical sliderarrowinc +{ + width: 18dp; + height: 22dp; +} + +.list scrollbarvertical sliderarrowdec { decorator: image(uparrowr.pcx); } +.list scrollbarvertical sliderarrowinc { decorator: image(dnarrowr.pcx); } +.list scrollbarvertical sliderarrowdec:active { decorator: image(uparrowp.pcx); } +.list scrollbarvertical sliderarrowinc:active { decorator: image(dnarrowp.pcx); } diff --git a/ui/progresswait.rcss b/ui/progresswait.rcss index c71216f19..ca77700df 100644 --- a/ui/progresswait.rcss +++ b/ui/progresswait.rcss @@ -11,7 +11,7 @@ body { font-family: LatoLatin; font-size: 12dp; - color: #d6d8cc; + color: #70ff00; width: 100%; height: 100%; @@ -30,13 +30,6 @@ body width: 284dp; height: 82.125dp; - - background-color: #23261fF2; - border-width: 2dp; - border-top-color: #6e7360; - border-left-color: #6e7360; - border-right-color: #10120e; - border-bottom-color: #10120e; } /* 22, 12, 148 x 11 dialog units. CTEXT with SS_CENTERIMAGE, so the caption is centred both @@ -71,10 +64,7 @@ body text-align: center; border-width: 1dp; - border-top-color: #10120e; - border-left-color: #10120e; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + border-color: #ffffff; } surface diff --git a/ui/progresswait.rml b/ui/progresswait.rml index e96dc9a85..636f6ab5c 100644 --- a/ui/progresswait.rml +++ b/ui/progresswait.rml @@ -1,10 +1,11 @@ Working + -
+
{{ caption }}
diff --git a/ui/reconnect.rcss b/ui/reconnect.rcss index 1a6f34477..de86c564a 100644 --- a/ui/reconnect.rcss +++ b/ui/reconnect.rcss @@ -47,12 +47,9 @@ width: 60dp; height: 16.25dp; - background-color: #14160f; + background-color: #000000b4; border-width: 1dp; - border-top-color: #10120e; - border-left-color: #10120e; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + border-color: #ffffff; } /* The bar shrinks and changes color as the wait on its seat drags on, which is what @@ -77,7 +74,7 @@ width: 418.5dp; height: 13dp; line-height: 13dp; - color: #e4e6da; + color: #70ff00; } /* The message list, 295 x 84 units at 22, 103. It is a LBS_NOSEL list box, which is what diff --git a/ui/savebrowser.rcss b/ui/savebrowser.rcss index 531ec053a..1e362371e 100644 --- a/ui/savebrowser.rcss +++ b/ui/savebrowser.rcss @@ -66,13 +66,10 @@ white-space: nowrap; overflow: hidden; - color: #e4e6da; - background-color: #14160f; - border-width: 2dp; - border-top-color: #10120e; - border-left-color: #10120e; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + color: #70ff00; + background-color: #000000b4; + border-width: 1dp; + border-color: #ffffff; } -.field:focus { background-color: #1d2017; } +.field:focus { background-color: #000000b4; } diff --git a/ui/selectmap.rcss b/ui/selectmap.rcss index 25b92eb54..e702957e2 100644 --- a/ui/selectmap.rcss +++ b/ui/selectmap.rcss @@ -56,12 +56,9 @@ width: 193.5dp; height: 130dp; - background-color: #14160f; + background-color: #000000b4; border-width: 1dp; - border-top-color: #10120e; - border-left-color: #10120e; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + border-color: #ffffff; } /* The picture takes its size from the provider, which is the frame's own extents in game diff --git a/ui/skirmish.rcss b/ui/skirmish.rcss index 08aed39cd..586b7239a 100644 --- a/ui/skirmish.rcss +++ b/ui/skirmish.rcss @@ -49,16 +49,13 @@ overflow: hidden; font-family: LatoLatin; - color: #e4e6da; - background-color: #14160f; - border-width: 2dp; - border-top-color: #10120e; - border-left-color: #10120e; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + color: #70ff00; + background-color: #000000b4; + border-width: 1dp; + border-color: #ffffff; } -#name:focus { background-color: #1d2017; } +#name:focus { background-color: #000000b4; } /* The two CBS_DROPDOWNLIST combo boxes, 78 dialog units wide at 22, 52 and 22, 82. A closed combo stands as tall as the item height ownrdraw.cpp sets, which is the 14 pixel @@ -74,13 +71,10 @@ width: 117dp; height: 20dp; - color: #b9bcae; - background-color: #2b2f25; - border-width: 2dp; - border-top-color: #14160f; - border-left-color: #14160f; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + color: #70ff00; + background-color: #000000b4; + border-width: 1dp; + border-color: #ffffff; } #side { top: 82.5dp; } @@ -104,23 +98,10 @@ width: 16dp; height: 16dp; - background-color: #33372c; - border-width: 2dp; - border-top-color: #767c68; - border-left-color: #767c68; - border-right-color: #14160f; - border-bottom-color: #14160f; + decorator: image(dnarrowr.pcx); } #side selectarrow:active, -#color selectarrow:active -{ - border-top-color: #14160f; - border-left-color: #14160f; - border-right-color: #767c68; - border-bottom-color: #767c68; -} - /* The dropped lists, capped at the 74 and 73 dialog units the template gives them. */ #side selectbox, #color selectbox @@ -128,12 +109,9 @@ width: 113dp; overflow-y: auto; - background-color: #14160f; + background-color: #000000b4; border-width: 1dp; - border-top-color: #6e7360; - border-left-color: #6e7360; - border-right-color: #10120e; - border-bottom-color: #10120e; + border-color: #ffffff; } #side selectbox { max-height: 120.25dp; } @@ -147,13 +125,13 @@ line-height: 18dp; padding: 0dp 4dp; white-space: nowrap; - color: #b9bcae; + color: #70ff00; } -#side selectbox option:hover { color: #e4e6da; } +#side selectbox option:hover { color: #70ff00; } #side selectbox option:checked, -#color selectbox option:checked { background-color: #3f4536; } +#color selectbox option:checked { background-color: #225061; } /* The preview frame, a GROUPBOX 215 x 106 dialog units at 22, 122. The picture inside it is drawn by the screen and reaches the document through the element. */ @@ -167,12 +145,9 @@ width: 322.5dp; height: 172.25dp; - background-color: #14160f; + background-color: #000000b4; border-width: 1dp; - border-top-color: #10120e; - border-left-color: #10120e; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + border-color: #ffffff; } /* The template's own CTEXT inside the frame, which the picture is drawn over the way the @@ -188,7 +163,7 @@ height: 21.125dp; line-height: 21.125dp; text-align: center; - color: #6e7360; + color: #909090; } /* The picture takes its size from the provider, which is the frame's interior in game @@ -211,10 +186,7 @@ box-sizing: border-box; border-width: 1dp; - border-top-color: #10120e; - border-left-color: #10120e; - border-right-color: #6e7360; - border-bottom-color: #6e7360; + border-color: #ffffff; } #optionbox { left: 173.5dp; top: 17.5dp; width: 250.5dp; height: 156dp; } diff --git a/ui/sound.rcss b/ui/sound.rcss index 62af3dae3..2961ca776 100644 --- a/ui/sound.rcss +++ b/ui/sound.rcss @@ -4,16 +4,13 @@ from the panel's content box and a bordered element's declared size taken inside its own border. - The panel is drawn rather than blitted because the dialog's own background is a PCX, and - PCX decoding arrives with the first screen that shows game art. Everything here stays - inside the styling profile docs/UI_DESIGN.md declares: text, ordinary layout, borders and - basic decorators, with no filter, layer, shader or transform. */ + The dialog look, including the panel artwork, comes from ui/optionsbase.rcss. */ body { font-family: LatoLatin; font-size: 12dp; - color: #d6d8cc; + color: #70ff00; width: 100%; height: 100%; @@ -32,13 +29,6 @@ body width: 437dp; height: 345.4dp; - - background-color: #23261fF2; - border-width: 2dp; - border-top-color: #6e7360; - border-left-color: #6e7360; - border-right-color: #10120e; - border-bottom-color: #10120e; } /* The three right-aligned captions, 70 x 15 dialog units at x 22. */ @@ -72,48 +62,6 @@ body #sound { top: 55.3dp; } #voice { top: 91dp; } -slider -{ - width: 100%; - height: 100%; -} - -sliderbar -{ - width: 14dp; - height: 100%; - - background-color: #5c6152; - border-width: 2dp; - border-top-color: #949a84; - border-left-color: #949a84; - border-right-color: #14160f; - border-bottom-color: #14160f; -} - -sliderbar:hover { background-color: #6f7563; } -sliderbar:active { background-color: #4a4e41; } - -slidertrack -{ - width: 100%; - height: 6dp; - margin-top: 9.2dp; - - background-color: #14160f; - border-width: 1dp; - border-top-color: #10120e; - border-left-color: #10120e; - border-right-color: #6e7360; - border-bottom-color: #6e7360; -} - -sliderarrowdec, sliderarrowinc -{ - width: 0dp; - height: 0dp; -} - /* The track list, 175 x 99 dialog units at 97, 82. */ #tracklist { @@ -124,7 +72,7 @@ sliderarrowdec, sliderarrowinc width: 262.5dp; height: 160.9dp; - background-color: #14160f; + background-color: #000000b4; overflow-y: auto; overflow-x: hidden; } @@ -138,41 +86,49 @@ sliderarrowdec, sliderarrowinc width: 250.5dp; padding: 1dp 4dp; white-space: nowrap; - color: #b9bcae; + color: #70ff00; } #tracklist scrollbarvertical { - width: 12dp; - background-color: #23261f; + width: 18dp; + background-color: #000000b4; } #tracklist scrollbarvertical slidertrack { - width: 12dp; + width: 18dp; margin-top: 0dp; - background-color: #14160f; + background-color: transparent; border-width: 0dp; } #tracklist scrollbarvertical sliderbar { - width: 12dp; + width: 18dp; min-height: 20dp; - background-color: #5c6152; - border-width: 2dp; - border-top-color: #949a84; - border-left-color: #949a84; - border-right-color: #14160f; - border-bottom-color: #14160f; + + decorator: tiled-vertical(sbgript.pcx, sbgripm.pcx, sbgripb.pcx); } -.track:hover { color: #e4e6da; } +#tracklist scrollbarvertical sliderarrowdec, +#tracklist scrollbarvertical sliderarrowinc +{ + width: 18dp; + height: 22dp; +} + +#tracklist scrollbarvertical sliderarrowdec { decorator: image(uparrowr.pcx); } +#tracklist scrollbarvertical sliderarrowinc { decorator: image(dnarrowr.pcx); } +#tracklist scrollbarvertical sliderarrowdec:active { decorator: image(uparrowp.pcx); } +#tracklist scrollbarvertical sliderarrowinc:active { decorator: image(dnarrowp.pcx); } + +.track:hover { color: #70ff00; } .track.picked { - background-color: #3f4536; - color: #e4e6da; + background-color: #225061; + color: #70ff00; } /* Play and Stop, 70 x 14 dialog units at x 22, and OK, 62 x 14 at 210, 189. */ @@ -183,33 +139,14 @@ sliderarrowdec, sliderarrowinc height: 22.8dp; line-height: 22.8dp; text-align: center; - - color: #e4e6da; - background-color: #33372c; - border-width: 2dp; - border-top-color: #767c68; - border-left-color: #767c68; - border-right-color: #14160f; - border-bottom-color: #14160f; } #play { left: 33dp; top: 139.8dp; width: 101dp; } #stop { left: 33dp; top: 183.6dp; width: 101dp; } #ok { left: 315dp; top: 307.1dp; width: 89dp; } -.button:hover { background-color: #474d3d; } -.button:active -{ - background-color: #2a2e24; - border-top-color: #14160f; - border-left-color: #14160f; - border-right-color: #767c68; - border-bottom-color: #767c68; -} - -/* Shuffle and Repeat, 70 x 14 dialog units at x 22. BS_FLAT check boxes, so a ticked one - reads as pressed in rather than as a tick beside a caption. */ +/* Shuffle and Repeat, 70 x 14 dialog units at x 22. */ .check { display: block; @@ -218,31 +155,11 @@ sliderarrowdec, sliderarrowinc width: 101dp; height: 22.8dp; line-height: 22.8dp; - text-align: center; - - color: #b9bcae; - background-color: #2b2f25; - border-width: 2dp; - border-top-color: #5c6152; - border-left-color: #5c6152; - border-right-color: #14160f; - border-bottom-color: #14160f; } #shuffle { top: 227.5dp; } #repeat { top: 271.4dp; } -.check:hover { background-color: #3b4032; } - -.check.ticked -{ - color: #e4e6da; - background-color: #4a5140; - border-top-color: #14160f; - border-left-color: #14160f; - border-right-color: #767c68; - border-bottom-color: #767c68; -} /* With no audio device every control the dialog disabled is dimmed and inert. OK stays live, because the dialog left it enabled. */ @@ -252,6 +169,5 @@ sliderarrowdec, sliderarrowinc .unavailable #play, .unavailable #stop { - color: #6b6e63; pointer-events: none; } diff --git a/ui/sound.rml b/ui/sound.rml index e9566124a..43ee7a971 100644 --- a/ui/sound.rml +++ b/ui/sound.rml @@ -1,10 +1,11 @@ Sound controls + -
+
Music Volume:
diff --git a/ui/soundlite.rcss b/ui/soundlite.rcss index b4b35221d..029cb61ba 100644 --- a/ui/soundlite.rcss +++ b/ui/soundlite.rcss @@ -4,16 +4,13 @@ taken from the panel's content box and a bordered element's declared size taken inside its own border. It carries the three volumes alone, as that template does. - The panel is drawn rather than blitted because the dialog's own background is a PCX, and - PCX decoding arrives with the first screen that shows game art. Everything here stays - inside the styling profile docs/UI_DESIGN.md declares: text, ordinary layout, borders and - basic decorators, with no filter, layer, shader or transform. */ + The dialog look, including the panel artwork, comes from ui/optionsbase.rcss. */ body { font-family: LatoLatin; font-size: 12dp; - color: #d6d8cc; + color: #70ff00; width: 100%; height: 100%; @@ -32,13 +29,6 @@ body width: 437dp; height: 178dp; - - background-color: #23261fF2; - border-width: 2dp; - border-top-color: #6e7360; - border-left-color: #6e7360; - border-right-color: #10120e; - border-bottom-color: #10120e; } /* The three right-aligned captions, 70 x 15 dialog units at x 22. */ @@ -72,48 +62,6 @@ body #sound { top: 65dp; } #voice { top: 100.8dp; } -slider -{ - width: 100%; - height: 100%; -} - -sliderbar -{ - width: 14dp; - height: 100%; - - background-color: #5c6152; - border-width: 2dp; - border-top-color: #949a84; - border-left-color: #949a84; - border-right-color: #14160f; - border-bottom-color: #14160f; -} - -sliderbar:hover { background-color: #6f7563; } -sliderbar:active { background-color: #4a4e41; } - -slidertrack -{ - width: 100%; - height: 6dp; - margin-top: 9.2dp; - - background-color: #14160f; - border-width: 1dp; - border-top-color: #10120e; - border-left-color: #10120e; - border-right-color: #6e7360; - border-bottom-color: #6e7360; -} - -sliderarrowdec, sliderarrowinc -{ - width: 0dp; - height: 0dp; -} - /* OK, 62 x 14 dialog units at 115, 86, centred across the panel as the template places it. */ .button { @@ -125,31 +73,13 @@ sliderarrowdec, sliderarrowinc height: 22.8dp; line-height: 22.8dp; text-align: center; - - color: #e4e6da; - background-color: #33372c; - border-width: 2dp; - border-top-color: #767c68; - border-left-color: #767c68; - border-right-color: #14160f; - border-bottom-color: #14160f; } -.button:hover { background-color: #474d3d; } - -.button:active -{ - background-color: #2a2e24; - border-top-color: #14160f; - border-left-color: #14160f; - border-right-color: #767c68; - border-bottom-color: #767c68; -} /* With no audio device the sliders are dimmed and inert. OK stays live, because the dialog left it enabled. */ .unavailable .slider { - color: #6b6e63; + color: #909090; pointer-events: none; } diff --git a/ui/soundlite.rml b/ui/soundlite.rml index 7a5e5f9e9..700c69117 100644 --- a/ui/soundlite.rml +++ b/ui/soundlite.rml @@ -1,10 +1,11 @@ Sound controls + -
+
Music Volume:
diff --git a/ui/version.rcss b/ui/version.rcss index f998d34c6..238f95b0c 100644 --- a/ui/version.rcss +++ b/ui/version.rcss @@ -3,16 +3,13 @@ 1.625 down. One authored dp is one game logical unit, so the screen keeps the size the dialog had while its text is rasterized at the window's own resolution. - The panel is drawn rather than blitted because the dialog's own background is a PCX, and - PCX decoding arrives with the first screen that shows game art. Everything here stays - inside the styling profile docs/UI_DESIGN.md declares: text, ordinary layout, borders and - basic decorators, with no filter, layer, shader or transform. */ + The dialog look, including the panel artwork, comes from ui/optionsbase.rcss. */ body { font-family: LatoLatin; font-size: 12dp; - color: #d6d8cc; + color: #70ff00; width: 100%; height: 100%; @@ -33,13 +30,6 @@ body 408 by 172 pixels less the two device-independent pixels of border on each side. */ width: 404dp; height: 168dp; - - background-color: #23261fF2; - border-width: 2dp; - border-top-color: #6e7360; - border-left-color: #6e7360; - border-right-color: #10120e; - border-bottom-color: #10120e; } /* The list box: 22, 12, 228 x 55 dialog units. It clips rather than scrolls, as the @@ -74,32 +64,6 @@ body height: 19dp; line-height: 19dp; text-align: center; - - color: #e4e6da; - background-color: #33372c; - border-width: 2dp; - border-top-color: #767c68; - border-left-color: #767c68; - border-right-color: #14160f; - border-bottom-color: #14160f; } -#ok:focus -{ - color: #ffffff; - background-color: #3d4234; -} -#ok:hover -{ - background-color: #474d3d; -} - -#ok:active -{ - background-color: #2a2e24; - border-top-color: #14160f; - border-left-color: #14160f; - border-right-color: #767c68; - border-bottom-color: #767c68; -} diff --git a/ui/version.rml b/ui/version.rml index 9068fd8c7..acdf9245c 100644 --- a/ui/version.rml +++ b/ui/version.rml @@ -1,14 +1,15 @@ Version information + -
+
{{ line }}
-
OK
+
OK
diff --git a/ui/waitbox.rcss b/ui/waitbox.rcss index 02e635fca..437ed383a 100644 --- a/ui/waitbox.rcss +++ b/ui/waitbox.rcss @@ -7,7 +7,7 @@ body { font-family: LatoLatin; font-size: 12dp; - color: #d6d8cc; + color: #70ff00; width: 100%; height: 100%; @@ -25,13 +25,6 @@ body width: 323dp; height: 100dp; - - background-color: #23261fF2; - border-width: 2dp; - border-top-color: #6e7360; - border-left-color: #6e7360; - border-right-color: #10120e; - border-bottom-color: #10120e; } /* 22, 12, 174 x 23 dialog units. */ @@ -60,26 +53,6 @@ body height: 18.75dp; line-height: 18.75dp; text-align: center; - - color: #e4e6da; - background-color: #33372c; - border-width: 2dp; - border-top-color: #767c68; - border-left-color: #767c68; - border-right-color: #14160f; - border-bottom-color: #14160f; } -#cancel:hover -{ - background-color: #474d3d; -} -#cancel:active -{ - background-color: #2a2e24; - border-top-color: #14160f; - border-left-color: #14160f; - border-right-color: #767c68; - border-bottom-color: #767c68; -} diff --git a/ui/waitbox.rml b/ui/waitbox.rml index 48e54a5a6..4de49e932 100644 --- a/ui/waitbox.rml +++ b/ui/waitbox.rml @@ -1,12 +1,13 @@ Please wait + -
+
{{ message }}
-
{{ cancelcaption }}
+
{{ cancelcaption }}
From 96a14c0fd7578dbf28e4934daa40f4a1b08cfa62 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 15:43:04 +0100 Subject: [PATCH 168/179] fix(ui): center the captions the owner-draw controls centered --- ui/desyncbase.rcss | 2 +- ui/keyboard.rcss | 2 +- ui/mapgenbase.rcss | 6 +++--- ui/reconnect.rcss | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ui/desyncbase.rcss b/ui/desyncbase.rcss index 11ba3a4fd..6ece9c3a1 100644 --- a/ui/desyncbase.rcss +++ b/ui/desyncbase.rcss @@ -141,4 +141,4 @@ } /* The three buttons, 70 x 12 dialog units at y 239. */ -.button { top: 388.375dp; width: 105dp; height: 19.5dp; line-height: 15.5dp; } +.button { top: 388.375dp; width: 105dp; height: 19.5dp; line-height: 19.5dp; } diff --git a/ui/keyboard.rcss b/ui/keyboard.rcss index a31749fce..3d296c455 100644 --- a/ui/keyboard.rcss +++ b/ui/keyboard.rcss @@ -160,7 +160,7 @@ top: 210.875dp; width: 127.5dp; height: 22.75dp; - line-height: 18.75dp; + line-height: 22.75dp; padding: 0dp 4dp; white-space: nowrap; overflow: hidden; diff --git a/ui/mapgenbase.rcss b/ui/mapgenbase.rcss index 6493fa446..b8c5dde09 100644 --- a/ui/mapgenbase.rcss +++ b/ui/mapgenbase.rcss @@ -142,9 +142,9 @@ /* The five buttons across the bottom row, 65 x 14 dialog units at y 220, and the two above the preview, 88 x 14. */ -.footbutton { top: 357.5dp; width: 97.5dp; height: 22.75dp; line-height: 15.5dp; } -.previewbutton { width: 132dp; height: 22.75dp; line-height: 15.5dp; } +.footbutton { top: 357.5dp; width: 97.5dp; height: 22.75dp; line-height: 22.75dp; } +.previewbutton { width: 132dp; height: 22.75dp; line-height: 22.75dp; } /* A BS_AUTOCHECKBOX, which the Firestorm and tournament templates put down the right hand side, 84 x 10 dialog units. */ -.check { width: 126dp; height: 16.25dp; line-height: 12.25dp; } +.check { width: 126dp; height: 16.25dp; line-height: 16.25dp; } diff --git a/ui/reconnect.rcss b/ui/reconnect.rcss index de86c564a..2ed7cb3a2 100644 --- a/ui/reconnect.rcss +++ b/ui/reconnect.rcss @@ -34,7 +34,7 @@ top: 0dp; width: 135dp; height: 22.75dp; - line-height: 15.5dp; + line-height: 22.75dp; } .seat .barframe @@ -89,5 +89,5 @@ top: 315.25dp; width: 442.5dp; height: 22.75dp; - line-height: 15.5dp; + line-height: 22.75dp; } From e8919f486f2e07256b61a643379e1b24b98cf891 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 16:29:23 +0100 Subject: [PATCH 169/179] fix(dialog): load the dialog pictures the surface cache lost --- code/drawhelp.cpp | 66 +++++++++++++++++++++++++++++++++++++++++++++++ code/drawhelp.h | 2 ++ code/restate.cpp | 15 ++++++++--- 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/code/drawhelp.cpp b/code/drawhelp.cpp index 4a77c574b..67b53ace4 100644 --- a/code/drawhelp.cpp +++ b/code/drawhelp.cpp @@ -25,6 +25,9 @@ #include "wstring.h" #include +#include +#include +#include extern unsigned int Wstring_Hash(Wstring & string); @@ -54,6 +57,65 @@ static void ODDrawCharRemap(Surface & dst_surf, const char * text, int max_chars static int ODColorToHiColor(COLORREF color); +/// +/// Reads a picture into the surface cache if it is not there already. +/// The cache is a pure lookup and never loads anything itself. The routine that filled it +/// up front went with the owner-draw dialogs, and no other point in startup is both after +/// the mix files are mounted and before every surviving screen paints, so a picture is read +/// when it is first asked for. A name that could not be read is not tried again. +/// +/// True to reduce the picture to the red component of its own +/// palette, which is how a coverage sheet is stored. +/// bool; Is the picture in the cache? +static bool ODCacheImage(char const * name, int bpp, bool red_channel) +{ + static std::set _attempted; + + if (SurfaceCache.GetSurface(name) != NULL) { + return(true); + } + if (!_attempted.insert(std::string(name)).second) { + return(false); + } + + if (!SurfaceCache.CachePCX(name, bpp, red_channel)) { + DebugString("TS: %s could not be read.\n", name); + return(false); + } + return(true); +} + + +/// +/// Reads a remap font's two sheets into the surface cache. +/// The index sheet keeps its palette indices and its palette; the alpha sheet is reduced to +/// the red component of its own palette, which is the coverage each pixel carries. +/// +static void ODCacheFontSheets(char const * font_name) +{ + char name[64]; + + snprintf(name, sizeof(name), "%si.pcx", font_name); + ODCacheImage(name, 1, false); + + snprintf(name, sizeof(name), "%sa.pcx", font_name); + ODCacheImage(name, 1, true); +} + + +/// +/// Fetches one of the dialog system's pictures, reading it on first request. +/// +/// File name of the .PCX, which is also its name in the cache. +/// Returns with the cached surface, or NULL if the picture could not be read. The +/// surface stays owned by the cache. +Surface * OD_Fetch_Image(char const * name) +{ + ODCacheImage(name, 2, false); + return(SurfaceCache.GetSurface(name)); +} + + static unsigned char OD_Glyph(char32_t code) { if (code < ' ') { @@ -243,6 +305,8 @@ static void ODDrawCharRemap(Surface & dst_surf, const char *text, int max_chars, int i; Rect draw_rect = rect; + ODCacheFontSheets(font_name); + char name_i[64]; strcpy(name_i, font_name); strcat(name_i, "i.pcx"); @@ -431,6 +495,8 @@ static bool ODGetFontMetrics(char const * font_name, FontMetrics * metrics) FontMetrics temp; memset(&temp, 0, sizeof(temp)); + ODCacheFontSheets(font_name); + char palette[768]; Surface * surf = SurfaceCache.GetSurface(buf, palette); if (surf == NULL) { diff --git a/code/drawhelp.h b/code/drawhelp.h index f19b1973d..b4b35ee46 100644 --- a/code/drawhelp.h +++ b/code/drawhelp.h @@ -28,6 +28,8 @@ #define OD_DRAW_CHAR_FLAG_VERTICAL_CENTER 4 int OD_Draw_Text_Remap(Surface & surface, const char * string, Rect const & rect, char const * name, COLORREF color, int flags, int char_spacing); + +Surface * OD_Fetch_Image(char const * name); int OD_Draw_Text(COLORREF color, HFONT font, Rect const & rect, const char * text, int len, int x_alignment, int y_alignment, Surface * surface); HFONT WS_Get_Font(HDC hdc, const char * face_name, int decipt_width, int decipt_height, int attributes); diff --git a/code/restate.cpp b/code/restate.cpp index 4df578c3b..b58742e78 100644 --- a/code/restate.cpp +++ b/code/restate.cpp @@ -118,7 +118,10 @@ class MyButton : public TextButtonClass { char buffer[40]; sprintf(buffer, "b%ce_li%d.pcx", IsPressed != false ? 'd' : 'u', height); - Surface * image = SurfaceCache.GetSurface(buffer); + Surface * image = OD_Fetch_Image(buffer); + if (image == NULL) { + return; + } origin.Height = image->Get_Height(); dest_rect = origin; dest_rect.Width = small_width; @@ -130,7 +133,10 @@ class MyButton : public TextButtonClass { HiddenSurface->Blit_From(dest_rect, *image, source_rect); sprintf(buffer, "b%ce_mi%d.pcx", IsPressed != false ? 'd' : 'u', height); - image = SurfaceCache.GetSurface(buffer); + image = OD_Fetch_Image(buffer); + if (image == NULL) { + return; + } rect = origin; rect.X += small_width; rect.Width -= width; @@ -138,7 +144,10 @@ class MyButton : public TextButtonClass { SurfaceCache.Draw(rect, *HiddenSurface, *image, 0, 0); sprintf(buffer, "b%ce_ri%d.pcx", IsPressed != false ? 'd' : 'u', height); - image = SurfaceCache.GetSurface(buffer); + image = OD_Fetch_Image(buffer); + if (image == NULL) { + return; + } dest_rect = origin; dest_rect.X += origin.Width - width; dest_rect.Width = width; From 9c8ff25a07db3090df10fd19b5abf2d1936a5f31 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 16:29:31 +0100 Subject: [PATCH 170/179] fix(video): build the blend masks when the video mode comes up --- code/video.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/code/video.cpp b/code/video.cpp index 28048612d..dd8e4581a 100644 --- a/code/video.cpp +++ b/code/video.cpp @@ -171,6 +171,11 @@ bool Video_Init(NativeWindow const & window, int drawablewidth, int drawableheig return(false); } + // The blend masks follow how the display surface packs its pixels. Video_Set_Mode runs + // only when the player changes resolution, so an ordinary session never reached it and + // every blend was left masking with zero, which draws black. + Prepare_Draw_Resources(); + Update_Scale_Info(); Update_Present_Interval(refreshrate); From e656905af37598e4e522e1256bb394c4a1b6d0d1 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 16:29:31 +0100 Subject: [PATCH 171/179] fix(dialog): index the remap font sheets through their own pointers --- code/drawhelp.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/code/drawhelp.cpp b/code/drawhelp.cpp index 67b53ace4..f4cc24a49 100644 --- a/code/drawhelp.cpp +++ b/code/drawhelp.cpp @@ -407,7 +407,15 @@ static void ODDrawCharRemap(Surface & dst_surf, const char *text, int max_chars, int cell_h = font_data.glyphHeight + font_data.topMargin; int chars_per_row = sheet_i->Get_Width() / (font_data.glyphWidth + font_data.leftMargin); int dst_stride = dst_surf.Stride() / 2; - int src_stride = sheet_i->Stride(); + + /* + * The two sheets are separate allocations, so the coverage sheet cannot be indexed + * by an offset from the color sheet: the difference between two unrelated pointers + * does not fit an int on a 64-bit host, which is what the inherited code stored it + * in. Each sheet is walked through its own pointer and its own stride instead. + */ + int index_stride = sheet_i->Stride(); + int alpha_stride = sheet_a->Stride(); int x = draw_rect.X; for (char const * cursor = text; cursor - text < max_chars; ) { @@ -421,30 +429,32 @@ static void ODDrawCharRemap(Surface & dst_surf, const char *text, int max_chars, int src_y = (glyph / chars_per_row) * cell_h; int src_y_end = src_y + cell_h; - int src_delta = src_i - src_a; - unsigned char *alpha_col = src_a + (src_y * src_stride + src_x); + unsigned char *alpha_col = src_a + (src_y * alpha_stride + src_x); + unsigned char *index_col = src_i + (src_y * index_stride + src_x); unsigned char *dst_col = dst + 2 * (dst_stride * draw_rect.Y + x); for (int sx = src_x; sx < src_x + cell_w; ++sx) { if (src_y < src_y_end) { unsigned short *dst_px = (unsigned short *)dst_col; unsigned char *alpha_px = alpha_col; + unsigned char *index_px = index_col; int sy = src_y_end - src_y; do { unsigned char alpha = *alpha_px; if (alpha != 0) { - unsigned char index = alpha_px[src_delta]; - *dst_px = OD_Blend_Color(*dst_px, remap_table[index], alpha); + *dst_px = OD_Blend_Color(*dst_px, remap_table[*index_px], alpha); } dst_px += dst_stride; - alpha_px += src_stride; + alpha_px += alpha_stride; + index_px += index_stride; --sy; } while (sy != 0); } ++alpha_col; + ++index_col; dst_col += 2; } From fe940bed203f6dbce6b35cb73de15a9ed21d3b07 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 16:42:59 +0100 Subject: [PATCH 172/179] feat(ui): draw the campaign dialog's text with the dlgsys sheets --- code/CMakeLists.txt | 6 + code/drawhelp.cpp | 222 ++++++++++++++++------ code/drawhelp.h | 18 ++ code/ui/uifont.cpp | 434 +++++++++++++++++++++++++++++++++++++++++++ code/ui/uiinternal.h | 5 + code/ui/uishell.cpp | 5 + docs/UI_DESIGN.md | 36 +++- ui/campaign.rcss | 17 ++ 8 files changed, 674 insertions(+), 69 deletions(-) create mode 100644 code/ui/uifont.cpp diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 6eaf16aa4..5f3f01142 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -186,6 +186,12 @@ set_source_files_properties(${OPENTS_UI_SOURCES} PROPERTIES COMPILE_OPTIONS "$<$:/Zc:preprocessor>" ) +# The bitmap font engine derives from RmlUi's own engine, whose header sits in the library's +# Source tree rather than its Include tree, so that one path goes on that one file. +set_property(SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/ui/uifont.cpp" APPEND PROPERTY + INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/thirdparty/RmlUi/Source" +) + set_property(SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/ui/uirender.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ui/uitexture.cpp" diff --git a/code/drawhelp.cpp b/code/drawhelp.cpp index f4cc24a49..321759285 100644 --- a/code/drawhelp.cpp +++ b/code/drawhelp.cpp @@ -40,21 +40,10 @@ unsigned short ODGComponentMask; unsigned short ODBComponentMask; -/* - * Measurements of one of the remap fonts, cached by ODGetFontMetrics. - */ -struct FontMetrics { - int charWidths[256]; /// inked width of each character, indexed by character code - int glyphWidth; /// width of the inked part of a glyph cell - int glyphHeight; /// height of the inked part of a glyph cell - int topMargin; /// blank rows above each row of glyphs - int leftMargin; /// blank columns before each glyph -}; - - -static bool ODGetFontMetrics(char const * font_name, FontMetrics * metrics); +static bool ODGetFontMetrics(char const * font_name, ODFontMetrics * metrics); static void ODDrawCharRemap(Surface & dst_surf, const char * text, int max_chars, Rect const & rect, char const * font_name, COLORREF color, char flags, int char_spacing); static int ODColorToHiColor(COLORREF color); +static void ODBuildRemapColors(COLORREF color, unsigned char const * palette, RGBClass * out); /// @@ -116,7 +105,12 @@ Surface * OD_Fetch_Image(char const * name) } -static unsigned char OD_Glyph(char32_t code) +/// +/// Maps a decoded code point onto the glyph the remap sheets index by. +/// +/// Returns with the Windows-1252 code of the glyph, or that of '?' for a code +/// point the sheets do not carry. +unsigned char OD_Font_Glyph(char32_t code) { if (code < ' ') { return((unsigned char)code); @@ -126,6 +120,12 @@ static unsigned char OD_Glyph(char32_t code) } +static unsigned char OD_Glyph(char32_t code) +{ + return(OD_Font_Glyph(code)); +} + + /// /// Sets up the color component masks used for blending. /// The masks depend on how the display surface packs its pixels, so this routine cannot @@ -193,7 +193,7 @@ int OD_Draw_Text_Remap(Surface & surface, const char * text, Rect const & rect, char const * line_ptr = text; Rect draw_rect = rect; - FontMetrics data; + ODFontMetrics data; if (!ODGetFontMetrics(name, &data)) { return(0); } @@ -289,6 +289,53 @@ static float ODCalcTextRemapFactor(int hue) } +/// +/// Shifts a font sheet's palette toward the color text is asked to be drawn in. +/// The sheets hold an intensity ramp of their own hue, so each entry keeps its own +/// saturation and value and is pulled around to the requested hue rather than replaced +/// by it. +/// +/// The 768-byte palette of the font's index sheet. +/// Receives one remapped color for each of the 256 palette entries. +static void ODBuildRemapColors(COLORREF color, unsigned char const * palette, RGBClass * out) +{ + RGBClass remap_rgb((unsigned char)color, (unsigned char)(color >> 8), (unsigned char)(color >> 16)); + HSVClass remap_hsv = remap_rgb; + + int hue = remap_hsv.Get_Hue(); + + int end = int(hue + 15.0); + float min_factor = 1.0f; + for (int i = int(hue - 15.0); i <= end; ++i) { + float factor = ODCalcTextRemapFactor(i); + if (factor < min_factor) { + min_factor = factor; + } + } + + unsigned char sat = (unsigned char)remap_hsv.Get_Saturation(); + unsigned char val = (unsigned char)remap_hsv.Get_Value(); + + float hue_float = (float)hue; + unsigned char const * pal = palette; + for (int i = 0; i < 256; ++i) { + RGBClass pal_rgb; + pal_rgb.Set_Red(pal[0]); + pal_rgb.Set_Green(pal[1]); + pal_rgb.Set_Blue(pal[2]); + HSVClass pal_hsv = pal_rgb; + + HSVClass out_hsv = pal_hsv; + out_hsv.Set_Hue((unsigned char)(int)(hue_float - (int)(68.0f - pal_hsv.Get_Hue()) * min_factor)); + out_hsv.Set_Saturation((unsigned char)((sat * out_hsv.Get_Saturation()) >> 8)); + out_hsv.Set_Value((unsigned char)((val * out_hsv.Get_Value()) >> 8)); + + out[i] = out_hsv; + pal += 3; + } +} + + /// /// Draws a line of text with a remapped bitmap font. /// This routine builds a table that shifts the font's own palette toward the color asked @@ -326,52 +373,16 @@ static void ODDrawCharRemap(Surface & dst_surf, const char *text, int max_chars, return; } - RGBClass remap_rgb((unsigned char)color, (unsigned char)(color >> 8), (unsigned char)(color >> 16)); - HSVClass remap_hsv = remap_rgb; - RGBClass pal_rgb; - HSVClass out_hsv; - - int hue = remap_hsv.Get_Hue(); - - int end = int(hue + 15.0); - float min_factor = 1.0f; - for (i = int(hue - 15.0); i <= end; ++i) { - float factor = ODCalcTextRemapFactor(i); - if (factor < min_factor) { - min_factor = factor; - } - } - - unsigned char sat = (unsigned char)remap_hsv.Get_Saturation(); - unsigned char val = (unsigned char)remap_hsv.Get_Value(); + RGBClass remap_colors[256]; + ODBuildRemapColors(color, (unsigned char *)palette, remap_colors); unsigned short remap_table[256]; - float hue_float = (float)hue; - unsigned char *pal = (unsigned char *)&palette; for (i = 0; i < 256; ++i) { - pal_rgb.Set_Red(pal[0]); - pal_rgb.Set_Green(pal[1]); - pal_rgb.Set_Blue(pal[2]); - HSVClass pal_hsv = pal_rgb; - - /* - * Start from the palette entry's HSV and adjust each channel. The - * wholesale copy is fully overwritten below. - */ - out_hsv = pal_hsv; - out_hsv.Set_Hue((unsigned char)(int)(hue_float - (int)(68.0f - pal_hsv.Get_Hue()) * min_factor)); - out_hsv.Set_Saturation((unsigned char)((sat * out_hsv.Get_Saturation()) >> 8)); - out_hsv.Set_Value((unsigned char)((val * out_hsv.Get_Value()) >> 8)); - - RGBClass out_rgb = out_hsv; - pal_rgb = out_rgb; - - int packed = (((out_rgb.Get_Blue() << 8) | out_rgb.Get_Green()) << 8) | out_rgb.Get_Red(); + int packed = (((remap_colors[i].Get_Blue() << 8) | remap_colors[i].Get_Green()) << 8) | remap_colors[i].Get_Red(); remap_table[i] = (unsigned short)ODColorToHiColor(packed); - pal += 3; } - FontMetrics font_data; + ODFontMetrics font_data; if (!ODGetFontMetrics(font_name, &font_data)) { return; } @@ -480,9 +491,9 @@ static void ODDrawCharRemap(Surface & dst_surf, const char *text, int max_chars, /// The base name of the font, without the sheet suffix. /// Buffer to fill in with the measurements. /// bool; Were the metrics available? -static bool ODGetFontMetrics(char const * font_name, FontMetrics * metrics) +static bool ODGetFontMetrics(char const * font_name, ODFontMetrics * metrics) { - static Dictionary metricsDict(Wstring_Hash); + static Dictionary metricsDict(Wstring_Hash); char buf[64]; strcpy(buf, font_name); @@ -492,7 +503,7 @@ static bool ODGetFontMetrics(char const * font_name, FontMetrics * metrics) name = (char *)font_name; name.toLower(); - FontMetrics * found = NULL; + ODFontMetrics * found = NULL; if (metricsDict.getPointer(name, &found)) { if (metrics != NULL) { *metrics = *found; @@ -502,7 +513,7 @@ static bool ODGetFontMetrics(char const * font_name, FontMetrics * metrics) DebugString("TS: Computing font metrics....\n"); - FontMetrics temp; + ODFontMetrics temp; memset(&temp, 0, sizeof(temp)); ODCacheFontSheets(font_name); @@ -600,7 +611,7 @@ static bool ODGetFontMetrics(char const * font_name, FontMetrics * metrics) * Store result in caller's buffer * ---------------------------------------------------------------- */ - memcpy(metrics, &temp, sizeof(FontMetrics)); + memcpy(metrics, &temp, sizeof(ODFontMetrics)); metricsDict.add(name, temp); @@ -608,6 +619,97 @@ static bool ODGetFontMetrics(char const * font_name, FontMetrics * metrics) } +/// +/// Measures a remap font. +/// +/// The base name of the font, without the sheet suffix. +/// bool; Could the font's sheets be read? +bool OD_Font_Metrics(char const * font_name, ODFontMetrics & metrics) +{ + return(ODGetFontMetrics(font_name, &metrics)); +} + + +/// +/// Composes a remap font's glyph sheet into premultiplied RGBA pixels. +/// The sheets are combined the way ODDrawCharRemap combines them per pixel: the coverage +/// sheet supplies alpha and the index sheet a palette entry shifted toward the color asked +/// for. The result is what that blend produces over black, so a caller can hand it to a +/// renderer that blends premultiplied source over what is already there. +/// +/// Receives width * height * 4 bytes in RGBA order. +/// bool; Could the font's sheets be read? +bool OD_Font_Sheet(char const * font_name, COLORREF color, int & width, int & height, std::vector & pixels) +{ + ODCacheFontSheets(font_name); + + char name_i[64]; + snprintf(name_i, sizeof(name_i), "%si.pcx", font_name); + + char palette[768]; + Surface * sheet_i = SurfaceCache.GetSurface(name_i, palette); + if (sheet_i == NULL) { + return(false); + } + + char name_a[64]; + snprintf(name_a, sizeof(name_a), "%sa.pcx", font_name); + + Surface * sheet_a = SurfaceCache.GetSurface(name_a, NULL); + if (sheet_a == NULL) { + return(false); + } + + width = sheet_i->Get_Width(); + height = sheet_i->Get_Height(); + if (width <= 0 || height <= 0 || sheet_a->Get_Width() < width || sheet_a->Get_Height() < height) { + return(false); + } + + RGBClass remap_colors[256]; + ODBuildRemapColors(color, (unsigned char *)palette, remap_colors); + + unsigned char * src_i = (unsigned char *)sheet_i->Lock(); + unsigned char * src_a = (unsigned char *)sheet_a->Lock(); + if (src_i == NULL || src_a == NULL) { + if (src_i != NULL) { + sheet_i->Unlock(); + } + if (src_a != NULL) { + sheet_a->Unlock(); + } + return(false); + } + + int index_stride = sheet_i->Stride(); + int alpha_stride = sheet_a->Stride(); + + pixels.assign((std::size_t)width * height * 4, 0); + + for (int y = 0; y < height; ++y) { + unsigned char const * row_i = src_i + (std::size_t)index_stride * y; + unsigned char const * row_a = src_a + (std::size_t)alpha_stride * y; + unsigned char * out = pixels.data() + (std::size_t)width * y * 4; + + for (int x = 0; x < width; ++x) { + unsigned char alpha = row_a[x]; + if (alpha != 0) { + RGBClass const & rgb = remap_colors[row_i[x]]; + out[0] = (unsigned char)((rgb.Get_Red() * alpha + 127) / 255); + out[1] = (unsigned char)((rgb.Get_Green() * alpha + 127) / 255); + out[2] = (unsigned char)((rgb.Get_Blue() * alpha + 127) / 255); + out[3] = alpha; + } + out += 4; + } + } + + sheet_a->Unlock(); + sheet_i->Unlock(); + return(true); +} + + /// /// Draws a line of text onto a surface. /// This routine borrows a device context from the surface, unlocking it as often as it diff --git a/code/drawhelp.h b/code/drawhelp.h index b4b35ee46..3525ee7b7 100644 --- a/code/drawhelp.h +++ b/code/drawhelp.h @@ -12,6 +12,8 @@ #include "surface.h" #include "win.h" +#include + /* * Drawing and window helpers shared by the screens that draw into the game's own * surfaces. The OD_ and WS_ names are inherited from the owner-draw dialogs these @@ -27,9 +29,25 @@ #define OD_DRAW_CHAR_ALIGN_FLAG_RIGHT 2 #define OD_DRAW_CHAR_FLAG_VERTICAL_CENTER 4 +/* + * Measurements of one of the remap fonts, derived from its own artwork. The sheets carry no + * metrics table, so every figure here is probed out of the ink. + */ +struct ODFontMetrics { + int charWidths[256]; /// inked width of each character, indexed by character code + int glyphWidth; /// width of the inked part of a glyph cell + int glyphHeight; /// height of the inked part of a glyph cell + int topMargin; /// blank rows above each row of glyphs + int leftMargin; /// blank columns before each glyph +}; + int OD_Draw_Text_Remap(Surface & surface, const char * string, Rect const & rect, char const * name, COLORREF color, int flags, int char_spacing); Surface * OD_Fetch_Image(char const * name); + +bool OD_Font_Metrics(char const * font_name, ODFontMetrics & metrics); +bool OD_Font_Sheet(char const * font_name, COLORREF color, int & width, int & height, std::vector & pixels); +unsigned char OD_Font_Glyph(char32_t code); int OD_Draw_Text(COLORREF color, HFONT font, Rect const & rect, const char * text, int len, int x_alignment, int y_alignment, Surface * surface); HFONT WS_Get_Font(HDC hdc, const char * face_name, int decipt_width, int decipt_height, int attributes); diff --git a/code/ui/uifont.cpp b/code/ui/uifont.cpp new file mode 100644 index 000000000..c1145b337 --- /dev/null +++ b/code/ui/uifont.cpp @@ -0,0 +1,434 @@ +/******************************************************************************* + * O P E N T S + ******************************************************************************* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright 2026 OpenTS contributors + * + * See LICENSE.md for applicable additional terms and warranty disclaimers. + ******************************************************************************/ + +// Draws document text with the game's own dlgsys bitmap sheets. +// +// The dialogs never drew their buttons, captions, tabs, check boxes, combo boxes or track +// bar numbers with a scalable face. They drew from a pair of 640x160 sheets, dlgsysi.pcx +// and dlgsysa.pcx, laid out as 16 by 16 cells of 40 by 10 pixels: the first holds palette +// indices, the second the coverage of each pixel. drawhelp.cpp owns both the metrics, which +// are probed out of the ink rather than tabulated anywhere, and the palette shift that +// turns the sheet's own ramp into the colour text is asked for. This file only turns what +// drawhelp hands over into a texture and a quad per glyph. +// +// RmlUi installs one font engine, and the list boxes and tooltips legitimately want the +// shipped TrueType face, so this derives from RmlUi's own engine and answers only for the +// dlgsys family, delegating everything else to it untouched. A face handle of ours is +// recognised by the registry below; anything else is the base engine's and is passed +// straight through. +// +// A document selects the sheets with `font-family: dlgsys`. Its `font-size` is read as the +// height of a glyph cell, so `font-size: 10dp` draws one sheet pixel per authored pixel and +// the text scales with the frame exactly as the artwork around it does. + +#include "always.h" + +#include "uiinternal.h" + +#include "dbgprint.h" +#include "drawhelp.h" +#include "utf8.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + + +// The family a document names to get the sheets, and the base name the sheets are stored +// under. drawhelp appends the "i" and "a" suffixes itself. +static char const _FontFamily[] = "dlgsys"; + + +// One remapped copy of the glyph sheet. The sheet is drawn at its own resolution and scaled +// by the quads, so a colour needs one texture whatever size a document asks for. +struct UIFontSheet +{ + Rml::CallbackTextureSource Source; + int Width = 0; + int Height = 0; +}; + + +// One size of the bitmap face. Everything here is derived from the sheet's own ink. +class UIFontFaceClass +{ + public: + explicit UIFontFaceClass(int size, ODFontMetrics const & metrics); + + int Advance(unsigned char glyph) const { return(_Advance[glyph]); } + int Width(char const * text, std::size_t length) const; + + Rml::FontMetrics const & Metrics(void) const { return(_Metrics); } + int Size(void) const { return(_Size); } + + float Scale(void) const { return(_Scale); } + int CellWidth(void) const { return(_Sheet.leftMargin + _Sheet.glyphWidth); } + int CellHeight(void) const { return(_Sheet.topMargin + _Sheet.glyphHeight); } + int TopMargin(void) const { return(_Sheet.topMargin); } + + private: + ODFontMetrics _Sheet; + Rml::FontMetrics _Metrics = {}; + int _Size = 0; + float _Scale = 1.0f; + int _Advance[256] = {}; +}; + + +UIFontFaceClass::UIFontFaceClass(int size, ODFontMetrics const & metrics) : + _Sheet(metrics), + _Size(size) +{ + int cell_height = _Sheet.topMargin + _Sheet.glyphHeight; + if (cell_height <= 0) { + cell_height = 1; + } + + _Scale = (float)size / (float)cell_height; + if (_Scale <= 0.0f) { + _Scale = 1.0f; + } + + for (int i = 0; i < 256; ++i) { + _Advance[i] = (int)std::lround((double)_Sheet.charWidths[i] * _Scale); + } + + // The sheets give inked extents, not typographic ones. The whole glyph sits above the + // baseline with nothing below it, so a line box centres the ink on itself: RmlUi puts + // the baseline half the leading below the top, which places the ink block exactly where + // OD_DRAW_CHAR_FLAG_VERTICAL_CENTER placed it. + _Metrics.size = size; + _Metrics.ascent = (float)_Sheet.glyphHeight * _Scale; + _Metrics.descent = 0.0f; + _Metrics.line_spacing = (float)cell_height * _Scale; + _Metrics.x_height = _Metrics.ascent * 0.5f; + _Metrics.underline_position = 0.0f; + _Metrics.underline_thickness = std::max(1.0f, _Scale); + _Metrics.has_ellipsis = false; +} + + +int UIFontFaceClass::Width(char const * text, std::size_t length) const +{ + int width = 0; + char const * cursor = text; + char const * end = text + length; + + while (cursor < end) { + char const * before = cursor; + char32_t code = UTF8::Decode(cursor); + if (cursor <= before) { + break; + } + width += _Advance[OD_Font_Glyph(code)]; + } + + return(width); +} + + +static std::map> _Faces; +static std::map _Sheets; +static bool _MetricsRead = false; +static bool _MetricsUsable = false; +static ODFontMetrics _SheetMetrics = {}; +static int _Version = 1; + + +/// +/// Measures the sheets once and remembers whether they could be read at all. +/// The sheets live in a mix file, so the first document shown before the mixes are mounted +/// would find nothing; a later attempt is not made, and the family falls back to whatever +/// the document lists after it. +/// +static bool Sheet_Metrics(ODFontMetrics & metrics) +{ + if (!_MetricsRead) { + _MetricsRead = true; + _MetricsUsable = OD_Font_Metrics(_FontFamily, _SheetMetrics); + + if (_MetricsUsable) { + DebugString("[UI] %s cells %dx%d, ink %dx%d, margins %d,%d.\n", _FontFamily, + _SheetMetrics.leftMargin + _SheetMetrics.glyphWidth, + _SheetMetrics.topMargin + _SheetMetrics.glyphHeight, + _SheetMetrics.glyphWidth, _SheetMetrics.glyphHeight, + _SheetMetrics.leftMargin, _SheetMetrics.topMargin); + } else { + DebugString("[UI] The %s font sheets could not be read.\n", _FontFamily); + } + } + + if (!_MetricsUsable) { + return(false); + } + + metrics = _SheetMetrics; + return(true); +} + + +/// +/// Returns the glyph sheet remapped to one colour, building and uploading it on first use. +/// +static Rml::Texture Sheet_Texture(Rml::RenderManager & manager, unsigned int color, int & width, int & height) +{ + auto found = _Sheets.find(color); + if (found == _Sheets.end()) { + int sheet_width = 0; + int sheet_height = 0; + std::vector pixels; + + if (!OD_Font_Sheet(_FontFamily, (COLORREF)color, sheet_width, sheet_height, pixels)) { + return(Rml::Texture()); + } + + UIFontSheet sheet; + sheet.Width = sheet_width; + sheet.Height = sheet_height; + sheet.Source = Rml::CallbackTextureSource( + [pixels = std::move(pixels), sheet_width, sheet_height](Rml::CallbackTextureInterface const & texture) -> bool { + return(texture.GenerateTexture( + Rml::Span(pixels.data(), pixels.size()), + Rml::Vector2i(sheet_width, sheet_height))); + }); + + found = _Sheets.emplace(color, std::move(sheet)).first; + } + + width = found->second.Width; + height = found->second.Height; + return(found->second.Source.GetTexture(manager)); +} + + +/// +/// Recovers the colour a caller asked for from the premultiplied one RmlUi hands over. +/// +static unsigned int Unpremultiplied_Color(Rml::ColourbPremultiplied colour) +{ + int alpha = colour.alpha; + int red = colour.red; + int green = colour.green; + int blue = colour.blue; + + if (alpha > 0 && alpha < 255) { + red = std::min(255, red * 255 / alpha); + green = std::min(255, green * 255 / alpha); + blue = std::min(255, blue * 255 / alpha); + } + + return((unsigned int)(red | (green << 8) | (blue << 16))); +} + + +// The bitmap engine answers for one family and hands everything else to RmlUi's own engine +// untouched, so a document that asks for the shipped TrueType face is unaffected. +class UIFontEngineClass : public Rml::FontEngineInterfaceDefault +{ + public: + Rml::FontFaceHandle GetFontFaceHandle(Rml::String const & family, Rml::Style::FontStyle style, + Rml::Style::FontWeight weight, int size) override; + Rml::FontEffectsHandle PrepareFontEffects(Rml::FontFaceHandle handle, Rml::FontEffectList const & effects) override; + Rml::FontMetrics const & GetFontMetrics(Rml::FontFaceHandle handle) override; + int GetStringWidth(Rml::FontFaceHandle handle, Rml::StringView string, + Rml::TextShapingContext const & shaping, Rml::Character prior) override; + int GenerateString(Rml::RenderManager & manager, Rml::FontFaceHandle handle, Rml::FontEffectsHandle effects, + Rml::StringView string, Rml::Vector2f position, Rml::ColourbPremultiplied colour, float opacity, + Rml::TextShapingContext const & shaping, Rml::TexturedMeshList & mesh_list) override; + int GetVersion(Rml::FontFaceHandle handle) override; + void ReleaseFontResources(void) override; +}; + + +static UIFontFaceClass * Bitmap_Face(Rml::FontFaceHandle handle) +{ + for (auto const & entry : _Faces) { + if ((Rml::FontFaceHandle)entry.second.get() == handle) { + return(entry.second.get()); + } + } + return(nullptr); +} + + +Rml::FontFaceHandle UIFontEngineClass::GetFontFaceHandle(Rml::String const & family, Rml::Style::FontStyle style, + Rml::Style::FontWeight weight, int size) +{ + if (Rml::StringUtilities::ToLower(family) != _FontFamily) { + return(Rml::FontEngineInterfaceDefault::GetFontFaceHandle(family, style, weight, size)); + } + + ODFontMetrics metrics; + if (size <= 0 || !Sheet_Metrics(metrics)) { + return(0); + } + + auto found = _Faces.find(size); + if (found == _Faces.end()) { + found = _Faces.emplace(size, std::make_unique(size, metrics)).first; + } + + return((Rml::FontFaceHandle)found->second.get()); +} + + +Rml::FontEffectsHandle UIFontEngineClass::PrepareFontEffects(Rml::FontFaceHandle handle, Rml::FontEffectList const & effects) +{ + if (Bitmap_Face(handle) != nullptr) { + return(0); + } + return(Rml::FontEngineInterfaceDefault::PrepareFontEffects(handle, effects)); +} + + +Rml::FontMetrics const & UIFontEngineClass::GetFontMetrics(Rml::FontFaceHandle handle) +{ + UIFontFaceClass const * face = Bitmap_Face(handle); + if (face != nullptr) { + return(face->Metrics()); + } + return(Rml::FontEngineInterfaceDefault::GetFontMetrics(handle)); +} + + +int UIFontEngineClass::GetStringWidth(Rml::FontFaceHandle handle, Rml::StringView string, + Rml::TextShapingContext const & shaping, Rml::Character prior) +{ + UIFontFaceClass const * face = Bitmap_Face(handle); + if (face == nullptr) { + return(Rml::FontEngineInterfaceDefault::GetStringWidth(handle, string, shaping, prior)); + } + + int width = face->Width(string.begin(), string.size()); + width += (int)std::lround((double)shaping.letter_spacing) * (int)string.size(); + return(width); +} + + +int UIFontEngineClass::GenerateString(Rml::RenderManager & manager, Rml::FontFaceHandle handle, + Rml::FontEffectsHandle effects, Rml::StringView string, Rml::Vector2f position, + Rml::ColourbPremultiplied colour, float opacity, Rml::TextShapingContext const & shaping, + Rml::TexturedMeshList & mesh_list) +{ + UIFontFaceClass const * face = Bitmap_Face(handle); + if (face == nullptr) { + return(Rml::FontEngineInterfaceDefault::GenerateString(manager, handle, effects, string, position, + colour, opacity, shaping, mesh_list)); + } + + int sheet_width = 0; + int sheet_height = 0; + Rml::Texture texture = Sheet_Texture(manager, Unpremultiplied_Color(colour), sheet_width, sheet_height); + if (!texture || sheet_width <= 0 || sheet_height <= 0) { + return(0); + } + + mesh_list.resize(1); + mesh_list[0].texture = texture; + Rml::Mesh & mesh = mesh_list[0].mesh; + mesh.vertices.reserve(string.size() * 4); + mesh.indices.reserve(string.size() * 6); + + float const scale = face->Scale(); + int const cell_width = face->CellWidth(); + int const cell_height = face->CellHeight(); + int const columns = (cell_width > 0) ? (sheet_width / cell_width) : 1; + + // The remapped sheet already carries the colour, so the quads only carry the opacity, + // which is what RmlUi's own engine does for a colour glyph. + Rml::ColourbPremultiplied const vertex_colour(colour.alpha, colour.alpha); + + Rml::Vector2f const dimensions((float)cell_width * scale, (float)cell_height * scale); + float const top = position.y - face->Metrics().ascent - (float)face->TopMargin() * scale; + int const spacing = (int)std::lround((double)shaping.letter_spacing); + + int line_width = 0; + char const * cursor = string.begin(); + char const * end = string.end(); + + while (cursor < end) { + char const * before = cursor; + char32_t code = UTF8::Decode(cursor); + if (cursor <= before) { + break; + } + + unsigned char glyph = OD_Font_Glyph(code); + if (glyph > ' ' && columns > 0) { + // Cell zero is blank; the sheet stores the glyph for code n in cell n + 1. + int cell = glyph + 1; + float left = (float)((cell % columns) * cell_width); + float upper = (float)((cell / columns) * cell_height); + + Rml::Vector2f const top_left(left / (float)sheet_width, upper / (float)sheet_height); + Rml::Vector2f const bottom_right((left + cell_width) / (float)sheet_width, + (upper + cell_height) / (float)sheet_height); + + // The blit started one pixel left of the pen, which is what puts the ink at the + // pen once the cell's own left margin is crossed. + Rml::Vector2f const origin(position.x + (float)line_width - scale, top); + + Rml::MeshUtilities::GenerateQuad(mesh, origin.Round(), dimensions, vertex_colour, + top_left, bottom_right); + } + + line_width += face->Advance(glyph) + spacing; + } + + return(std::max(line_width, 0)); +} + + +int UIFontEngineClass::GetVersion(Rml::FontFaceHandle handle) +{ + if (Bitmap_Face(handle) != nullptr) { + return(_Version); + } + return(Rml::FontEngineInterfaceDefault::GetVersion(handle)); +} + + +void UIFontEngineClass::ReleaseFontResources(void) +{ + _Sheets.clear(); + ++_Version; + Rml::FontEngineInterfaceDefault::ReleaseFontResources(); +} + + +static UIFontEngineClass _FontEngine; + + +Rml::FontEngineInterface * UI_Font_Interface(void) +{ + return(&_FontEngine); +} + + +void UI_Font_Shutdown(void) +{ + _Sheets.clear(); + _Faces.clear(); + _MetricsRead = false; + _MetricsUsable = false; +} diff --git a/code/ui/uiinternal.h b/code/ui/uiinternal.h index fddf99d9f..1ff17a5b2 100644 --- a/code/ui/uiinternal.h +++ b/code/ui/uiinternal.h @@ -22,6 +22,7 @@ namespace Rml { class RenderInterface; class SystemInterface; class FileInterface; + class FontEngineInterface; } @@ -37,6 +38,10 @@ struct UIImageData // uitexture.cpp bool UI_Decode_Image(char const * source, UIImageData & image); +// uifont.cpp +Rml::FontEngineInterface * UI_Font_Interface(void); +void UI_Font_Shutdown(void); + // uirender.cpp Rml::RenderInterface * UI_Render_Interface(void); bool UI_Render_Init(void); diff --git a/code/ui/uishell.cpp b/code/ui/uishell.cpp index 6bf82aa72..6c7cf824b 100644 --- a/code/ui/uishell.cpp +++ b/code/ui/uishell.cpp @@ -376,6 +376,10 @@ bool UI_Init(void) Rml::SetFileInterface(UI_File_Interface()); Rml::SetRenderInterface(UI_Render_Interface()); + // One font engine serves the whole process. This one answers for the game's own bitmap + // sheets and hands every other family to RmlUi's FreeType engine unchanged. + Rml::SetFontEngineInterface(UI_Font_Interface()); + if (!Rml::Initialise()) { DebugString("[UI] RmlUi could not be started.\n"); UI_Render_Shutdown(); @@ -428,6 +432,7 @@ void UI_Shutdown(void) _Context = nullptr; Rml::Shutdown(); + UI_Font_Shutdown(); UI_Render_Shutdown(); _Initialized = false; diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index 230cc1e55..84a99b4bf 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -265,6 +265,7 @@ today. | `uirender.cpp` | RmlUi render interface and the ImGui renderer on bgfx; the only UI file that includes bgfx | | `uisystem.cpp` | RmlUi system interface: time, logging to `DebugString`, cursor, clipboard, string translation | | `uifile.cpp` | RmlUi file interface over `CCFileClass` | +| `uifont.cpp` | RmlUi font engine: the `dlgsys` bitmap sheets, delegating every other family to RmlUi's own engine | | `uitexture.cpp` | image decoding, SHP and PCX conversion, surface-backed textures | | `uiscreen.h`, `uirmlview.h` | presenter, intent, and result contracts; the RmlUi view base | | `uidev.cpp` | ImGui context and developer overlays | @@ -616,15 +617,32 @@ pointers, and a presenter never holds a provider. ### Fonts -Fonts use RmlUi's FreeType engine with an OFL sans-serif shipped in `ui/`. -The legacy dialogs already draw with a system TrueType face, so this changes -nothing about their look. RmlUi uses one font engine per process, installed -with `SetFontEngineInterface` before `Rml::Initialise`, and the built-in -engine is not reachable from a custom one. In-game text that must match the -bitmap fonts, needed only by the post-migration sidebar view, has two routes: -convert the game's `.fnt` faces to TrueType at build time, or write a bitmap -engine over `WWFontClass` data as RmlUi's `bitmap_font` sample does and -commit every document to bitmap faces. That choice waits for that view. +Documents use RmlUi's FreeType engine with an OFL sans-serif shipped in +`ui/`. RmlUi installs one font engine per process, with +`SetFontEngineInterface` before `Rml::Initialise`, so `uifont.cpp` derives +from `FontEngineInterfaceDefault` rather than replacing it: it answers for +one family and hands every other one to the FreeType engine untouched. That +header lives in RmlUi's `Source` tree rather than its `Include` tree, so the +build puts that one path on that one file. + +The family it answers for is `dlgsys`, the remap sheets the dialogs drew +their buttons, statics, tabs, check boxes, combo boxes and track bar values +from. `drawhelp.cpp` owns the sheets: `OD_Font_Metrics` probes the cell size +and every character's inked width out of the artwork, and `OD_Font_Sheet` +composes the coverage sheet and the palette-shifted index sheet into +premultiplied RGBA for one text colour. `uifont.cpp` uploads that as one +texture per colour and emits a quad per glyph. A document states the height +of a glyph cell as its `font-size`, so `font-size: 18dp` against the 14 by +18 cells of `dlgsys` draws one sheet pixel per authored pixel. The sheets +give inked extents rather than typographic ones, so the face reports the +whole glyph as ascent and nothing as descent, which makes RmlUi's half +leading centre the ink on the line box the way +`OD_DRAW_CHAR_FLAG_VERTICAL_CENTER` centred it on a control. + +`ui/campaign.rcss` is the only document that asks for it. Whether it spreads +is an open decision. In-game text that must match the `WWFontClass` faces, +needed only by the post-migration sidebar view, is a separate problem: those +are a different format and this engine does not read them. ### Strings diff --git a/ui/campaign.rcss b/ui/campaign.rcss index 16c569509..d7b215479 100644 --- a/ui/campaign.rcss +++ b/ui/campaign.rcss @@ -76,3 +76,20 @@ #ok { left: 179.5dp; } #cancel { left: 265dp; } + + +/* A trial of the dialog system's own bitmap font, scoped to this document. + + The dialogs drew a push button's caption, a static and a track bar's value from the + dlgsys remap sheets, not from a scalable face; a list box was the control that used the + system face, so #campaignlist keeps the shipped one. font-size names the height of a + glyph cell in dp. The sheets are 640 by 160 with 14 by 18 cells, so 18dp draws one sheet + pixel per authored pixel and the text follows the frame scale exactly as the artwork + around it does. The selectors are held under #dialog so nothing here can reach another + document. */ +#dialog .caption, +#dialog .button +{ + font-family: dlgsys; + font-size: 18dp; +} From 477f6449c0f9b17b1c010468200978a3980d8deb Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 17:07:41 +0100 Subject: [PATCH 173/179] feat(ui): point sample the overlay textures --- code/ui/uirender.cpp | 17 ++++++++++++----- docs/UI_DESIGN.md | 11 ++++++++--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/code/ui/uirender.cpp b/code/ui/uirender.cpp index e99a6ca96..06794781d 100644 --- a/code/ui/uirender.cpp +++ b/code/ui/uirender.cpp @@ -56,6 +56,13 @@ static int _OriginY = 0; static int _Width = 0; static int _Height = 0; +// The documents draw the game's own 640x400-era artwork and its bitmap font, magnified by +// whatever the frame scale is. Linear filtering softens both; point sampling keeps the +// pixels the artists drew. Art and text share this so they never disagree. +static const uint64_t _SamplerFlags = + BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP + | BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT | BGFX_SAMPLER_MIP_POINT; + static const uint64_t _BlendState = BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_MSAA | BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_INV_SRC_ALPHA); @@ -188,7 +195,7 @@ void UIRenderInterface::RenderGeometry(Rml::CompiledGeometryHandle handle, Rml:: bgfx::TextureHandle bound = Texture_From_Handle((uintptr_t)texture); bgfx::setTexture(0, _TextureSampler, bgfx::isValid(bound) ? bound : _WhiteTexture, - BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + _SamplerFlags); if (ScissorEnabled) { // RmlUi reports the region relative to the context, which sits at the frame's top @@ -249,7 +256,7 @@ Rml::TextureHandle UIRenderInterface::GenerateTexture(Rml::Span bgfx::TextureHandle texture = bgfx::createTexture2D( (uint16_t)dimensions.x, (uint16_t)dimensions.y, false, 1, bgfx::TextureFormat::RGBA8, - BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP, + _SamplerFlags, bgfx::copy(source.data(), (uint32_t)source.size())); return((Rml::TextureHandle)Handle_From_Texture(texture)); @@ -323,7 +330,7 @@ bool UI_Render_Init(void) // pixel and takes its colour from the vertices alone. const uint32_t white = 0xFFFFFFFF; _WhiteTexture = bgfx::createTexture2D(1, 1, false, 1, bgfx::TextureFormat::RGBA8, - BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP, bgfx::copy(&white, sizeof(white))); + _SamplerFlags, bgfx::copy(&white, sizeof(white))); if (!bgfx::isValid(_Program) || !bgfx::isValid(_TextureSampler) || !bgfx::isValid(_WhiteTexture)) { UI_Render_Shutdown(); @@ -404,7 +411,7 @@ void UI_Render_ImGui(ImDrawData * data) if (texture->Status == ImTextureStatus_WantCreate) { bgfx::TextureHandle created = bgfx::createTexture2D( (uint16_t)texture->Width, (uint16_t)texture->Height, false, 1, bgfx::TextureFormat::RGBA8, - BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP, + _SamplerFlags, bgfx::copy(texture->GetPixels(), (uint32_t)(texture->Width * texture->Height * 4))); texture->SetTexID((ImTextureID)Handle_From_Texture(created)); @@ -465,7 +472,7 @@ void UI_Render_ImGui(ImDrawData * data) bgfx::TextureHandle texture = Texture_From_Handle((uintptr_t)command.GetTexID()); bgfx::setTexture(0, _TextureSampler, bgfx::isValid(texture) ? texture : _WhiteTexture, - BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + _SamplerFlags); bgfx::setState(_BlendState); bgfx::setVertexBuffer(0, &vertices, command.VtxOffset, vertexcount - command.VtxOffset); diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index 84a99b4bf..66f39dddb 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -298,6 +298,11 @@ frame's top-left corner. Draw order is the software frame and its scaling passes, RmlUi documents in the context's document order, ImGui, then the hardware cursor. +Every overlay texture is point sampled. The documents draw the game's own +640x400-era artwork and its bitmap font magnified by the frame scale, which at +a 640x400 frame in a 3456x2160 window is 5.4x; linear filtering softens both. +The art and the text share one sampler state so they cannot disagree. + One RmlUi context holds every document. A second context is justified only by an independent coordinate space or lifetime. Data-model names are unique among live screens, binding storage is owned by the view and outlives the @@ -640,9 +645,9 @@ leading centre the ink on the line box the way `OD_DRAW_CHAR_FLAG_VERTICAL_CENTER` centred it on a control. `ui/campaign.rcss` is the only document that asks for it. Whether it spreads -is an open decision. In-game text that must match the `WWFontClass` faces, -needed only by the post-migration sidebar view, is a separate problem: those -are a different format and this engine does not read them. +is an open decision. In-game text that must match the `WWFontClass` faces, needed only by the +post-migration sidebar view, is a separate problem: those are a different +format and this engine does not read them. ### Strings From c898cfb5adb84967c6ab0b6bd52cc98a8b81342c Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 17:07:41 +0100 Subject: [PATCH 174/179] feat(ui): draw every dialog's text with the dlgsys sheets --- docs/UI_DESIGN.md | 14 ++++++++++++-- ui/campaign.rcss | 17 ----------------- ui/desyncbase.rcss | 8 ++++++++ ui/gameoptionswol.rcss | 8 ++++++++ ui/keyboard.rcss | 9 +++++++++ ui/mapgenbase.rcss | 8 ++++++++ ui/messagebox.rcss | 8 +++++++- ui/mpguest.rcss | 4 ++++ ui/mphost.rcss | 4 ++++ ui/optionsbase.rcss | 37 +++++++++++++++++++++++++++++++++++-- ui/progresswait.rcss | 8 ++++++++ ui/skirmish.rcss | 12 ++++++++++++ ui/waitbox.rcss | 8 +++++++- 13 files changed, 122 insertions(+), 23 deletions(-) diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index 66f39dddb..55c7d7747 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -644,8 +644,18 @@ whole glyph as ascent and nothing as descent, which makes RmlUi's half leading centre the ink on the line box the way `OD_DRAW_CHAR_FLAG_VERTICAL_CENTER` centred it on a control. -`ui/campaign.rcss` is the only document that asks for it. Whether it spreads -is an open decision. In-game text that must match the `WWFontClass` faces, needed only by the +`ui/optionsbase.rcss` names which controls draw from it, and every shipped +document links that sheet, so the split is stated once: a push button, a check +box, a static caption, a tab, a combo box and a track bar's value take +`dlgsys`; a list box and its rows, an edit field, a message log and the hotkey +capture control keep the shipped face, which is what the dialogs did. Eight +documents add a rule of their own for a static the templates left without a +class. A static caption does not clip, because a glyph cell is 18dp and the +templates give a static as little as 13dp, while `StaticCtrlProc` had no such +limit; the two captions whose text comes from the game rather than from a +template ask for the clip back. + +In-game text that must match the `WWFontClass` faces, needed only by the post-migration sidebar view, is a separate problem: those are a different format and this engine does not read them. diff --git a/ui/campaign.rcss b/ui/campaign.rcss index d7b215479..16c569509 100644 --- a/ui/campaign.rcss +++ b/ui/campaign.rcss @@ -76,20 +76,3 @@ #ok { left: 179.5dp; } #cancel { left: 265dp; } - - -/* A trial of the dialog system's own bitmap font, scoped to this document. - - The dialogs drew a push button's caption, a static and a track bar's value from the - dlgsys remap sheets, not from a scalable face; a list box was the control that used the - system face, so #campaignlist keeps the shipped one. font-size names the height of a - glyph cell in dp. The sheets are 640 by 160 with 14 by 18 cells, so 18dp draws one sheet - pixel per authored pixel and the text follows the frame scale exactly as the artwork - around it does. The selectors are held under #dialog so nothing here can reach another - document. */ -#dialog .caption, -#dialog .button -{ - font-family: dlgsys; - font-size: 18dp; -} diff --git a/ui/desyncbase.rcss b/ui/desyncbase.rcss index 6ece9c3a1..4b498e453 100644 --- a/ui/desyncbase.rcss +++ b/ui/desyncbase.rcss @@ -142,3 +142,11 @@ /* The three buttons, 70 x 12 dialog units at y 239. */ .button { top: 388.375dp; width: 105dp; height: 19.5dp; line-height: 19.5dp; } + +/* The dialog's own heading, an LTEXT static. + The dlgsys sheets, as optionsbase.rcss gives every other static. */ +#header +{ + font-family: dlgsys; + font-size: 18dp; +} diff --git a/ui/gameoptionswol.rcss b/ui/gameoptionswol.rcss index 3a647cc12..ac8d69b06 100644 --- a/ui/gameoptionswol.rcss +++ b/ui/gameoptionswol.rcss @@ -83,3 +83,11 @@ #connectionlabel, #connectionvalue { top: 176.75dp; } #speedlabel, #speedvalue { top: 212.5dp; } + +/* The group box caption, a static. + The dlgsys sheets, as optionsbase.rcss gives every other static. */ +#group +{ + font-family: dlgsys; + font-size: 18dp; +} diff --git a/ui/keyboard.rcss b/ui/keyboard.rcss index 3d296c455..b1d09fb94 100644 --- a/ui/keyboard.rcss +++ b/ui/keyboard.rcss @@ -180,3 +180,12 @@ #reset { left: 31dp; top: 292.125dp; width: 81dp; } #ok { left: 278.5dp; top: 292.125dp; width: 75dp; } #cancel { left: 394dp; top: 292.125dp; width: 75dp; } + +/* Both statics. #capture stands where msctls_hotkey32 stood and keeps the system face. + The dlgsys sheets, as optionsbase.rcss gives every other static. */ +#descriptionbox, +#description +{ + font-family: dlgsys; + font-size: 18dp; +} diff --git a/ui/mapgenbase.rcss b/ui/mapgenbase.rcss index b8c5dde09..c4f934b2d 100644 --- a/ui/mapgenbase.rcss +++ b/ui/mapgenbase.rcss @@ -148,3 +148,11 @@ /* A BS_AUTOCHECKBOX, which the Firestorm and tournament templates put down the right hand side, 84 x 10 dialog units. */ .check { width: 126dp; height: 16.25dp; line-height: 16.25dp; } + +/* The preview frame's caption, a static. + The dlgsys sheets, as optionsbase.rcss gives every other static. */ +#previewword +{ + font-family: dlgsys; + font-size: 18dp; +} diff --git a/ui/messagebox.rcss b/ui/messagebox.rcss index 4ddd6608d..a3671ed31 100644 --- a/ui/messagebox.rcss +++ b/ui/messagebox.rcss @@ -66,4 +66,10 @@ body /* A lone button takes the middle slot, as the dialog moved it there. */ .first.centred { left: 148dp; } - +/* The message, a static. + The dlgsys sheets, as optionsbase.rcss gives every other static. */ +#text +{ + font-family: dlgsys; + font-size: 18dp; +} diff --git a/ui/mpguest.rcss b/ui/mpguest.rcss index 640d88495..157af8600 100644 --- a/ui/mpguest.rcss +++ b/ui/mpguest.rcss @@ -103,3 +103,7 @@ #cancel { left: 416.5dp; top: 347.375dp; } #accept { left: 523dp; top: 347.375dp; } + +/* The map name comes from the scenario, not from a template, so this one keeps the clip + optionsbase.rcss drops for a static caption. At 16.25dp a dlgsys cell is not clipped. */ +#scenarioname { overflow: hidden; } diff --git a/ui/mphost.rcss b/ui/mphost.rcss index 8e92cc5ec..89f7735dd 100644 --- a/ui/mphost.rcss +++ b/ui/mphost.rcss @@ -94,3 +94,7 @@ #cancel { left: 434.5dp; top: 353.875dp; width: 79.5dp; } #go { left: 529dp; top: 353.875dp; width: 81dp; } + +/* The map name comes from the scenario, not from a template, so this one keeps the clip + optionsbase.rcss drops for a static caption. At 16.25dp a dlgsys cell is not clipped. */ +#scenarioname { overflow: hidden; } diff --git a/ui/optionsbase.rcss b/ui/optionsbase.rcss index 81733cc87..b625d6a5e 100644 --- a/ui/optionsbase.rcss +++ b/ui/optionsbase.rcss @@ -129,13 +129,21 @@ body pointer-events: none; } -/* A static caption. A document says where it sits and how it is aligned. */ +/* A static caption. A document says where it sits and how it is aligned. + + It does not clip. A dlgsys glyph cell is 18dp and the templates give a static as little + as 13dp, so a clipping caption would cut the shadow row off a capital and the tail off a + descender; StaticCtrlProc had no such limit, because ODDrawCharRemap blits without + clipping to the control. RmlUi clips both axes together or neither, and no static caption + in any shipped document is wider than its control -- the tightest has 4.5dp to spare -- + so there is nothing for a clip to protect here. A caption whose text comes from the game + rather than the templates asks for the clip back in its own stylesheet. */ .caption { display: block; position: absolute; white-space: nowrap; - overflow: hidden; + overflow: visible; } /* A TBS_NOTICKS track bar. The original drew no groove: the control is the wallpaper @@ -240,3 +248,28 @@ body .list scrollbarvertical sliderarrowinc { decorator: image(dnarrowr.pcx); } .list scrollbarvertical sliderarrowdec:active { decorator: image(uparrowp.pcx); } .list scrollbarvertical sliderarrowinc:active { decorator: image(dnarrowp.pcx); } + +/* Which face a control draws with. + + The dialogs drew a push button, a check box, a static caption, a tab, a combo box and a + track bar's value from the dlgsys remap sheets, and reached for the system face only for + a list box, a tooltip and the hotkey control; an edit box went through that face too. + That split is not decoration -- it is most of why the screens read as the original -- so + it is reproduced here rather than applied to everything. + + font-size names the height of a glyph cell. The sheets carry 14 by 18 cells, so 18dp + draws one sheet pixel per authored pixel and the text follows the frame scale exactly as + the artwork around it does. docs/UI_DESIGN.md, "Fonts", owns the split. + + Everything not named here keeps the shipped face body declares: the lists and their rows, + the edit fields, the message logs and the hotkey capture control. */ +.button, +.check, +.caption, +.label, +.prose, +select +{ + font-family: dlgsys; + font-size: 18dp; +} diff --git a/ui/progresswait.rcss b/ui/progresswait.rcss index ca77700df..e23ffa993 100644 --- a/ui/progresswait.rcss +++ b/ui/progresswait.rcss @@ -72,3 +72,11 @@ surface display: inline-block; vertical-align: middle; } + +/* The caption, a static. + The dlgsys sheets, as optionsbase.rcss gives every other static. */ +#text +{ + font-family: dlgsys; + font-size: 18dp; +} diff --git a/ui/skirmish.rcss b/ui/skirmish.rcss index 586b7239a..0bbb76ed5 100644 --- a/ui/skirmish.rcss +++ b/ui/skirmish.rcss @@ -241,3 +241,15 @@ #multimap { left: 439dp; top: 318.125dp; width: 165dp; } #accept { left: 439dp; top: 345.75dp; width: 75dp; } #cancel { left: 529dp; top: 345.75dp; width: 75dp; } + +/* The preview frame's caption, a static. + The dlgsys sheets, as optionsbase.rcss gives every other static. */ +#previewtext +{ + font-family: dlgsys; + font-size: 18dp; +} + +/* The map name comes from the scenario, not from a template, so this one keeps the clip + optionsbase.rcss drops for a static caption. At 16.25dp a dlgsys cell is not clipped. */ +#scenarioname { overflow: hidden; } diff --git a/ui/waitbox.rcss b/ui/waitbox.rcss index 437ed383a..f47e1b61b 100644 --- a/ui/waitbox.rcss +++ b/ui/waitbox.rcss @@ -55,4 +55,10 @@ body text-align: center; } - +/* The message, a static. + The dlgsys sheets, as optionsbase.rcss gives every other static. */ +#text +{ + font-family: dlgsys; + font-size: 18dp; +} From f2e31ad50c40c7d79c1a0693f0fa0cd12e59d37c Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 17:27:42 +0100 Subject: [PATCH 175/179] fix(ui): give the font sheets back before RmlUi drops its render manager --- code/ui/uifont.cpp | 24 ++++++++++++++++++++++-- docs/UI_DESIGN.md | 8 ++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/code/ui/uifont.cpp b/code/ui/uifont.cpp index c1145b337..038ffa842 100644 --- a/code/ui/uifont.cpp +++ b/code/ui/uifont.cpp @@ -246,6 +246,7 @@ static unsigned int Unpremultiplied_Color(Rml::ColourbPremultiplied colour) class UIFontEngineClass : public Rml::FontEngineInterfaceDefault { public: + void Shutdown(void) override; Rml::FontFaceHandle GetFontFaceHandle(Rml::String const & family, Rml::Style::FontStyle style, Rml::Style::FontWeight weight, int size) override; Rml::FontEffectsHandle PrepareFontEffects(Rml::FontFaceHandle handle, Rml::FontEffectList const & effects) override; @@ -416,6 +417,23 @@ void UIFontEngineClass::ReleaseFontResources(void) } +/// +/// Gives back the glyph sheets while RmlUi still owns the render manager that has to free +/// them. +/// Rml::Shutdown calls this and then clears its render managers. Releasing a sheet after +/// that point dereferences a destroyed texture database, so this is the last moment a font +/// engine may hold a render resource. Rml::ReleaseFontResources, which also drops them, is +/// a collection entry point the application calls and is not part of shutdown. +/// +void UIFontEngineClass::Shutdown(void) +{ + _Sheets.clear(); + _Faces.clear(); + ++_Version; + Rml::FontEngineInterfaceDefault::Shutdown(); +} + + static UIFontEngineClass _FontEngine; @@ -427,8 +445,10 @@ Rml::FontEngineInterface * UI_Font_Interface(void) void UI_Font_Shutdown(void) { - _Sheets.clear(); - _Faces.clear(); + // The sheets and the faces are gone by now: Rml::Shutdown took them through the engine's + // own Shutdown, which is the only point where a render resource can still be released. + // Nothing here may touch one. What is left is the measurement of the artwork, which the + // next shell start probes again because the surface cache may have been emptied. _MetricsRead = false; _MetricsUsable = false; } diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index 55c7d7747..ec8647d16 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -655,6 +655,14 @@ templates give a static as little as 13dp, while `StaticCtrlProc` had no such limit; the two captions whose text comes from the game rather than from a template ask for the clip back. +A font engine gives its render resources back in `FontEngineInterface::Shutdown`, +which `Rml::Shutdown` calls while the render managers are still alive, and not +in `ReleaseFontResources`. That second method is the entry point behind +`Rml::ReleaseFontResources`, which an application calls to collect memory; no +part of shutdown reaches it. Holding a `CallbackTexture` past +`FontEngineInterface::Shutdown` releases it against a destroyed texture +database. + In-game text that must match the `WWFontClass` faces, needed only by the post-migration sidebar view, is a separate problem: those are a different format and this engine does not read them. From f2c64ef1c70e8b6450e4f5ad1636bda75179414a Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 18:47:57 +0100 Subject: [PATCH 176/179] Point the command catalogue at the code that survived OwnerDraw Build_Hotkey_String moved to keyboard.cpp, and the main menu no longer reads the keyboard, so its version and credits shortcuts are gone. --- code/ui/uikeyboard.cpp | 3 -- .../changes/main-menu-keyboard-shortcuts.md | 24 ++++++++++++++ manual/data/command-adapters.yaml | 32 ++----------------- manual/data/commands.yaml | 26 --------------- manual/data/tombstones.yaml | 14 ++++++++ 5 files changed, 41 insertions(+), 58 deletions(-) create mode 100644 manual/changes/main-menu-keyboard-shortcuts.md diff --git a/code/ui/uikeyboard.cpp b/code/ui/uikeyboard.cpp index a10f9efd1..6f0f3e5dd 100644 --- a/code/ui/uikeyboard.cpp +++ b/code/ui/uikeyboard.cpp @@ -57,9 +57,6 @@ #include -// Build_Hotkey_String lives in ownrdraw.cpp and is the only thing this screen wants from -// there. It spells a key, not a control, and moves with the rest of the keyboard support -// when OwnerDraw is retired. std::string UIKeyboardPresenterClass::Key_Name(int key) { char buffer[64]; diff --git a/manual/changes/main-menu-keyboard-shortcuts.md b/manual/changes/main-menu-keyboard-shortcuts.md new file mode 100644 index 000000000..2f86c32d7 --- /dev/null +++ b/manual/changes/main-menu-keyboard-shortcuts.md @@ -0,0 +1,24 @@ +--- +title: Retire the main menu's version and credits shortcuts +category: fix +release: 0.2.0 +breaking: true +migration: +- Open the version screen from the main menu instead of pressing Ctrl+V. +- Start the credits from the main menu instead of pressing Ctrl+Alt+C. Escape still stops them. +targets: +- type: command + id: fixed:main-menu-version + effect: removed +- type: command + id: fixed:main-menu-credits + effect: removed +credit: [OpenTS contributors] +--- + +The classic main menu read the keyboard directly and opened the version dialog on Ctrl+V and +the credits on Ctrl+Alt+C. `Main_Menu` now shows the menu screen rather than polling for keys, +so neither shortcut has anywhere to be handled and both are gone. + +Only the shortcuts were lost. The version screen and the credits are both still reached from +the menu itself, and Escape still stops the credits. diff --git a/manual/data/command-adapters.yaml b/manual/data/command-adapters.yaml index 9fb84cd49..3a4142207 100644 --- a/manual/data/command-adapters.yaml +++ b/manual/data/command-adapters.yaml @@ -225,24 +225,6 @@ fixed_controls: - { file: code/mpscore.cpp, function: MultiScore::User_Input, expression: VK_LBUTTON } - { file: code/mpscore.cpp, function: MultiScore::User_Input, expression: VK_ESCAPE } - { file: code/mpscore.cpp, function: MultiScore::User_Input, expression: VK_SPACE } - - id: fixed:main-menu-version - title: Open version information - description: Opens the version dialog from the classic main menu. - audience: player - bindings: [Ctrl+V] - context: Classic main menu - availability: *all - sites: - - { file: code/init.cpp, function: Main_Menu, expression: KN_V+KN_CTRL_BIT } - - id: fixed:main-menu-credits - title: Open credits - description: Opens the credits from the classic main menu. - audience: player - bindings: [Ctrl+Alt+C] - context: Classic main menu - availability: *all - sites: - - { file: code/init.cpp, function: Main_Menu, expression: VK_C+KN_CTRL_BIT+KN_ALT_BIT } - id: fixed:skip-credits title: Exit credits description: Stops the credits and returns to the menu. @@ -349,8 +331,6 @@ fixed_exclusions: reason: Generic graphic-menu polling and activation, not a named application control. - site: { file: code/grphmsct.cpp, function: GM_Build_Key, expression: VK_NONE } reason: Sentinel used while translating a menu accelerator into a virtual key. - - site: { file: code/init.cpp, function: Main_Menu, expression: KN_RLSE_BIT } - reason: Filters release events before the classic main-menu cheat handler. - sites: - { file: code/init.cpp, function: Init_Commands, expression: KN_DELETE } - { file: code/init.cpp, function: Init_Commands, expression: KN_ESC } @@ -389,16 +369,10 @@ fixed_exclusions: - { file: code/msglist.cpp, function: MessageListClass::Input, expression: KN_BACKSPACE } - { file: code/msglist.cpp, function: MessageListClass::Input, expression: KN_ESC } reason: Message editor polling, cursor placement, and text-edit termination. - - site: { file: code/ownrdraw.cpp, function: CtrlProc_Internal, expression: VK_TAB } - reason: Windows dialog focus traversal handled by the shared control procedure. - - sites: - - { file: code/ownrdraw.cpp, function: EditBoxCtrlProc, expression: VK_TAB } - - { file: code/ownrdraw.cpp, function: EditBoxCtrlProc, expression: VK_RETURN } - reason: Windows edit-control navigation handled by the shared dialog procedure. - sites: - - { file: code/ownrdraw.cpp, function: Build_Hotkey_String, expression: VK_MENU } - - { file: code/ownrdraw.cpp, function: Build_Hotkey_String, expression: VK_CONTROL } - - { file: code/ownrdraw.cpp, function: Build_Hotkey_String, expression: VK_SHIFT } + - { file: code/keyboard.cpp, function: Build_Hotkey_String, expression: VK_MENU } + - { file: code/keyboard.cpp, function: Build_Hotkey_String, expression: VK_CONTROL } + - { file: code/keyboard.cpp, function: Build_Hotkey_String, expression: VK_SHIFT } reason: Converts modifier virtual keys to display names; it does not consume input. - sites: - { file: code/restate.cpp, function: RestateMission::User_Input, expression: KN_NONE } diff --git a/manual/data/commands.yaml b/manual/data/commands.yaml index d286e7be0..5713a6a5b 100644 --- a/manual/data/commands.yaml +++ b/manual/data/commands.yaml @@ -2117,32 +2117,6 @@ fixed_controls: _provenance: source: code/mpscore.cpp guard: null -- id: fixed:main-menu-version - route_id: fixed-main-menu-version - kind: fixed - title: Open version information - description: Opens the version dialog from the classic main menu. - audience: player - availability: *id001 - bindings: - - Ctrl+V - context: Classic main menu - _provenance: - source: code/init.cpp - guard: null -- id: fixed:main-menu-credits - route_id: fixed-main-menu-credits - kind: fixed - title: Open credits - description: Opens the credits from the classic main menu. - audience: player - availability: *id001 - bindings: - - Ctrl+Alt+C - context: Classic main menu - _provenance: - source: code/init.cpp - guard: null - id: fixed:skip-credits route_id: fixed-skip-credits kind: fixed diff --git a/manual/data/tombstones.yaml b/manual/data/tombstones.yaml index 6b92c250b..8de1cc6c2 100644 --- a/manual/data/tombstones.yaml +++ b/manual/data/tombstones.yaml @@ -1,5 +1,19 @@ # Removed settings live here only after source history establishes their removal. # They remain searchable and directly addressable, but active indexes omit them. +- type: command + id: fixed:main-menu-version + route: /commands/fixed-main-menu-version/ + search_aliases: + - Open version information + - Ctrl+V + summary: Opened the version dialog from the classic main menu. The main menu no longer reads the keyboard for it; the version screen is still reached from the menu itself. +- type: command + id: fixed:main-menu-credits + route: /commands/fixed-main-menu-credits/ + search_aliases: + - Open credits + - Ctrl+Alt+C + summary: Opened the credits from the classic main menu. The main menu no longer reads the keyboard for it; the credits still run and Escape still stops them. - type: command id: fixed:cancel-modem-operation route: /commands/fixed-cancel-modem-operation/ From 26f5c4db3a32c2f5d4cc6599142e0667471bc394 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 18:57:09 +0100 Subject: [PATCH 177/179] Document the macOS data script, build and run steps --- README.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/README.md b/README.md index 44e6bacc4..080f09adc 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ OpenTS is a community-led, open-source reconstruction of *Command & Conquer: Tiberian Sun*. Instead of patching or extending the retail executable, it rebuilds the engine as a standalone program. +This fork carries a native Apple Silicon macOS build of that engine. See +[Running on macOS](#running-on-macos) for the data script, the build and how to +start it. Everything below describes OpenTS itself and applies here too. + OpenTS gives equal weight to two goals: maintaining a playable engine and providing a capable platform for modding and engine development. Work on one goal should not come at the expense of the other. @@ -70,6 +74,59 @@ UTF-8 code page, which needs Windows 10 version 1903 or newer. Older Windows keeps its own code page, so game text still shows, but a path or file name holding a character that code page lacks may fail. +## Running on macOS + +This fork builds and runs the engine natively on Apple Silicon. The game data +still comes from a copy of Tiberian Sun you own. + +Install the tools: + +```bash +brew install cmake ninja +brew install --cask steamcmd +``` + +Fetch the game data from your own Steam account: + +```bash +./scripts/get-assets.sh +``` + +The script downloads app 2229880 into `Run/`, skips the Windows executables the +engine replaces, and checks that the archives startup needs actually arrived. +Steam Guard prompts for a code on first login. **Quit the Steam desktop client +first**: steamcmd shares its data directory, and a running client holds a lock +that makes steamcmd hang after "Verifying installation..." with no error. Set +`OPENTS_GAME_DIR` to put the data somewhere other than `Run/`. + +Build the engine: + +```bash +git submodule update --init --recursive +cmake -S . -B build/native -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DOPENTS_EXPERIMENTAL_NATIVE=ON +cmake --build build/native --target OpenTS +``` + +Build the `OpenTS` target rather than everything: the C++ test harnesses pass +MSVC-only flags and link `kernel32`, so they do not configure here. + +Run it: + +```bash +build/native/bin/Game -DATADIR=Run -USERDIR=build/native/user +``` + +`-USERDIR` is where saves, `SUN.INI` and logs are written, so pointing it at a +scratch directory leaves an existing install untouched. Full screen is a +setting in the display options screen. + +Windows supplies the window, message loop and cursor itself; every other +platform gets them from `platform/win32compat`, which serves the Win32 surface +the engine is written against out of [SDL](https://github.com/libsdl-org/SDL). +[Building OpenTS](docs/BUILDING.md) covers the build in full. + ## Documentation The [OpenTS manual](https://opents-developers.github.io/OpenTS/) documents From 9aaa70b94c2ff04b815a5f6e8cfb34c6aa105779 Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 19:04:42 +0100 Subject: [PATCH 178/179] Re-anchor the manual's source contracts on the ported code --- manual/content/internals/locomotion.md | 6 +- manual/site/scripts/check-render.mjs | 2 +- .../documentation-source-contract.test.mjs | 80 +++++++++++-------- 3 files changed, 52 insertions(+), 36 deletions(-) diff --git a/manual/content/internals/locomotion.md b/manual/content/internals/locomotion.md index 02cd3c0a0..d2055d75e 100644 --- a/manual/content/internals/locomotion.md +++ b/manual/content/internals/locomotion.md @@ -28,11 +28,11 @@ Movement, destination, layer, occupation, and locomotor-specific drawing queries | Operation | State transition | | --- | --- | -| `Begin_Piggyback(previous)` | Stores `previous` inside the new locomotor. A null pointer returns `E_POINTER`; an already occupied slot returns `E_FAIL`. | +| `Begin_Piggyback(previous)` | Takes ownership of `previous` and stores it inside the new locomotor. It refuses a null locomotor or an already occupied slot, and a refused locomotor is destroyed rather than returned to the caller. | | Replace `FootClass::Locomotion` | Makes the new locomotor the object's active movement interface. The new locomotor must already be linked to the same object. | -| `End_Piggyback(&FootClass::Locomotion)` | Writes the stored locomotor back into the object member and releases the piggyback slot. No stored locomotor returns `S_FALSE`; a null output pointer returns `E_POINTER`. | +| `End_Piggyback()` | Gives the stored locomotor back to the caller and empties the piggyback slot. It gives back nothing when no locomotor is stored. | -`FootClass::Link_DropPod` applies this sequence with the ballistic locomotor: it retains the passenger's current locomotor through `Begin_Piggyback`, then installs the ballistic interface. Drop-pod touchdown passes the address of `FootClass::Locomotion` to `End_Piggyback` before attempting ground placement. +`FootClass::Link_DropPod` applies this sequence with the ballistic locomotor: it retains the passenger's current locomotor through `Begin_Piggyback`, then installs the ballistic interface. Drop-pod touchdown assigns what `End_Piggyback` gives back to `FootClass::Locomotion` before attempting ground placement. Callers that perform opportunistic restoration first consult `Is_Ok_To_End`. The drop-pod touchdown path calls `End_Piggyback` directly at ground contact because its descent state already establishes the transition. diff --git a/manual/site/scripts/check-render.mjs b/manual/site/scripts/check-render.mjs index 94a46df6e..e6d7b1f77 100644 --- a/manual/site/scripts/check-render.mjs +++ b/manual/site/scripts/check-render.mjs @@ -57,7 +57,7 @@ const cases = [ ['mapping/missions/tmission-loop/index.html', ['Jump to line', 'one-based']], ['internals/class-hierarchy/index.html', ['Object and type system', 'Primary runtime hierarchy', 'Type-definition hierarchy', 'AbstractTypeClass', 'code/abstype.h']], ['internals/radio/index.html', ['Radio contact protocol', 'Contact state', 'Messages and responses', 'Compute_CRC', 'code/radio.cpp']], - ['internals/locomotion/index.html', ['Locomotion and piggybacking', 'FootClass::Locomotion', 'IPiggyback', 'Piggyback_CLSID']], + ['internals/locomotion/index.html', ['Locomotion and piggybacking', 'FootClass::Locomotion', 'IPiggyback', 'Class_ID']], ['reference/enums/mission/index.html', ['data-enum-table', 'MISSION_HUNT', 'Stored value', 'Used by', 'TMISSION_DO']], ['systems/drop-pods/index.html', ['ots-page-subtitle', 'Entry paths', 'Approach and descent', 'Touchdown']], ['systems/base-adjacency/index.html', ['ots-page-subtitle', 'Base placement and adjacency', 'Placement decision order', 'Adjacent']], diff --git a/manual/site/tests/documentation-source-contract.test.mjs b/manual/site/tests/documentation-source-contract.test.mjs index d45716bbd..4dc60e2c6 100644 --- a/manual/site/tests/documentation-source-contract.test.mjs +++ b/manual/site/tests/documentation-source-contract.test.mjs @@ -36,7 +36,7 @@ test('Drop pod approach selection keeps its ordered candidates and unconditional const droppod = source('code/droppod.cpp'); const moveTo = functionBody( droppod, - 'void STDMETHODCALLTYPE DropPodLocomotionClass::Move_To(Coord to)', + 'void DropPodLocomotionClass::Move_To(Coord to)', ); assert.match( @@ -74,7 +74,7 @@ test('Drop pod directions retain their hard-coded airborne and landing-art mappi const drawingCode = functionBody( droppod, - 'int STDMETHODCALLTYPE DropPodLocomotionClass::Drawing_Code(void)', + 'int DropPodLocomotionClass::Drawing_Code(void)', ); assert.match(drawingCode, /Direction\s*%\s*2/); assertOrdered(infantry, [ @@ -84,13 +84,13 @@ test('Drop pod directions retain their hard-coded airborne and landing-art mappi const process = functionBody( droppod, - 'boolean STDMETHODCALLTYPE DropPodLocomotionClass::Process(void)', + 'bool DropPodLocomotionClass::Process(void)', ); assert.match(process, /Rule->DropPod\[Direction\s*%\s*Rule->DropPod\.Count\(\)\]/); const moveTo = functionBody( droppod, - 'void STDMETHODCALLTYPE DropPodLocomotionClass::Move_To(Coord to)', + 'void DropPodLocomotionClass::Move_To(Coord to)', ); assertOrdered(moveTo, [ 'dropcoord.Z += Rule->DropPodHeight;', @@ -102,13 +102,13 @@ test('Drop pod directions retain their hard-coded airborne and landing-art mappi test('Blocked Drop pod touchdown retains its exact damage, animation, and deletion payload', () => { const process = functionBody( source('code/droppod.cpp'), - 'boolean STDMETHODCALLTYPE DropPodLocomotionClass::Process(void)', + 'bool DropPodLocomotionClass::Process(void)', ); assertOrdered(process, [ 'FootClass * linked = LinkedTo;', 'coord = linked->PositionCoord;', 'linked->Limbo();', - 'End_Piggyback(&LinkedTo->Locomotion);', + 'LinkedTo->Locomotion = End_Piggyback();', 'if (!linked->Unlimbo(coord, DIR_N)) {', 'Explosion_Damage(coord, 100, LinkedTo, Rule->C4Warhead);', 'Combat_Anim(100, Rule->C4Warhead, LAND_CLEAR, coord)', @@ -414,15 +414,11 @@ test('A resume is judged before it is loaded, and the save answers for the rest' 'gameloaded = true;', ], 'a network resume seats the players and opens the network before the save is read'); - for (const dialog of ['IDD_OPT_CTRL_WOL']) { - const template = source('code/language/language.rc'); - const body = template.slice(template.indexOf(dialog + ' DIALOG')); - assert.match( - body.slice(0, body.indexOf('END')), - /IDC_SAVE_GAME/, - `${dialog} offers the synchronized save the options handler has always known`, - ); - } + assert.match( + source('ui/gameoptionswol.rml'), + /id="save" data-class-disabled="!cansave" data-event-click="press\('save'\)"/, + 'the internet options offer the synchronized save the options screen has always known', + ); assertOrdered(functionBody(source('code/saveload.cpp'), 'bool Reconcile_Players(void)'), [ 'stricmp(Session.Players[i]->Name, Houses[house]->IniName) == 0', @@ -456,14 +452,14 @@ test('Saved games are named in one folder rather than searched for', () => { assertOrdered(functionBody(gamedirs, 'std::string Saved_Game_Name(char const * filename)'), [ 'UserDirectory + SavedGamesFolder', - 'CreateDirectory(folder.c_str(), NULL);', + 'Make_Directory(folder);', ], 'a saved game is named inside the user directory, and the folder is made on the way'); for (const [file, signature] of [ ['code/saveload.cpp', 'bool Save_Game(const char *file_name, char const * descr)'], ['code/saveload.cpp', 'bool Load_Game(const char *file_name)'], ['code/saveload.cpp', 'bool Get_Savefile_Info(char const * name, SaveVersionInfo * info)'], - ['code/loaddlg.cpp', 'void LoadOptionsClass::Fill_List(HWND window)'], + ['code/loaddlg.cpp', 'void LoadOptionsClass::Build_List(void)'], ['code/loaddlg.cpp', 'bool LoadOptionsClass::Files_Present(void)'], ['code/loaddlg.cpp', 'bool LoadOptionsClass::Delete_File(const char * file_name)'], ]) { @@ -475,7 +471,7 @@ test('Saved games are named in one folder rather than searched for', () => { } assert.doesNotMatch( - functionBody(source('code/loaddlg.cpp'), 'void LoadOptionsClass::Fill_List(HWND window)') + + functionBody(source('code/loaddlg.cpp'), 'void LoadOptionsClass::Build_List(void)') + functionBody(source('code/loaddlg.cpp'), 'bool LoadOptionsClass::Files_Present(void)'), /Search_Files\(/, 'the listing no longer scans the folders the game reads from', @@ -527,20 +523,24 @@ test('A multiplayer load replaces the match around the seats it keeps', () => { 'Reset_Multiplayer_Save_State();', ], 'the old traffic is discarded, the save read, the seats matched, and the connections rebuilt in that order'); - const template = source('code/language/language.rc'); - const body = template.slice(template.indexOf('IDD_OPT_CTRL_WOL DIALOG')); assert.match( - body.slice(0, body.indexOf('END')), - /IDC_LOAD_GAME/, + source('ui/gameoptionswol.rml'), + /id="load" data-class-disabled="!canload" data-event-click="press\('load'\)"/, 'the internet options offer the load the master starts for every machine', ); - assertOrdered(definitionFrom(source('code/goptions.cpp'), 'INT_PTR CALLBACK Game_Options_Dialog_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam)'), [ - 'case IDC_LOAD_GAME:', - 'LoadOptionsClass().Load()', - 'Multiplayer_Load_Is_Allowed()', + const gameoptions = source('code/ui/uigameoptions.cpp'); + assertOrdered(functionBody(gameoptions, 'void UIGameOptionsPresenterClass::Execute(UIIntent const & intent)'), [ + 'intent.Action == UI_GAMEOPT_LOAD', + 'Is_Solo_Session()', + 'Pending = SUB_LOAD;', + 'SaveManager.Multiplayer_Load_Is_Allowed()', 'SpecialDialog = SDLG_LOAD;', - ], 'a network game defers the list to the menu loop rather than nesting it in the options dialog'); + ], 'a network game defers the list to the menu loop rather than opening it from the options screen'); + assertOrdered(functionBody(gameoptions, 'void UIGameOptionsPresenterClass::Run_Pending(void)'), [ + 'case SUB_LOAD:', + 'LoadOptionsClass().Load()', + ], 'a solo game opens the list itself'); assertOrdered(definitionFrom(source('code/conquer.cpp'), 'void Ingame_Menu_Dialog(void)'), [ 'case SDLG_OPTIONS:', @@ -817,12 +817,28 @@ test('A computer player draws a country from the lobby roster', () => { }); test('A lobby side entry carries its country', () => { - const netdlg = source('code/netdlg2.cpp'); + const lobby = source('code/ui/uilobby.cpp'); + const skirmish = source('code/ui/uiskirmish.cpp'); + + for (const [screen, text, signature] of [ + ['the network lobby', lobby, 'void UILobbyPresenterClass::Build_Identity_Lists(void)'], + ['the skirmish setup', skirmish, 'void UISkirmishPresenterClass::Refresh(void)'], + ]) { + assertOrdered(functionBody(text, signature), [ + 'if (!house->IsMultiplay) continue;', + 'if (index == Session.House) {', + 'SelectedSide = (int)Sides.size();', + 'Sides.push_back(SideType{(char const *)house->GivenName, index});', + ], `${screen} lists the countries that may be played, and each entry carries its country`); + } + + assert.match(functionBody(lobby, 'void UILobbyPresenterClass::Host_Side(int row)'), /House = Sides\[row\]\.Country;/, 'the selection is read back through its country'); + assert.match(functionBody(skirmish, 'void UISkirmishPresenterClass::Read_Identity(void)'), /Session\.House = \(HousesType\)Sides\[SelectedSide\]\.Country;/, 'the skirmish entry stores a country, not a position'); - assertOrdered(functionBody(netdlg, 'void Fill_Country_Box(HWND combo)'), ['CB_INSERTSTRING', 'CB_SETITEMDATA'], 'each entry carries its country'); - assert.match(functionBody(netdlg, 'int Country_From_Box(HWND combo)'), /CB_GETITEMDATA/, 'the selection is read back through its country'); - assert.doesNotMatch(netdlg, /CB_SETCURSEL, Session\.House/, 'no box is positioned by a country index'); - assert.doesNotMatch(source('code/skirmish.cpp'), /Session\.House = ComboBox_GetCurSel/, 'the skirmish box stores a country, not a position'); + for (const text of [lobby, skirmish]) { + assert.doesNotMatch(text, /SelectedSide = (\(int\))?Session\.House/, 'no list is positioned by a country index'); + assert.doesNotMatch(text, /Session\.House = \(HousesType\)SelectedSide/, 'no position is stored as a country'); + } }); test('A side is declared in the side list alone', () => { From f30fcf2a9828a4f15b3c1370d39067c433c742cc Mon Sep 17 00:00:00 2001 From: OpenTS Spike Date: Wed, 9 Sep 2026 19:16:09 +0100 Subject: [PATCH 179/179] Discover the UI screens' keys and restore the menu shortcuts --- manual/MAINTAINING.md | 6 +- .../changes/main-menu-keyboard-shortcuts.md | 24 ----- manual/data/command-adapters.yaml | 98 +++++++++++++++++++ manual/data/commands.yaml | 26 +++++ manual/data/tombstones.yaml | 14 --- manual/tools/commands_engine.py | 5 +- 6 files changed, 132 insertions(+), 41 deletions(-) delete mode 100644 manual/changes/main-menu-keyboard-shortcuts.md diff --git a/manual/MAINTAINING.md b/manual/MAINTAINING.md index 01a174911..d16a691ac 100644 --- a/manual/MAINTAINING.md +++ b/manual/MAINTAINING.md @@ -73,8 +73,10 @@ spelling live where it works. Command discovery is also fail-closed. Objects registered through `AllCommands` form the rebindable command catalog. Every discovered direct key handler and launch-parser branch needs exactly one public adapter or one -reasoned exclusion. Command IDs are case-sensitive. Do not infer default -bindings from a declaration or nearby code. +reasoned exclusion. Discovery reads the key names of every layer that delivers +one, so a screen driven by the UI toolkit is scanned for its key identifiers as +the game's own handlers are scanned for theirs. Command IDs are case-sensitive. +Do not infer default bindings from a declaration or nearby code. Enums are authored selections backed by explicit source adapters. Documenting an existing fixed domain is documentation work, not an engine change. Its diff --git a/manual/changes/main-menu-keyboard-shortcuts.md b/manual/changes/main-menu-keyboard-shortcuts.md deleted file mode 100644 index 2f86c32d7..000000000 --- a/manual/changes/main-menu-keyboard-shortcuts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Retire the main menu's version and credits shortcuts -category: fix -release: 0.2.0 -breaking: true -migration: -- Open the version screen from the main menu instead of pressing Ctrl+V. -- Start the credits from the main menu instead of pressing Ctrl+Alt+C. Escape still stops them. -targets: -- type: command - id: fixed:main-menu-version - effect: removed -- type: command - id: fixed:main-menu-credits - effect: removed -credit: [OpenTS contributors] ---- - -The classic main menu read the keyboard directly and opened the version dialog on Ctrl+V and -the credits on Ctrl+Alt+C. `Main_Menu` now shows the menu screen rather than polling for keys, -so neither shortcut has anywhere to be handled and both are gone. - -Only the shortcuts were lost. The version screen and the credits are both still reached from -the menu itself, and Escape still stops the credits. diff --git a/manual/data/command-adapters.yaml b/manual/data/command-adapters.yaml index 3a4142207..30a72383f 100644 --- a/manual/data/command-adapters.yaml +++ b/manual/data/command-adapters.yaml @@ -225,6 +225,24 @@ fixed_controls: - { file: code/mpscore.cpp, function: MultiScore::User_Input, expression: VK_LBUTTON } - { file: code/mpscore.cpp, function: MultiScore::User_Input, expression: VK_ESCAPE } - { file: code/mpscore.cpp, function: MultiScore::User_Input, expression: VK_SPACE } + - id: fixed:main-menu-version + title: Open version information + description: Opens the version dialog from the classic main menu. + audience: player + bindings: [Ctrl+V] + context: Classic main menu + availability: *all + sites: + - { file: code/ui/uimainmenu.cpp, function: BindEventCallback, expression: KI_V } + - id: fixed:main-menu-credits + title: Open credits + description: Opens the credits from the classic main menu. + audience: player + bindings: [Ctrl+Alt+C] + context: Classic main menu + availability: *all + sites: + - { file: code/ui/uimainmenu.cpp, function: BindEventCallback, expression: KI_C } - id: fixed:skip-credits title: Exit credits description: Stops the credits and returns to the menu. @@ -423,6 +441,86 @@ fixed_exclusions: reason: >- Reads the modifiers that qualify the shell's Debug-only developer keys. The keys those modifiers qualify are the controls; this test is not one of them. + - sites: + - { file: code/ui/uiabort.cpp, function: BindEventCallback, expression: KI_ESCAPE } + - { file: code/ui/uicampaign.cpp, function: BindEventCallback, expression: KI_ESCAPE } + - { file: code/ui/uicampaign.cpp, function: BindEventCallback, expression: KI_RETURN } + - { file: code/ui/uicampaign.cpp, function: BindEventCallback, expression: KI_NUMPADENTER } + - { file: code/ui/uidisplayconfirm.cpp, function: BindEventCallback, expression: KI_ESCAPE } + - { file: code/ui/uidisplayconfirm.cpp, function: BindEventCallback, expression: KI_RETURN } + - { file: code/ui/uidisplayconfirm.cpp, function: BindEventCallback, expression: KI_NUMPADENTER } + - { file: code/ui/uidisplayoptions.cpp, function: BindEventCallback, expression: KI_ESCAPE } + - { file: code/ui/uidisplayoptions.cpp, function: BindEventCallback, expression: KI_RETURN } + - { file: code/ui/uidisplayoptions.cpp, function: BindEventCallback, expression: KI_NUMPADENTER } + - { file: code/ui/uigamecontrols.cpp, function: BindEventCallback, expression: KI_ESCAPE } + - { file: code/ui/uigamecontrols.cpp, function: BindEventCallback, expression: KI_RETURN } + - { file: code/ui/uigamecontrols.cpp, function: BindEventCallback, expression: KI_NUMPADENTER } + - { file: code/ui/uigameoptions.cpp, function: BindEventCallback, expression: KI_ESCAPE } + - { file: code/ui/uigameoptions.cpp, function: BindEventCallback, expression: KI_RETURN } + - { file: code/ui/uigameoptions.cpp, function: BindEventCallback, expression: KI_NUMPADENTER } + - { file: code/ui/uigametype.cpp, function: BindEventCallback, expression: KI_ESCAPE } + - { file: code/ui/uigametype.cpp, function: BindEventCallback, expression: KI_RETURN } + - { file: code/ui/uigametype.cpp, function: BindEventCallback, expression: KI_NUMPADENTER } + - { file: code/ui/uilobby.cpp, function: BindEventCallback, expression: KI_ESCAPE } + - { file: code/ui/uimainoptions.cpp, function: BindEventCallback, expression: KI_ESCAPE } + - { file: code/ui/uimainoptions.cpp, function: BindEventCallback, expression: KI_RETURN } + - { file: code/ui/uimainoptions.cpp, function: BindEventCallback, expression: KI_NUMPADENTER } + - { file: code/ui/uimapgen.cpp, function: BindEventCallback, expression: KI_ESCAPE } + - { file: code/ui/uimessagebox.cpp, function: BindEventCallback, expression: KI_ESCAPE } + - { file: code/ui/uimessagebox.cpp, function: BindEventCallback, expression: KI_RETURN } + - { file: code/ui/uimessagebox.cpp, function: BindEventCallback, expression: KI_NUMPADENTER } + - { file: code/ui/uimpselect.cpp, function: BindEventCallback, expression: KI_ESCAPE } + - { file: code/ui/uimpselect.cpp, function: BindEventCallback, expression: KI_RETURN } + - { file: code/ui/uimpselect.cpp, function: BindEventCallback, expression: KI_NUMPADENTER } + - { file: code/ui/uireconnect.cpp, function: BindEventCallback, expression: KI_ESCAPE } + - { file: code/ui/uisavebrowser.cpp, function: BindEventCallback, expression: KI_ESCAPE } + - { file: code/ui/uisavebrowser.cpp, function: BindEventCallback, expression: KI_RETURN } + - { file: code/ui/uisavebrowser.cpp, function: BindEventCallback, expression: KI_NUMPADENTER } + - { file: code/ui/uiscenariopick.cpp, function: BindEventCallback, expression: KI_ESCAPE } + - { file: code/ui/uiscenariopick.cpp, function: BindEventCallback, expression: KI_RETURN } + - { file: code/ui/uiscenariopick.cpp, function: BindEventCallback, expression: KI_NUMPADENTER } + - { file: code/ui/uiskirmish.cpp, function: BindEventCallback, expression: KI_ESCAPE } + - { file: code/ui/uiskirmish.cpp, function: BindEventCallback, expression: KI_RETURN } + - { file: code/ui/uiskirmish.cpp, function: BindEventCallback, expression: KI_NUMPADENTER } + - { file: code/ui/uisound.cpp, function: BindEventCallback, expression: KI_RETURN } + - { file: code/ui/uisound.cpp, function: BindEventCallback, expression: KI_NUMPADENTER } + - { file: code/ui/uiversion.cpp, function: BindEventCallback, expression: KI_ESCAPE } + - { file: code/ui/uiversion.cpp, function: BindEventCallback, expression: KI_RETURN } + - { file: code/ui/uiversion.cpp, function: BindEventCallback, expression: KI_NUMPADENTER } + reason: >- + A screen's own cancel and accept keys. Escape answers as the template's IDCANCEL did + and Enter as the arm Windows sent IDOK to, so each key presses a button the screen + already shows rather than dispatching a control of its own. + - sites: + - { file: code/ui/uidesync.cpp, function: BindEventCallback, expression: KI_RETURN } + - { file: code/ui/uidesync.cpp, function: BindEventCallback, expression: KI_NUMPADENTER } + - { file: code/ui/uilobby.cpp, function: BindEventCallback, expression: KI_RETURN } + - { file: code/ui/uilobby.cpp, function: BindEventCallback, expression: KI_NUMPADENTER } + reason: Sends the line typed in a chat field, which the edit control's own return handling did. + - sites: + - { file: code/ui/uikeyboard.cpp, function: BindEventCallback, expression: KI_ESCAPE } + - { file: code/ui/uikeyboard.cpp, function: BindEventCallback, expression: KI_RETURN } + - { file: code/ui/uikeyboard.cpp, function: BindEventCallback, expression: KI_NUMPADENTER } + - { file: code/ui/uikeyboard.cpp, function: BindEventCallback, expression: KI_TAB } + reason: >- + Keys the hotkey capture control refuses so they still leave the screen or move the + focus, which is what the control it stands in for left to the dialog manager. + - sites: + - { file: code/ui/uishell.cpp, function: UI_Virtual_Key, expression: KI_A } + - { file: code/ui/uishell.cpp, function: UI_Virtual_Key, expression: KI_Z } + - { file: code/ui/uishell.cpp, function: UI_Virtual_Key, expression: KI_0 } + - { file: code/ui/uishell.cpp, function: UI_Virtual_Key, expression: KI_9 } + - { file: code/ui/uishell.cpp, function: UI_Virtual_Key, expression: KI_NUMPAD0 } + - { file: code/ui/uishell.cpp, function: UI_Virtual_Key, expression: KI_NUMPAD9 } + - { file: code/ui/uishell.cpp, function: UI_Virtual_Key, expression: KI_F1 } + - { file: code/ui/uishell.cpp, function: UI_Virtual_Key, expression: KI_F24 } + - { file: code/ui/uishell.cpp, function: UI_Virtual_Key, expression: KI_NUMPADENTER } + reason: >- + Bounds of the letter, digit, keypad and function-key runs, and the keypad Enter that + folds onto Return, in the translation from a toolkit identifier back to a virtual key. + It names a key for a screen to record; it dispatches nothing. + - site: { file: code/ui/uishell.cpp, function: UI_Handle_Window_Message, expression: KI_UNKNOWN } + reason: Sentinel for a key the toolkit does not name, which the shell drops before any document sees it. - sites: - { file: code/wdtsel.cpp, function: Selection::Pick_Territory, expression: KN_NONE } - { file: code/wdtsel.cpp, function: Selection::Pick_Territory, expression: KN_LMOUSE } diff --git a/manual/data/commands.yaml b/manual/data/commands.yaml index 5713a6a5b..9778cd0f7 100644 --- a/manual/data/commands.yaml +++ b/manual/data/commands.yaml @@ -2117,6 +2117,32 @@ fixed_controls: _provenance: source: code/mpscore.cpp guard: null +- id: fixed:main-menu-version + route_id: fixed-main-menu-version + kind: fixed + title: Open version information + description: Opens the version dialog from the classic main menu. + audience: player + availability: *id001 + bindings: + - Ctrl+V + context: Classic main menu + _provenance: + source: code/ui/uimainmenu.cpp + guard: null +- id: fixed:main-menu-credits + route_id: fixed-main-menu-credits + kind: fixed + title: Open credits + description: Opens the credits from the classic main menu. + audience: player + availability: *id001 + bindings: + - Ctrl+Alt+C + context: Classic main menu + _provenance: + source: code/ui/uimainmenu.cpp + guard: null - id: fixed:skip-credits route_id: fixed-skip-credits kind: fixed diff --git a/manual/data/tombstones.yaml b/manual/data/tombstones.yaml index 8de1cc6c2..6b92c250b 100644 --- a/manual/data/tombstones.yaml +++ b/manual/data/tombstones.yaml @@ -1,19 +1,5 @@ # Removed settings live here only after source history establishes their removal. # They remain searchable and directly addressable, but active indexes omit them. -- type: command - id: fixed:main-menu-version - route: /commands/fixed-main-menu-version/ - search_aliases: - - Open version information - - Ctrl+V - summary: Opened the version dialog from the classic main menu. The main menu no longer reads the keyboard for it; the version screen is still reached from the menu itself. -- type: command - id: fixed:main-menu-credits - route: /commands/fixed-main-menu-credits/ - search_aliases: - - Open credits - - Ctrl+Alt+C - summary: Opened the credits from the classic main menu. The main menu no longer reads the keyboard for it; the credits still run and Escape still stops them. - type: command id: fixed:cancel-modem-operation route: /commands/fixed-cancel-modem-operation/ diff --git a/manual/tools/commands_engine.py b/manual/tools/commands_engine.py index dccf3b2ce..92510f25c 100644 --- a/manual/tools/commands_engine.py +++ b/manual/tools/commands_engine.py @@ -301,7 +301,10 @@ def _guard_map(text): def _key_expression(value): - tokens = re.findall(r"\b(?:KN|VK)_[A-Z0-9_]+\b", value) + # A screen names a key the way the layer that delivers it does. The game's own + # handlers use KN_ and VK_; a screen driven by the UI toolkit sees its KI_ + # identifiers, and discovery has to reach both or a whole screen's keys go unseen. + tokens = re.findall(r"\b(?:KN|VK|KI)_[A-Z0-9_]+\b", value) return "+".join(dict.fromkeys(tokens)) if tokens else None