diff --git a/README.md b/README.md index fe466e50..e91dd4ed 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Jazz² Resurrection is reimplementation of the game **Jazz Jackrabbit 2** releas * Install dependencies: `sudo apt install libglew2.2 libglfw3 libsdl2-2.0-0 libopenal1 libvorbisfile3 libopenmpt0` * Alternatively, install provided `.deb` or `.rpm` package and dependencies should be installed automatically * Copy contents of original *Jazz Jackrabbit 2* directory to `‹Game›/Source/` - * If packages are used, the files must be copied to `~/.local/share/Jazz² Resurrection/Source/` instead + * If packages are used, the files must be copied to `~/.local/share/Jazz² Resurrection/Source/` or `/usr/local/share/Jazz² Resurrection/Source/` instead, please follow instructions of specific package * Run `‹Game›/jazz2` or `‹Game›/jazz2_sdl2` application * If packages are used, the game should be visible in application list diff --git a/Sources/Common.h b/Sources/Common.h index 865d42bb..cf6f112c 100644 --- a/Sources/Common.h +++ b/Sources/Common.h @@ -33,6 +33,10 @@ #include +#if !defined(NCINE_INSTALL_PREFIX) && defined(DEATH_TARGET_UNIX) +# define NCINE_INSTALL_PREFIX "/usr/local" +#endif + // Check platform-specific capabilities #if defined(WITH_SDL) || defined(DEATH_TARGET_WINDOWS_RT) # define NCINE_HAS_GAMEPAD_RUMBLE diff --git a/Sources/Jazz2/ContentResolver.cpp b/Sources/Jazz2/ContentResolver.cpp index 737582db..a7c5d460 100644 --- a/Sources/Jazz2/ContentResolver.cpp +++ b/Sources/Jazz2/ContentResolver.cpp @@ -207,7 +207,7 @@ namespace Jazz2 # elif defined(NCINE_OVERRIDE_CONTENT_PATH) _contentPath = NCINE_OVERRIDE_CONTENT_PATH; # else - _contentPath = "/usr/share/" NCINE_LINUX_PACKAGE "/Content/"; + _contentPath = NCINE_INSTALL_PREFIX "/share/" NCINE_LINUX_PACKAGE "/Content/"; # endif # if defined(NCINE_PACKAGED_CONTENT_PATH) // If Content is packaged with binaries, always use standard XDG paths for everything else @@ -226,18 +226,25 @@ namespace Jazz2 auto localStorage = fs::GetLocalStorage(); if (!localStorage.empty()) { // Use "$XDG_DATA_HOME/Jazz² Resurrection/" if exists (for backward compatibility), otherwise "$XDG_DATA_HOME/{NCINE_LINUX_PACKAGE}/" - _sourcePath = fs::CombinePath(localStorage, "Jazz² Resurrection/Source/"_s); - if (fs::DirectoryExists(_sourcePath)) { - _cachePath = fs::CombinePath(localStorage, "Jazz² Resurrection/Cache/"_s); - } else { + _cachePath = fs::CombinePath(localStorage, "Jazz² Resurrection/Cache/"_s); + if (!fs::DirectoryExists(_cachePath)) { auto appData = fs::CombinePath(localStorage, NCINE_LINUX_PACKAGE); - _sourcePath = fs::CombinePath(appData, "Source/"_s); _cachePath = fs::CombinePath(appData, "Cache/"_s); } } else { - _sourcePath = "Source/"_s; _cachePath = "Cache/"_s; } + + // Prefer system-wide Source only if it exists and local one doesn't exist + _sourcePath = fs::CombinePath(fs::GetDirectoryName(_cachePath), "Source/"_s); + if (!fs::FindPathCaseInsensitive(fs::CombinePath(_sourcePath, "Anims.j2a"_s)) && + !fs::FindPathCaseInsensitive(fs::CombinePath(_sourcePath, "AnimsSw.j2a"_s))) { + auto systemWideSource = NCINE_INSTALL_PREFIX "/share/" NCINE_LINUX_PACKAGE "/Source/"; + if (fs::FindPathCaseInsensitive(fs::CombinePath(systemWideSource, "Anims.j2a"_s)) || + fs::FindPathCaseInsensitive(fs::CombinePath(systemWideSource, "AnimsSw.j2a"_s))) { + _sourcePath = systemWideSource; + } + } } else { // Fallback to relative paths _contentPath = "Content/"_s; diff --git a/Sources/Shared/IO/FileSystem.cpp b/Sources/Shared/IO/FileSystem.cpp index 5b5199b0..0d4850bd 100644 --- a/Sources/Shared/IO/FileSystem.cpp +++ b/Sources/Shared/IO/FileSystem.cpp @@ -701,73 +701,91 @@ namespace Death { namespace IO { #if !defined(DEATH_TARGET_WINDOWS) && !defined(DEATH_TARGET_SWITCH) String FileSystem::FindPathCaseInsensitive(const StringView path) { - if (Exists(path)) { + if (path.empty() || Exists(path)) { return path; } - std::size_t l = path.size(); - char* p = (char*)alloca(l + 1); - strncpy(p, path.data(), l); - p[l] = '\0'; - std::size_t rl = 0; - bool isAbsolute = (p[0] == '/' || p[0] == '\\'); + DIR* d = nullptr; + String result = path; + MutableStringView partialResult = result; + char* nextPartBegin; - String result(NoInit, path.size() + (isAbsolute ? 0 : 2)); + while (MutableStringView separator = partialResult.findLast('/')) { + if DEATH_UNLIKELY(separator.begin() == result.begin()) { + // Nothing left, only first slash of absolute path + break; + } - DIR* d; - if (isAbsolute) { - d = ::opendir("/"); - p = p + 1; - } else { - d = ::opendir("."); - result[0] = '.'; - result[1] = '\0'; - rl = 1; + partialResult = partialResult.prefix(separator.begin()); + separator[0] = '\0'; + d = ::opendir(result.data()); + separator[0] = '/'; + if (d != nullptr) { + nextPartBegin = separator.end(); + break; + } } - bool last = false; - char* c = strsep(&p, "/"); - while (c) { - if (d == nullptr) { - return {}; + if (d == nullptr) { + if (result[0] == '/' || result[0] == '\\') { + d = ::opendir("/"); + nextPartBegin = result.begin() + 1; + } else { + d = ::opendir("."); + nextPartBegin = result.begin(); } - if (last) { - ::closedir(d); + if DEATH_UNLIKELY(d == nullptr) { return {}; } + } + + while (true) { + partialResult = result.suffix(nextPartBegin); + MutableStringView nextSeparator = partialResult.findOr('/', result.end()); + if DEATH_UNLIKELY(nextSeparator.begin() == nextPartBegin) { + // Skip empty parts + nextPartBegin = nextSeparator.end(); + continue; + } - result[rl] = '/'; - rl += 1; - result[rl] = '\0'; + bool hasNextSeparator = (nextSeparator.begin() != result.end()); + if DEATH_LIKELY(hasNextSeparator) { + nextSeparator[0] = '\0'; + } struct dirent* entry = ::readdir(d); while (entry != nullptr) { - if (::strcasecmp(c, entry->d_name) == 0) { - strcpy(&result[rl], entry->d_name); - rl += strlen(entry->d_name); - + if (::strcasecmp(partialResult.begin(), entry->d_name) == 0) { + std::size_t fileNameLength = std::strlen(entry->d_name); + DEATH_DEBUG_ASSERT(partialResult.begin() + fileNameLength == nextSeparator.begin()); + std::memcpy(partialResult.begin(), entry->d_name, fileNameLength); ::closedir(d); + + nextPartBegin = nextSeparator.end(); + if (!hasNextSeparator || nextPartBegin == result.end()) { + if (hasNextSeparator) { + nextSeparator[0] = '/'; + } + return result; + } + d = ::opendir(result.data()); + if DEATH_UNLIKELY(d == nullptr) { + return {}; + } + nextSeparator[0] = '/'; break; } entry = ::readdir(d); } - if (entry == nullptr) { - strcpy(&result[rl], c); - rl += strlen(c); - last = true; + if DEATH_UNLIKELY(entry == nullptr) { + ::closedir(d); + return {}; } - - c = strsep(&p, "/"); } - - if (d != nullptr) { - ::closedir(d); - } - return result; } #endif diff --git a/cmake/ncine_compiler_options.cmake b/cmake/ncine_compiler_options.cmake index 9ca461ad..6d981fdc 100644 --- a/cmake/ncine_compiler_options.cmake +++ b/cmake/ncine_compiler_options.cmake @@ -10,6 +10,10 @@ target_compile_definitions(${NCINE_APP} PUBLIC "NCINE_VERSION=\"${NCINE_VERSION} string(TIMESTAMP NCINE_BUILD_YEAR "%Y") target_compile_definitions(${NCINE_APP} PUBLIC "NCINE_BUILD_YEAR=\"${NCINE_BUILD_YEAR}\"") +if(UNIX) + target_compile_definitions(${NCINE_APP} PUBLIC "NCINE_INSTALL_PREFIX=\"${CMAKE_INSTALL_PREFIX}\"") +endif() + if(NCINE_OVERRIDE_CONTENT_PATH) message(STATUS "Using overriden `Content` path: ${NCINE_OVERRIDE_CONTENT_PATH}") target_compile_definitions(${NCINE_APP} PUBLIC "NCINE_OVERRIDE_CONTENT_PATH=\"${NCINE_OVERRIDE_CONTENT_PATH}\"")