Descent3/cfile/tests/cfile_tests.cpp
Jason Yundt f5d2a43863 Add -aditionaldir option
Before this change, Descent 3 would look for all of its game data files
in a single directory. This change allows users to spread out Descent
3’s game data over multiple directories.

Building Descent 3 produces multiple files that can be freely
redistributed (Descent3, d3-linux.hog, online/Direct TCP~IP.d3c, etc.).
Running Descent 3 requires those files and several additional files that
cannot be freely redistributed. Before this change, the files that were
redistributable had to be in the same directory as the files that were
not redistributable. This change makes it so that they can be in
separate directories.

The main motivation behind this change is to allow people to package
Descent 3 for Linux in a reasonable manner. For the most part, binary
packages for Descent 3 will contain all of the freely redistributable
components. Package managers will copy those components into system
directories that are owned by root and that users probably shouldn’t
edit manually. Users will then create a new directory and copy the game
data from their copy of Descent 3 into that new directory. Users will
then be able to run:

  Descent3 -setdir <path-to-proprietary-files> -additionaldir <path-to-open-source-files>

The -additionaldir option can also be used to support more complicated
scenarios. For example, if the user is using Debian’s
game-data-packager [1], then they would do something like this:

  Descent3 -setdir <path-to-writable-directory> -additionaldir <path-to-gdp-directory> -additionaldir <path-to-open-source-files>

The -additionaldir option can also be used to load a mod that replaces
.hog files:

  Descent3 -setdir <path-to-base-game-data> -additionaldir <path-to-mod-files>

[1]: <https://github.com/DescentDevelopers/Descent3/issues/373#issuecomment-2120330650>
2024-09-29 14:07:53 -04:00

118 lines
3.9 KiB
C++

/*
* Descent 3
* Copyright (C) 2024 Descent Developers
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <filesystem>
#include <vector>
#include <gtest/gtest.h>
#include "cfile.h"
void add_cwd_to_base_directories() {
cf_AddBaseDirectory(std::filesystem::current_path());
}
TEST(D3, CFileIO) {
add_cwd_to_base_directories();
int lib_handle = cf_OpenLibrary("TestDir/test.hog");
CFILE *file_handle = cfopen("lowercase.txt", "rb");
char buf[5];
cf_ReadString(buf, 5, file_handle);
EXPECT_STREQ(buf, "TEST");
cf_Rewind(file_handle);
EXPECT_EQ(cf_ReadByte(file_handle), 84);
cf_Rewind(file_handle);
EXPECT_EQ(cf_ReadShort(file_handle), 17748);
cf_Rewind(file_handle);
EXPECT_EQ(cf_ReadInt(file_handle), 1414743380);
cf_Rewind(file_handle);
cf_CloseLibrary(lib_handle);
}
TEST(D3, CFileLibrary) {
add_cwd_to_base_directories();
// First pass - without search path in "TestDir" (i.e. not search actual files in directory)
// Second pass - with search path (files in directory goes first)
for (int i = 0; i < 2; i++) {
if (i != 0) {
EXPECT_EQ(cf_SetSearchPath("TestDir"), true);
}
int lib_handle = cf_OpenLibrary("TestDir/test.hog");
EXPECT_NE(lib_handle, 0);
CFILE *file_handle = cf_OpenFileInLibrary("lowercase.txt", lib_handle);
EXPECT_NE(file_handle, nullptr);
file_handle = cfopen("lowercase.txt", "rb");
EXPECT_NE(file_handle, nullptr);
// Length in hog is 4, in directory is 0
EXPECT_EQ(cfilelength(file_handle), i == 0 ? 4 : 0);
EXPECT_EQ(cf_CalculateFileCRC(file_handle), i == 0 ? 4008350648 : 0);
cf_ClearAllSearchPaths();
cf_CloseLibrary(lib_handle);
}
// Try to search on non-initialized paths and files
CFILE *file_handle = cfopen("lowercase.txt", "rb");
EXPECT_EQ(file_handle, nullptr);
}
TEST(D3, CFileLocatePath) {
const std::vector<std::filesystem::path> test_paths = {
std::filesystem::path("TestDir") / "CamelCase.txt",
std::filesystem::path("TestDir") / "lowercase.txt",
std::filesystem::path("TestDir") / "UPPERCASE.TXT",
};
std::filesystem::path filename_new = cf_LocatePath(std::filesystem::path("no-exist-dir") / "no-exist-file.txt");
EXPECT_TRUE(filename_new.empty());
filename_new = cf_LocatePath("no-exist-file.txt");
EXPECT_TRUE(filename_new.empty());
auto cwd = std::filesystem::current_path();
for (auto const &item : test_paths) {
auto directory = cwd / item.parent_path();
cf_ClearBaseDirectories();
cf_AddBaseDirectory(directory);
std::filesystem::path file = item.filename();
std::string file_lc = item.filename().u8string();
std::transform(file_lc.begin(), file_lc.end(), file_lc.begin(), ::tolower);
std::string file_uc = item.filename().u8string();
std::transform(file_uc.begin(), file_uc.end(), file_uc.begin(), ::toupper);
EXPECT_FALSE(cf_LocatePath(file_lc).empty());
EXPECT_FALSE(cf_LocatePath(file_uc).empty());
// Now try case-insensitive path with non-existing path.
// Expected not found on case-sensitive fs.
file_lc = item.u8string();
std::transform(file_lc.begin(), file_lc.end(), file_lc.begin(), ::tolower);
file_uc = item.u8string();
std::transform(file_uc.begin(), file_uc.end(), file_uc.begin(), ::toupper);
EXPECT_TRUE(cf_LocatePath(file_lc).empty());
EXPECT_TRUE(cf_LocatePath(file_uc).empty());
}
}