You are viewing a potentially older version of this package. View all versions.
CeruleanCutlass-Cloudward-0.1.1 icon

Cloudward

Sync your Outward saves across devices via a mounted/shared folder: boot-time reconcile, offline-first play, fork detection/resolution, crash-safe session lock. Verified across real devices; some offline/crash-recovery edge cases still being staged.

Date uploaded 2 days ago
Version 0.1.1
Download link CeruleanCutlass-Cloudward-0.1.1.zip
Downloads 4
Dependency string CeruleanCutlass-Cloudward-0.1.1

This mod requires the following mods to function

BepInEx-BepInExPack_Outward-5.4.19 icon
BepInEx-BepInExPack_Outward

BepInEx pack for Outward.

Preferred version: 5.4.19
CeruleanCutlass-ForgeKit-0.1.0 icon
CeruleanCutlass-ForgeKit

Dependency-free dev-tooling for Outward BepInEx mods: file-driven dev command loop, self-test harness, on-screen toasts, player-ready lifecycle wait, and embedded/override table loaders.

Preferred version: 0.1.0

README

Cloudward — sync your saves across devices

📖 Full documentation: Cloudward wiki page

Actively in use across real devices. The compute layer (mount guard, fork detection, lock/lease, reconciliation) is fully unit-tested, and the full sync loop — boot-time reconcile, offline play, clean two-device handoff, and both paused and auto fork resolution — has been live-verified across real physical machines over a real NAS share, including three bugs found, fixed, and re-verified in that process. Still being staged: some offline/reconnect edge cases and crash-recovery scenarios that need a human at the keyboard to exercise. It is inert by default (Enable=false) — installing it changes nothing until you turn it on. Back up your SaveGames/ folder before your first real sync.

Outward has no built-in way to carry a save between two computers — Steam Cloud is deliberately unsupported for it. Cloudward fills that gap: point it at a folder mounted on every device you play on (a NAS share, a Syncthing folder, a mapped drive) and it keeps your character in sync.

Requires: BepInEx 5 (Outward's Mono branch — see Compatibility) and ForgeKit. No SideLoader.

Installing

Install from Thunderstore and the dependency comes with it. Installing by hand: drop ForgeKit/ and Cloudward/ side by side into BepInEx/plugins/. If BepInEx logs a dependency error and refuses to load Cloudward, ForgeKit is missing.

How it works

The game always writes to its own local save folder — Cloudward never redirects that. The mount you point it at is a synced replica, not the source of truth:

  • At boot, Cloudward reconciles: it pulls in anything newer on the share before the character list loads, so a character you played on another device shows up here.
  • While you play, completed save snapshots push out to the share once you're online and holding the sync lock.
  • Offline just works. No mount, no marker, or another device holding the lock — Cloudward plays entirely from local and catches up next time it can reach the share.

A marker file in the mount root is how Cloudward tells "actually mounted" from "empty directory because the share isn't connected right now" — an unmounted mountpoint looks exactly like an empty folder to your OS, so without the marker a sync could silently start writing to local disk instead of the share. Cloudward creates the marker itself on first run (config-only setup), remembering it did so per mount path — so a later launch with the share disconnected is correctly read as "offline", not re-marked onto the placeholder. A share that already holds real save data always has its marker restored. Set AutoCreateMarker=false for strict manual setup, where syncmarker is required.

A session lock (a small heartbeat file on the share) makes sure only one device pushes at a time. It's crash-safe: a lock whose heartbeat goes stale (the device crashed, lost power, whatever) is reclaimed automatically — nothing can wedge your saves.

If two devices genuinely diverge

If the same character was played offline on two devices before either could sync, Cloudward resolves it automatically per the [Sync] ForkResolution setting — and always backs up the version it doesn't keep to .cloudward-forks/, so nothing is ever lost:

  • NewestWins (default) — keep whichever device has the most recent in-game save.
  • ThisDevice / OtherDevice — always keep this / the other device's version.
  • KeepBoth — keep this device's version live and preserve the other (v1: a restorable backup; a true second save slot is planned for v2).
  • AskMe — don't guess; wait for you to run syncresolve local|share|both.

syncresolve is always available as a manual override.

Setup

  1. Mount the same shared folder on every device you play Outward on (NFS/SMB/autofs on Linux or Deck; a mapped drive or UNC path on Windows; a local Syncthing folder also works). Low-latency is best — a LAN share or local sync folder, not something with multi-second round trips.

  2. Launch Outward once so Cloudward generates its config file, then quit. The file is:

    BepInEx/config/cobalt.cloudward.cfg
    

    (Outward and BepInEx rewrite this file when the game exits, so edit it only while the game is closed — changes made while it's running are overwritten on quit.)

  3. Open that file and set two keys in the [Sync] section — Enable and MountPath. Example:

    [Sync]
    Enable = true
    MountPath = Z:/mnt/nas/outward
    

    MountPath format: Outward runs under Proton on Linux and the Steam Deck, so the path is a Wine drive path — Z: maps to the filesystem root /. For a share mounted at /mnt/nas/outward you'd write MountPath = Z:/mnt/nas/outward. On native Windows, use a normal path or UNC, e.g. MountPath = Z:\OutwardSync or MountPath = \\NAS\OutwardSync. Use the same shared folder (and the same Steam account) on every device → they all resolve to one SaveGames/<SteamID> folder on the share.

  4. Relaunch. That's it — Cloudward auto-creates the mount marker on first run. (Prefer to bless the share by hand? Set AutoCreateMarker = false and run the syncmarker command, or touch <mount>/.outward-sync-root, once.)

To choose how conflicts resolve, set [Sync] ForkResolution (see the settings table below) — e.g. ForkResolution = ThisDevice. The default, NewestWins, is usually what you want.

Settings

BepInEx/config/cobalt.cloudward.cfg, created on first launch:

Setting Default What it does
[Sync] Enable false Master switch. Off = vanilla local-only saves.
MountPath "" Root of the shared/mounted folder to sync through. Empty = inert.
MarkerFileName .outward-sync-root Sentinel file that marks the share as actually mounted.
AutoCreateMarker true Auto-create the marker so config-only setup works (just set MountPath). Created once per mount path and remembered, so an absent marker on a later launch reads as "disconnected → offline" rather than being re-stamped onto a placeholder; a share that already holds real data always gets its marker restored. false = strict manual mode (syncmarker required).
OnMountDown FallbackLocal What to do if the mount isn't live at launch. FallbackLocal = play local + warn; RefuseAndLog = same, but log louder (useful for headless/dedicated sessions).
OnLockHeld FallbackLocal What to do about pushing when another device holds a live lock (pulls always happen regardless). FallbackLocal = defer pushes and accumulate locally until it frees (safe default); ForceTake = push anyway — only if you're certain the other device is dead.
ForkResolution NewestWins How a diverged character (played offline on two devices) is auto-resolved: NewestWins = keep the most recent in-game save; ThisDevice / OtherDevice = always keep this / the other device's version; KeepBoth = keep this device's live and preserve the other (v1: a restorable backup); AskMe = wait for a manual syncresolve. The version not kept is always backed up to .cloudward-forks/.
[Lock] HeartbeatSeconds 30 How often the held lock's heartbeat refreshes (and the mount gets re-checked).
StaleSeconds 180 A lock heartbeat older than this is dead and reclaimable by any device — crash recovery.
DeviceName your hostname This device's name in the lock file. Must be unique per device — own-device locks reclaim instantly, so two devices sharing a name would never block each other.

Example configuration

BepInEx/config/cobalt.cloudward.cfg — created on first launch. Excerpt (defaults; set at least Enable and MountPath to turn it on):

[Sync]
## Master switch. Off = vanilla local-only saves.
Enable = false
## Root of the shared/mounted folder to sync through. Empty = inert.
MountPath =
## Sentinel file that marks the share as actually mounted.
MarkerFileName = .outward-sync-root
AutoCreateMarker = true
## FallbackLocal | RefuseAndLog
OnMountDown = FallbackLocal
## FallbackLocal | ForceTake
OnLockHeld = FallbackLocal
## NewestWins | ThisDevice | OtherDevice | KeepBoth | AskMe
ForkResolution = NewestWins

[Lock]
HeartbeatSeconds = 30
StaleSeconds = 180
## This device's name in the lock file; defaults to your hostname. Must be unique per device.
DeviceName = <your hostname>

The dev command channel is BepInEx/config/Cloudward_cmd.txt (see the verb table below). Cloudward ships no config-override data tables.

Without any settings changes

Everything is also driven by writing a line into BepInEx/config/Cloudward_cmd.txt — it runs on the next poll, even while the game is paused.

Verb What it does
syncstatus Dump sync state: mode, local/share paths, lock holder, pending forks.
syncnow Force a reconcile + push right now (pushes only — a pull needs the next launch).
syncresolve <local|share|both> [uid] Resolve a paused fork.
synclock / synclock release Show the current lock holder / push-and-release your own.
syncmarker Manually stamp the mount-liveness marker into MountPath (optional — auto-created unless AutoCreateMarker=false).
selftest Sanity-check the install; look for [SELFTEST] … DONE in the log.

Compatibility

Outward must be on its Mono Steam beta branch, not the default IL2CPP build (Properties → Betas → select mono). If the game runs but no BepInEx mods load and there's no crash log, this is almost always why.

License

Apache License 2.0 — see LICENSE. You may use, modify, and redistribute this mod (including in commercial mods) provided you keep the copyright/license notice; see the license text for the full terms.