Compare commits

..

4 commits

12 changed files with 346 additions and 143 deletions

View file

@ -40,6 +40,7 @@
#include "../Managers/BackupManager.h"
#include "../Managers/MassManager.h"
#include "../Managers/ProfileManager.h"
#include "../Managers/StagedMassManager.h"
#include "../ToastQueue/ToastQueue.h"
#include "../UpdateChecker/UpdateChecker.h"
@ -260,6 +261,8 @@ class Application: public Platform::Sdl2Application, public efsw::FileWatchListe
Containers::Pointer<Managers::MassManager> _massManager;
GameObjects::Mass* _currentMass = nullptr;
Containers::Pointer<Managers::StagedMassManager> _stagedMassManager;
GameObjects::Weapon* _currentWeapon = nullptr;
Containers::Pointer<efsw::FileWatcher> _fileWatcher;

View file

@ -68,7 +68,7 @@ Application::fileUpdateEvent(SDL_Event& event) {
std::strlen(static_cast<char*>(event.user.data1)), nullptr};
if((event.user.code & StagedUpdate) == StagedUpdate) {
_massManager->refreshStagedMass(filename);
_stagedMassManager->refreshMass(filename);
return;
}

View file

@ -117,6 +117,8 @@ Application::initialiseManager() {
_backupManager.emplace();
_stagedMassManager.emplace();
event.user.code = InitSuccess;
SDL_PushEvent(&event);
}

View file

@ -438,7 +438,7 @@ Application::drawMassManager() {
Containers::StringView file = *static_cast<Containers::String*>(payload->Data);
if(!_massManager->importMass(file, i)) {
if(!_stagedMassManager->import(file, i, _currentProfile->account(), _currentProfile->isDemo())) {
_queue.addToast(Toast::Type::Error, _massManager->lastError());
}
}
@ -530,26 +530,26 @@ Application::drawMassManager() {
openUri(Utility::Path::toNativeSeparators(conf().directories().staging));
}
for(const auto& pair : _massManager->stagedMasses()) {
for(const auto& mass : _stagedMassManager->stagedMasses()) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
Containers::String staged_formatted = Utility::format("{} ({})", pair.second, pair.first);
Containers::String staged_formatted = Utility::format("{} ({})", mass.name, mass.filename);
ImGui::Selectable(staged_formatted.data());
if((ImGui::CalcTextSize(staged_formatted.data()).x + ImGui::GetStyle().FramePadding.x) > ImGui::GetContentRegionAvail().x) {
drawTooltip(staged_formatted.data());
}
if(ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) {
ImGui::SetDragDropPayload("StagedMass", &(pair.first), sizeof(Containers::String));
ImGui::SetDragDropPayload("StagedMass", &(mass.filename), sizeof(Containers::String));
ImGui::Text("%s - Staged", pair.second.data());
ImGui::Text("%s - Staged", mass.name.cbegin());
ImGui::EndDragDropSource();
}
ImGui::TableSetColumnIndex(1);
ImGui::PushID(pair.first.data());
ImGui::PushID(mass.filename.data());
if(ImGui::SmallButton(ICON_FA_TRASH_ALT)) {
staged_mass_to_delete = pair.first;
staged_mass_to_delete = mass.filename;
ImGui::OpenPopup("Confirmation##DeleteStagedMassConfirmation");
}
drawTooltip("Delete");
@ -644,7 +644,7 @@ Application::drawDeleteStagedMassPopup(Containers::StringView filename) {
ImGui::PushTextWrapPos(float(windowSize().x()) * 0.40f);
ImGui::Text("Are you sure you want to delete the staged M.A.S.S. named %s ? This operation is irreversible.",
_massManager->stagedMasses().at(filename).data());
_stagedMassManager->at(filename).filename.cbegin());
ImGui::PopTextWrapPos();
if(ImGui::BeginTable("##DeleteStagedMassLayout", 2)) {
@ -655,8 +655,9 @@ Application::drawDeleteStagedMassPopup(Containers::StringView filename) {
ImGui::TableSetColumnIndex(1);
if(ImGui::Button("Yes")) {
if(!_massManager->deleteStagedMass(filename)) {
_queue.addToast(Toast::Type::Error, _massManager->lastError());
if(!_stagedMassManager->remove(filename)) {
_queue.addToast(Toast::Type::Error,
"Couldn't delete the staged M.A.S.S. at " + filename + ": " + _stagedMassManager->lastError());
}
ImGui::CloseCurrentPopup();
}

View file

@ -199,6 +199,9 @@ add_executable(MassBuilderSaveTool
Managers/MassManager.cpp
Managers/ProfileManager.h
Managers/ProfileManager.cpp
Managers/StagedMass.h
Managers/StagedMassManager.h
Managers/StagedMassManager.cpp
Managers/Vfs/Directory.h
Maps/ArmourSlots.hpp
Maps/BulletLauncherAttachmentStyles.hpp
@ -234,7 +237,7 @@ target_compile_definitions(MassBuilderSaveTool PRIVATE
SAVETOOL_VERSION_MINOR=5
SAVETOOL_VERSION_PATCH=0
SAVETOOL_VERSION_PRERELEASE=true
SAVETOOL_CODENAME="Friendly Valkyrie"
SAVETOOL_CODENAME="Fuckin' UE5..."
SAVETOOL_SUPPORTED_GAME_VERSION="0.11.x"
)

View file

@ -44,6 +44,12 @@ static const std::map<std::int32_t, Containers::StringView> mission_id_map {{
{114, "Mission 15 - Outposts Line of Defense"_s},
{115, "Mission 16 - Hidden in the Pass"_s},
{116, "Mission 17 - Homebase Security"_s},
{117, "Mission 18 - Molewarp Protection Deal"_s},
{118, "Mission 19 - Behind the Walls of Ice"_s},
{119, "Mission 20 - Odin in the Sea of Flames"_s},
{120, "Mission 21 - Retracing Ruined Shelter"_s},
{121, "Mission 22 - The Traitor"_s},
{122, "Mission 23 - Duel of Aces"_s},
// Hunting grounds
{200, "Hunt 1 - Desert Pathway Safety"_s},
@ -52,6 +58,8 @@ static const std::map<std::int32_t, Containers::StringView> mission_id_map {{
{203, "Hunt 4 - Depths of the Machineries"_s},
{204, "Hunt 5 - Crater Crashers"_s},
{205, "Hunt 6 - Prototype Performance Tests"_s},
{206, "Hunt 7 - A Mess in Manufacturing"_s},
{207, "Hunt 8 - Visitors in Volcanic Fissures"_s},
// Challenges
{300, "Challenge 1 - Redline Battlefront"_s},

View file

@ -108,7 +108,27 @@ static const Corrade::Containers::Array<StoryProgressPoint> story_progress
{0x06A5, "Chapter 3"_s, "Returned to hangar"_s, "After mission 16"_s},
{0x06A6, "Chapter 3"_s, "Got mission 17 briefing"_s, "After mission 16"_s},
{0x0708, "Chapter 3"_s, "Debriefing"_s, "After mission 17"_s},
{0x0709, "Chapter 3"_s, "Returned to hangar"_s, "After mission 17"_s},
{0x070A, "Chapter 3"_s, "Got hunt 6 briefing"_s, "After mission 17"_s},
{0x070B, "Chapter 3"_s, "Returned to hangar"_s, "After mission 17"_s},
{0x070C, "Chapter 3"_s, "Got mission 18 briefing"_s, "After mission 17"_s},
{0x076C, "Chapter 3"_s, "Debriefing"_s, "After mission 18"_s},
{0x076D, "Chapter 3"_s, "Returned to hangar"_s, "After mission 18"_s},
{0x076E, "Chapter 3"_s, "Got hunt 7 and mission 19 briefing"_s, "After mission 18"_s},
{0x07D0, "Chapter 4"_s, "Debriefing"_s, "After mission 19"_s},
{0x07D1, "Chapter 4"_s, "Returned to hangar"_s, "After mission 19"_s},
{0x07D2, "Chapter 4"_s, "Got mission 20 briefing"_s, "After mission 19"_s},
{0x0834, "Chapter 4"_s, "Debriefing"_s, "After mission 20"_s},
{0x0835, "Chapter 4"_s, "Returned to hangar"_s, "After mission 20"_s},
{0x0836, "Chapter 4"_s, "Got hunt 8 and mission 21 briefing"_s, "After mission 20"_s},
{0x0898, "Chapter 4"_s, "Debriefing"_s, "After mission 21"_s},
{0x0899, "Chapter 4"_s, "Returned to hangar"_s, "After mission 21"_s},
{0x089A, "Chapter 4"_s, "Got mission 22 briefing"_s, "After mission 21"_s},
{0x08FC, "Chapter 4"_s, "Debriefing"_s, "After mission 22"_s},
{0x08FD, "Chapter 4"_s, "Returned to hangar"_s, "After mission 22"_s},
{0x08FE, "Chapter 4"_s, "Got mission 23 briefing"_s, "After mission 22"_s},
{0x0960, "Chapter 4"_s, "Returned to hangar"_s, "After mission 23"_s},
}
};

View file

@ -37,8 +37,6 @@ MassManager::MassManager(Containers::StringView account, bool demo):
Utility::format("{}Unit{:.2d}{}.sav", demo ? "Demo"_s : ""_s, i, _account));
new(&_hangars[i]) GameObjects::Mass{mass_filename};
}
refreshStagedMasses();
}
Containers::StringView
@ -65,49 +63,6 @@ MassManager::refreshHangar(std::int32_t hangar) {
_hangars[hangar] = GameObjects::Mass{mass_filename};
}
bool
MassManager::importMass(Containers::StringView staged_fn, std::int32_t hangar) {
if(hangar < 0 || hangar >= 32) {
_lastError = "Hangar index out of range.";
LOG_ERROR(_lastError);
return false;
}
auto it = _stagedMasses.find(Containers::String::nullTerminatedView(staged_fn));
if(it == _stagedMasses.end()) {
_lastError = "Couldn't find "_s + staged_fn + " in the staged M.A.S.S.es."_s;
LOG_ERROR(_lastError);
return false;
}
Containers::String source = Utility::Path::join(conf().directories().staging, staged_fn);
Utility::Path::copy(source, source + ".tmp"_s);
{
GameObjects::Mass mass{source + ".tmp"_s};
if(!mass.updateAccount(_account)) {
_lastError = mass.lastError();
Utility::Path::remove(source + ".tmp"_s);
return false;
}
}
Containers::String dest = Utility::Path::join(conf().directories().gameSaves, _hangars[hangar].filename());
if(Utility::Path::exists(dest)) {
Utility::Path::remove(dest);
}
if(!Utility::Path::move(source + ".tmp"_s, dest)) {
_lastError = Utility::format("Couldn't move {} to hangar {:.2d}", staged_fn, hangar + 1);
LOG_ERROR(_lastError);
return false;
}
return true;
}
bool
MassManager::exportMass(std::int32_t hangar) {
if(hangar < 0 || hangar >= 32) {
@ -192,80 +147,4 @@ MassManager::deleteMass(std::int32_t hangar) {
return true;
}
const std::map<Containers::String, Containers::String>&
MassManager::stagedMasses() {
return _stagedMasses;
}
void
MassManager::refreshStagedMasses() {
_stagedMasses.clear();
using Utility::Path::ListFlag;
auto file_list = Utility::Path::list(conf().directories().staging,
ListFlag::SkipSpecial|ListFlag::SkipDirectories|ListFlag::SkipDotAndDotDot);
if(!file_list) {
LOG_ERROR_FORMAT("{} couldn't be opened.", conf().directories().staging);
return;
}
auto iter = std::remove_if(file_list->begin(), file_list->end(), [](Containers::StringView file){
return !file.hasSuffix(".sav"_s);
});
auto list_view = file_list->exceptSuffix(file_list->end() - iter);
LOG_INFO("Scanning for staged M.A.S.S.es...");
for(Containers::StringView file : list_view) {
auto name = GameObjects::Mass::getNameFromFile(Utility::Path::join(conf().directories().staging, file));
if(name) {
LOG_INFO_FORMAT("Found staged M.A.S.S.: {}", *name);
_stagedMasses[file] = *name;
}
else {
LOG_WARNING_FORMAT("Skipped {}.", file);
}
}
}
void
MassManager::refreshStagedMass(Containers::StringView filename) {
LOG_INFO_FORMAT("Refreshing staged unit with filename {}.", filename);
bool file_exists = Utility::Path::exists(Utility::Path::join(conf().directories().staging, filename));
auto it = _stagedMasses.find(filename);
if(file_exists) {
auto name = GameObjects::Mass::getNameFromFile(Utility::Path::join(conf().directories().staging, filename));
if(name) {
_stagedMasses[filename] = *name;
}
else if(it != _stagedMasses.cend()) {
_stagedMasses.erase(it);
}
}
else if(it != _stagedMasses.cend()) {
_stagedMasses.erase(it);
}
}
bool
MassManager::deleteStagedMass(Containers::StringView filename) {
if(_stagedMasses.find(filename) == _stagedMasses.cend()) {
_lastError = "The file "_s + filename + " couldn't be found in the list of staged M.A.S.S.es."_s;
LOG_ERROR(_lastError);
return false;
}
if(!Utility::Path::remove(Utility::Path::join(conf().directories().staging, filename))) {
_lastError = filename + " couldn't be deleted: " + std::strerror(errno);
LOG_ERROR(_lastError);
return false;
}
return true;
}
}

View file

@ -16,8 +16,6 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <map>
#include <Corrade/Containers/StaticArray.h>
#include <Corrade/Containers/String.h>
#include <Corrade/Containers/StringView.h>
@ -38,17 +36,11 @@ class MassManager {
void refreshHangar(int hangar);
bool importMass(Containers::StringView staged_fn, int hangar);
bool exportMass(int hangar);
bool moveMass(int source, int destination);
bool deleteMass(int hangar);
auto stagedMasses() -> std::map<Containers::String, Containers::String> const&;
void refreshStagedMasses();
void refreshStagedMass(Containers::StringView filename);
bool deleteStagedMass(Containers::StringView filename);
private:
Containers::StringView _account;
bool _demo;
@ -56,8 +48,6 @@ class MassManager {
Containers::String _lastError;
Containers::StaticArray<32, GameObjects::Mass> _hangars{NoInit};
std::map<Containers::String, Containers::String> _stagedMasses;
};
}

30
src/Managers/StagedMass.h Normal file
View file

@ -0,0 +1,30 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
//
// 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 <https://www.gnu.org/licenses/>.
#include <Corrade/Containers/String.h>
using namespace Corrade;
namespace mbst::Managers {
struct StagedMass {
Containers::String filename;
Containers::String name;
};
}

View file

@ -0,0 +1,215 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
//
// 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 <https://www.gnu.org/licenses/>.
#include <algorithm>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Utility/Path.h>
#include "../Configuration/Configuration.h"
#include "../GameObjects/Mass.h"
#include "../Logger/Logger.h"
#include "../Utilities/Temp.h"
#include "StagedMassManager.h"
namespace mbst::Managers {
StagedMassManager::StagedMassManager() {
refresh();
}
Containers::StringView
StagedMassManager::lastError() {
return _lastError;
}
Containers::ArrayView<const StagedMass>
StagedMassManager::stagedMasses() const {
return _stagedMasses;
}
const StagedMass&
StagedMassManager::at(Containers::StringView filename) const {
for(const auto& mass : _stagedMasses) {
if(mass.filename == filename) {
return mass;
}
}
CORRADE_ASSERT_UNREACHABLE("Invalid staged M.A.S.S.!", StagedMass{});
}
void
StagedMassManager::refresh() {
_stagedMasses = Containers::Array<StagedMass>{};
LOG_INFO("Scanning for staged M.A.S.S.es...");
scanSubdir(""_s);
}
void
StagedMassManager::refreshMass(Containers::StringView filename) {
LOG_INFO_FORMAT("Refreshing staged unit with filename {}.", filename);
bool file_exists = Utility::Path::exists(Utility::Path::join(conf().directories().staging, filename));
auto index = _stagedMasses.size();
for(std::size_t i = 0; i < _stagedMasses.size(); ++i) {
if(_stagedMasses[i].filename == filename) {
index = i;
break;
}
}
if(file_exists) {
if(auto name = GameObjects::Mass::getNameFromFile(Utility::Path::join(conf().directories().staging, filename))) {
arrayAppend(_stagedMasses, StagedMass{filename, *name});
std::sort(_stagedMasses.begin(), _stagedMasses.end(),
[](const StagedMass& a, const StagedMass& b)->bool{
if(a.filename.contains('/') && !b.filename.contains('/')) {
return true;
}
return a.filename < b.filename;
}
);
}
else if(index != _stagedMasses.size()) {
arrayRemove(_stagedMasses, index);
}
}
else if(index != _stagedMasses.size()) {
arrayRemove(_stagedMasses, index);
}
}
bool
StagedMassManager::import(Containers::StringView filename, int index, Containers::StringView new_account, bool demo) {
if(index < 0 || index >= 32) {
LOG_ERROR(_lastError = "Hangar index out of range."_s);
return false;
}
bool found = false;
for(auto& mass : _stagedMasses) {
if(mass.filename == filename) {
found = true;
break;
}
}
if(!found) {
LOG_ERROR(_lastError = "Couldn't find "_s + filename + " in the staged M.A.S.S.es."_s);
return false;
}
auto source = Utility::Path::join(conf().directories().staging, filename);
auto temp_path = Utilities::getTempPath(Utility::Path::split(filename).second());
Utility::Path::copy(source, temp_path);
{
GameObjects::Mass mass{temp_path};
if(!mass.updateAccount(new_account)) {
LOG_ERROR(_lastError = mass.lastError());
Utility::Path::remove(temp_path);
return false;
}
}
auto dest = Utility::Path::join(conf().directories().gameSaves,
Utility::format("{}Unit{}{}.sav", demo ? "Demo" : "", index, new_account));
if(Utility::Path::exists(dest)) {
Utility::Path::remove(dest);
}
if(!Utility::Path::move(temp_path, dest)) {
_lastError = Utility::format("Couldn't move {} to hangar {:.2d}", filename, index + 1);
LOG_ERROR(_lastError);
return false;
}
return true;
}
bool
StagedMassManager::remove(Containers::StringView filename) {
bool found = false;
for(auto& mass : _stagedMasses) {
if(mass.filename == filename) {
found = true;
break;
}
}
if(!found || !Utility::Path::remove(Utility::Path::join(conf().directories().staging, filename))) {
LOG_ERROR(_lastError = Utility::format("{} couldn't be found.", filename));
return false;
}
return true;
}
void
StagedMassManager::scanSubdir(Containers::StringView subdir) {
static std::uint8_t depth = 0;
auto full_subdir = Utility::Path::join(conf().directories().staging, subdir);
using Flag = Utility::Path::ListFlag;
auto files = Utility::Path::list(full_subdir, Flag::SkipSpecial|Flag::SkipDirectories);
if(!files) {
LOG_ERROR_FORMAT("{} couldn't be opened.", full_subdir);
return;
}
auto iter = std::remove_if(files->begin(), files->end(), [](Containers::StringView file){
return !file.hasSuffix(".sav"_s);
});
auto files_view = files->exceptSuffix(files->end() - iter);
for(Containers::StringView file : files_view) {
if(auto name = GameObjects::Mass::getNameFromFile(Utility::Path::join(full_subdir, file))) {
LOG_INFO_FORMAT("Found staged M.A.S.S.: {}", *name);
arrayAppend(_stagedMasses, InPlaceInit, file, *name);
}
else {
LOG_WARNING_FORMAT("Skipped {}.", file);
}
}
if(depth == 5) {
return;
}
auto subdirs = Utility::Path::list(full_subdir, Flag::SkipFiles|Flag::SkipSpecial|Flag::SkipDotAndDotDot);
if(!subdirs) {
LOG_ERROR_FORMAT("Couldn't list contents of {}.", full_subdir);
}
depth++;
for(auto& dir : *subdirs) {
scanSubdir(Utility::Path::join(subdir, dir));
}
depth--;
}
}

View file

@ -0,0 +1,52 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
//
// 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 <https://www.gnu.org/licenses/>.
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/ArrayView.h>
#include <Corrade/Containers/String.h>
#include <Corrade/Containers/StringView.h>
#include "StagedMass.h"
using namespace Corrade;
namespace mbst::Managers {
class StagedMassManager {
public:
explicit StagedMassManager();
auto lastError() -> Containers::StringView;
auto stagedMasses() const -> Containers::ArrayView<const StagedMass>;
auto at(Containers::StringView filename) const -> const StagedMass&;
void refresh();
void refreshMass(Containers::StringView filename);
bool import(Containers::StringView filename, int index, Containers::StringView new_account, bool demo);
bool remove(Containers::StringView filename);
private:
void scanSubdir(Containers::StringView subdir);
Containers::String _lastError;
Containers::Array<StagedMass> _stagedMasses;
};
}