diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index b57651c..c918b2c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -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
diff --git a/src/Managers/StagedMass.h b/src/Managers/StagedMass.h
new file mode 100644
index 0000000..0d48fa9
--- /dev/null
+++ b/src/Managers/StagedMass.h
@@ -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 .
+
+#include
+
+using namespace Corrade;
+
+namespace mbst::Managers {
+
+struct StagedMass {
+ Containers::String filename;
+ Containers::String name;
+};
+
+}
diff --git a/src/Managers/StagedMassManager.cpp b/src/Managers/StagedMassManager.cpp
new file mode 100644
index 0000000..20bd5fa
--- /dev/null
+++ b/src/Managers/StagedMassManager.cpp
@@ -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 .
+
+#include
+
+#include
+#include
+
+#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
+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{};
+
+ 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--;
+}
+
+}
diff --git a/src/Managers/StagedMassManager.h b/src/Managers/StagedMassManager.h
new file mode 100644
index 0000000..87b1630
--- /dev/null
+++ b/src/Managers/StagedMassManager.h
@@ -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 .
+
+#include
+#include
+#include
+#include
+
+#include "StagedMass.h"
+
+using namespace Corrade;
+
+namespace mbst::Managers {
+
+class StagedMassManager {
+ public:
+ explicit StagedMassManager();
+
+ auto lastError() -> Containers::StringView;
+
+ auto stagedMasses() const -> Containers::ArrayView;
+ 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 _stagedMasses;
+};
+
+}