System & File Utilities
Timing, debug output, error codes, memory helpers, directory and file operations, environment variables, the WinMain entry-point bridge, and the transparent fopen path normaliser.
Timing & synchronisation
| Function | Header | Status | Description |
|---|---|---|---|
Sleep(DWORD dwMilliseconds) |
synchapi.h | IMPLEMENTED | Suspends the calling thread for the specified number of milliseconds. Implemented via POSIX usleep(ms * 1000). A value of 0 yields the thread. |
GetTickCount(void) |
sysinfoapi.h | IMPLEMENTED | Returns milliseconds elapsed since the process started, via SDL_GetTicks(). Wraps after ~49.7 days (32-bit overflow). For timing deltas, always subtract two values. |
// Frame-rate limiter: cap at 20 fps DWORD frameStart = GetTickCount(); GameUpdate(); GameRender(); DWORD elapsed = GetTickCount() - frameStart; if (elapsed < 50) Sleep(50 - elapsed); // wait remainder of 50 ms frame // Measure elapsed time DWORD t0 = GetTickCount(); DoWork(); DWORD ms = GetTickCount() - t0; printf("Took %lu ms\n", (unsigned long)ms); // Simple delay Sleep(200); // pause 200 ms
Debug output
void WINAPI OutputDebugString(LPCSTR lpOutputString);
On Free API, OutputDebugString is implemented via SDL_Log,
so output appears in the terminal / logcat depending on platform.
On a real Windows build with a debugger attached it would also appear in the
debugger output window.
OutputDebugString("Game initialised\n"); // Build a formatted string first (OutputDebugString has no printf formatting) char buf[256]; snprintf(buf, sizeof(buf), "Score: %d, Level: %d\n", score, level); OutputDebugString(buf); // Or use wsprintfA (Win32 printf variant, 1024-byte buffer) wsprintfA(buf, "HP: %d/%d", hp, maxHp); OutputDebugString(buf);
Error codes
| Function | Status | Description |
|---|---|---|
GetLastError(void) |
PARTIAL | Returns the last error code set for the calling thread. In Free API, this mirrors errno after POSIX calls. Not all Free API functions set the last error. |
SetLastError(DWORD dwErrCode) |
PARTIAL | Sets the thread-local last error code. |
if (!CreateDirectoryA("saves", NULL)) { DWORD err = GetLastError(); char buf[128]; snprintf(buf, sizeof(buf), "CreateDirectory failed, error %lu\n", (unsigned long)err); OutputDebugString(buf); }
Memory macros & GlobalMemoryStatus
Memory macros (HEADER_ONLY)
| Macro | Expands to | Description |
|---|---|---|
ZeroMemory(dst, len) | memset(dst, 0, len) | Fill a buffer with zeros. Equivalent to C++ value-initialisation. |
FillMemory(dst, len, fill) | memset(dst, fill, len) | Fill a buffer with a byte value. |
CopyMemory(dst, src, len) | memmove(dst, src, len) | Copy bytes; memmove handles overlapping regions correctly. |
WNDCLASSA wc; ZeroMemory(&wc, sizeof(wc)); // clear struct (same as = {}) char buf[256]; FillMemory(buf, sizeof(buf), 0xFF); // fill with 0xFF BYTE src[64], dst[64]; CopyMemory(dst, src, sizeof(src)); // copy 64 bytes
GlobalMemoryStatus
void WINAPI GlobalMemoryStatus(LPMEMORYSTATUS lpBuffer);
Fills a MEMORYSTATUS structure with approximate memory statistics.
On Linux, reads /proc/meminfo. On other platforms, values are
approximations. Do not rely on these values for precise resource decisions.
MEMORYSTATUS ms; ms.dwLength = sizeof(ms); GlobalMemoryStatus(&ms); // dwMemoryLoad: 0-100% memory load estimate // dwTotalPhys / dwAvailPhys: total and free physical RAM in bytes printf("RAM load: %lu%% Free: %lu MB\n", (unsigned long)ms.dwMemoryLoad, (unsigned long)(ms.dwAvailPhys / (1024*1024)));
File & directory operations
| Function | Status | Description |
|---|---|---|
CreateDirectoryA(path, sa) |
PARTIAL | Creates a single directory. Path is normalised (drive letter removed, backslashes → slashes, leading slashes stripped). Calls mkdir(path, 0755). Treats EEXIST as success. Does not create missing parent directories. Security attributes (sa) are ignored. |
RemoveDirectoryA(path) |
PARTIAL | Calls POSIX rmdir. Fails if the directory is not empty. |
DeleteFileA(path) |
PARTIAL | Calls POSIX remove. Returns TRUE on success. No backslash normalisation applied. |
SetEnvironmentVariableA(name, value) |
PARTIAL | Calls POSIX setenv(name, value, 1). Pass NULL for value to unset. |
// Create save directory (safe to call if already exists) CreateDirectoryA("saves", NULL); // Create with Windows-style path (drive + backslashes) CreateDirectoryA("C:\\MyGame\\Saves", NULL); // → internally treated as "MyGame/Saves" relative to CWD // Delete a save file if (!DeleteFileA("saves/slot1.sav")) { OutputDebugString("Delete failed\n"); } // Set a custom SoundFont path at runtime SetEnvironmentVariableA("FREE_API_SOUNDFONT", "/usr/share/sounds/sf2/FluidR3_GM.sf2");
Low-level file I/O (_lopen / _lread / _lclose)
| Function | Status | Description |
|---|---|---|
_lopen(path, iReadWrite) | PARTIAL | Opens file read-only via POSIX open(O_RDONLY). The iReadWrite mode is ignored — only reading is supported. Returns a file descriptor (int), or -1 on error. |
_lread(hFile, buf, n) | PARTIAL | Reads up to n bytes into buf via POSIX read. Returns bytes actually read, or HFILE_ERROR on error. |
_lclose(hFile) | PARTIAL | Closes the file descriptor via POSIX close. |
int fh = _lopen("data/level01.dat", OF_READ); if (fh == -1) { OutputDebugString("open failed\n"); return; } BYTE header[16]; UINT read = _lread(fh, header, sizeof(header)); // ... process data ... _lclose(fh);
Win32 resource stubs
Win32 embedded resources (icons, strings, bitmaps compiled into the EXE) are not supported. All resource functions compile cleanly and return NULL/0 so that code using them does not crash.
| Function | Status | Returns |
|---|---|---|
GetModuleHandleA(LPCSTR name) | STUB | NULL |
FindResourceA(hMod, name, type) | STUB | NULL |
LoadResource(hMod, hRes) | STUB | NULL |
LockResource(hRes) | STUB | NULL |
SizeofResource(hMod, hRes) | STUB | 0 |
FreeResource(hRes) | STUB | FALSE |
UnlockResource(hRes) | STUB | FALSE |
LoadStringA(hInst, uID, buf, cch) | STUB | 0 |
WinMain bridge
Win32 games use WinMain instead of main. Free API
provides a macro and a helper function to bridge the two.
| Symbol | Status | Description |
|---|---|---|
FREE_API_IMPLEMENT_WINMAIN() |
PARTIAL | C++ macro that generates a main(int argc, char** argv) which calls FreeApiRunWinMain(&WinMain, argc, argv). Only defined when _WIN32 is not defined (non-Windows builds). On Windows, the real WinMain startup code is used instead. |
FreeApiRunWinMain(entryPoint, argc, argv) |
PARTIAL | Sets _pgmptr = argv[0], builds lpCmdLine by joining argv[1..] with spaces, then calls entryPoint(NULL, NULL, lpCmdLine, SW_SHOW). |
_pgmptr |
PARTIAL | Declared extern char* on non-Windows. Set to argv[0] by FreeApiRunWinMain. Some legacy games read this to locate their executable directory. |
#include <windows.h> // This macro generates: // int main(int argc, char** argv) { // return FreeApiRunWinMain(&WinMain, argc, argv); // } // Followed immediately by the WinMain body you provide. FREE_API_IMPLEMENT_WINMAIN() { // hInstance = NULL (not needed) // hPrevInstance = NULL // lpCmdLine = command-line args joined by spaces // nCmdShow = SW_SHOW (5) WNDCLASSA wc = {}; wc.lpfnWndProc = MyWindowProc; wc.lpszClassName = "GameWindow"; RegisterClass(&wc); HWND hwnd = CreateWindowA("GameWindow", "My Game", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 800, 600, NULL, NULL, hInstance, NULL); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; }
main() separate (e.g. for SDL3 Android support),
call FreeApiRunWinMain directly and forward-declare WinMain:
#include <windows.h> int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int); // forward declaration int main(int argc, char** argv) { return FreeApiRunWinMain(&WinMain, argc, argv); }
fopen path-normalisation wrapper
Legacy Win32 games hard-code backslash paths and sometimes include a Windows
drive letter. This wrapper is applied transparently via a #define fopen
in every C++ translation unit that includes <windows.h>.
The wrapper performs these steps in order:
- Remove Windows drive letter —
"C:\path"→"\path" - Convert all
\to/ - Strip any remaining leading slashes so the path is relative
- Call
::fopen(normalized_path, mode) - On failure, retry with the basename uppercased (case-sensitive filesystem fallback)
// Original Win32 game code — unchanged, works on Linux: FILE* f1 = fopen("data\\config.def", "r"); // → opens "data/config.def" FILE* f2 = fopen("C:\\MyGame\\DATA\\LEVEL01.BLP", "rb"); // → tries "MyGame/DATA/LEVEL01.BLP" // → if missing, retries "MyGame/DATA/level01.BLP" (uppercase fallback) FILE* f3 = fopen("PLANET\\data\\map.blp", "rb"); // → opens "PLANET/data/map.blp"
<windows.h> do not
get the wrapper. In practice, all game files are C++.