Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
StartupResources.cpp
Go to the documentation of this file.
2
4#include "ResourceRuntime.hpp"
5#include "UDUtils.hpp"
6
7#include <SDL3/SDL.h>
8
9#include <array>
10#include <atomic>
11#include <iostream>
12#include <memory>
13
14namespace monopoly::startup
15{
16 namespace
17 {
18 std::optional<std::filesystem::path> selectedDataOverrides;
19 data::ResourceContext selectedContext{};
20
21 struct FolderResult
22 {
23 std::atomic<bool> finished{};
24 std::string path;
25 std::string error;
26 };
27
29 void* userdata, const char* const* files, int)
30 {
31 // SDL can call back from another thread, including after the user
32 // requested quit. Keep the result owned until the callback exits.
33 std::unique_ptr<std::shared_ptr<FolderResult>> owner(
34 static_cast<std::shared_ptr<FolderResult>*>(userdata));
35 auto& result = **owner;
36 if (!files) result.error = SDL_GetError();
37 else if (files[0]) result.path = files[0];
38 result.finished.store(true, std::memory_order_release);
39 }
40
41 std::expected<std::optional<std::string>, std::string> chooseFolder()
42 {
43 auto result = std::make_shared<FolderResult>();
45 new std::shared_ptr<FolderResult>(result), nullptr, nullptr, false);
46 bool quit = false;
47 while (!result->finished.load(std::memory_order_acquire))
48 {
49 SDL_Event event{};
50 if (SDL_WaitEventTimeout(&event, 20) && event.type == SDL_EVENT_QUIT)
51 quit = true;
52 }
53 if (!result->error.empty()) return std::unexpected(result->error);
54 if (quit || result->path.empty()) return std::optional<std::string>{};
55 return std::optional<std::string>{result->path};
56 }
57 }
58
59 std::expected<ResourceArguments, std::string> parseResourceArguments(
60 std::span<const std::string_view> arguments)
61 {
62 ResourceArguments result;
63 bool editionSpecified{};
64 bool languageSpecified{};
65 for (std::size_t index = 0; index < arguments.size(); ++index)
66 {
67 const auto argument = arguments[index];
68 if (argument.starts_with("--edition="))
69 {
71 return std::unexpected("--edition was specified twice");
72 editionSpecified = true;
73 const auto value = argument.substr(std::string_view("--edition=").size());
74 if (value == "usa") result.context.board = data::BoardEdition::Usa;
75 else if (value == "europe") result.context.board = data::BoardEdition::Europe;
76 else return std::unexpected("--edition requires usa or europe");
77 }
78 else if (argument.starts_with("--language="))
79 {
81 return std::unexpected("--language was specified twice");
82 languageSpecified = true;
83 const auto value = argument.substr(std::string_view("--language=").size());
84 if (value == "en-us") result.context.language = data::LanguageId::EnglishUs;
85 else if (value == "en-uk") result.context.language = data::LanguageId::EnglishUk;
86 else if (value == "fr") result.context.language = data::LanguageId::French;
87 else return std::unexpected("--language requires en-us, en-uk or fr");
88 }
89 else if (argument == "--edition" || argument == "--language")
90 return std::unexpected("resource context switches require --edition=usa|europe or --language=en-us|en-uk|fr");
91 else if (argument == "--data-root")
92 {
93 if (result.dataRoot || index + 1 == arguments.size() ||
94 arguments[index + 1].empty() ||
95 arguments[index + 1].starts_with("--"))
96 return std::unexpected(
97 "--data-root requires one absolute installation folder");
98 result.dataRoot = arguments[++index];
99 }
100 else if (argument == "--data-overrides")
101 {
102 if (result.dataOverrides || index + 1 == arguments.size() ||
103 arguments[index + 1].empty() ||
104 arguments[index + 1].starts_with("--"))
105 return std::unexpected(
106 "--data-overrides requires one absolute manifest path");
107 result.dataOverrides = arguments[++index];
108 }
109 else if (argument == "--check-resources")
110 {
111 if (result.checkOnly)
112 return std::unexpected("--check-resources was specified twice");
113 result.checkOnly = true;
114 }
115 else result.remaining.push_back(argument);
116 }
117 return result;
118 }
119
120 std::expected<void, std::string> selectResourceRoot(std::string_view utf8Root)
121 {
122 const std::filesystem::path root{
123 std::u8string(utf8Root.begin(), utf8Root.end())};
124 const auto paths = data::ResourcePaths::create(std::array{root});
125 if (!paths) return std::unexpected(paths.error().detail);
126 auto* environment = SDL_GetEnvironment();
127 const std::string value(utf8Root);
128 if (!environment || !SDL_SetEnvironmentVariable(
129 environment, "MONOPOLY_DATA_ROOT", value.c_str(), true))
130 return std::unexpected(std::string(SDL_GetError()));
131 return {};
132 }
133
134 std::expected<void, std::string> selectResourceContext(data::ResourceContext context)
135 {
137 return std::unexpected("resource board edition must be USA or Europe");
139 return std::unexpected("resource language must have a source-defined bank triplet");
140 selectedContext = context;
141 return {};
142 }
143
148
149
150 std::expected<void, std::string> selectDataOverrideManifest(
151 std::string_view utf8Path)
152 {
153 const std::filesystem::path path{
154 std::u8string(utf8Path.begin(), utf8Path.end())};
155 if (!path.is_absolute())
156 return std::unexpected(
157 "--data-overrides requires an absolute manifest path");
158
159 std::error_code error;
160 if (!std::filesystem::is_regular_file(path, error))
161 return std::unexpected(
162 "data override manifest does not exist or is not a file");
163
164 selectedDataOverrides = path.lexically_normal();
165 return {};
166 }
167
168
169 std::optional<std::filesystem::path> dataOverrideManifest()
170 {
172 }
173
174
176 {
177 for (;;)
178 {
179 std::string details;
181 details = "The configured installation folder is invalid.\n";
182 else
183 {
184 const auto* paths = udutils::resourcePaths();
186 const auto root = paths->roots().front().u8string();
187 std::cout << "Resource folder: "
188 << std::string(root.begin(), root.end()) << '\n';
189 if (issues.empty())
190 {
191 std::cout << "All eight required banks and the language catalog "
192 "can be opened. Gameplay assets remain to be exercised.\n";
194 {
195 const auto overrides =
197 if (!overrides)
198 {
199 const auto& error = overrides.error();
200 const auto path = error.path.u8string();
201 std::cerr << "DATA override manifest failed ["
202 << data::dataErrorCodeName(error.code) << "] "
203 << std::string(path.begin(), path.end())
204 << ": " << error.detail << '\n';
206 }
207 std::cout << "Loose DATA overrides: "
208 << overrides->size() << " logical items.\n";
209 }
211 }
212 for (const auto& issue : issues)
213 {
214 std::cerr << '[' << data::dataErrorCodeName(issue.code)
215 << "] " << issue.detail << '\n';
217 details += "Missing: ";
218 else details += "Cannot read: ";
219 // Bank errors start with the expected relative filename;
220 // detailed parser diagnostics remain available in stderr.
221 details += issue.detail.substr(0, issue.detail.find(": "));
222 details += '\n';
223 }
224 }
226
227 const std::string message =
228 "Choose the game installation folder containing Dat_Mon.\n\n" +
229 details + "\nThe original game files are required to start.";
230 const std::array<SDL_MessageBoxButtonData, 2> buttons{{
231 {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, "Choose folder"},
233 }};
234 const SDL_MessageBoxData dialog{SDL_MESSAGEBOX_WARNING, nullptr,
235 "Monopoly - Game files", message.c_str(),
236 static_cast<int>(buttons.size()), buttons.data(), nullptr};
237 int choice = 0;
238 if (!SDL_ShowMessageBox(&dialog, &choice))
239 {
240 std::cerr << "Resource setup dialog failed: " << SDL_GetError() << '\n';
242 }
243 if (choice != 1) return ResourceSetupResult::Cancelled;
244 const auto folder = chooseFolder();
245 if (!folder)
246 {
247 std::cerr << "Folder selection failed: " << folder.error() << '\n';
249 "Monopoly - Folder selection", folder.error().c_str(), nullptr);
251 }
253 const auto selected = selectResourceRoot(**folder);
254 if (!selected)
255 {
256 std::cerr << "Selected folder is invalid: " << selected.error() << '\n';
258 "Monopoly - Game files", "The selected folder cannot be used.", nullptr);
259 }
260 }
261 }
262}
std::size_t root
unsigned index
std::string path
std::atomic< bool > finished
std::string error
std::uint16_t value
Definition Timers.cpp:16
static std::expected< ResourcePaths, DataError > create(std::span< const std::filesystem::path > roots)
const LanguageBankTriplet * findLanguageBankTriplet(LanguageId language) noexcept
std::vector< DataError > inspectResourceInstallation(const ResourcePaths &paths, ResourceContext context, ArchiveOpenOptions options)
std::string_view dataErrorCodeName(DataErrorCode code) noexcept
std::expected< std::vector< DataSourceOverride >, DataError > loadLooseDataOverrides(const std::filesystem::path &manifestPath)
data::ResourceContext resourceContext() noexcept
std::optional< std::filesystem::path > dataOverrideManifest()
std::expected< void, std::string > selectResourceContext(data::ResourceContext context)
ResourceSetupResult prepareResources(bool interactive)
std::expected< ResourceArguments, std::string > parseResourceArguments(std::span< const std::string_view > arguments)
std::expected< void, std::string > selectDataOverrideManifest(std::string_view utf8Path)
std::expected< void, std::string > selectResourceRoot(std::string_view utf8Root)
const data::ResourcePaths * resourcePaths() noexcept
Definition UDUtils.cpp:77
bool generateINIFile()
Definition UDUtils.cpp:29
std::optional< std::string > dataOverrides
std::optional< std::string > dataRoot
std::vector< std::string_view > remaining