Installing and managing mods
The Library interface turns catalogue metadata into content your game can load. It downloads a mod file, verifies it, extracts it, records it in a per-user state file, keeps it current against the server, validates it, and removes it again. The installed-mod record it maintains is the only sanctioned answer to the question your game asks every session: which mods am I allowed to load right now.
This page documents the Library API in cfcore-sdk-ue. For the five installation methods a game can choose between, see Mod installation methods and Using different methods. Nothing here resolves a mod's dependencies: that is on Mod dependencies. For the bundled in-game UI that drives all of this, see Mod browser.
In C++ the Library is CFCoreContext::GetInstance()->Library(), an ICFCoreLibrary*. In Blueprint it is CFCoreSubsystem and the cfcore|Library palette category.
The SDK must be initialized and its completion delegate must have fired, or calls return ECFCoreErrorCodes::FailedToInitialize. modsDirectory, modsDirectoryMode and userDataDirectory must be set in FCFCoreSettings; missing values give MissingModsDirectory, MissingModsDirectoryMode and MissingUserDataDirectory. modsDirectoryMode defaults to EModsDirectoryMode::CFCore, so only an explicit None triggers the second. initOptions.userContextId should carry a stable per-player identifier (the logged-in console user, or the Steam or EOS identifier on PC); without it the SDK uses "local" and every profile on the machine shares one library.
Library calls belong on the game thread, and entry points such as GetInstalledMods assert IsInGameThread(). Delegates fire on the game thread, so you can touch UObjects in them, while compression and disk IO run on their own threads. State is written to library.json under [userDataDirectory]/[game id]/, which must stay writable or you get FailedToLoadModsStateFromDisk and FailedToSaveModsStateToDisk.
The Library interface
C++ method on ICFCoreLibrary | Blueprint node | What it does |
|---|---|---|
SyncWithServer | Synchronize Installed Mods with Server | Refresh every installed mod against the server so statuses reflect available updates |
GetInstalledMods | Get Installed Mods | Mods installed for the current game and user context, including ones pending install |
GetSystemInstalledMods | Get Global Installed Mods | Every mod installed on the machine, across all user contexts |
GetModsDirInfo | Get Mods Directory Info | Mods directory plus total and free disk space |
Install(FCFCoreMod, ...) | Install Mod | Install the mod's latest file |
Install(const FCFCoreMod&, const FFile&, ...) | (no dedicated node) | Install one specific file |
Install(const FCFCoreMod&, const FFile&, const FInstallModAdditionalParams&, ...) | Install Mod Extended | Install a specific file with tracking, throttling, dynamic-content and origin params |
CancelInstallation | Cancel Mod Installation | Cancel an in-flight install by mod id |
Uninstall | Uninstall Mod | Remove an installed mod by mod id |
PerformModsValidation | Perform Mods Validation | Validate against server file details and per-module fingerprints, flag invalid mods |
UpdateInstalledModsProperties | Update Installed Mods Properties | Persist enabled and loadOrder |
CleanTempDir | Clean Temp Directory | Forced cleanup of stale temp files and unclaimed mod folders |
OnModInstallProgress() | OnModInstallProgress (assignable event) | Multicast progress feed for every install in the process |
OnModInstalled() | OnModInstalled (assignable event) | Multicast completion feed for every install |
ClientServerLibrary() | (see multiplayer and servers) | Access ICFCoreClientServerLibrary for server and client mod sets |
The C++ method is SyncWithServer; the subsystem function behind the Blueprint node is SynchronizeWithServer. AssureServerModsUpdated and AssureClientModsUpdated live on ICFCoreClientServerLibrary in cfcore|Library|ClientServer and are covered on the multiplayer and servers page. Set FCFCoreSettings::isServer to true before calling AssureServerModsUpdated, and isServerPcOnly if the server only serves PC clients.
Both getters filter out hidden mods. A Pending mod that is not in the installation queue and whose downloadInfo.canResume is false is leftover state from a crashed session: it is not returned to your game, and the SDK uninstalls it in its own validation pass.
The installed mod model
FInstalledMod is the record for a mod that is installed or on its way.
| Field | Type | Meaning |
|---|---|---|
id | FGuid | Local installation id. The only way to identify an unmanaged mod, which has no CurseForge mod id |
dateInstalled | FDateTime | Set on every successful install, updates included. Read as "when this file landed", not first install |
dateUpdated | FDateTime | When the file was updated or installed. Also refreshed by SyncWithServer when the main file changed |
status | EInstalledModStatus | The load gate. See below |
pathOnDisk | FString | Path to the content, relative to the mods root. In CFCore mode it already begins with [game id] |
enabled | bool | Whether the current user context has it enabled. Defaults to true |
unmanaged | bool | Not installed from the CurseForge servers and not detectable by the SDK, so usually not on CurseForge at all |
details | FCFCoreMod | Mod metadata: name, authors, categories, premiumDetails |
installedFile | FFile | The revision actually on disk |
latestUpdatedFile | FFile | The main file the server currently reports. During an install, the file being installed. An id differing from installedFile.id means an update is available |
dynamicContent | bool | Assets are on disk but the player does not own the mod and did not request it. Viewable, not usable |
loadOrder | int32 | Optional. Defaults to MAX_int32, meaning unordered and last |
installProgress | FLibraryProgress | Live install progress. Transient, never persisted |
users | TSet<FString> | User context ids that have this mod installed. Not exposed to Blueprint |
downloadInfo | FInstalledModDownloadInfo | Resumable-download bookkeeping |
preInstallStatus (EInstalledModStatus) also exists: the status held before the current install. It neither persists nor reaches Blueprint, and lets the SDK tag a reinstall over an Invalid mod as a fix. FUserModState, built by FUserModState::FromInstalledMod, is the per-user slice that persists to disk: id, modId, enabled, loadOrder, dynamicContent. That is why those three properties are per player while the extracted content is shared across users on the machine.
Do not build an absolute path by combining FModsDirInfo::pathOnDisk with FInstalledMod::pathOnDisk. The first is the mods directory including the [game id] segment; the second is relative to the mods root and in CFCore mode already contains that segment, so concatenating them repeats the game id. Resolve the mods root from FCFCoreSettings::modsDirectory, then combine. In Flat mode the two match, so the bug only appears in CFCore mode, the default.
EModsDirectoryMode | Numeric | Layout |
|---|---|---|
None | 0 | Unset. Produces MissingModsDirectoryMode |
CFCore | 1 | [mods directory]/[game id]/[modId]_[fileId]/. The recommended choice |
Flat | 2 | Content directly under the mods directory, placed per FFileModule::name rather than per mod. A file with an empty modules array has nothing moved into place |
Installed mod status
EInstalledModStatus is the load gate. Treat this table as authoritative.
| Value | Numeric | Meaning | Safe to load |
|---|---|---|---|
Pending | 0 | Queued or mid-install. Content on disk is incomplete | No |
OutOfDate | 1 | Complete on disk, but the server reports a different main file | Yes, and offer an update |
Normal | 2 | Installed, complete, matching the newest known file | Yes |
Invalid | 3 | Deleted or modified on disk. Also set by every validation failure below | No |
WorkingCopy | 4 | Reserved for working-copy support. No code path sets it in the current release | Yes |
Uploading | 5 | Declared, not set by any Library flow in the current release | No |
Modified | 6 | Locally diverged content. The client and server flow treats it as needing reinstall. No Library flow sets it in the current release | Yes |
Uninstalled | 7 | An uninstall failed partway. Ignore the mod; the SDK retries later, for example at the next initialization | No |
| Transition | Where | Condition |
|---|---|---|
any to Pending | Install start | Written with preInstallStatus and latestUpdatedFile before transfer |
Pending to Normal | Install success | installedFile becomes the installed file |
Normal to OutOfDate | SyncWithServer | Server main file id differs from installedFile.id. Only Normal is promoted, so Invalid is never masked as merely out of date |
any to Invalid | Validation | See the reason table below |
Invalid to Normal or OutOfDate | PerformModsValidation | Revalidates cleanly. OutOfDate when installedFile.id differs from latestUpdatedFile.id |
any to Uninstalled | Uninstall start | Written before files are removed, so an interrupted uninstall leaves a resumable marker. Skipped when another user still claims the mod |
Pending to Normal or OutOfDate | Install failed over an existing install | OutOfDate when installedFile.id < latestUpdatedFile.id. A failed first-time install removes the record |
Deciding whether a mod is safe to load
- Call
GetInstalledModsat the moment you load content, not once at boot. The list changes while the player is in a menu. - Reject
PendingandInvalid. Also rejectUninstalled, since the SDK is still retrying the removal. That leavesNormal,OutOfDateandModified. - Reject
enabled == false, the player's persisted choice. Load a mod withdynamicContent == truethe same as any other: its assets need to be on disk so other players in a session can see it.dynamicContentgates use by the local player, not loading, so check it separately wherever your game grants functionality, such as equipping a cosmetic. - Resolve the location by combining the mods root with
pathOnDisk, then sort byloadOrderascending if your game is load-order sensitive.
#include <cfcore_context.h>
#include <library/cfcore_library.h>
#include <library/models/installed_mod.h>
void UMyModLoader::CollectLoadableMods() {
cfcore::CFCoreContext::GetInstance()->Library()->GetInstalledMods(
cfcore::ICFCoreLibrary::FGetInstalledModsDelegate::CreateLambda(
[](const TArray<FInstalledMod>& InstalledMods) {
TArray<FInstalledMod> Loadable;
for (const FInstalledMod& Mod : InstalledMods) {
if (!Mod.enabled) {
continue;
}
if ((Mod.status == EInstalledModStatus::Pending) ||
(Mod.status == EInstalledModStatus::Invalid) ||
(Mod.status == EInstalledModStatus::Uninstalled)) {
continue;
}
// Mod.dynamicContent still loads here, so its assets render for
// other players. Gate the local player's use of it separately.
Loadable.Add(Mod);
}
Loadable.Sort([](const FInstalledMod& A, const FInstalledMod& B) {
return A.loadOrder < B.loadOrder;
});
// Mount each Loadable[i].pathOnDisk relative to the mods root.
}));
}
Blueprint: Get Installed Mods has an on_installed_mods pin carrying an installed_mods array plus an on_error pin. Loop, break each FInstalledMod, gate on status and enabled. A mod with dynamicContent true still loads the same way, gate the local player's use of it separately. To split and sort in one step use the Blueprint-pure SplitInstalledMods helper on UCFCoreBPLibrary (cfcore|Utility): it takes InInstalledMods and an FSplitInstalledModsOptions and returns OutFirstInstalledMods and OutSecondInstalledMods. Both option members are BlueprintReadOnly, byEnabled defaulting to false and sortByLoadOrder to true.
Installing a mod
Three C++ Install overloads, two Blueprint nodes. The overload supporting more params is the preferred choice beyond the simplest case.
- Obtain an
FCFCoreModfrom the API. - Choose the file. An empty
FFile, or one whoseidis0, installs the latest file. Install Mod Extended withInFileunconnected therefore behaves exactly like Install Mod. - Fill in
FInstallModAdditionalParamsfor tracking, throttling, dynamic content or a non-default origin. - Call
Install. The request is validated synchronously, then enqueued. Your first progress callback isELibraryProgressState::Pending. - Follow progress through the per-call delegate, or
OnModInstallProgressfor a shared UI. Handle completion: the finishedFInstalledModon success, anFCFCoreErroron failure.
Synchronous validation rejects bad requests before enqueueing: no file to install gives MissingLatestFileInformation, an empty downloadUrl gives MissingFileInformation, a file whose modId does not match the mod gives FileNotBelongingToMod, a mod id of 0 or a gameId mismatch gives InvalidModParams, and a mod already queued gives ModAlreadyBeingInstalled.
#include <library/models/install_mod_additional_params.h>
void UMyModManager::InstallLatest(const FCFCoreMod& Mod) {
FInstallModAdditionalParams Params;
Params.origin = EModInstallOrigin::ModPage;
Params.tracking.Add(TEXT("surface"), TEXT("in_game_browser"));
cfcore::CFCoreContext::GetInstance()->Library()->Install(
Mod,
FFile(), // id == 0 means "install the latest file"
Params,
cfcore::ICFCoreLibrary::FInstallProgressDelegate::CreateLambda(
[](const FLibraryProgress& Progress) {
UE_LOG(LogTemp, Display, TEXT("%s: %d%% (%lld B/s)"),
*UEnum::GetDisplayValueAsText(Progress.state).ToString(),
Progress.dataTransfer.progress,
Progress.dataTransfer.transferRateBytesPerSecond);
}),
cfcore::ICFCoreLibrary::FInstalledDelegate::CreateLambda(
[](const TOptional<FInstalledMod>& OptInstalled,
const TOptional<FCFCoreError>& OptError) {
if (OptError.IsSet()) {
UE_LOG(LogTemp, Error, TEXT("Install failed: %s"),
*OptError.GetValue().description);
return;
}
// OptInstalled.GetValue().pathOnDisk is relative to the mods root.
}));
}
Blueprint: Install Mod has mod, on_progress, on_installed and on_error pins. Install Mod Extended has InMod, InFile, InAdditionalParams, OnProgress, OnInstalled and OnError. Check the error path before reading the optional installed mod, which is only meaningful on success.
Installs are queued. FCFCoreSettings::maxConcurrentInstallations caps parallelism and defaults to cfcore::kCFCoreDefaultMaxConcurrentInstallation, which is 3. The queue applies FMath::Max(1, maxConcurrentInstallations), so values below 1 behave as 1, and requests beyond the cap sit in the queue reporting Pending.
Installing a mod already present at the same file id, with a status that does not require reinstall, transfers nothing and registers the calling user context against the existing content. That is the supported way to give a second profile on the machine access to content already on disk.
Install additional parameters
Every field on FInstallModAdditionalParams is BlueprintReadWrite, so Blueprint can build the struct with a Make node and feed InAdditionalParams.
| Field | Type | Default | What it does |
|---|---|---|---|
tracking | TMap<FString, FString> | empty | Key and value pairs sent with the download, for custom partner reports. For example separating browser installs from server-join installs |
throttleDownloadKbps | int32 | 0 | Download cap in kilobytes per second. 0 means no throttling. 100 means 102,400 bytes per second |
dynamicContent | bool | false | Install as dynamic content: assets land on disk but the mod is not owned or player-installed |
origin | EModInstallOrigin | ModPage | Where the install came from, for attribution |
EModInstallOrigin | Numeric | Meaning | Set by the SDK |
|---|---|---|---|
ModPage | 0 | Installed from a mod page | Struct default |
ServerModule | 1 | Came from a server module | Forced by the client and server join flow |
SubscribeModule | 2 | Came from the subscription flow | Forced by the subscription sync service |
DynamicDownload | 3 | A dynamic-content download | Forced when dynamicContent is true |
ResumeDownload | 4 | A resumed, interrupted download | Reported in install analytics |
ModTile | 5 | Installed from a mod tile, distinct from opening the full mod page | Passed by the caller. Nothing in cfcore sets it |
The SDK writes reserved keys into tracking. On any install over an existing installation it adds type, set to update when the file id changes, fix when preInstallStatus was Invalid, and other otherwise. A dynamic-content install adds dc set to true. A resumed chunked download adds resume set to true. The client-server join path separately adds origin set to join_srv on the mods it installs, see multiplayer and servers. Pick your own key names outside type, dc, resume and origin.
throttleDownloadKbps and parallel chunked downloading are mutually exclusive. FCFCoreSettingsDownloads::maxParallelChunks (range 1 to 32, default 1) sets how many chunks download at once, and any throttleDownloadKbps above 0 makes the SDK ignore it and fetch one chunk at a time. Throttling is very inefficient for large files, so reserve it for small downloads; its intended use is background installs during gameplay, where you do not want to hurt the player's ping. Parallel chunking is also forced to one chunk on PS5, where parallel chunks corrupt downloaded files. Disk write pressure is capped separately by FCFCoreSettingsThrottling::diskWriteBytesPerSec, where 0 means no limit.
Dynamic content
Dynamic content solves one multiplayer problem: player A owns a cosmetic, and players B and C need to see it without owning it. Install with dynamicContent set to true; the SDK forces origin to DynamicDownload and adds the dc tracking entry, so do not set origin yourself. The resulting FInstalledMod has dynamicContent set to true: render it for other players, do not grant its use to the local player. The flag persists per user. If the player later buys or installs the mod, call the normal install path; the SDK flips dynamicContent to false without redownloading, provided the requested file id already matches installedFile.id and the status is not Pending, OutOfDate, Invalid or Uninstalled. See Premium dynamic content enforcement for how ownership is enforced on top of this.
Blueprint: the flag lives on the InAdditionalParams pin of Install Mod Extended and comes back on the dynamicContent member of the broken-out FInstalledMod.
If you host premium dynamic content, populate FCFCoreSettings::dynamicContentCategoryIds with the category ids that carry it. The SDK uses that set to enforce the premium ownership check on dynamic content, which covers a player editing local library metadata. Without it the check has nothing to key on. To have premium mods enabled for your game, contact cfforstudios@overwolf.com. See Premium dynamic content enforcement for the full enforcement mechanism.
Tracking progress
Progress arrives as FLibraryProgress, mirrored onto FInstalledMod::installProgress during installation, so a UI bound to the installed-mod list can read progress without a separate subscription.
FLibraryProgress field | Type | Meaning |
|---|---|---|
modId | int64 | The mod this progress belongs to |
fileId | int64 | The file revision being transferred |
state | ELibraryProgressState | Current phase. Defaults to Pending |
dataTransfer | FLibraryProgressDataTransfer | Numbers for the current phase |
FLibraryProgressDataTransfer field | Type | Meaning |
|---|---|---|
progress | int32 | Percent, 0 to 100 |
transferredBytes | int64 | Bytes so far. Only relevant while downloading or uploading |
transferRateBytesPerSecond | int64 | Current rate. Only relevant while downloading or uploading |
filename | FString | File being operated on. Only relevant for zip, copy and move |
ELibraryProgressState in declaration order. Only Pending has an explicit value; the rest are sequential.
| Value | Numeric | What is happening | Emitted by a Library flow | Terminal |
|---|---|---|---|---|
Pending | 0 | Queued, waiting for a free install slot | Yes | No |
Downloading | 1 | Transferring the file. Byte count and rate are meaningful | Yes | No |
Uploading | 2 | Transferring data to the server | Declared only | No |
Validating | 3 | Checking the content, including hash verification | Yes | No |
PendingUnzipping | 4 | Waiting to start extraction | Yes | No |
Unzipping | 5 | Extracting. filename is meaningful | Yes | No |
PendingZipping | 6 | Waiting to start compression | Declared only | No |
Zipping | 7 | Compressing | Declared only | No |
Patching | 8 | Applying a delta patch to the previously installed file | Yes | No |
Copying | 9 | Moving or copying content into place | Yes | No |
CleaningUp | 10 | Removing temporary files | Yes | No |
Cancelling | 11 | Declared for in-flight cancellation. No code path sets it in the current release | Declared only | No |
SuccessfullyCompleted | 12 | Finished successfully | Yes | Yes |
FailedToComplete | 13 | Failed, including when cancelled | Yes | Yes |
The two channels are not redundant. The per-call delegate reports only your install. OnModInstallProgress and OnModInstalled report every install in the process, including ones started elsewhere in your game, by the subscription flow, or by the bundled browser UI. Bind the multicast events once, where you own the shared install UI, and use the per-call delegate as the completion signal for your own request.
The Blueprint Initialize node clears these two library delegates. On successful initialization it calls Clear() on ICFCoreLibrary::OnModInstallProgress() and ICFCoreLibrary::OnModInstalled(), then binds its own forwarders so it can rebroadcast through the subsystem's assignable OnModInstallProgress and OnModInstalled properties. Any C++ handler bound to the library delegates before that point is silently discarded, with no warning and no error.
This only bites a project that mixes bindings. If you initialize in Blueprint and want install events in C++, either bind your C++ handlers after the Blueprint initialize callback has fired, or bind to the subsystem's assignable delegates instead of the library's. If you initialize in C++, nothing clears your handlers.
Blueprint: OnModInstallProgress and OnModInstalled are assignable events on CFCoreSubsystem in cfcore|Library; bind them with Assign or Bind Event to. OnModInstallProgress passes a Progress of type FLibraryProgress. OnModInstalled passes an InstalledMod of type FInstalledMod and, unlike the C++ multicast delegate, carries no error parameter. For readable sizes use the Blueprint-pure helpers on UCFCoreBPLibrary (cfcore|Utility): FormatFileSize returns a B, KB, MB or GB string, BreakFileSize returns an FCFCoreFileSize with separate kb, mb and gb members.
Cancelling an installation
- Call
CancelInstallation(ModId, FCancelInstallDelegate), whose delegate carries a singleTOptional<FCFCoreError>. Cancel by mod id: there is no install handle in this API. InstalledModNotFoundmeans no local record exists for that mod id.FailedToCancelActionmeans no install request for that mod id is in the queue, which is the case once the install finished or was never enqueued.- The cancelled path reports
InstallCancelledorDownloadCancelled, withFailedToCompleteas the terminal progress state. The SDK never emitsCancelling, so do not wait for it.
Blueprint: Cancel Mod Installation (cfcore|Library), pins mod_id, on_success and on_error.
Updating mods
There is no separate update call on ICFCoreLibrary. An update is an install of a newer file over an existing installation, and the SDK detects that shape. If you use the shipped mod browser instead, its UCFCoreUISubsystem does expose a distinct UpdateMod(const FCFCoreMod& mod, EModInstallOrigin InstallOrigin), which internally calls the same install path with an empty file.
- Call
SyncWithServer. This populateslatestUpdatedFileand flips eligibleNormalmods toOutOfDate. - Call
GetInstalledModsand select mods whosestatusisOutOfDate, or whoselatestUpdatedFile.iddiffers frominstalledFile.id. Those sets are not identical: a mod sitting atInvalidkeeps that status through a sync but still gets a freshlatestUpdatedFile. - Call
Installfor each, passing an emptyFFileorlatestUpdatedFileexplicitly. On successinstalledFilebecomes the new file andstatusreturns toNormal.
Blueprint: Synchronize Installed Mods with Server (pins on_success, on_error), then Get Installed Mods, then Install Mod or Install Mod Extended per mod.
Call SyncWithServer sparingly. It is a whole-library server round trip. Legitimate triggers are startup, returning to the main menu, and the player opening or refreshing a mod browser. Never on tick.
SyncWithServer skips mods in the installation queue, so it will not overwrite latestUpdatedFile or status under an in-flight install. It also skips mods whose details.id is 0 or whose details.gameId does not match your configured gameId, so unmanaged mods are never synced. Separately, if the requested file id already matches installedFile.id and the status is not Invalid, Pending or Uninstalled, the install completes early without transferring anything, so you do not need to pre-filter for "already up to date".
The dynamic content path uses a different, wider status set for the same decision. It re-installs on Pending, OutOfDate, Invalid or Uninstalled, so it adds OutOfDate to the three above. The two sets are deliberately different: a normal install treats an out-of-date mod as present and leaves it alone, while the dynamic content pass refreshes it. If you compare the two behaviours and see a discrepancy, that is why.
Delta patch updates
When a newer file is available the SDK can download a binary patch against the file already on disk instead of the whole archive. You do not orchestrate this: during an install classified as an update the SDK checks for a delta itself, and if it uses one you see ELibraryProgressState::Patching. The install runs as a sequence of strategies where the first success wins, so a delta strategy that bails out falls through to the full download. ICFCoreApi::GetModFileDeltaDiff(mod_id, new_file_id, old_file_id, delegate) asks whether a delta exists; its FGetModFileDeltaDiffDelegate carries a TOptional<FFileDeltaDiff> and a TOptional<FCFCoreApiResponseError>.
Blueprint: there is no delta-diff node on CFCoreSubsystem in the current release, so querying a delta explicitly is C++ only. Blueprint-only projects still get delta updates, because the check happens inside the install flow.
FFileDeltaDiff field | Type | Meaning |
|---|---|---|
id | int64 | Delta diff record id |
gameId | int64 | Game the delta belongs to |
modId | int64 | Mod the delta belongs to |
oldFileId | int64 | Source file the patch applies to |
newFileId | int64 | File the patch produces |
status | ECFCoreFileDeltaDiffStatus | Availability |
downloadUrl | FString | Where the patch is fetched from |
fileName | FString | Patch file name |
dateCreated | FDateTime | When it was created |
dateModified | FDateTime | When it was last modified |
fileLength | int64 | Patch length |
fileSizeOnDisk | int64 | Patch size on disk |
hashes | TArray<FFileHash> | Hashes for the patch file |
extraJsonData | FString | Raw JSON. The SDK parses it for the diff method and per-file patch plan, and rejects the delta when it cannot |
ECFCoreFileDeltaDiffStatus | Numeric | Meaning |
|---|---|---|
None | 0 | No delta |
Generating | 1 | Still being produced server side |
Approved | 2 | Available for use |
Deleted | 3 | Removed |
Delta updates are gated by the server-side rollout flag FMePhasingData::deltaDiffs, which is 0 in the model. Every phasing property is an integer from 0 to 100 where 0 means disabled for all, so a player may or may not receive delta updates independently of your build. Your update flow must not depend on Patching appearing. To have delta diffs enabled for your game, contact cfforstudios@overwolf.com.
Patch failures have their own codes and all fall back to a full download. BindiffInvalidHeader and BindiffFailedToApplyPatch come from the binary diff services; UnsupportedDiffMethod from the apply-patch command when the payload names a method this client has no service for; UnsupportedStrategy and UnsupportedSchemaVersion when the feature is off, the request is not an update, or the schema is unsupported. The last two need no game-side handling.
Resumable downloads
An interrupted download does not have to start over. FInstalledMod::downloadInfo carries the bookkeeping.
FInstalledModDownloadInfo field | Type | Meaning |
|---|---|---|
canResume | bool | Whether the current download is resumable |
fileId | int64 | File the partial download belongs to |
lastModified | FDateTime | When the temporary file was last written |
fileSizeInBytes | int64 | Full expected size |
tempFile | FString | Absolute path to the temporary file |
downloaded | TArray<FDownloadedChunk> | Byte ranges already on disk |
Each FDownloadedChunk is a from_bytes and to_bytes pair, which is how the SDK reconstructs the missing ranges when maxParallelChunks is above 1. A resumable partial download survives cleanup for up to four days from downloadInfo.lastModified, and a forced cleanup deletes it regardless of age. Resumed installs report the origin ResumeDownload in install analytics. Resumption is gated by the server-side flag FMePhasingData::resumableDownloads, which is 100 in the model, so do not build UI that promises it as a guarantee.
Enabling, disabling and load order
enabled and loadOrder are player choices that must survive a restart. UpdateInstalledModsProperties is the only sanctioned way to persist either of them, load order included.
- Build one
FInstalledModPropertiesper mod you are changing. - Identify the mod with
id(the localFGuid) ormodId. If both are set,idwins. For unmanaged mods you must useid, because they have no CurseForge mod id. - Set both
enabledandloadOrder. Both are written, so always send the current value of the one you are not changing. - Submit the whole array in one call and wait for the completion delegate before re-reading the installed list.
FInstalledModProperties field | Type | Default | Meaning |
|---|---|---|---|
id | FGuid | invalid | Local installation id. Takes precedence over modId. The only way to address an unmanaged mod |
modId | int64 | 0 | CurseForge mod id. Used when id is unset |
enabled | bool | true | Whether the current user context has the mod enabled |
loadOrder | int32 | MAX_int32 | Load order. MAX_int32 means unordered |
#include <library/models/installed_mod_properties.h>
void UMyModManager::SetModEnabled(const FInstalledMod& Mod, bool bEnabled) {
FInstalledModProperties Props;
Props.id = Mod.id; // Works for managed and unmanaged mods.
Props.enabled = bEnabled;
Props.loadOrder = Mod.loadOrder; // Preserve the existing order.
cfcore::CFCoreContext::GetInstance()->Library()
->UpdateInstalledModsProperties(
TArray<FInstalledModProperties>{ Props },
cfcore::ICFCoreLibrary::FUpdateInstalledModsPropertiesDelegate
::CreateLambda([](const TOptional<FCFCoreError>& OptError) {
// OptError is set when nothing was persisted.
}));
}
Blueprint: Update Installed Mods Properties (cfcore|Library), pins InInstalledModsProperties, OnSuccess and OnError. For drag-and-drop reordering use the Blueprint-pure UpdateInstalledModsLoadOrder helper on UCFCoreBPLibrary (cfcore|Utility): it takes InInstalledMods, InModIndexToUpdate and InNewLoadOrder, and returns OutOrderedInstalledMods for display plus OutOrderedInstalledModsProperties to feed straight into the node above.
The mod browser ships parallel helpers on CFCoreUIInstallProgressModHelperFunctionsLibrary that operate on its own FInstallProgressMod wrapper instead of FInstalledMod directly, including one that emits an FInstalledModProperties array ready to feed into Update Installed Mods Properties. Use those if you are extending the shipped UI rather than building your own load-order screen.
These are per-user properties. Disabling a mod for one player on a shared device does not disable it for another.
Validating installed mods
Players edit mod folders, antivirus quarantines files, disks fail mid-write. Validation finds that out before the content reaches your loader. Two passes exist and they do not check the same things.
| Pass | Who runs it | What it checks |
|---|---|---|
| Quick validation | The SDK, for example at library initialization | Resolves Pending leftovers from a previous run, checks that every directory named in installedFile.modules exists, and (only when FMePhasingData::modFileSizeValidation is enabled for the player) that the summed on-disk size matches installedFile.fileSizeOnDisk. Skips unmanaged mods and mods already at Invalid |
PerformModsValidation | Your game | Fetches server-side file details for each installedFile.id, computes per-module fingerprints, and compares them against the server's FFileModule::fingerprint |
- Pass the array from
GetInstalledModsorGetSystemInstalledMods, or a subset. The delegate carries aTOptional<TArray<FInstalledMod>>of the invalid mods plus aTOptional<FCFCoreError>; test the error first. - Any mod found invalid has its status set to
Invalid, and that status is written to the on-disk state. The returned set is drawn from all user contexts on the machine, filtered to the mod ids you passed. Because it persists, you can find invalid mods later withGetInstalledModsalone. - Recover by reinstalling.
Invalidis one of the statuses that makes a subsequentInstallreal work rather than a no-op. A mod that revalidates cleanly moves back toNormalorOutOfDate.
A mod is marked Invalid for any of these reasons. The first three are the SDK's own documented list; the rest are the recorded reasons in the validation implementation.
| Reason | Which pass records it |
|---|---|
| The mod id is unknown to the CurseForge servers | PerformModsValidation, logged as "Server details missing" when no server file matches installedFile.id |
| The mod does not exist on the local disk | Both, via the module and size checks |
| The mod does not match the server-side checksum hash | PerformModsValidation, via per-module fingerprints |
The installed-file record is invalid: installedFile.id is 0, or installedFile.modId does not match details.id | Both |
A directory named by one of installedFile.modules is missing on disk | Quick validation |
| Fingerprint calculation failed, or a computed fingerprint does not match the server's | PerformModsValidation |
Total on-disk size does not match installedFile.fileSizeOnDisk | Quick validation, and only when FMePhasingData::modFileSizeValidation is enabled for the player |
Blueprint: Perform Mods Validation (cfcore|Library), pins installed_mods, on_success and on_error. The on_success delegate carries invalid_installed_mods, containing only the mods that failed.
The checksum process can be lengthy depending on mod size, and the fingerprint step reads the module content of every mod you pass. Run it where a pause is acceptable: a menu, a launcher screen, or an explicit "verify files" button. Not during gameplay, and not on a loading screen you have promised will be fast.
Files your game generates at runtime inside a mod folder would break the size and fingerprint checks. Add them to FCFCoreSettings::ignoredDynamicModFiles, which already contains assetregistry.bin. Keep the filenames lower case, as the setting's comment requires, because the SDK lower-cases before matching. The quick pass never clears an Invalid verdict on its own: use PerformModsValidation or a reinstall.
Uninstalling a mod
- Call
Uninstallwith the CurseForge mod id. Its delegate carries the removedFInstalledModand aTOptional<FCFCoreError>, so you can update UI without a re-query.InstalledModNotFoundmeans no local record exists. - If more than one user context claims the mod, the SDK removes the current user's claim and leaves the files. Shared content is not deleted out from under another player.
- If this was the last claim, the SDK marks the record
Uninstalled, then removes the files. It proceeds with removal even if writing the marker failed. If removal fails partway the record staysUninstalled: ignore it in your loader, and the SDK retries later, for example at the next initialization.
Blueprint: Uninstall Mod (cfcore|Library), pins mod_id, on_uninstalled and on_error.
Do not hold open file handles into a mod directory while an install, update or uninstall can run. The SDK needs write and delete access to those paths. Unmount and release mod content first.
Uninstall is addressed by mod id, so it cannot target an unmanaged mod, whose details.id is 0. Unmanaged removal happens through the SDK's own scan: when the folder disappears, the record goes with it.
Unmanaged mods
Unmanaged mods are folders in the player's mods directory that were not installed through the plugin, which usually means they do not exist on CurseForge. The reason to support them is creator workflow: a mod author can drop a work-in-progress build into the folder and iterate before uploading.
FCFCoreSettingsUnmanagedMods field | Type | Default | What it does |
|---|---|---|---|
enabled | bool | false | Detect unmanaged mods in the mod directory and treat each as an unmanaged installed mod |
scanOneLevelUp | bool | false | Also scan one level up. Applies when modsDirectoryMode is CFCore; implemented as a pass over the mods root that ignores the per-game subfolder, so it has no effect in Flat mode |
- Detected folders appear in
GetInstalledModswithunmanagedset totrue,enabledset totrue,details.idat0,details.nameset to the folder name, anddetails.gameIdset to your configured game id. - They can be enabled, disabled and ordered like any other mod, but only through
FInstalledModProperties::id. - They are skipped by quick validation and never synced with the server. If the folder is deleted, the SDK removes the record on its next scan.
- An unmanaged mod whose status is anything other than
Normalis treated as invalid by the scan and dropped from the tracked set. If a folder with the same relative path is rescanned, the record is reinstated atNormal. - The scan ignores anything that is not a directory, and any entry whose name starts with a dot.
With modsDirectoryMode set to Flat, a stale or partly removed managed mod folder can be picked up by this scan as an unmanaged mod. The SDK guards one direction only, by excluding unmanaged mods when it decides which folders are stale during cleanup. The safer configuration is CFCore mode plus a mods directory your game controls.
Disk space and temp cleanup
Running out of disk mid-install produces a failure the player will blame on your game. Check first.
- Call
GetModsDirInfobefore a batch of installs. Its delegate carries aTOptional<FModsDirInfo>and aTOptional<FCFCoreError>. - Compare
freeDiskSizeInBytesagainst the sum ofFFile::fileSizeOnDiskfor everything you are installing. That value is the extracted footprint, since it is what the SDK validates the summed on-disk size against. Leave headroom for the downloaded archive and the temp directory, which live under the mods directory during an install. - If space is tight, call
CleanTempDir. Present sizes withFormatFileSizeorBreakFileSizerather than raw bytes.
FModsDirInfo field | Type | Meaning |
|---|---|---|
pathOnDisk | FString | Absolute path to the mods directory, including [game id] in CFCore mode. Do not combine it with FInstalledMod::pathOnDisk |
totalDiskSizeInBytes | int64 | Total size of the disk the mods directory sits on |
freeDiskSizeInBytes | int64 | Free space on that disk |
Blueprint: Get Mods Directory Info (cfcore|Library), pins OnModsDirInfo and OnError, where OnModsDirInfo carries a ModsDirInfo of type FModsDirInfo. To reclaim space, Clean Temp Directory, pins OnSuccess and OnError.
CleanTempDir does more than clean the temp directory. It runs the SDK's cleanup with forced cleanup on, which also scans the mods directory and deletes CFCore-mode mod folders that no installed mod claims. Unmanaged mods are excluded from the claimed set on purpose. The whole cleanup is gated on the server-side flag FMePhasingData::deleteStaleModDirs, which is 100 in the model, so on a client where it is off the call does nothing. It will not delete files currently downloading or installing, so it is safe as a player-facing "free up space" action, but it does remove resumable partial downloads, so an interrupted download restarts from the beginning afterwards.
Errors specific to install and management
FCFCoreError carries isError, a code of type ECFCoreErrorCodes, a nested apiError of type FCFCoreApiResponseError, and a description. Branch on code, never on description. The full code list lives on Error handling. Beyond the request-validation, cancel and delta codes named above, these occur in Library flows: FailedToDownloadFile (retry, a resumable download picks up where it stopped), DownloadedFileHasInvalidHash (retry, do not load the content), FailedToUnzip (check free space first), FailedToMoveModDirectory, FailedDeletingOutputDirectory and FailedDeletingOutputFile (usually a held file handle or permissions), FileSystemError (surface a disk-level message, not a mod-level one), ModsNotOwnedByUser (route the player to purchase first), and MissingModsDetails (commonly a non-standard local mod that is not on the CurseForge servers).
Escalation
For CurseForge-side enablement of anything referenced here, including premium mods, dynamic content ownership checks and delta diff rollout, contact cfforstudios@overwolf.com. Game configuration and API keys live in the Developer Portal at console.curseforge.com.