make linux case-insensitive when loading files#4531
make linux case-insensitive when loading files#4531PieterVdc wants to merge 1 commit intodkfans:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds a Unix-like (non-Windows) case-insensitive file lookup path so the engine can find/open/delete files even when the requested filename casing doesn’t match what’s on disk (helpful when running content authored for case-insensitive filesystems).
Changes:
- Added a non-Windows helper that scans a directory and matches filenames via
strcasecmp(). - Updated
LbFileExists,LbFileOpen,LbFileLength, andLbFileDeleteto use case-insensitive resolution when the exact path doesn’t exist. - Simplified
create_directory_for_file()platform handling to treat all non-Windows platforms as POSIXmkdir(path, mode).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| char dir_path[PATH_MAX]; | ||
| size_t dir_len = 0; |
There was a problem hiding this comment.
PATH_MAX is used for dir_path/actual_fname stack buffers, but PATH_MAX is not guaranteed to be defined by <limits.h> on all Unix-like targets. Consider adding a local fallback guard (#ifndef PATH_MAX ...) like the one in src/custom_sprites.c to avoid build failures on platforms where it’s missing.
| // Split fname into directory and filename | ||
| const char *last_slash = strrchr(fname, '/'); | ||
| const char *filename; | ||
| char dir_path[PATH_MAX]; | ||
| size_t dir_len = 0; | ||
|
|
||
| if (last_slash != NULL) { | ||
| dir_len = last_slash - fname; | ||
| if (dir_len >= sizeof(dir_path)) { | ||
| return 0; | ||
| } | ||
| if (dir_len > 0) { | ||
| memcpy(dir_path, fname, dir_len); | ||
| dir_path[dir_len] = '\0'; | ||
| } else { | ||
| strcpy(dir_path, "/"); | ||
| } | ||
| filename = last_slash + 1; | ||
| } else { | ||
| strcpy(dir_path, "."); | ||
| filename = fname; | ||
| } | ||
|
|
||
| DIR *dir = opendir(dir_path); | ||
| if (dir == NULL) { | ||
| return 0; | ||
| } |
There was a problem hiding this comment.
find_case_insensitive_file() only case-normalizes the final path component. If the directory portion of fname has case mismatches (e.g. Data/Levels/file.dat vs data/levels/file.dat), opendir(dir_path) will fail and the lookup won’t be case-insensitive for the full path. If the intent of this PR is “case-insensitive when loading files” for full paths, consider resolving each path component case-insensitively while walking the directory tree.
| // Try to find the file case-insensitively on Unix-like systems | ||
| if (access(fname, F_OK) != 0 && find_case_insensitive_file(fname, actual_fname, sizeof(actual_fname))) { | ||
| open_fname = actual_fname; | ||
| } | ||
| #endif | ||
|
|
||
| if ( !LbFileExists(fname) ) |
There was a problem hiding this comment.
In LbFileOpen(), the non-Windows path resolution does access() + find_case_insensitive_file(), but then the code still calls LbFileExists(fname), which will repeat access() and potentially repeat the directory scan. Consider restructuring to avoid the double lookup (e.g., base existence checks on open_fname, or have a single helper that both resolves and reports existence).
| // Try to find the file case-insensitively on Unix-like systems | |
| if (access(fname, F_OK) != 0 && find_case_insensitive_file(fname, actual_fname, sizeof(actual_fname))) { | |
| open_fname = actual_fname; | |
| } | |
| #endif | |
| if ( !LbFileExists(fname) ) | |
| int access_rc = access(fname, F_OK); | |
| // Try to find the file case-insensitively on Unix-like systems | |
| if (access_rc != 0 && find_case_insensitive_file(fname, actual_fname, sizeof(actual_fname))) { | |
| open_fname = actual_fname; | |
| } | |
| #endif | |
| #if !defined(_WIN32) | |
| int file_exists = (access_rc == 0) || (open_fname != fname); | |
| #else | |
| int file_exists = LbFileExists(fname); | |
| #endif | |
| if ( !file_exists ) |
| static int find_case_insensitive_file(const char *fname, char *actual_fname, size_t buflen) | ||
| { | ||
| // Split fname into directory and filename | ||
| const char *last_slash = strrchr(fname, '/'); | ||
| const char *filename; | ||
| char dir_path[PATH_MAX]; | ||
| size_t dir_len = 0; | ||
|
|
||
| if (last_slash != NULL) { |
There was a problem hiding this comment.
The new find_case_insensitive_file() helper uses 4-space indentation, while the surrounding code in this file predominantly uses 2-space indentation. Aligning the indentation/style here will keep formatting consistent and reduce diff noise in future edits.
| const char *open_fname = fname; | ||
| #if !defined(_WIN32) | ||
| char actual_fname[PATH_MAX]; | ||
| // Try to find the file case-insensitively on Unix-like systems | ||
| if (access(fname, F_OK) != 0 && find_case_insensitive_file(fname, actual_fname, sizeof(actual_fname))) { | ||
| open_fname = actual_fname; | ||
| } | ||
| #endif | ||
| TbFileHandle handle = fopen(open_fname, "rb"); |
There was a problem hiding this comment.
The case-insensitive path resolution logic (access(...) != 0 && find_case_insensitive_file(...)) is duplicated in LbFileOpen, LbFileLength, and LbFileDelete. Consider factoring this into a small internal helper to prevent behavior from drifting between call sites (and to centralize any future improvements, like handling directory-component case mismatches).
No description provided.