Legacy File I/O & Directory Operations
MSVC CRT-compatible file search API (_findfirst / _findnext /
_findclose), POSIX access-mode constants, and directory navigation
helpers from direct.h (_chdir, _getcwd,
_mkdir).
<io.h> and <direct.h> are not
pulled in by <windows.h>. Include them explicitly:
#include <io.h> and #include <direct.h>.
_finddata_t — file search result
The structure filled by _findfirst and _findnext to
describe each matching file.
struct _finddata_t { unsigned attrib; // file attributes (_A_NORMAL, _A_SUBDIR, …) time_t time_create; // creation time (may be 0 if unavailable) time_t time_access; // last access time time_t time_write; // last modification time long size; // file size in bytes char name[260]; // file name (without path), null-terminated }; #define _MAX_FNAME 260 // maximum file name length including null
| Field | Description |
|---|---|
attrib | Bitmask of file attributes. _A_SUBDIR (0x10) is set for directories. _A_NORMAL (0x00) for regular files. |
time_create | File creation time as time_t. May be 0 on Linux (creation time not stored in most file systems). |
time_access | Last access time. |
time_write | Last modification time. Most reliable for sorting by recency. |
size | File size in bytes. Only meaningful for regular files. |
name | File name without directory path, null-terminated. Maximum 259 chars + null. |
_findfirst / _findnext / _findclose
intptr_t _findfirst(const char* filespec, struct _finddata_t* fileinfo); // Returns a search handle, or -1 on failure (no match or error). int _findnext(intptr_t handle, struct _finddata_t* fileinfo); // Returns 0 on success (next file found), -1 when no more files. int _findclose(intptr_t handle); // Always call this to release the handle. Returns 0 on success.
Implementation: backed by POSIX opendir /
readdir and fnmatch for pattern matching.
The filespec path may use Windows backslashes — they are
normalised to slashes internally. Wildcard characters * and
? are supported in the file-name part.
_findfirst, _findnext, and
_findclose as STUB in io.h — they return -1/failure.
The implementation in src/crt_io.cpp wraps POSIX readdir. If you
need directory listing, test with a real build and check whether your target
platform has the implementation compiled in.
#include <windows.h> #include <io.h> void ListBlupiFiles(const char* dir) { char pattern[MAX_PATH]; snprintf(pattern, sizeof(pattern), "%s/*.blp", dir); struct _finddata_t fd; intptr_t handle = _findfirst(pattern, &fd); if (handle == -1) { OutputDebugString("No .blp files found\n"); return; } do { if (!(fd.attrib & _A_SUBDIR)) { // skip sub-directories char msg[MAX_PATH + 16]; snprintf(msg, sizeof(msg), " Found: %s (%ld bytes)\n", fd.name, fd.size); OutputDebugString(msg); } } while (_findnext(handle, &fd) == 0); _findclose(handle); }
#include <vector> #include <string> #include <io.h> std::vector<std::string> FindLevels(const char* basePath) { std::vector<std::string> result; char pattern[512]; snprintf(pattern, sizeof(pattern), "%s/level*.blp", basePath); struct _finddata_t fd; intptr_t h = _findfirst(pattern, &fd); if (h == -1) return result; do { if (!(fd.attrib & _A_SUBDIR)) result.push_back(std::string(basePath) + "/" + fd.name); } while (_findnext(h, &fd) == 0); _findclose(h); return result; }
access() / _access() — check file existence
| Constant | Value | Meaning |
|---|---|---|
F_OK | 0 | File exists. |
X_OK | 1 | Execute permission. |
W_OK | 2 | Write permission. |
R_OK | 4 | Read permission. |
On non-Windows platforms, the system access() function is already
available. On Windows builds, Free API provides a forward declaration of
_access() that resolves against the MSVCRT.
#include <io.h> #if defined(_WIN32) # define file_exists(p) (_access((p), F_OK) == 0) #else # define file_exists(p) (access((p), F_OK) == 0) #endif if (file_exists("saves/autosave.sav")) { LoadGame("saves/autosave.sav"); } // Check whether a SoundFont exists const char* sf = "assets/soundfont/default.sf2"; if (!file_exists(sf)) { OutputDebugString("WARNING: SoundFont not found — music will be silent\n"); }
Directory navigation (_chdir, _getcwd, _mkdir)
| Function | Status | Description |
|---|---|---|
_chdir(const char* path) |
PARTIAL | Changes the current working directory. Wraps POSIX chdir(path). Returns 0 on success, -1 on failure (sets errno). Path normalisation (backslash → slash) is applied. |
_getcwd(char* buf, int maxlen) |
PARTIAL | Gets the current working directory into buf (up to maxlen chars). Wraps POSIX getcwd. Returns buf on success, NULL on failure. |
_mkdir(const char* path) |
PARTIAL | Creates a directory with permissions 0755. Wraps POSIX mkdir(path, 0755). Returns 0 on success, -1 on failure. Does not create intermediate directories. |
#include <windows.h> #include <direct.h> // Get and print current directory char cwd[MAX_PATH]; if (_getcwd(cwd, sizeof(cwd))) { char msg[MAX_PATH + 16]; snprintf(msg, sizeof(msg), "Working dir: %s\n", cwd); OutputDebugString(msg); } // Navigate to the game data directory char dataDir[MAX_PATH]; snprintf(dataDir, sizeof(dataDir), "%s/data", cwd); if (_chdir(dataDir) != 0) { OutputDebugString("Cannot change to data directory\n"); } // Create a save sub-directory if (_mkdir("saves") != 0 && errno != EEXIST) { OutputDebugString("Failed to create saves/\n"); }
_mkdir (from direct.h) and CreateDirectoryA
(from winbase.h) both create a single directory. The difference:
_mkdir returns an int and sets errno;
CreateDirectoryA returns BOOL and sets the Win32 last-error code.
Either may be used; CreateDirectoryA also normalises backslash paths.
// Free API does not create parent directories automatically. // Create them one level at a time: void EnsurePath(const char* path) { char tmp[MAX_PATH]; snprintf(tmp, sizeof(tmp), "%s", path); for (char* p = tmp + 1; *p; p++) { if (*p == '/' || *p == '\\') { *p = '\0'; _mkdir(tmp); // ignore errors (dir may already exist) *p = '/'; } } _mkdir(tmp); } // Usage: EnsurePath("saves/slot1/data");