mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Add support for Oblivion Remastered
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
|
||||
## Introduction
|
||||
|
||||
LOOT is a plugin load order optimisation tool for Starfield, TES III: Morrowind, TES IV: Oblivion, TES V: Skyrim, TES V: Skyrim Special Edition, TES V: Skyrim VR, Fallout 3, Fallout: New Vegas, Fallout 4, Fallout 4 VR and OpenMW. It is designed to assist mod users in avoiding detrimental conflicts, by automatically calculating a load order that satisfies all plugin dependencies and maximises each plugin's impact on the user's game.
|
||||
LOOT is a plugin load order optimisation tool for Starfield, TES III: Morrowind, TES IV: Oblivion, TES IV: Oblivion Remastered, TES V: Skyrim, TES V: Skyrim Special Edition, TES V: Skyrim VR, Fallout 3, Fallout: New Vegas, Fallout 4, Fallout 4 VR and OpenMW. It is designed to assist mod users in avoiding detrimental conflicts, by automatically calculating a load order that satisfies all plugin dependencies and maximises each plugin's impact on the user's game.
|
||||
|
||||
LOOT also provides some load order error checking, including checks for requirements, incompatibilities and cyclic dependencies. In addition, it provides a large number of plugin-specific usage notes, bug warnings and Bash Tag suggestions.
|
||||
|
||||
|
||||
@@ -52,7 +52,9 @@ enum struct GameType : unsigned int {
|
||||
/** Starfield */
|
||||
starfield,
|
||||
/** OpenMW */
|
||||
openmw
|
||||
openmw,
|
||||
/** The Elder Scrolls IV: Oblivion Remastered */
|
||||
oblivionRemastered
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,8 @@ const char* DescribeGameType(GameType gameType) {
|
||||
return "Starfield";
|
||||
case GameType::openmw:
|
||||
return "OpenMW";
|
||||
case GameType::oblivionRemastered:
|
||||
return "The Elder Scrolls IV: Oblivion Remastered";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
@@ -120,6 +120,9 @@ std::filesystem::path Game::DataPath() const {
|
||||
return gamePath_ / "Data Files";
|
||||
} else if (type_ == GameType::openmw) {
|
||||
return gamePath_ / "resources" / "vfs";
|
||||
} else if (type_ == GameType::oblivionRemastered) {
|
||||
return gamePath_ / "OblivionRemastered" / "Content" / "Dev" / "ObvData" /
|
||||
"Data";
|
||||
} else {
|
||||
return gamePath_ / "Data";
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ unsigned int mapGameId(GameType gameType) {
|
||||
return LIBLO_GAME_STARFIELD;
|
||||
case GameType::openmw:
|
||||
return LIBLO_GAME_OPENMW;
|
||||
case GameType::oblivionRemastered:
|
||||
return LIBLO_GAME_OBLIVION_REMASTERED;
|
||||
default:
|
||||
throw std::logic_error("Unexpected game type");
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ int mapGameType(GameType gameType) {
|
||||
case GameType::tes3:
|
||||
return LCI_GAME_MORROWIND;
|
||||
case GameType::tes4:
|
||||
case GameType::oblivionRemastered:
|
||||
return LCI_GAME_OBLIVION;
|
||||
case GameType::tes5:
|
||||
return LCI_GAME_SKYRIM;
|
||||
|
||||
+27
-12
@@ -152,7 +152,8 @@ std::vector<std::filesystem::path> FindAssociatedArchives(
|
||||
// This assumes that Skyrim VR works the same way as Skyrim SE.
|
||||
return FindAssociatedArchivesWithSuffixes(
|
||||
pluginPath, BSA_FILE_EXTENSION, {"", " - Textures"});
|
||||
case GameType::tes4: {
|
||||
case GameType::tes4:
|
||||
case GameType::oblivionRemastered: {
|
||||
// Oblivion .esp files can load archives which begin with the plugin
|
||||
// basename.
|
||||
if (!boost::iends_with(pluginPath.filename().u8string(), ".esp")) {
|
||||
@@ -189,7 +190,8 @@ void HandleEspluginError(unsigned int returnCode, std::string_view operation) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto err = fmt::format("esplugin failed to {}. Error code: {}", operation, returnCode);
|
||||
auto err = fmt::format(
|
||||
"esplugin failed to {}. Error code: {}", operation, returnCode);
|
||||
|
||||
const char* e = nullptr;
|
||||
esp_get_error_message(&e);
|
||||
@@ -207,7 +209,7 @@ void HandleEspluginError(unsigned int returnCode, std::string_view operation) {
|
||||
throw std::system_error(returnCode, esplugin_category(), err);
|
||||
}
|
||||
|
||||
template<typename ...Args>
|
||||
template<typename... Args>
|
||||
void HandleEspluginError(unsigned int returnCode,
|
||||
std::string_view message,
|
||||
Args... args) {
|
||||
@@ -228,17 +230,30 @@ void HandleEspluginError(unsigned int returnCode,
|
||||
return HandleEspluginError(returnCode, getMessage());
|
||||
}
|
||||
|
||||
std::string GetPluginName(GameType gameType,
|
||||
const std::filesystem::path& pluginPath) {
|
||||
return gameType == GameType::openmw
|
||||
? pluginPath.filename().u8string()
|
||||
: loot::TrimDotGhostExtension(pluginPath.filename().u8string());
|
||||
}
|
||||
|
||||
std::unique_ptr<::Plugin, decltype(&esp_plugin_free)> MakeEspluginPtr() {
|
||||
return std::unique_ptr<::Plugin, decltype(&esp_plugin_free)>(nullptr,
|
||||
esp_plugin_free);
|
||||
}
|
||||
|
||||
bool ShouldIgnoreMasterFlag(GameType gameType) {
|
||||
return gameType == GameType::openmw ||
|
||||
gameType == GameType::oblivionRemastered;
|
||||
}
|
||||
|
||||
Plugin::Plugin(const GameType gameType,
|
||||
const GameCache& gameCache,
|
||||
const std::filesystem::path& pluginPath,
|
||||
const bool headerOnly) :
|
||||
name_(gameType == GameType::openmw
|
||||
? pluginPath.filename().u8string()
|
||||
: TrimDotGhostExtension(pluginPath.filename().u8string())),
|
||||
esPlugin(
|
||||
std::unique_ptr<::Plugin, decltype(&esp_plugin_free)>(nullptr,
|
||||
esp_plugin_free)),
|
||||
ignoreMasterFlag_(gameType == GameType::openmw),
|
||||
name_(GetPluginName(gameType, pluginPath)),
|
||||
esPlugin(MakeEspluginPtr()),
|
||||
ignoreMasterFlag_(ShouldIgnoreMasterFlag(gameType)),
|
||||
isEmpty_(true) {
|
||||
auto logger = getLogger();
|
||||
|
||||
@@ -595,8 +610,7 @@ std::string Plugin::GetDescription() const {
|
||||
}
|
||||
|
||||
std::unique_ptr<Vec_PluginMetadata, decltype(&esp_plugins_metadata_free)>
|
||||
Plugin::GetPluginsMetadata(
|
||||
const std::vector<const Plugin*>& plugins) {
|
||||
Plugin::GetPluginsMetadata(const std::vector<const Plugin*>& plugins) {
|
||||
if (plugins.empty()) {
|
||||
return std::unique_ptr<Vec_PluginMetadata,
|
||||
decltype(&esp_plugins_metadata_free)>(
|
||||
@@ -638,6 +652,7 @@ unsigned int Plugin::GetEspluginGameId(GameType gameType) {
|
||||
case GameType::openmw:
|
||||
return ESP_GAME_MORROWIND;
|
||||
case GameType::tes4:
|
||||
case GameType::oblivionRemastered:
|
||||
return ESP_GAME_OBLIVION;
|
||||
case GameType::tes5:
|
||||
return ESP_GAME_SKYRIM;
|
||||
|
||||
@@ -60,9 +60,11 @@ TEST_P(GameTest, constructingShouldStoreTheGivenValues) {
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
TEST_P(GameTest,
|
||||
constructingShouldThrowOnLinuxIfLocalPathIsNotGivenExceptForMorrowind) {
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw) {
|
||||
TEST_P(
|
||||
GameTest,
|
||||
constructingShouldThrowOnLinuxIfLocalPathIsNotGivenExceptForMorrowindOpenMWAndOblivionRemastered) {
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw ||
|
||||
GetParam() == GameType::oblivionRemastered) {
|
||||
EXPECT_NO_THROW(Game(GetParam(), gamePath));
|
||||
} else {
|
||||
EXPECT_THROW(Game(GetParam(), gamePath), std::system_error);
|
||||
|
||||
@@ -147,7 +147,8 @@ TEST_P(LoadOrderHandlerTest, constructorShouldNotThrowIfNoLocalPathIsSet) {
|
||||
#else
|
||||
TEST_P(LoadOrderHandlerTest,
|
||||
constructorShouldNotThrowIfNoLocalPathIsSetAndGameTypeIsMorrowind) {
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw) {
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw ||
|
||||
GetParam() == GameType::oblivionRemastered) {
|
||||
EXPECT_NO_THROW(LoadOrderHandler(GetParam(), gamePath));
|
||||
} else {
|
||||
EXPECT_THROW(LoadOrderHandler(GetParam(), gamePath), std::system_error);
|
||||
|
||||
@@ -284,7 +284,8 @@ TEST_P(PluginTest, loadingHeaderOnlyShouldReadHeaderData) {
|
||||
|
||||
EXPECT_EQ(blankEsm, plugin.GetName());
|
||||
EXPECT_TRUE(plugin.GetMasters().empty());
|
||||
if (GetParam() == GameType::openmw) {
|
||||
if (GetParam() == GameType::openmw ||
|
||||
GetParam() == GameType::oblivionRemastered) {
|
||||
EXPECT_FALSE(plugin.IsMaster());
|
||||
} else {
|
||||
EXPECT_TRUE(plugin.IsMaster());
|
||||
@@ -294,7 +295,8 @@ TEST_P(PluginTest, loadingHeaderOnlyShouldReadHeaderData) {
|
||||
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw) {
|
||||
EXPECT_FLOAT_EQ(1.2f, plugin.GetHeaderVersion().value());
|
||||
} else if (GetParam() == GameType::tes4) {
|
||||
} else if (GetParam() == GameType::tes4 ||
|
||||
GetParam() == GameType::oblivionRemastered) {
|
||||
EXPECT_FLOAT_EQ(0.8f, plugin.GetHeaderVersion().value());
|
||||
} else if (GetParam() == GameType::starfield) {
|
||||
EXPECT_FLOAT_EQ(0.96f, plugin.GetHeaderVersion().value());
|
||||
@@ -316,7 +318,8 @@ TEST_P(PluginTest, loadingWholePluginShouldReadHeaderData) {
|
||||
|
||||
EXPECT_EQ(blankEsm, plugin.GetName());
|
||||
EXPECT_TRUE(plugin.GetMasters().empty());
|
||||
if (GetParam() == GameType::openmw) {
|
||||
if (GetParam() == GameType::openmw ||
|
||||
GetParam() == GameType::oblivionRemastered) {
|
||||
EXPECT_FALSE(plugin.IsMaster());
|
||||
} else {
|
||||
EXPECT_TRUE(plugin.IsMaster());
|
||||
@@ -326,7 +329,8 @@ TEST_P(PluginTest, loadingWholePluginShouldReadHeaderData) {
|
||||
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw) {
|
||||
EXPECT_FLOAT_EQ(1.2f, plugin.GetHeaderVersion().value());
|
||||
} else if (GetParam() == GameType::tes4) {
|
||||
} else if (GetParam() == GameType::tes4 ||
|
||||
GetParam() == GameType::oblivionRemastered) {
|
||||
EXPECT_FLOAT_EQ(0.8f, plugin.GetHeaderVersion().value());
|
||||
} else if (GetParam() == GameType::starfield) {
|
||||
EXPECT_FLOAT_EQ(0.96f, plugin.GetHeaderVersion().value());
|
||||
@@ -508,7 +512,8 @@ TEST_P(
|
||||
.LoadsArchive();
|
||||
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw ||
|
||||
GetParam() == GameType::tes4)
|
||||
GetParam() == GameType::tes4 ||
|
||||
GetParam() == GameType::oblivionRemastered)
|
||||
EXPECT_FALSE(loadsArchive);
|
||||
else
|
||||
EXPECT_TRUE(loadsArchive);
|
||||
@@ -575,7 +580,8 @@ TEST_P(
|
||||
|
||||
if (GetParam() == GameType::tes4 || GetParam() == GameType::fo3 ||
|
||||
GetParam() == GameType::fonv || GetParam() == GameType::fo4 ||
|
||||
GetParam() == GameType::fo4vr) {
|
||||
GetParam() == GameType::fo4vr ||
|
||||
GetParam() == GameType::oblivionRemastered) {
|
||||
EXPECT_TRUE(loadsArchive);
|
||||
} else {
|
||||
EXPECT_FALSE(loadsArchive);
|
||||
@@ -595,7 +601,8 @@ TEST_P(
|
||||
|
||||
if (GetParam() == GameType::tes4 || GetParam() == GameType::fo3 ||
|
||||
GetParam() == GameType::fonv || GetParam() == GameType::fo4 ||
|
||||
GetParam() == GameType::fo4vr) {
|
||||
GetParam() == GameType::fo4vr ||
|
||||
GetParam() == GameType::oblivionRemastered) {
|
||||
EXPECT_TRUE(loadsArchive);
|
||||
} else {
|
||||
EXPECT_FALSE(loadsArchive);
|
||||
|
||||
@@ -40,7 +40,7 @@ along with LOOT. If not, see
|
||||
|
||||
namespace loot {
|
||||
namespace test {
|
||||
static const std::array<GameType, 11> ALL_GAME_TYPES = {
|
||||
static const std::array<GameType, 12> ALL_GAME_TYPES = {
|
||||
GameType::tes3,
|
||||
GameType::tes4,
|
||||
GameType::tes5,
|
||||
@@ -52,6 +52,7 @@ static const std::array<GameType, 11> ALL_GAME_TYPES = {
|
||||
GameType::fo4vr,
|
||||
GameType::starfield,
|
||||
GameType::openmw,
|
||||
GameType::oblivionRemastered,
|
||||
};
|
||||
|
||||
class CommonGameTestFixture : public ::testing::Test {
|
||||
@@ -238,8 +239,11 @@ protected:
|
||||
}
|
||||
}
|
||||
for (const auto& plugin : loadOrder) actual.push_back(plugin.second);
|
||||
} else if (gameType_ == GameType::tes5) {
|
||||
std::ifstream in(localPath / "loadorder.txt");
|
||||
} else if (gameType_ == GameType::tes5 ||
|
||||
gameType_ == GameType::oblivionRemastered) {
|
||||
const auto& parentPath =
|
||||
gameType_ == GameType::oblivionRemastered ? dataPath : localPath;
|
||||
std::ifstream in(parentPath / "loadorder.txt");
|
||||
while (in) {
|
||||
std::string line;
|
||||
std::getline(in, line);
|
||||
@@ -420,7 +424,8 @@ private:
|
||||
std::string getMasterFile() const {
|
||||
if (gameType_ == GameType::tes3 || gameType_ == GameType::openmw)
|
||||
return "Morrowind.esm";
|
||||
else if (gameType_ == GameType::tes4)
|
||||
else if (gameType_ == GameType::tes4 ||
|
||||
gameType_ == GameType::oblivionRemastered)
|
||||
return "Oblivion.esm";
|
||||
else if (gameType_ == GameType::tes5 || gameType_ == GameType::tes5se ||
|
||||
gameType_ == GameType::tes5vr)
|
||||
@@ -442,6 +447,8 @@ private:
|
||||
return "resources/vfs";
|
||||
} else if (gameType_ == GameType::tes3) {
|
||||
return "Data Files";
|
||||
} else if (gameType_ == GameType::oblivionRemastered) {
|
||||
return "OblivionRemastered/Content/Dev/ObvData/Data";
|
||||
} else {
|
||||
return "Data";
|
||||
}
|
||||
@@ -453,6 +460,7 @@ private:
|
||||
case GameType::openmw:
|
||||
return 0x790DC6FB;
|
||||
case GameType::tes4:
|
||||
case GameType::oblivionRemastered:
|
||||
return 0x374E2A6F;
|
||||
case GameType::starfield:
|
||||
return 0xDE586309;
|
||||
@@ -479,7 +487,9 @@ private:
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::ofstream out(localPath / "Plugins.txt");
|
||||
const auto& parentPath =
|
||||
gameType_ == GameType::oblivionRemastered ? dataPath : localPath;
|
||||
std::ofstream out(parentPath / "Plugins.txt");
|
||||
for (const auto& plugin : loadOrder) {
|
||||
if (supportsLightPlugins(gameType_)) {
|
||||
if (plugin.second)
|
||||
@@ -506,8 +516,11 @@ private:
|
||||
}
|
||||
modificationTime += std::chrono::seconds(60);
|
||||
}
|
||||
} else if (gameType_ == GameType::tes5) {
|
||||
std::ofstream out(localPath / "loadorder.txt");
|
||||
} else if (gameType_ == GameType::tes5 ||
|
||||
gameType_ == GameType::oblivionRemastered) {
|
||||
const auto& parentPath =
|
||||
gameType_ == GameType::oblivionRemastered ? dataPath : localPath;
|
||||
std::ofstream out(parentPath / "loadorder.txt");
|
||||
for (const auto& plugin : loadOrder) out << plugin.first << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ std::filesystem::path getSourcePluginsPath(GameType gameType) {
|
||||
using std::filesystem::absolute;
|
||||
if (gameType == GameType::tes3 || gameType == GameType::openmw) {
|
||||
return absolute("./testing-plugins/Morrowind/Data Files");
|
||||
} else if (gameType == GameType::tes4) {
|
||||
} else if (gameType == GameType::tes4 ||
|
||||
gameType == GameType::oblivionRemastered) {
|
||||
return absolute("./testing-plugins/Oblivion/Data");
|
||||
} else if (gameType == GameType::starfield) {
|
||||
return absolute("./testing-plugins/Starfield/Data");
|
||||
|
||||
Reference in New Issue
Block a user