This TODO reflects the current repository state. It is written as a development checklist for the repository itself, not as an analysis of any uploaded archive.
The current CMake configuration is focused on the native FREEDIRECT backend.
Current state:
SPEEDY_BLUPI_BACKENDis restricted toFREEDIRECT.- The game executable links to:
free-apifree-direct
- The game does not link SDL directly.
- Vendored SDL support exists through
cmake/ThirdPartySDL.cmake. .gitmodulesincludes:dxsdk3third_party/SDLthird_party/SDL_imagethird_party/SDL_mixer
FREE_USE_SYSTEM_SDLdefaults toOFF, so SDL should be built from vendored submodules by default.- System SDL should only be used when explicitly requested with
-DFREE_USE_SYSTEM_SDL=ON.
TODO:
- Verify that a clean checkout builds after running:
git submodule update --init --recursive
cmake -S . -B build -DSPEEDY_BLUPI_BACKEND=FREEDIRECT
cmake --build build -j- Verify that the default Linux build does not require system-installed SDL packages.
- Verify that
FREE_USE_SYSTEM_SDL=ONstill works as an optional developer override. - Verify that
cmake/ThirdPartySDL.cmakeonly defines/configures vendored SDL and does not leak SDL into the game target. - Verify that
free-apiandfree-directlink SDL dependencies asPRIVATE, notPUBLIC. - Verify that the game target does not receive SDL include directories transitively.
- Verify that no game source file contains direct SDL usage.
- Verify that public Free API / Free Direct headers do not expose SDL types.
Current CMake state is already much better than an earlier system-SDL setup, but there are still cleanup points.
The game executable currently uses:
target_include_directories(SPEEDY_BLUPI_WINDOWS
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
)Executable targets normally do not need public include directories.
TODO:
- Consider changing this to
PRIVATE:
target_include_directories(SPEEDY_BLUPI_WINDOWS
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
)- Ensure this does not affect the current build.
Current CMake has separate MSVC and non-MSVC compile options.
Non-MSVC options include:
-fpermissive
-fms-extensions
-fPIC
-w
-Wno-narrowing
-Wno-int-to-pointer-cast
-g
-O0MSVC options currently include:
/permissive-
/W0TODO:
- Verify whether
/permissive-is appropriate for this decompiled code. - If MSVC fails due to old/decompiled C++ constructs, consider whether
/permissive-should be removed. - Keep compiler-option changes separate from gameplay/decompilation fixes.
- Avoid changing warning policy unless needed for a specific compiler/build failure.
- Ensure Linux, Windows/MSVC, and Windows/MinGW or Clang builds are handled intentionally.
cmake/ThirdPartySDL.cmake defines:
function(configure_vendored_sdl)and the main CMakeLists.txt calls:
configure_vendored_sdl()This is currently consistent.
TODO:
- Keep the function name consistent.
- Do not reintroduce old names such as
free_eggbert_configure_sdl_dependencies()unless a compatibility wrapper is intentionally needed. - Consider whether
CMAKE_SOURCE_DIRshould be replaced with a more local path such asPROJECT_SOURCE_DIRor a path based onCMAKE_CURRENT_LIST_DIR, especially if this project is ever used as a subproject. - Verify that the SDL submodule directories contain actual checked-out sources after
git submodule update --init --recursive.
free_eggbert_copy_sdl_runtime(SPEEDY_BLUPI_WINDOWS) copies SDL runtime libraries on Windows.
TODO:
- Verify that
$<TARGET_FILE:SDL3::SDL3>works for the actual SDL target exported by vendored SDL. - Verify the same for
SDL3_image::SDL3_imageandSDL3_mixer::SDL3_mixer. - If alias targets do not work with
$<TARGET_FILE:...>, use the concrete SDL shared-library targets exposed by SDL. - Ensure runtime copying does not add SDL as a direct game dependency.
- Ensure the executable can run from the build directory on Windows after copying DLLs.
The intended architecture is:
Free Eggbert game code
↓
Free API / Free Direct public compatibility headers
↓
Free API / Free Direct implementation
↓
SDL / SDL_image / SDL_mixer internals
The game is allowed to include WinAPI-like and DirectX-like compatibility headers such as:
windows.h
windowsx.h
wtypes.h
mmsystem.h
digitalv.h
ddraw.h
dsound.h
dplay.h
direct.h
io.h
The game must not include SDL directly.
TODO:
- Keep WinAPI-like / DirectX-like includes in the game source acceptable.
- Do not force the game to include SDL.
- Do not force the game to link SDL directly.
- Do not add Free API headers as source files to the game executable.
- Keep SDL private to Free API / Free Direct.
- Keep public Free API / Free Direct APIs WinAPI-like / DirectX-like, not SDL-like.
- Scan game sources for accidental SDL usage:
grep -R "#include <SDL\|#include \"SDL\|SDL_" -n src include- Scan CMake for accidental direct SDL linkage from the game target.
The game currently runs in fullscreen mode on Free Direct / Free API, but the same fullscreen path may fail when built against the original DirectX 3 SDK.
This suggests that Free Direct may currently be more tolerant than real DirectDraw, or that the decompiled fullscreen initialization path is not fully correct.
The main suspected issue is that CPixmap::Create() may be called twice during startup without clearly releasing the previously created DirectDraw objects.
Startup currently initializes the pixmap like this:
g_pPixmap = new CPixmap;
if (!g_pPixmap->Create(g_hWnd, totalDim, g_bFullScreen,
g_mouseType, g_bTrueColorBack, g_bTrueColorDecor))
return InitFail("Create pixmap", TRUE);
if (!g_pPixmap->CacheAll(TRUE, g_hWnd, g_bFullScreen,
g_bTrueColorBack, g_bTrueColorDecor,
g_mouseType, "init.blp", 0))
return FALSE;However, CPixmap::CacheAll() currently calls Create() again:
if (Create(hWnd, dim, bFullScreen, mouseType, bTrueColorDecor, bTrueColor) == FALSE)
{
return FALSE;
}This may cause startup to do the following:
new CPixmap
-> CPixmap::Create()
-> DirectDrawCreate
-> SetCooperativeLevel
-> SetDisplayMode
-> CreateSurface primary
-> CreateSurface back
-> CreateSurface mouse
-> CPixmap::CacheAll(TRUE, ...)
-> CPixmap::Create() again
-> DirectDrawCreate again
-> SetCooperativeLevel again
-> SetDisplayMode again
-> CreateSurface primary again
Real DirectDraw may reject the second initialization attempt with errors such as:
DDERR_PRIMARYSURFACEALREADYEXISTS
DDERR_EXCLUSIVEMODEALREADYSET
DDERR_NOEXCLUSIVEMODE
DDERR_INVALIDPARAMS
DDERR_WRONGMODE
Free Direct may tolerate this if it does not strictly enforce all original DirectDraw lifetime and exclusive-mode rules.
TODO:
- Confirm whether
CPixmap::Create()is really called twice during startup. - Add logging at the start and end of
CPixmap::Create(). - Log whether
m_lpDD,m_lpDDSPrimary,m_lpDDSBack, andm_lpDDSMousealready exist before creating new DirectDraw objects. - Log all DirectDraw
HRESULTvalues during both the first and second initialization attempts. - Determine whether the double
Create()call is intentional or a decompilation artifact. - Decide which function owns DirectDraw initialization:
- either
DoInit()creates the pixmap, - or
CacheAll()creates/recreates the pixmap, - but both should not create DirectDraw objects without a clear release/reinitialization path.
- either
- If recreation is intentional, add or verify a correct cleanup path before the second initialization.
- Do not silently make Free Direct more tolerant until the real DirectDraw behavior is understood.
In include/pixmap.hpp, the declaration is:
BOOL Create(HWND hwnd, POINT dim, BOOL bFullScreen, int mouseType,
BOOL bTrueColor, BOOL bTrueColorDecor);This suggests the intended order is probably:
bTrueColorBack
bTrueColorDecor
However, the definition in src/pixmap.cpp is currently:
BOOL CPixmap::Create(HWND hwnd, POINT dim,
BOOL bFullScreen, int mouseType,
BOOL bTrueColorDecor, BOOL bTrueColor)The parameter names are swapped.
Then the function assigns:
m_bTrueColorDecor = bTrueColorDecor;
m_bTrueColorBack = bTrueColor;In blupi.cpp, the call is:
Create(..., g_bTrueColorBack, g_bTrueColorDecor)But the definition interprets this as:
g_bTrueColorBack -> decor
g_bTrueColorDecor -> back
Inside CacheAll(), the call appears to compensate by using the opposite order:
Create(hWnd, dim, bFullScreen, mouseType, bTrueColorDecor, bTrueColor)This may work accidentally when both values are the same, but may break when they differ.
TODO:
- Audit the intended meaning of
bTrueColorBackandbTrueColorDecor. - Compare declaration, definition, and all call sites of
CPixmap::Create(). - Determine whether the parameter order is a decompilation artifact.
- Rename parameters locally for clarity once the intended order is confirmed.
- Avoid changing behavior until logging proves the current behavior is wrong.
- Test configurations where background and decor truecolor settings differ.
CPixmap::CacheAll(FALSE) currently contains code similar to:
if (cache == FALSE)
{
delete this;
hWnd = m_hWnd;
bFullScreen = m_bFullScreen;
bTrueColor = m_bTrueColorBack;
bTrueColorDecor = m_bTrueColorDecor;
mouseType = m_mouseType;
}This is unsafe.
After:
delete this;the object is destroyed, but the code then continues to read members such as:
m_hWnd
m_bFullScreen
m_bTrueColorBack
m_bTrueColorDecor
m_mouseType
Reading object members after delete this is undefined behavior.
TODO:
- Investigate whether this code path is actually executed.
- Treat this as a likely decompilation artifact or incorrectly reconstructed logic.
- Reconstruct the intended original behavior.
- Never access object members after
delete this. - If object recreation is required, save needed values before destruction.
- Prefer explicit cleanup/reinitialize methods over
delete this.
There is suspicious decompiled code similar to:
char image[12];
*(char*)image = (LXIMAGE) << 64;
rect.bottom = LYIMAGE;
rect.left = LOWORD(image);
rect.top = HIWORD(image);
rect.right = HIWORD(image);
DrawImage(0, 0, rect, 1);
Display();This does not make sense.
Problems:
LXIMAGE is likely 640, so shifting it by 64 is nonsensical.
image is a local char array.
LOWORD(image) / HIWORD(image) on a pointer or array is suspicious.
rect is probably initialized incorrectly.
The original code may have been something like:
SetRect(&rect, 0, 0, LXIMAGE, LYIMAGE);or another normal rectangle initialization.
TODO:
- Investigate this code in Ghidra.
- Compare with Planet Blupi source if a similar initialization exists.
- Replace this only after confirming the intended behavior.
- Verify what rectangle should be passed to
DrawImage(). - Add comments once the original intent is understood.
- Do not leave pointer-based
LOWORD/HIWORDartifacts if they are proven invalid.
CPixmap::Create() currently uses this DirectDraw setup:
DirectDrawCreate(NULL, &m_lpDD, NULL);
if (m_bFullScreen)
SetCooperativeLevel(hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
else
SetCooperativeLevel(hwnd, DDSCL_NORMAL);
if (m_bFullScreen)
SetDisplayMode(dim.x, dim.y, colorMode);
CreateSurface(primary);
CreateSurface(system-memory backbuffer);
CreateSurface(system-memory mousebuffer);This is not a classic fullscreen flip-chain setup.
It does not appear to use:
DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX
DDSD_BACKBUFFERCOUNT
GetAttachedSurface
Flip
However, this may still be valid if the game draws into a system-memory backbuffer and then blits to the primary surface.
Likely intended model:
draw everything into m_lpDDSBack
then Blt from m_lpDDSBack to the primary surface
Real DirectDraw fullscreen mode is stricter about:
display mode
color depth
primary surface caps
exclusive mode
palette state
surface lifetime
Free Direct may be simpler because it can internally do something like:
SDL fullscreen window
software/system-memory surface
copy/blit to renderer
TODO:
- Confirm whether the game uses
Bltrather thanFlip. - Confirm whether the original game used a primary-surface + system-memory-backbuffer model.
- Verify whether this model works with real DirectDraw 3 in fullscreen.
- Log
SetDisplayMode()parameters and return values. - Log primary and backbuffer
DDSURFACEDESCvalues. - Verify palette behavior in 8-bit mode.
- Verify 16-bit / truecolor behavior.
- Do not assume the lack of a flip chain is a bug until tested against original behavior.
Add detailed logging around DirectDraw initialization calls:
-
DirectDrawCreate -
SetCooperativeLevel -
SetDisplayMode -
CreateSurfacefor the primary surface -
CreateSurfacefor the back buffer -
CreateSurfacefor the mouse surface -
CreateClipper -
SetPalette -
Blt -
Flip, if ever used
Log the following values:
- fullscreen/windowed mode
- requested width and height
- requested color depth
-
DDSURFACEDESC.dwSize -
DDSURFACEDESC.dwFlags -
DDSCAPS.dwCaps - returned
HRESULT - whether DirectDraw objects already exist before initialization
Minimum useful diagnostic patch:
CPixmap::Create enter
CPixmap::Create leave
m_lpDD before creation
m_lpDDSPrimary before creation
m_lpDDSBack before creation
m_lpDDSMouse before creation
bFullScreen
bTrueColorBack / bTrueColorDecor
requested width / height
requested color depth
DirectDrawCreate HRESULT
SetCooperativeLevel HRESULT
SetDisplayMode HRESULT
CreateSurface primary HRESULT
CreateSurface back HRESULT
CreateSurface mouse HRESULT
WinMain() starts a multimedia timer:
g_updateTimer = timeSetEvent(g_timerInterval, g_timerInterval / 4,
TimerStep, NULL, TIME_PERIODIC);Then it runs a classic Win32 message loop:
while (TRUE)
{
if (PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE))
{
if (!GetMessage(&msg, NULL, 0, 0))
return msg.wParam;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
if (!g_bActive) WaitMessage();
}
}The timer callback posts update messages, and the window procedure handles WM_UPDATE roughly by doing:
UpdateFrame();
SetDecor();
g_pPixmap->Display();This is important for future web support.
Emscripten does not work well with a traditional blocking infinite message loop. The web version will likely need a Free API / launcher-level main-loop adapter rather than direct changes to the game logic.
TODO:
- Keep the native Win32-style loop intact for desktop builds.
- For web builds, design a Free API or launcher-level adapter.
- Do not hack
src/blupi.cppjust to satisfy Emscripten. - Identify the minimum frame function needed for web:
- event pump
WM_UPDATEUpdateFrame()SetDecor()Display()
- Investigate whether
WinMain()can be adapted without modifying game logic. - Keep web work separate from DirectDraw fullscreen debugging.
Current state:
#define _BASS FALSEWith _BASS set to FALSE, the active implementation is probably the old DirectSound / MCI-style path in soundold.cpp:
#if !_BASS || _LEGACY
...
#endifsound.cpp uses the opposite condition:
#if _BASS && !_LEGACY
...
#endifBoth files are present in the project, but preprocessor conditions should prevent conflicting definitions.
This works, but the setup is fragile. The README still mentions BASS / BASSMIDI, so the documentation and current configuration should be verified.
TODO:
- Document the currently active audio backend.
- Verify whether
_BASS FALSEis intentional. - Verify whether README audio notes are outdated or still relevant.
- Do not mix audio backend cleanup with DirectDraw fullscreen investigation.
- Later, consider a CMake option such as:
FREE_EGGBERT_AUDIO_BACKEND=DSOUND|BASS|STUB- Keep audio backend selection separate from SDL vendoring and fullscreen debugging.
CNetwork::CreateProvider(0) is called during startup:
g_pNetwork = new CNetwork;
g_pNetwork->CreateProvider(0);However, CreateProvider() returns FALSE immediately if:
if (index >= m_providers.nb) return FALSE;Since m_providers.nb starts at 0, and no required EnumProviders() call is visible before this startup call, network initialization likely fails silently during normal startup.
The return value appears to be ignored, so the game continues in singleplayer.
There is also suspicious receive logic: Receive() reads into a local dataBuffer, zeroes pDest, but does not appear to copy the received data into pDest before returning TRUE.
TODO:
- Treat networking / DirectPlay as incomplete.
- Verify whether singleplayer intentionally ignores failed network setup.
- Audit
CNetwork::EnumProviders(). - Audit
CNetwork::CreateProvider(). - Audit
CNetwork::Receive()and whether it should copy data into the caller buffer. - Do not let DirectPlay work block DirectDraw fullscreen or rendering work.
- Consider stubbing network cleanly for singleplayer builds.
Current .gitmodules still contains:
[submodule "dxsdk3"]
path = dxsdk3
url = https://github.com/jummy0/dxsdk3.git
Current CMake is focused on FREEDIRECT, but the repository still contains legacy Visual Studio / DirectX-related project files and the dxsdk3 submodule reference.
TODO:
- Decide whether
dxsdk3remains required for reference, legacy builds, or real DirectX 3 SDK comparison builds. - If
dxsdk3is only historical/reference material, document that clearly. - If real DirectX 3 SDK comparison builds are still needed, add explicit build instructions.
- Keep DirectX 3 SDK comparison work separate from Free Direct portability work.
Do not try to solve SDL vendoring, audio, web support, CMake cleanup, DirectPlay, and DirectDraw fullscreen at the same time.
Recommended priority order:
- Add detailed logging around DirectDraw initialization.
- Verify whether
CPixmap::Create()is really called twice. - Capture the exact
HRESULTfrom the secondDirectDrawCreate,SetCooperativeLevel,SetDisplayMode, andCreateSurfacecalls. - Decide ownership of
CPixmap::Create():- either
DoInit()creates it, - or
CacheAll()creates it, - but not both without a clear cleanup path.
- either
- Clarify the
bTrueColorBack/bTrueColorDecorparameter order. - Keep unrelated systems out of this investigation.
- Only after the fullscreen path is understood, continue with web/Android portability work.
There are at least three strong reasons why fullscreen can behave differently on Free Direct than on the original DirectX 3 SDK:
CPixmap::Create()appears to be called twice during startup without releasing previous DirectDraw objects.bTrueColorBack/bTrueColorDecorare inconsistent between declaration, definition, and call sites.CacheAll()contains likely decompilation artifacts such as:delete thisfollowed by further member access,- suspicious
LOWORD/HIWORDuse on local data, - nonsensical rectangle initialization.
Therefore, this is not only a question of Free Direct being tolerant. There are concrete suspicious areas in the decompiled game code that real DirectX 3 SDK may expose as errors.
TODO:
- Treat the current fullscreen mismatch as a game reconstruction / DirectDraw contract investigation.
- Do not assume Free Direct is wrong just because it runs.
- Do not assume the decompiled game code is correct just because Free Direct tolerates it.
- Use DirectX 3 SDK errors as useful feedback about invalid or incomplete reconstruction.
- Eventually consider adding a strict compatibility mode to Free Direct:
FREE_DIRECT_STRICT=1
Strict mode could warn or fail when the game uses DirectDraw in a way that the original DirectX 3 SDK would likely reject.
Goal:
Make the decompiled fullscreen path valid enough to work both on Free Direct / Free API and the original DirectX 3 SDK, or clearly document why Free Direct intentionally accepts behavior that real DirectDraw rejects.