From d7cefd491f1617f93b11403554a5b1460235668f Mon Sep 17 00:00:00 2001 From: slipher Date: Wed, 29 Apr 2026 18:45:58 -0500 Subject: [PATCH 1/2] Remove pointless slash normalization in hash functions When names are looked up in hash tables, the equality testing is done with stricmp; there is no special behavior for backslashes. So it is wrong to treat them specially in the hash function: the hashing and equality functions should go hand-in-hand. --- src/engine/renderer/tr_image.cpp | 5 ----- src/engine/renderer/tr_shader.cpp | 12 +----------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index 3d87ecf326..e0bcd76d00 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -76,11 +76,6 @@ unsigned int GenerateImageHashValue( const char *fname ) { letter = Str::ctolower( fname[ i ] ); - if ( letter == '\\' ) - { - letter = '/'; // damn path names - } - hash += ( unsigned )( letter ) * ( i + 119 ); i++; } diff --git a/src/engine/renderer/tr_shader.cpp b/src/engine/renderer/tr_shader.cpp index bffc8d2f72..0d06255a8b 100644 --- a/src/engine/renderer/tr_shader.cpp +++ b/src/engine/renderer/tr_shader.cpp @@ -123,17 +123,7 @@ static unsigned int generateHashValue( const char *fname, const int size ) if ( letter == '.' ) { - break; // don't include extension - } - - if ( letter == '\\' ) - { - letter = '/'; // damn path names - } - - if ( letter == PATH_SEP ) - { - letter = '/'; // damn path names + break; // don't include extension. FIXME: could have multiple dots } hash += ( unsigned )( letter ) * ( i + 119 ); From 46dd86163fdf698e628738d71c18095970d6fb99 Mon Sep 17 00:00:00 2001 From: slipher Date: Thu, 23 Apr 2026 22:08:54 -0500 Subject: [PATCH 2/2] Allow backslashes, extra slashes in shader/image names Remove initial slashes or multiple consecutive slashes and convert \ to / when looking up shaders or images. Useful for Smokin' Guns assets. --- src/common/FileSystem.cpp | 21 +++++++++++++++++++++ src/common/FileSystem.h | 5 +++++ src/engine/renderer/tr_image.cpp | 14 ++++++++------ src/engine/renderer/tr_shader.cpp | 3 ++- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/common/FileSystem.cpp b/src/common/FileSystem.cpp index 45bd66b8b0..abed38d07f 100644 --- a/src/common/FileSystem.cpp +++ b/src/common/FileSystem.cpp @@ -477,6 +477,27 @@ std::string Build(Str::StringRef base, Str::StringRef path) return out; } +std::string NormalizeSlashes(Str::StringRef path) +{ + std::string out; + out.reserve(path.size()); + bool lastSlash = true; + + for (char c : path) { + if (c == '/' || c == '\\') { + if (!lastSlash) { + lastSlash = true; + out.push_back('/'); + } + } else { + out.push_back( c ); + lastSlash = false; + } + } + + return out; +} + std::string DirName(Str::StringRef path) { if (path.empty()) diff --git a/src/common/FileSystem.h b/src/common/FileSystem.h index 725afa66d8..787e95bb43 100644 --- a/src/common/FileSystem.h +++ b/src/common/FileSystem.h @@ -177,6 +177,11 @@ namespace Path { // Build a path from components std::string Build(Str::StringRef base, Str::StringRef path); + // Replace \ with / + // Remove multiple consecutive slashes + // Remove initial slashes + std::string NormalizeSlashes(Str::StringRef path); + // Get the directory portion of a path: // a/b/c => a/b // a/b/ => a diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index e0bcd76d00..9330290c7c 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -1818,19 +1818,21 @@ Finds or loads the given image. Returns nullptr if it fails, not a default image. ============== */ -image_t *R_FindImageFile( const char *imageName, imageParams_t &imageParams ) +image_t *R_FindImageFile( const char *imageName0, imageParams_t &imageParams ) { - if ( !imageName ) + if ( !imageName0 ) { return nullptr; } - unsigned hash = GenerateImageHashValue( imageName ); + std::string imageName = FS::Path::NormalizeSlashes( imageName0 ); + + unsigned hash = GenerateImageHashValue( imageName.c_str() ); // See if the image is already loaded. for ( image_t *image = r_imageHashTable[ hash ]; image; image = image->next ) { - if ( !Q_strnicmp( imageName, image->name, sizeof( image->name ) ) ) + if ( Str::IsIEqual( imageName, image->name ) ) { if ( imageParams == image->initialParams || r_allowImageParamMismatch.Get() ) { @@ -1889,7 +1891,7 @@ image_t *R_FindImageFile( const char *imageName, imageParams_t &imageParams ) byte *pic[ MAX_TEXTURE_MIPS * MAX_TEXTURE_LAYERS ]; pic[ 0 ] = nullptr; - R_LoadImage( imageName, pic, &width, &height, &numLayers, &numMips, &imageParams.bits ); + R_LoadImage( imageName.c_str(), pic, &width, &height, &numLayers, &numMips, &imageParams.bits); if ( *pic ) { @@ -1909,7 +1911,7 @@ image_t *R_FindImageFile( const char *imageName, imageParams_t &imageParams ) R_ProcessLightmap( *pic, width, height, imageParams.bits ); } - image_t *image = R_CreateImage( imageName, (const byte **)pic, width, height, numMips, imageParams ); + image_t *image = R_CreateImage( imageName.c_str(), (const byte**)pic, width, height, numMips, imageParams); image->initialParams = initialParams; Z_Free( *pic ); diff --git a/src/engine/renderer/tr_shader.cpp b/src/engine/renderer/tr_shader.cpp index 0d06255a8b..13f9ea981d 100644 --- a/src/engine/renderer/tr_shader.cpp +++ b/src/engine/renderer/tr_shader.cpp @@ -6237,7 +6237,8 @@ shader_t *R_FindShader( const char *name, int flags ) return tr.defaultShader; } - COM_StripExtension3( name, strippedName, sizeof( strippedName ) ); + COM_StripExtension3( FS::Path::NormalizeSlashes( name ).c_str(), + strippedName, sizeof( strippedName ) ); hash = generateHashValue( strippedName, FILE_HASH_SIZE );