Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions UnleashedRecomp/locale/locale.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,13 @@ std::unordered_map<std::string_view, std::unordered_map<ELanguage, std::string>>
{ ELanguage::Italian, "Il contenuto verrà installato nella cartella di questo programma.\n\n" }
}
},
{
// Notes: %s is the install location chosen by the user.
"Installer_Page_CheckSpaceCustom",
{
{ ELanguage::English, "The content will be installed to:\n%s\n\n" }
}
},
{
// Japanese Notes: This localization should include furigana.
"Installer_Page_Installing",
Expand Down Expand Up @@ -484,6 +491,12 @@ std::unordered_map<std::string_view, std::unordered_map<ELanguage, std::string>>
{ ELanguage::Italian, "AGGIUNGI CARTELLA" }
}
},
{
"Installer_Button_ChangeLocation",
{
{ ELanguage::English, "CHANGE LOCATION" }
}
},
{
// Notes: message appears when using the "Add Files" option and choosing any file that is not an Xbox 360 game dump.
// Japanese Notes: This localization should include furigana.
Expand All @@ -497,6 +510,12 @@ std::unordered_map<std::string_view, std::unordered_map<ELanguage, std::string>>
{ ELanguage::Italian, "I seguenti file non sono validi:" }
}
},
{
"Installer_Message_InvalidInstallLocation",
{
{ ELanguage::English, "The selected location cannot\nbe used for the installation.\n\nPlease make sure that you have\npermission to write to it." }
}
},
{
// Notes: message appears in the event there are some invalid files after adding the DLC and moving onto the next step.
// Japanese Notes: This localization should include furigana.
Expand Down Expand Up @@ -714,6 +733,13 @@ std::unordered_map<std::string_view, std::unordered_map<ELanguage, std::string>>
{ ELanguage::Italian, "Impossibile allocare la memoria per il gioco.\n\nAssicurati che:\n\n- Soddisfi i requisiti minimi di sistema (8 GB).\n- Il tuo file di paging sia configurato con almeno 4 o 8 GB di memoria virtuale." }
}
},
{
// Notes: %s is the install location saved in the configuration file.
"System_GameInstallPathMissing",
{
{ ELanguage::English, "The game's install location was not found:\n\n%s\n\nPlease make sure the drive is connected, or run the game with the --install argument to choose a new location." }
}
},
{
"IntegrityCheck_Success",
{
Expand Down
14 changes: 13 additions & 1 deletion UnleashedRecomp/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ int main(int argc, char *argv[])
Journal journal;
double lastProgressMiB = 0.0;
double lastTotalMib = 0.0;
Installer::checkInstallIntegrity(GAME_INSTALL_DIRECTORY, journal, [&]()
Installer::checkInstallIntegrity(GetGamePath(), journal, [&]()
{
constexpr double MiBDivisor = 1024.0 * 1024.0;
constexpr double MiBProgressThreshold = 128.0;
Expand Down Expand Up @@ -324,6 +324,15 @@ int main(int argc, char *argv[])

HostStartup();

if (!forceInstaller && !std::string(Config::GameInstallPath).empty() && !std::filesystem::exists(GetGamePath()))
{
char text[1024];
std::u8string installPathU8 = GetGamePath().u8string();
snprintf(text, sizeof(text), Localise("System_GameInstallPathMissing").c_str(), (const char*)installPathU8.c_str());
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, GameWindow::GetTitle(), text, GameWindow::s_pWindow);
std::_Exit(1);
}

std::filesystem::path modulePath;
bool isGameInstalled = Installer::checkGameInstall(GetGamePath(), modulePath);
bool runInstallerWizard = forceInstaller || forceDLCInstaller || !isGameInstalled;
Expand All @@ -339,6 +348,9 @@ int main(int argc, char *argv[])
{
std::_Exit(0);
}

// The install location may have been changed by the installer.
Installer::checkGameInstall(GetGamePath(), modulePath);
}

ModLoader::Init();
Expand Down
2 changes: 1 addition & 1 deletion UnleashedRecomp/mod/mod_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ void ModLoader::Init()
{
configIni = {};

if (!configIni.read(GetGamePath() / "cpkredir.ini"))
if (!configIni.read(GetDefaultGamePath() / "cpkredir.ini"))
return;
}

Expand Down
156 changes: 145 additions & 11 deletions UnleashedRecomp/ui/installer_wizard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <ui/button_guide.h>
#include <ui/message_window.h>
#include <ui/game_window.h>
#include <user/paths.h>
#include <decompressor.h>
#include <exports.h>
#include <sdl_listener.h>
Expand Down Expand Up @@ -102,6 +103,7 @@ static bool g_isDisappearing = false;
static bool g_isQuitting = false;

static std::filesystem::path g_installPath;
static bool g_installPathCustom = false;
static std::filesystem::path g_gameSourcePath;
static std::filesystem::path g_updateSourcePath;
static std::array<std::filesystem::path, int(DLC::Count)> g_dlcSourcePaths;
Expand Down Expand Up @@ -158,6 +160,7 @@ static bool g_pickerTutorialTriggered = false;
static bool g_pickerTutorialFolderMode = false;
static bool g_currentPickerVisible = false;
static bool g_currentPickerFolderMode = false;
static bool g_currentPickerInstallLocation = false;
static int g_currentMessageResult = -1;
static ImVec2 g_joypadAxis = {};
static int g_currentCursorIndex = -1;
Expand Down Expand Up @@ -413,6 +416,33 @@ static int DLCIndex(DLC dlc)
return (int)(dlc) - 1;
}

static void SetInstallPath(const std::filesystem::path &path)
{
const std::filesystem::path defaultPath = GetDefaultGamePath();
std::error_code ec;
g_installPath = path;
g_installPathCustom = path != defaultPath && !std::filesystem::equivalent(path, defaultPath, ec);
}

static std::string GetInstallPathText()
{
std::u8string pathU8 = g_installPath.u8string();
std::string text(pathU8.begin(), pathU8.end());

constexpr size_t MaxLength = 32;
if (text.length() > MaxLength)
{
// Keep the end of the path and avoid cutting a UTF-8 character in half.
size_t start = text.length() - (MaxLength - 3);
while (start < text.length() && (text[start] & 0xC0) == 0x80)
start++;

text = "..." + text.substr(start);
}

return text;
}

static double ComputeMotionInstaller(double timeAppear, double timeDisappear, double offset, double total)
{
return ComputeMotion(timeAppear, offset, total) * (1.0 - ComputeMotion(timeDisappear, ALL_ANIMATIONS_FULL_DURATION - offset - total, total));
Expand Down Expand Up @@ -732,7 +762,16 @@ static void DrawDescriptionContainer()
double availableGiB = double(g_installerAvailableSize) / DivisorGiB;
snprintf(requiredSpaceText, sizeof(requiredSpaceText), Localise("Installer_Step_RequiredSpace").c_str(), requiredGiB);
snprintf(availableSpaceText, sizeof(availableSpaceText), (g_installerAvailableSize > 0) ? Localise("Installer_Step_AvailableSpace").c_str() : "", availableGiB);
snprintf(descriptionText, sizeof(descriptionText), "%s%s\n%s", GetWizardText(g_currentPage).c_str(), requiredSpaceText, availableSpaceText);

std::string pageText = GetWizardText(g_currentPage);
if (g_installPathCustom)
{
char customPageText[256];
snprintf(customPageText, sizeof(customPageText), Localise("Installer_Page_CheckSpaceCustom").c_str(), GetInstallPathText().c_str());
pageText = customPageText;
}

snprintf(descriptionText, sizeof(descriptionText), "%s%s\n%s", pageText.c_str(), requiredSpaceText, availableSpaceText);
}
else if (g_currentPage == WizardPage::InstallFailed)
{
Expand Down Expand Up @@ -1109,6 +1148,24 @@ static bool ConvertPathSet(const nfdpathset_t *pathSet, std::list<std::filesyste

static void PickerThreadProcess()
{
if (g_currentPickerInstallLocation)
{
nfdnchar_t *path = nullptr;
nfdresult_t result = NFD_PickFolderN(&path, nullptr);
if (result == NFD_OKAY)
{
g_currentPickerResults.emplace_back(std::filesystem::path(path));
NFD_FreePathN(path);
}
else if (result == NFD_ERROR)
{
g_currentPickerErrorMessage = NFD_GetError();
}

g_currentPickerResultsReady = true;
return;
}

const nfdpathset_t *pathSet;
nfdresult_t result = NFD_ERROR;
if (g_currentPickerFolderMode)
Expand All @@ -1133,7 +1190,7 @@ static void PickerThreadProcess()
g_currentPickerResultsReady = true;
}

static void PickerStart(bool folderMode) {
static void PickerStart(bool folderMode, bool installLocation = false) {
if (g_currentPickerThread != nullptr)
{
g_currentPickerThread->join();
Expand All @@ -1142,6 +1199,7 @@ static void PickerStart(bool folderMode) {

g_currentPickerResults.clear();
g_currentPickerFolderMode = folderMode;
g_currentPickerInstallLocation = installLocation;
g_currentPickerResultsReady = false;
g_currentPickerVisible = true;

Expand Down Expand Up @@ -1172,6 +1230,58 @@ static void PickerShow(bool folderMode)
}
}

static void UpdateAvailableSpace()
{
std::error_code ec;
std::filesystem::space_info spaceInfo = std::filesystem::space(g_installPath, ec);
g_installerAvailableSize = ec ? 0 : spaceInfo.available;
}

static bool IsDirectoryWritable(const std::filesystem::path &directory)
{
const std::filesystem::path testFile = directory / ".unleashedrecomp_write_test";

{
std::ofstream stream(testFile, std::ios::binary);
if (!stream.is_open())
return false;
}

std::error_code ec;
std::filesystem::remove(testFile, ec);
return true;
}

static void ChangeInstallLocation(const std::filesystem::path &path)
{
std::error_code ec;
if (!std::filesystem::is_directory(path, ec) || !IsDirectoryWritable(path))
{
g_currentMessagePrompt = Localise("Installer_Message_InvalidInstallLocation");
g_currentMessagePromptConfirmation = false;
return;
}

SetInstallPath(path);
UpdateAvailableSpace();
}

static void SaveInstallPath()
{
std::string installPath;
if (g_installPathCustom)
{
std::u8string pathU8 = g_installPath.generic_u8string();
installPath.assign(pathU8.begin(), pathU8.end());
}

if (std::string(Config::GameInstallPath) != installPath)
{
Config::GameInstallPath = installPath;
Config::Save();
}
}

static bool ParseSourcePaths(std::list<std::filesystem::path> &paths)
{
assert((g_currentPage == WizardPage::SelectGameAndUpdate) || (g_currentPage == WizardPage::SelectDLC));
Expand Down Expand Up @@ -1294,6 +1404,21 @@ static void DrawSourcePickers()
PickerShow(true);
}
}
else if (g_currentPage == WizardPage::CheckSpace && !g_gameSourcePath.empty())
{
// The location can't be changed when only adding DLC to an existing installation.
constexpr float CHANGE_BUTTON_MAX_TEXT_WIDTH = 168.0f;
const std::string &changeLocationText = Localise("Installer_Button_ChangeLocation");
float squashRatio;
ImVec2 textSize = ComputeTextSize(g_dfsogeistdFont, changeLocationText.c_str(), 20.0f, squashRatio, CHANGE_BUTTON_MAX_TEXT_WIDTH);
ImVec2 min = { g_aspectRatioOffsetX + Scale(CONTAINER_X + BOTTOM_X_GAP), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP) };
ImVec2 max = { g_aspectRatioOffsetX + Scale(CONTAINER_X + BOTTOM_X_GAP + textSize.x * squashRatio + BUTTON_TEXT_GAP), g_aspectRatioOffsetY + Scale(CONTAINER_Y + CONTAINER_HEIGHT + BOTTOM_Y_GAP + BUTTON_HEIGHT) };
DrawButton(min, max, changeLocationText.c_str(), false, true, buttonPressed, CHANGE_BUTTON_MAX_TEXT_WIDTH);
if (buttonPressed)
{
PickerStart(true, true);
}
}
}

static void DrawSources()
Expand Down Expand Up @@ -1328,6 +1453,9 @@ static void DrawInstallingProgress()
g_installerThread.reset();
g_installerEndTime = ImGui::GetTime();
g_currentPage = g_installerFailed ? WizardPage::InstallFailed : WizardPage::InstallSucceeded;

if (!g_installerFailed)
SaveInstallPath();
}
}
}
Expand Down Expand Up @@ -1369,12 +1497,7 @@ static void InstallerStart()

static bool InstallerParseSources(std::string &errorMessage)
{
std::error_code spaceErrorCode;
std::filesystem::space_info spaceInfo = std::filesystem::space(g_installPath, spaceErrorCode);
if (!spaceErrorCode)
{
g_installerAvailableSize = spaceInfo.available;
}
UpdateAvailableSpace();

Installer::Input installerInput;
installerInput.gameSource = g_gameSourcePath;
Expand Down Expand Up @@ -1716,9 +1839,16 @@ static void PickerCheckResults()
g_currentPickerErrorMessage.clear();
}

if (!g_currentPickerResults.empty() && ParseSourcePaths(g_currentPickerResults))
if (!g_currentPickerResults.empty())
{
g_pickerTutorialCleared[g_pickerTutorialFolderMode] = true;
if (g_currentPickerInstallLocation)
{
ChangeInstallLocation(g_currentPickerResults.front());
}
else if (ParseSourcePaths(g_currentPickerResults))
{
g_pickerTutorialCleared[g_pickerTutorialFolderMode] = true;
}
}

g_currentPickerResultsReady = false;
Expand Down Expand Up @@ -1846,7 +1976,11 @@ void InstallerWizard::Shutdown()

bool InstallerWizard::Run(std::filesystem::path installPath, bool skipGame)
{
g_installPath = installPath;
// Use the default location if the saved one is not available.
if (!std::filesystem::exists(installPath))
installPath = GetDefaultGamePath();

SetInstallPath(installPath);

EmbeddedPlayer::Init();
NFD_Init();
Expand Down
1 change: 1 addition & 0 deletions UnleashedRecomp/user/config_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ CONFIG_DEFINE_LOCALISED("System", bool, ControlTutorial, true);
CONFIG_DEFINE_LOCALISED("System", bool, AchievementNotifications, true);
CONFIG_DEFINE_ENUM_LOCALISED("System", ETimeOfDayTransition, TimeOfDayTransition, ETimeOfDayTransition::Xbox);
CONFIG_DEFINE("System", bool, ShowConsole, false);
CONFIG_DEFINE("System", std::string, GameInstallPath, "");

CONFIG_DEFINE_ENUM_LOCALISED("Input", ECameraRotationMode, HorizontalCamera, ECameraRotationMode::Normal);
CONFIG_DEFINE_ENUM_LOCALISED("Input", ECameraRotationMode, VerticalCamera, ECameraRotationMode::Normal);
Expand Down
11 changes: 11 additions & 0 deletions UnleashedRecomp/user/paths.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "paths.h"
#include <os/process.h>
#include <user/config.h>

std::filesystem::path g_executableRoot = os::process::GetExecutableRoot();
std::filesystem::path g_userPath = BuildUserPath();
Expand Down Expand Up @@ -56,3 +57,13 @@ const std::filesystem::path& GetUserPath()
{
return g_userPath;
}

std::filesystem::path GetGamePath()
{
const std::string installPath = Config::GameInstallPath;

if (!installPath.empty())
return std::u8string_view((const char8_t*)installPath.c_str());

return GetDefaultGamePath();
}
Loading