Skip to main content

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.

info

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.

danger

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 ICFCoreLibraryBlueprint nodeWhat it does
SyncWithServerSynchronize Installed Mods with ServerRefresh every installed mod against the server so statuses reflect available updates
GetInstalledModsGet Installed ModsMods installed for the current game and user context, including ones pending install
GetSystemInstalledModsGet Global Installed ModsEvery mod installed on the machine, across all user contexts
GetModsDirInfoGet Mods Directory InfoMods directory plus total and free disk space
Install(FCFCoreMod, ...)Install ModInstall 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 ExtendedInstall a specific file with tracking, throttling, dynamic-content and origin params
CancelInstallationCancel Mod InstallationCancel an in-flight install by mod id
UninstallUninstall ModRemove an installed mod by mod id
PerformModsValidationPerform Mods ValidationValidate against server file details and per-module fingerprints, flag invalid mods
UpdateInstalledModsPropertiesUpdate Installed Mods PropertiesPersist enabled and loadOrder
CleanTempDirClean Temp DirectoryForced 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.

note

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.

FieldTypeMeaning
idFGuidLocal installation id. The only way to identify an unmanaged mod, which has no CurseForge mod id
dateInstalledFDateTimeSet on every successful install, updates included. Read as "when this file landed", not first install
dateUpdatedFDateTimeWhen the file was updated or installed. Also refreshed by SyncWithServer when the main file changed
statusEInstalledModStatusThe load gate. See below
pathOnDiskFStringPath to the content, relative to the mods root. In CFCore mode it already begins with [game id]
enabledboolWhether the current user context has it enabled. Defaults to true
unmanagedboolNot installed from the CurseForge servers and not detectable by the SDK, so usually not on CurseForge at all
detailsFCFCoreModMod metadata: name, authors, categories, premiumDetails
installedFileFFileThe revision actually on disk
latestUpdatedFileFFileThe main file the server currently reports. During an install, the file being installed. An id differing from installedFile.id means an update is available
dynamicContentboolAssets are on disk but the player does not own the mod and did not request it. Viewable, not usable
loadOrderint32Optional. Defaults to MAX_int32, meaning unordered and last
installProgressFLibraryProgressLive install progress. Transient, never persisted
usersTSet<FString>User context ids that have this mod installed. Not exposed to Blueprint
downloadInfoFInstalledModDownloadInfoResumable-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.

warning

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.

EModsDirectoryModeNumericLayout
None0Unset. Produces MissingModsDirectoryMode
CFCore1[mods directory]/[game id]/[modId]_[fileId]/. The recommended choice
Flat2Content 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.

ValueNumericMeaningSafe to load
Pending0Queued or mid-install. Content on disk is incompleteNo
OutOfDate1Complete on disk, but the server reports a different main fileYes, and offer an update
Normal2Installed, complete, matching the newest known fileYes
Invalid3Deleted or modified on disk. Also set by every validation failure belowNo
WorkingCopy4Reserved for working-copy support. No code path sets it in the current releaseYes
Uploading5Declared, not set by any Library flow in the current releaseNo
Modified6Locally diverged content. The client and server flow treats it as needing reinstall. No Library flow sets it in the current releaseYes
Uninstalled7An uninstall failed partway. Ignore the mod; the SDK retries later, for example at the next initializationNo
TransitionWhereCondition
any to PendingInstall startWritten with preInstallStatus and latestUpdatedFile before transfer
Pending to NormalInstall successinstalledFile becomes the installed file
Normal to OutOfDateSyncWithServerServer main file id differs from installedFile.id. Only Normal is promoted, so Invalid is never masked as merely out of date
any to InvalidValidationSee the reason table below
Invalid to Normal or OutOfDatePerformModsValidationRevalidates cleanly. OutOfDate when installedFile.id differs from latestUpdatedFile.id
any to UninstalledUninstall startWritten before files are removed, so an interrupted uninstall leaves a resumable marker. Skipped when another user still claims the mod
Pending to Normal or OutOfDateInstall failed over an existing installOutOfDate when installedFile.id < latestUpdatedFile.id. A failed first-time install removes the record

Deciding whether a mod is safe to load

  1. Call GetInstalledMods at the moment you load content, not once at boot. The list changes while the player is in a menu.
  2. Reject Pending and Invalid. Also reject Uninstalled, since the SDK is still retrying the removal. That leaves Normal, OutOfDate and Modified.
  3. Reject enabled == false, the player's persisted choice. Load a mod with dynamicContent == true the same as any other: its assets need to be on disk so other players in a session can see it. dynamicContent gates use by the local player, not loading, so check it separately wherever your game grants functionality, such as equipping a cosmetic.
  4. Resolve the location by combining the mods root with pathOnDisk, then sort by loadOrder ascending 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.

  1. Obtain an FCFCoreMod from the API.
  2. Choose the file. An empty FFile, or one whose id is 0, installs the latest file. Install Mod Extended with InFile unconnected therefore behaves exactly like Install Mod.
  3. Fill in FInstallModAdditionalParams for tracking, throttling, dynamic content or a non-default origin.
  4. Call Install. The request is validated synchronously, then enqueued. Your first progress callback is ELibraryProgressState::Pending.
  5. Follow progress through the per-call delegate, or OnModInstallProgress for a shared UI. Handle completion: the finished FInstalledMod on success, an FCFCoreError on 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.

note

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.

note

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.

FieldTypeDefaultWhat it does
trackingTMap<FString, FString>emptyKey and value pairs sent with the download, for custom partner reports. For example separating browser installs from server-join installs
throttleDownloadKbpsint320Download cap in kilobytes per second. 0 means no throttling. 100 means 102,400 bytes per second
dynamicContentboolfalseInstall as dynamic content: assets land on disk but the mod is not owned or player-installed
originEModInstallOriginModPageWhere the install came from, for attribution
EModInstallOriginNumericMeaningSet by the SDK
ModPage0Installed from a mod pageStruct default
ServerModule1Came from a server moduleForced by the client and server join flow
SubscribeModule2Came from the subscription flowForced by the subscription sync service
DynamicDownload3A dynamic-content downloadForced when dynamicContent is true
ResumeDownload4A resumed, interrupted downloadReported in install analytics
ModTile5Installed from a mod tile, distinct from opening the full mod pagePassed by the caller. Nothing in cfcore sets it
warning

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.

warning

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.

warning

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 fieldTypeMeaning
modIdint64The mod this progress belongs to
fileIdint64The file revision being transferred
stateELibraryProgressStateCurrent phase. Defaults to Pending
dataTransferFLibraryProgressDataTransferNumbers for the current phase
FLibraryProgressDataTransfer fieldTypeMeaning
progressint32Percent, 0 to 100
transferredBytesint64Bytes so far. Only relevant while downloading or uploading
transferRateBytesPerSecondint64Current rate. Only relevant while downloading or uploading
filenameFStringFile being operated on. Only relevant for zip, copy and move

ELibraryProgressState in declaration order. Only Pending has an explicit value; the rest are sequential.

ValueNumericWhat is happeningEmitted by a Library flowTerminal
Pending0Queued, waiting for a free install slotYesNo
Downloading1Transferring the file. Byte count and rate are meaningfulYesNo
Uploading2Transferring data to the serverDeclared onlyNo
Validating3Checking the content, including hash verificationYesNo
PendingUnzipping4Waiting to start extractionYesNo
Unzipping5Extracting. filename is meaningfulYesNo
PendingZipping6Waiting to start compressionDeclared onlyNo
Zipping7CompressingDeclared onlyNo
Patching8Applying a delta patch to the previously installed fileYesNo
Copying9Moving or copying content into placeYesNo
CleaningUp10Removing temporary filesYesNo
Cancelling11Declared for in-flight cancellation. No code path sets it in the current releaseDeclared onlyNo
SuccessfullyCompleted12Finished successfullyYesYes
FailedToComplete13Failed, including when cancelledYesYes

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.

warning

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

  1. Call CancelInstallation(ModId, FCancelInstallDelegate), whose delegate carries a single TOptional<FCFCoreError>. Cancel by mod id: there is no install handle in this API.
  2. InstalledModNotFound means no local record exists for that mod id. FailedToCancelAction means no install request for that mod id is in the queue, which is the case once the install finished or was never enqueued.
  3. The cancelled path reports InstallCancelled or DownloadCancelled, with FailedToComplete as the terminal progress state. The SDK never emits Cancelling, 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.

  1. Call SyncWithServer. This populates latestUpdatedFile and flips eligible Normal mods to OutOfDate.
  2. Call GetInstalledMods and select mods whose status is OutOfDate, or whose latestUpdatedFile.id differs from installedFile.id. Those sets are not identical: a mod sitting at Invalid keeps that status through a sync but still gets a fresh latestUpdatedFile.
  3. Call Install for each, passing an empty FFile or latestUpdatedFile explicitly. On success installedFile becomes the new file and status returns to Normal.

Blueprint: Synchronize Installed Mods with Server (pins on_success, on_error), then Get Installed Mods, then Install Mod or Install Mod Extended per mod.

warning

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.

note

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".

note

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 fieldTypeMeaning
idint64Delta diff record id
gameIdint64Game the delta belongs to
modIdint64Mod the delta belongs to
oldFileIdint64Source file the patch applies to
newFileIdint64File the patch produces
statusECFCoreFileDeltaDiffStatusAvailability
downloadUrlFStringWhere the patch is fetched from
fileNameFStringPatch file name
dateCreatedFDateTimeWhen it was created
dateModifiedFDateTimeWhen it was last modified
fileLengthint64Patch length
fileSizeOnDiskint64Patch size on disk
hashesTArray<FFileHash>Hashes for the patch file
extraJsonDataFStringRaw JSON. The SDK parses it for the diff method and per-file patch plan, and rejects the delta when it cannot
ECFCoreFileDeltaDiffStatusNumericMeaning
None0No delta
Generating1Still being produced server side
Approved2Available for use
Deleted3Removed
warning

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.

note

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 fieldTypeMeaning
canResumeboolWhether the current download is resumable
fileIdint64File the partial download belongs to
lastModifiedFDateTimeWhen the temporary file was last written
fileSizeInBytesint64Full expected size
tempFileFStringAbsolute path to the temporary file
downloadedTArray<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.

  1. Build one FInstalledModProperties per mod you are changing.
  2. Identify the mod with id (the local FGuid) or modId. If both are set, id wins. For unmanaged mods you must use id, because they have no CurseForge mod id.
  3. Set both enabled and loadOrder. Both are written, so always send the current value of the one you are not changing.
  4. Submit the whole array in one call and wait for the completion delegate before re-reading the installed list.
FInstalledModProperties fieldTypeDefaultMeaning
idFGuidinvalidLocal installation id. Takes precedence over modId. The only way to address an unmanaged mod
modIdint640CurseForge mod id. Used when id is unset
enabledbooltrueWhether the current user context has the mod enabled
loadOrderint32MAX_int32Load 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.

note

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.

note

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.

PassWho runs itWhat it checks
Quick validationThe SDK, for example at library initializationResolves 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
PerformModsValidationYour gameFetches server-side file details for each installedFile.id, computes per-module fingerprints, and compares them against the server's FFileModule::fingerprint
  1. Pass the array from GetInstalledMods or GetSystemInstalledMods, or a subset. The delegate carries a TOptional<TArray<FInstalledMod>> of the invalid mods plus a TOptional<FCFCoreError>; test the error first.
  2. 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 with GetInstalledMods alone.
  3. Recover by reinstalling. Invalid is one of the statuses that makes a subsequent Install real work rather than a no-op. A mod that revalidates cleanly moves back to Normal or OutOfDate.

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.

ReasonWhich pass records it
The mod id is unknown to the CurseForge serversPerformModsValidation, logged as "Server details missing" when no server file matches installedFile.id
The mod does not exist on the local diskBoth, via the module and size checks
The mod does not match the server-side checksum hashPerformModsValidation, via per-module fingerprints
The installed-file record is invalid: installedFile.id is 0, or installedFile.modId does not match details.idBoth
A directory named by one of installedFile.modules is missing on diskQuick validation
Fingerprint calculation failed, or a computed fingerprint does not match the server'sPerformModsValidation
Total on-disk size does not match installedFile.fileSizeOnDiskQuick 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.

warning

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.

note

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

  1. Call Uninstall with the CurseForge mod id. Its delegate carries the removed FInstalledMod and a TOptional<FCFCoreError>, so you can update UI without a re-query. InstalledModNotFound means no local record exists.
  2. 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.
  3. 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 stays Uninstalled: 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.

warning

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.

note

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 fieldTypeDefaultWhat it does
enabledboolfalseDetect unmanaged mods in the mod directory and treat each as an unmanaged installed mod
scanOneLevelUpboolfalseAlso 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 GetInstalledMods with unmanaged set to true, enabled set to true, details.id at 0, details.name set to the folder name, and details.gameId set 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 Normal is 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 at Normal.
  • The scan ignores anything that is not a directory, and any entry whose name starts with a dot.
warning

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.

  1. Call GetModsDirInfo before a batch of installs. Its delegate carries a TOptional<FModsDirInfo> and a TOptional<FCFCoreError>.
  2. Compare freeDiskSizeInBytes against the sum of FFile::fileSizeOnDisk for 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.
  3. If space is tight, call CleanTempDir. Present sizes with FormatFileSize or BreakFileSize rather than raw bytes.
FModsDirInfo fieldTypeMeaning
pathOnDiskFStringAbsolute path to the mods directory, including [game id] in CFCore mode. Do not combine it with FInstalledMod::pathOnDisk
totalDiskSizeInBytesint64Total size of the disk the mods directory sits on
freeDiskSizeInBytesint64Free 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.

warning

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.