1 diff --git a/README.md b/README.md
2 index fe466e50..e91dd4ed 100644
5 @@ -46,7 +46,7 @@ Jazz² Resurrection is reimplementation of the game **Jazz Jackrabbit 2** releas
6 * Install dependencies: `sudo apt install libglew2.2 libglfw3 libsdl2-2.0-0 libopenal1 libvorbisfile3 libopenmpt0`
7 * Alternatively, install provided `.deb` or `.rpm` package and dependencies should be installed automatically
8 * Copy contents of original *Jazz Jackrabbit 2* directory to `‹Game›/Source/`
9 - * If packages are used, the files must be copied to `~/.local/share/Jazz² Resurrection/Source/` instead
10 + * 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
11 * Run `‹Game›/jazz2` or `‹Game›/jazz2_sdl2` application
12 * If packages are used, the game should be visible in application list
14 diff --git a/Sources/Common.h b/Sources/Common.h
15 index 865d42bb..cf6f112c 100644
16 --- a/Sources/Common.h
17 +++ b/Sources/Common.h
22 +#if !defined(NCINE_INSTALL_PREFIX) && defined(DEATH_TARGET_UNIX)
23 +# define NCINE_INSTALL_PREFIX "/usr/local"
26 // Check platform-specific capabilities
27 #if defined(WITH_SDL) || defined(DEATH_TARGET_WINDOWS_RT)
28 # define NCINE_HAS_GAMEPAD_RUMBLE
29 diff --git a/Sources/Jazz2/ContentResolver.cpp b/Sources/Jazz2/ContentResolver.cpp
30 index 737582db..a7c5d460 100644
31 --- a/Sources/Jazz2/ContentResolver.cpp
32 +++ b/Sources/Jazz2/ContentResolver.cpp
33 @@ -207,7 +207,7 @@ namespace Jazz2
34 # elif defined(NCINE_OVERRIDE_CONTENT_PATH)
35 _contentPath = NCINE_OVERRIDE_CONTENT_PATH;
37 - _contentPath = "/usr/share/" NCINE_LINUX_PACKAGE "/Content/";
38 + _contentPath = NCINE_INSTALL_PREFIX "/share/" NCINE_LINUX_PACKAGE "/Content/";
40 # if defined(NCINE_PACKAGED_CONTENT_PATH)
41 // If Content is packaged with binaries, always use standard XDG paths for everything else
42 @@ -226,18 +226,25 @@ namespace Jazz2
43 auto localStorage = fs::GetLocalStorage();
44 if (!localStorage.empty()) {
45 // Use "$XDG_DATA_HOME/Jazz² Resurrection/" if exists (for backward compatibility), otherwise "$XDG_DATA_HOME/{NCINE_LINUX_PACKAGE}/"
46 - _sourcePath = fs::CombinePath(localStorage, "Jazz² Resurrection/Source/"_s);
47 - if (fs::DirectoryExists(_sourcePath)) {
48 - _cachePath = fs::CombinePath(localStorage, "Jazz² Resurrection/Cache/"_s);
50 + _cachePath = fs::CombinePath(localStorage, "Jazz² Resurrection/Cache/"_s);
51 + if (!fs::DirectoryExists(_cachePath)) {
52 auto appData = fs::CombinePath(localStorage, NCINE_LINUX_PACKAGE);
53 - _sourcePath = fs::CombinePath(appData, "Source/"_s);
54 _cachePath = fs::CombinePath(appData, "Cache/"_s);
57 - _sourcePath = "Source/"_s;
58 _cachePath = "Cache/"_s;
61 + // Prefer system-wide Source only if it exists and local one doesn't exist
62 + _sourcePath = fs::CombinePath(fs::GetDirectoryName(_cachePath), "Source/"_s);
63 + if (!fs::FindPathCaseInsensitive(fs::CombinePath(_sourcePath, "Anims.j2a"_s)) &&
64 + !fs::FindPathCaseInsensitive(fs::CombinePath(_sourcePath, "AnimsSw.j2a"_s))) {
65 + auto systemWideSource = NCINE_INSTALL_PREFIX "/share/" NCINE_LINUX_PACKAGE "/Source/";
66 + if (fs::FindPathCaseInsensitive(fs::CombinePath(systemWideSource, "Anims.j2a"_s)) ||
67 + fs::FindPathCaseInsensitive(fs::CombinePath(systemWideSource, "AnimsSw.j2a"_s))) {
68 + _sourcePath = systemWideSource;
72 // Fallback to relative paths
73 _contentPath = "Content/"_s;
74 diff --git a/Sources/Shared/IO/FileSystem.cpp b/Sources/Shared/IO/FileSystem.cpp
75 index 5b5199b0..0d4850bd 100644
76 --- a/Sources/Shared/IO/FileSystem.cpp
77 +++ b/Sources/Shared/IO/FileSystem.cpp
78 @@ -701,73 +701,91 @@ namespace Death { namespace IO {
79 #if !defined(DEATH_TARGET_WINDOWS) && !defined(DEATH_TARGET_SWITCH)
80 String FileSystem::FindPathCaseInsensitive(const StringView path)
83 + if (path.empty() || Exists(path)) {
87 - std::size_t l = path.size();
88 - char* p = (char*)alloca(l + 1);
89 - strncpy(p, path.data(), l);
92 - bool isAbsolute = (p[0] == '/' || p[0] == '\\');
94 + String result = path;
95 + MutableStringView partialResult = result;
96 + char* nextPartBegin;
98 - String result(NoInit, path.size() + (isAbsolute ? 0 : 2));
99 + while (MutableStringView separator = partialResult.findLast('/')) {
100 + if DEATH_UNLIKELY(separator.begin() == result.begin()) {
101 + // Nothing left, only first slash of absolute path
107 - d = ::opendir("/");
110 - d = ::opendir(".");
114 + partialResult = partialResult.prefix(separator.begin());
115 + separator[0] = '\0';
116 + d = ::opendir(result.data());
117 + separator[0] = '/';
118 + if (d != nullptr) {
119 + nextPartBegin = separator.end();
125 - char* c = strsep(&p, "/");
127 - if (d == nullptr) {
129 + if (d == nullptr) {
130 + if (result[0] == '/' || result[0] == '\\') {
131 + d = ::opendir("/");
132 + nextPartBegin = result.begin() + 1;
134 + d = ::opendir(".");
135 + nextPartBegin = result.begin();
140 + if DEATH_UNLIKELY(d == nullptr) {
146 + partialResult = result.suffix(nextPartBegin);
147 + MutableStringView nextSeparator = partialResult.findOr('/', result.end());
148 + if DEATH_UNLIKELY(nextSeparator.begin() == nextPartBegin) {
149 + // Skip empty parts
150 + nextPartBegin = nextSeparator.end();
157 + bool hasNextSeparator = (nextSeparator.begin() != result.end());
158 + if DEATH_LIKELY(hasNextSeparator) {
159 + nextSeparator[0] = '\0';
162 struct dirent* entry = ::readdir(d);
163 while (entry != nullptr) {
164 - if (::strcasecmp(c, entry->d_name) == 0) {
165 - strcpy(&result[rl], entry->d_name);
166 - rl += strlen(entry->d_name);
168 + if (::strcasecmp(partialResult.begin(), entry->d_name) == 0) {
169 + std::size_t fileNameLength = std::strlen(entry->d_name);
170 + DEATH_DEBUG_ASSERT(partialResult.begin() + fileNameLength == nextSeparator.begin());
171 + std::memcpy(partialResult.begin(), entry->d_name, fileNameLength);
174 + nextPartBegin = nextSeparator.end();
175 + if (!hasNextSeparator || nextPartBegin == result.end()) {
176 + if (hasNextSeparator) {
177 + nextSeparator[0] = '/';
182 d = ::opendir(result.data());
183 + if DEATH_UNLIKELY(d == nullptr) {
186 + nextSeparator[0] = '/';
190 entry = ::readdir(d);
193 - if (entry == nullptr) {
194 - strcpy(&result[rl], c);
197 + if DEATH_UNLIKELY(entry == nullptr) {
202 - c = strsep(&p, "/");
205 - if (d != nullptr) {
212 diff --git a/cmake/ncine_compiler_options.cmake b/cmake/ncine_compiler_options.cmake
213 index 9ca461ad..6d981fdc 100644
214 --- a/cmake/ncine_compiler_options.cmake
215 +++ b/cmake/ncine_compiler_options.cmake
216 @@ -10,6 +10,10 @@ target_compile_definitions(${NCINE_APP} PUBLIC "NCINE_VERSION=\"${NCINE_VERSION}
217 string(TIMESTAMP NCINE_BUILD_YEAR "%Y")
218 target_compile_definitions(${NCINE_APP} PUBLIC "NCINE_BUILD_YEAR=\"${NCINE_BUILD_YEAR}\"")
221 + target_compile_definitions(${NCINE_APP} PUBLIC "NCINE_INSTALL_PREFIX=\"${CMAKE_INSTALL_PREFIX}\"")
224 if(NCINE_OVERRIDE_CONTENT_PATH)
225 message(STATUS "Using overriden `Content` path: ${NCINE_OVERRIDE_CONTENT_PATH}")
226 target_compile_definitions(${NCINE_APP} PUBLIC "NCINE_OVERRIDE_CONTENT_PATH=\"${NCINE_OVERRIDE_CONTENT_PATH}\"")