1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
|
#ifndef MODORGANIZER_INSTANCEMANAGER_INCLUDED
#define MODORGANIZER_INSTANCEMANAGER_INCLUDED
#include <QDir>
#include <QSettings>
#include <QString>
#include <atomic>
#include <memory>
#include <mutex>
#include <uibase/iinstance.h>
namespace MOBase
{
class IPluginGame;
}
class Settings;
class PluginContainer;
// represents an instance, either global or portable
//
// if setup() is not called, the game plugin is not available and the INI is
// not processed at all, so name(), directory() and isPortable() really are the
// only meaningful functions
//
// setup() must be called when MO wants to use the instance, it will read the
// INI, figure out the game plugin to use and set it up by calling
// setGameVaraint(), setGamePath(), etc. on it
//
// when setup() fails because the game name/directory or variant are missing,
// setGame() and setVariant() can be called before retrying setup(); this
// happens on startup if that information is missing
//
class Instance : public MOBase::IInstance
{
public:
// returned by setup()
//
enum class SetupResults
{
// instance is ready to be used
Okay,
// error while reading the INI
BadIni,
// both the game name and directory are missing from the ini; setup() will
// attempt to recover if either are missing, but not when both are
IniMissingGame,
// either:
// 1) there is no plugin with the given name, or
// 2) if the name is missing, no plugin can handle the game directory
PluginGone,
// the selected plugin does not consider the game directory as being valid
GameGone,
// there is no game variant specified in the INI, but the plugin requires
// one
MissingVariant
};
// a file or directory owned by this instance, used by objectsForDeletion()
//
struct Object
{
// path to the file or directory
QString path;
// whether this object must be deleted to properly delete the instance;
// typically true only for the instance directory itself, but not for the
// base directory, etc.
bool mandatoryDelete;
Object(QString p, bool d = false) : path(std::move(p)), mandatoryDelete(d) {}
// puts mandatory delete on top
//
bool operator<(const Object& o) const
{
if (mandatoryDelete && !o.mandatoryDelete) {
return true;
} else if (!mandatoryDelete && o.mandatoryDelete) {
return false;
}
return false;
}
};
// an instance that lives in the given directory; `portable` must be `true`
// if this is a portable instance
//
// `profileName` can be given to override what's in the INI; this typically
// happens when the profile is overriden on the command line
//
Instance(QString dir, bool portable, QString profileName = {});
// finds the appropriate game plugin and sets it up so MO can use it; this
// calls readFromIni() first
//
// setup() tries to recover from some errors, but can fail for a variety of
// reasons, see SetupResults
//
SetupResults setup(PluginContainer& plugins);
// overrides the game name and directory
//
void setGame(const QString& name, const QString& dir);
// overrides the game variant
//
void setVariant(const QString& name);
// returns the instance name; this is the directory name or "Portable" for
// portable instances
//
// be careful when using this function to check whether two instances are the
// same, some parts of MO use an empty string to represent portable instances,
// but this function will return "Portable" for them; it's safer to check
// for isPortable() first
//
// can be called without setup()
//
QString displayName() const;
// returns either:
// 1) the game name from the INI, if readFromIni() was called;
// 2) gameName() from the game plugin if it was missing and setup() was
// called; or
// 3) whatever was given in setGame()
//
QString gameName() const;
// returns either:
// 1) the game directory from the INI, if readFromIni() was called;
// 2) gameDirectory() from the game plugin if it was missing and setup()
// was called; or
// 3) whatever was given in setGame()
//
QString gameDirectory() const;
// returns the instance directory as given in the constructor
//
QString directory() const;
// returns the base directory; empty if readFromIni() hasn't been called
//
QString baseDirectory() const;
// returns whether this is a portable instance, as given in the constructor
//
bool isPortable() const;
// returns the selected game plugin; will return null if setup() hasn't been
// called, or if it failed
//
MOBase::IPluginGame* gamePlugin() const;
// returns either:
// 1) the profile name given in the constructor if not empty;
// 2) the profile name from the INI if readFromIni() was called, or
// 3) the default profile name if it's missing (see
// AppConfig::defaultProfileName())
//
QString profileName() const;
// returns the path to the INI file for this instance; the file may not
// exist
//
QString iniPath() const;
// whether this is the currently active instance in MO
//
bool isActive() const;
// returns a list of files and directories that must be deleted when deleting
// this instance; this will read the INI and fail if it's not accessible
//
// returns an empty list on failure
//
std::vector<Object> objectsForDeletion() const;
private:
QString m_dir;
bool m_portable;
mutable QString m_gameName, m_gameDir, m_gameVariant, m_baseDir;
MOBase::IPluginGame* m_plugin;
mutable QString m_profile;
mutable std::mutex m_iniValuesMutex;
mutable std::atomic<bool> m_iniValuesRead{false};
// reads in values from the INI if they were not given yet:
// - game name
// - game directory
// - game variant
// - profile name
//
// note that setup() already calls this
//
// returns false if the ini couldn't be read from
//
bool readFromIni() const;
// figures out the game plugin for this instance
//
SetupResults getGamePlugin(PluginContainer& plugins);
// figures out the profile name for this instance
//
void getProfile(const Settings& s) const;
// updates the ini with the given values and the ones found by setup()
//
void updateIni();
};
// manages global and portable instances
//
class InstanceManager
{
public:
// there is only one manager; this isn't called instance() because it's hella
// confusing
//
static InstanceManager& singleton();
// overrides instance name found in registry
//
void overrideInstance(const QString& instanceName);
// overrides profile name from INI for currentInstance()
//
void overrideProfile(const QString& profileName);
// clears instance and profile overrides, used when restarting MO to select
// another instance
//
void clearOverrides();
// returns a game plugin that considers the given directory valid
//
// this will check for an INI file in the directory and use its game name
// and directory if available
//
// if there is no INI, if it's missing these values or if there are no game
// plugins that can handle these values, this returns the first plugin that
// considers the given directory valid
//
// returns null if all of this fails
//
const MOBase::IPluginGame*
gamePluginForDirectory(const QString& dir, const PluginContainer& plugins) const;
MOBase::IPluginGame* gamePluginForDirectory(const QString& dir,
PluginContainer& plugins) const;
// clears the instance name from the registry; on restart, this will make MO
// either select the portable instance if it exists, or display the instance
// selection/creation dialog
//
void clearCurrentInstance();
// returns the current instance from the registry; this may be empty if the
// instance name in the registry is empty or non-existent and there is no
// portable instance set up
//
std::shared_ptr<Instance> currentInstance() const;
// sets the instance name in the registry so the same instance is opened next
// time MO runs
//
void setCurrentInstance(const QString& name);
// whether MO should allow the user to change the current instance from the
// user interface
//
bool allowedToChangeInstance() const;
// whether a portable instance exists; this basically checks for an INI in
// the application directory
//
bool portableInstanceExists() const;
// whether any instance exists, whether global or portable
//
bool hasAnyInstances() const;
// returns the absolute path to the portable instance, regardless of whether
// one exists
//
QString portablePath() const;
// returns the absolute path to the directory that contains global instances
// (typically AppData/Local/ModOrganizer)
//
QString globalInstancesRootPath() const;
// returns the list of absolute path to all existing global instances; this
// does not include the portable instance
//
std::vector<QString> globalInstancePaths() const;
// returns the global Instance with the given name, or null if it doesn't exist
//
std::shared_ptr<const Instance> getGlobalInstance(const QString& instanceName) const;
// sanitizes the given instance name and either
// 1) returns it if there is no instance with this name
// 2) tries to add " (N)" at the end until it works
//
// may return an empty string if no unique name can be found
//
QString makeUniqueName(const QString& instanceName) const;
// returns whether a global instance with this name already exists
//
bool instanceExists(const QString& instanceName) const;
// returns the absolute path of a global instance with the given name; this
// does not check if the name is valid or if exists
//
QString instancePath(const QString& instanceName) const;
// returns the absolute path to the INI file for the given instance directory;
// the file may not exist
//
QString iniPath(const QString& instanceDir) const;
private:
InstanceManager();
private:
std::optional<QString> m_overrideInstanceName;
std::optional<QString> m_overrideProfileName;
};
// see setupInstance()
//
enum class SetupInstanceResults
{
Okay,
TryAgain,
SelectAnother,
Exit
};
// if there are no instances configured, global or portable, shows the
// create instance dialog and returns the new instance or empty if the user
// cancelled
//
// if there is at least one instance available, unconditionally show the
// instance manager dialog and returns the selected instance or empty if the
// user cancelled
//
std::shared_ptr<Instance> selectInstance();
// calls instance.setup() tries to handle problems by itself:
//
// - if the ini is missing some information, will show dialogs and ask the user
// to fill in what's required (such as the game directory, variant, etc.);
// if successful, returns TryAgain and setupInstance() can be called again
// with the same instance
//
// - if the instance cannot be used (no game plugin found for it, ini can't
// be read, etc.), returns SelectAnother
//
// - if the user cancels at any point, returns Exit
//
// - if the instance has been set up correctly, returns Okay
//
SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc);
#endif // MODORGANIZER_INSTANCEMANAGER_INCLUDED
|