Skip to content

Commit db60034

Browse files
committed
Add experimental support for pulling Data Packs from Ultimate archives
1 parent a260049 commit db60034

File tree

6 files changed

+317
-27
lines changed

6 files changed

+317
-27
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ ob_fetch_qx(
8484

8585
# Fetch libfp (build and import from source)
8686
include(OB/Fetchlibfp)
87-
ob_fetch_libfp("v0.5.6")
87+
ob_fetch_libfp("ed105fc35b92cd64c1cddfc35b51cc0027974afb")
8888

8989
# Fetch QI-QMP (build and import from source)
9090
include(OB/FetchQI-QMP)

lib/backend/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ set(BACKEND_IMPLEMENTATION
5757
task/t-mount.cpp
5858
task/t-sleep.h
5959
task/t-sleep.cpp
60+
tools/archiveaccess.h
61+
tools/archiveaccess.cpp
6062
tools/blockingprocessmanager.h
6163
tools/blockingprocessmanager.cpp
6264
tools/deferredprocessmanager.h

lib/backend/src/kernel/core.cpp

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// Qx Includes
55
#include <qx/utility/qx-helpers.h>
66
#include <qx/core/qx-system.h>
7+
#include <qx/core/qx-integrity.h>
78

89
// libfp Includes
910
#include <fp/fp-install.h>
@@ -25,6 +26,7 @@
2526
#ifdef __linux__
2627
#include "task/t-awaitdocker.h"
2728
#endif
29+
#include "tools/archiveaccess.h"
2830
#include "utility.h"
2931
#include "_buildinfo.h"
3032

@@ -428,6 +430,10 @@ void Core::attachFlashpoint(std::unique_ptr<Fp::Install> flashpointInstall)
428430
* or a future feature (hopefully not). Either way it just points to the PHP server port.
429431
*/
430432
#endif
433+
434+
// Setup archive access
435+
if(info->edition() == Fp::Install::VersionInfo::Ultimate)
436+
mGamesArchive = std::make_unique<ArchiveAccess>(*this, ArchiveAccess::GameData);
431437
}
432438

433439
QString Core::resolveFullAppPath(const QString& appPath, const QString& platform)
@@ -679,36 +685,76 @@ Qx::Error Core::enqueueDataPackTasks(const Fp::GameData& gameData)
679685
QString packFilename = tk->datapackFilename(gameData);
680686
logEvent(LOG_EVENT_DATA_PACK_PATH.arg(packPath));
681687

682-
// Enqueue pack download if it's not available
688+
// Try to acquire data pack if it's not present
683689
if(!tk->datapackIsPresent(gameData))
684690
{
685691
logEvent(LOG_EVENT_DATA_PACK_MISS);
686692

687-
TDownload* downloadTask = new TDownload(*this);
688-
downloadTask->setStage(Task::Stage::Auxiliary);
689-
downloadTask->setDescription(u"data pack "_s + packFilename);
690-
TDownloadError packError = downloadTask->addDatapack(tk, &gameData);
691-
if(packError.isValid())
693+
auto edition = mFlashpointInstall->versionInfo()->edition();
694+
if(edition == Fp::Install::VersionInfo::Ultimate)
692695
{
693-
postDirective<DError>(packError);
694-
return packError;
696+
/* TODO: Might want to make all of this its own task, and have a static, singleton, manager
697+
* type (e.g. ArchiveAccessManager) that holds the archive instances, instead of core,
698+
* which is then used by that task, and then here we just setup the task. This way we
699+
* can also have more specific errors instead of just the one (with events).
700+
*/
701+
Q_ASSERT(mGamesArchive);
702+
logEvent(LOG_EVENT_DATA_PACK_FROM_ARCHIVE);
703+
704+
// Check archive
705+
QByteArray datapackData = mGamesArchive->getFileContents(DATA_PACK_FROM_ARCHIVE_TEMPLATE.arg(packFilename));
706+
if(datapackData.isEmpty())
707+
{
708+
logEvent(LOG_EVENT_DATA_PACK_FROM_ARCHIVE_NOT_FOUND);
709+
CoreError err(CoreError::CannotObtainDatapack);
710+
postDirective<DError>(err);
711+
return err;
712+
}
713+
714+
logEvent(LOG_EVENT_DATA_PACK_FROM_ARCHIVE_FOUND);
715+
if(Qx::Integrity::crc32(datapackData) != gameData.crc32())
716+
{
717+
logEvent(LOG_EVENT_DATA_PACK_FROM_ARCHIVE_CORRUPT);
718+
CoreError err(CoreError::CannotObtainDatapack);
719+
postDirective<DError>(err);
720+
return err;
721+
}
722+
723+
// Save to disk
724+
QFile packFile(packPath);
725+
if(auto writeOp = Qx::writeBytesToFile(packFile, datapackData); writeOp.isFailure())
726+
return writeOp;
727+
728+
logEvent(LOG_EVENT_DATA_PACK_FROM_ARCHIVE_SAVED);
729+
}
730+
else // Basically just Infinity
731+
{
732+
TDownload* downloadTask = new TDownload(*this);
733+
downloadTask->setStage(Task::Stage::Auxiliary);
734+
downloadTask->setDescription(u"data pack "_s + packFilename);
735+
TDownloadError packError = downloadTask->addDatapack(tk, &gameData);
736+
if(packError.isValid())
737+
{
738+
postDirective<DError>(packError);
739+
return packError;
740+
}
741+
742+
mTaskQueue.push(downloadTask);
743+
logTask(downloadTask);
744+
745+
// Add task to update DB with onDiskState
746+
int gameDataId = gameData.id();
747+
748+
TGeneric* onDiskUpdateTask = new TGeneric(*this);
749+
onDiskUpdateTask->setStage(Task::Stage::Auxiliary);
750+
onDiskUpdateTask->setDescription(u"Update GameData onDisk state."_s);
751+
onDiskUpdateTask->setAction([gameDataId, this]{
752+
return mFlashpointInstall->database()->updateGameDataOnDiskState({gameDataId}, true);
753+
});
754+
755+
mTaskQueue.push(onDiskUpdateTask);
756+
logTask(onDiskUpdateTask);
695757
}
696-
697-
mTaskQueue.push(downloadTask);
698-
logTask(downloadTask);
699-
700-
// Add task to update DB with onDiskState
701-
int gameDataId = gameData.id();
702-
703-
TGeneric* onDiskUpdateTask = new TGeneric(*this);
704-
onDiskUpdateTask->setStage(Task::Stage::Auxiliary);
705-
onDiskUpdateTask->setDescription(u"Update GameData onDisk state."_s);
706-
onDiskUpdateTask->setAction([gameDataId, this]{
707-
return mFlashpointInstall->database()->updateGameDataOnDiskState({gameDataId}, true);
708-
});
709-
710-
mTaskQueue.push(onDiskUpdateTask);
711-
logTask(onDiskUpdateTask);
712758
}
713759
else
714760
logEvent(LOG_EVENT_DATA_PACK_FOUND);

lib/backend/src/kernel/core.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ class QX_ERROR_TYPE(CoreError, "CoreError", 1200)
3939
TitleNotFound,
4040
TooManyResults,
4141
ConfiguredServerMissing,
42-
UnknownDatapackParam
42+
UnknownDatapackParam,
43+
CannotObtainDatapack
4344
};
4445

4546
//-Class Variables-------------------------------------------------------------
@@ -52,7 +53,8 @@ class QX_ERROR_TYPE(CoreError, "CoreError", 1200)
5253
{TitleNotFound, u"Could not find the title in the Flashpoint database."_s},
5354
{TooManyResults, u"More results than can be presented were returned in a search."_s},
5455
{ConfiguredServerMissing, u"The configured server was not found within the Flashpoint services store."_s},
55-
{UnknownDatapackParam, u"Unrecognized datapack parameters were present. The game likely won't work correctly."_s}
56+
{UnknownDatapackParam, u"Unrecognized datapack parameters were present. The game likely won't work correctly."_s},
57+
{CannotObtainDatapack, u"The specified datapack could not be obtained via edition appropriate means."_s}
5658
};
5759

5860
//-Instance Variables-------------------------------------------------------------
@@ -84,6 +86,7 @@ class Install;
8486
class GameData;
8587
}
8688
class TExec;
89+
class ArchiveAccess;
8790

8891
class Core : public QObject, public Directorate
8992
{
@@ -133,6 +136,11 @@ class Core : public QObject, public Directorate
133136
static inline const QString LOG_EVENT_DATA_PACK_NEEDS_MOUNT = u"Title Data Pack requires mounting"_s;
134137
static inline const QString LOG_EVENT_DATA_PACK_NEEDS_EXTRACT = u"Title Data Pack requires extraction"_s;
135138
static inline const QString LOG_EVENT_DATA_PACK_ALREADY_EXTRACTED = u"Extracted files already present"_s;
139+
static inline const QString LOG_EVENT_DATA_PACK_FROM_ARCHIVE = u"Retrieving Data Pack from archive"_s;
140+
static inline const QString LOG_EVENT_DATA_PACK_FROM_ARCHIVE_NOT_FOUND = u"Data Pack could not be found in the archive!"_s;
141+
static inline const QString LOG_EVENT_DATA_PACK_FROM_ARCHIVE_CORRUPT = u"Data Pack from archive is corrupted!"_s;
142+
static inline const QString LOG_EVENT_DATA_PACK_FROM_ARCHIVE_FOUND = u"Data Pack found in archive."_s;
143+
static inline const QString LOG_EVENT_DATA_PACK_FROM_ARCHIVE_SAVED = u"Sourced Data Pack from archive."_s;
136144
static inline const QString LOG_EVENT_APP_PATH_ALT = u"App path \"%1\" maps to alternative \"%2\"."_s;
137145
static inline const QString LOG_EVENT_SERVICES_FROM_LAUNCHER = u"Using services from standard Launcher due to companion mode."_s;
138146
static inline const QString LOG_EVENT_LAUNCHER_WATCH = u"Starting bide on Launcher process..."_s;
@@ -203,6 +211,7 @@ class Core : public QObject, public Directorate
203211

204212
// Helper
205213
static const int FIND_ENTRY_LIMIT = 20;
214+
static inline const QString DATA_PACK_FROM_ARCHIVE_TEMPLATE = u"Flashpoint Ultimate/Data/Games/%1"_s;
206215

207216
// Protocol
208217
static inline const QString FLASHPOINT_PROTOCOL_SCHEME = u"flashpoint://"_s;
@@ -217,6 +226,7 @@ class Core : public QObject, public Directorate
217226

218227
// Handles
219228
std::unique_ptr<Fp::Install> mFlashpointInstall;
229+
std::unique_ptr<ArchiveAccess> mGamesArchive;
220230

221231
// Processing
222232
ServicesMode mServicesMode;
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Unit Include
2+
#include "archiveaccess.h"
3+
4+
// Qx Includes
5+
#include <qx/io/qx-common-io.h>
6+
7+
// Project Includes
8+
#include "kernel/core.h"
9+
#include "utility.h"
10+
11+
//===============================================================================================================
12+
// MounterError
13+
//===============================================================================================================
14+
15+
//-Constructor-------------------------------------------------------------
16+
//Private:
17+
ArchiveAccessError::ArchiveAccessError(Type t, const QString& s) :
18+
mType(t),
19+
mSpecific(s)
20+
{}
21+
22+
//-Instance Functions-------------------------------------------------------------
23+
//Public:
24+
bool ArchiveAccessError::isValid() const { return mType != NoError; }
25+
QString ArchiveAccessError::specific() const { return mSpecific; }
26+
ArchiveAccessError::Type ArchiveAccessError::type() const { return mType; }
27+
28+
//Private:
29+
Qx::Severity ArchiveAccessError::deriveSeverity() const { return Qx::Warning; }
30+
quint32 ArchiveAccessError::deriveValue() const { return mType; }
31+
QString ArchiveAccessError::derivePrimary() const { return ERR_STRINGS.value(mType); }
32+
QString ArchiveAccessError::deriveSecondary() const { return mSpecific; }
33+
34+
//===============================================================================================================
35+
// ArchiveAccess
36+
//===============================================================================================================
37+
38+
//-Constructor-------------------------------------------------------------
39+
//Public:
40+
ArchiveAccess::ArchiveAccess(Core& core, Type type) :
41+
Directorate(core.director()),
42+
mType(type)
43+
{
44+
logEvent(MSG_INIT.arg(ENUM_NAME(mType)));
45+
46+
// Initialize parts
47+
QStringList partPaths;
48+
if(auto archDirOp = Qx::dirContentList(partPaths, mArchivesDir, { ZIP_FILTER_TEMPLATE.arg(ENUM_NAME(type)) }); archDirOp.isFailure())
49+
{
50+
logError(archDirOp);
51+
return;
52+
}
53+
54+
if(partPaths.isEmpty())
55+
{
56+
logError(ArchiveAccessError(ArchiveAccessError::NoParts, ENUM_NAME(type)));
57+
return;
58+
}
59+
logEvent(MSG_PART_COUNT.arg(mParts.size()));
60+
61+
for(const auto& pp : std::as_const(partPaths))
62+
mParts.emplace_back(pp);
63+
}
64+
65+
//-Instance Functions-------------------------------------------------------------
66+
//Private:
67+
bool ArchiveAccess::readyPart(QuaZip& part)
68+
{
69+
if(part.isOpen())
70+
{
71+
logEvent(MSG_PART_OPEN);
72+
return true;
73+
}
74+
75+
logEvent(MSG_PREPARING_PART.arg(part.getZipName()));
76+
if(!part.open(QuaZip::mdUnzip))
77+
{
78+
logError(ArchiveAccessError(ArchiveAccessError::NoParts, part.getZipName()));
79+
return false;
80+
}
81+
82+
return true;
83+
}
84+
85+
//Public:
86+
QString ArchiveAccess::name() const { return NAME; }
87+
88+
QByteArray ArchiveAccess::getFileContents(const QString& inZipPath)
89+
{
90+
/* Maybe return an ArchiveAccessError, for cases where something unexpected happened
91+
* (can't open zip, or file not found), and treat those cases as an error instead
92+
* of a warning.
93+
*/
94+
logEvent(MSG_FILE_SEARCH.arg(ENUM_NAME(mType), inZipPath));
95+
96+
// Simply search all parts (what the stock launcher does, though I'm sure it could be more optimal)
97+
for(auto& part : mParts)
98+
{
99+
if(!readyPart(part))
100+
continue;
101+
102+
if(part.setCurrentFile(inZipPath))
103+
{
104+
QuaZipFile file(&part); // auto-closes on destruct
105+
if(!file.open(QIODevice::ReadOnly))
106+
{
107+
logError(ArchiveAccessError(ArchiveAccessError::CantOpenFile, part.getZipName()));
108+
break;
109+
}
110+
111+
return file.readAll();
112+
}
113+
}
114+
115+
logError(ArchiveAccessError(ArchiveAccessError::FileNotFound));
116+
return {};
117+
}

0 commit comments

Comments
 (0)