Skip to content

Releases: HarryBMa/pc-gamepak

v1.1.0

Choose a tag to compare

@github-actions github-actions released this 03 Oct 15:51

The release where the cartridge stopped being only a way to carry a game and
started carrying everything around it: the saves, the hours, the compiled
shaders. And where the launcher stopped being the only place a cartridge can
open.

The filesystem default changed to NTFS

exFAT held that place from the start and is the one filesystem that cannot hold
what a cartridge carries. No symlinks, so Steam cannot unpack Proton onto a
cartridge — it dies on the first of 1,892 of them, reporting AppError_11,
"Disk write error", which says nothing about why. No executable bit, so a
carried Linux game has to be a shell script. No permissions that survive a
replug.

Existing cartridges are unaffected: this changes what the wizard offers to
format a new drive as. exFAT is still one dropdown away — and so are five more
now: ext4, XFS, F2FS, HFS+ and APFS, for a cartridge that only ever meets one
kind of machine. The picker says what each costs at the point of choosing: which
desktops read it with nothing installed, whether Proton runs from it, its label
limit, and what would have to be installed first. One list, read from the code
that does the formatting, rather than a copy in the window that had already
drifted. A format this machine has no mkfs for is shown anyway, greyed, with
the package to install — so "why is btrfs missing?" has an answer on screen.

If a Mac has to write to the drive, pick exFAT. macOS reads NTFS and does
not write it, so an NTFS cartridge can be played from and copied off on a Mac
and cannot take a save back.

A side effect: cartridges are now named God of War Ragnarok rather than
truncated to eleven characters, because the volume label limit follows the
filesystem.

Saves travel with the cartridge

Two mechanisms, and which one applies depends on whether the cartridge carries
the game or points at it.

  • save= lines name where a game keeps its saves, as {appdata}/Foo/Saves
    — a role the host resolves, because the three platforms disagree about where
    saves go. Insert and eject copy whichever side changed. If both changed,
    neither is touched and the launcher says so; anything replaced is kept
    beside it, three deep.
  • portable_home=yes gives a game the cartridge carries its whole home
    directory on the drive. Every save it writes lands there with nothing
    declared, nothing copied, and no conflict possible — there is only ever one
    copy.

A carried game's save is pushed to the cartridge as soon as the game exits,
not only on eject. Quitting the game and pulling the drive out is what somebody
who has finished playing does, and it used to cost them the session.

Off until switched on, in Settings. It is the one thing here that writes to a
directory in your home on the say-so of a file on a drive.

The cartridge counts its own hours

Launches, playtime and last-played go in .gamepak/stats.json on the drive, so
the count follows the cartridge between machines rather than staying on the PC
that happened to play it. The open session re-stamps itself once a minute, so a
crash or a pulled drive costs a minute rather than the session — and whichever
machine sees the cartridge next settles whatever the last one abandoned.

For a game the cartridge carries, the launcher started the process and so waits
for it: the session ends when the game exits, not when the window is closed
afterwards. A steam:// game is somebody else's launcher's child and there is
nothing to wait on, so the window's lifetime is still the bound there.

On by default: one small file, written to the drive you just pressed Play on.

Compiled shaders, if the cartridge asks

shader_cache=drive carries Steam's per-game shader cache, which is what stops a
second machine stuttering through the first hour of a game the cartridge has
already played.

Per cartridge rather than a global setting, because it is a trade against the
drive's own speed: free on a fast NVMe cartridge, and a wait on a cheap stick.
Only whoever made the cartridge knows which it is.

Where a cartridge opens is now a choice

The launcher is one front-end, not the only one. It ships with the project and is
on by default; everything else is a plugin, off until switched on. More than one
may be on at once.

  • PC GamePak launcher — the cartridge's own window.
  • Steam Deck row — cartridge games on the Steam home screen, through the
    Decky plugin. Switch the
    launcher off on a Deck and no window appears over the top of it.
  • Playnite library — named, and not built yet.

The whole contract between the launcher and a plugin is one boolean in
settings.json. No socket, no daemon, nothing to keep in step.

And what the launcher does on insert

A new setting with four answers: open the window (the default, and what it has
always done), start the game, post a notification, or do nothing.

Starting the game only ever starts a game your PC already has — a
steam://-class URI. A program carried on the cartridge still waits for a
click, and so does a collection, which has no single game to mean. Nothing on a
cartridge runs without a click, and a setting left switched on is not consent to
run a stranger's binary.

notify_only uses the desktop's own notification on Linux and a
notification-area balloon on Windows. If neither can be posted, the window opens
rather than the insert passing in silence.

Eject now says what is in the way

Before it takes the volume away, it asks the system who is using it and names
them: "TombRaider.exe (4812) is running from the cartridge". Then two choices —
Force quit and eject, which asks each process to quit and waits five seconds
before killing anything, because a game asked to quit writes its save first; or
leave the drive mounted. If something survives being killed, the cartridge stays
mounted and says so.

On Linux this sees open files, memory-mapped files, working directories and
programs running from the drive, and counts the processes it was not allowed to
look at rather than implying the drive is idle. On Windows it sees programs
running from the volume.

A cartridge can be checked against somebody else's

build-cart and verify-cart print a digest: one SHA-256 over every copied
file's path, length and checksum. Verifying answers "did these bytes survive";
the digest answers "is this the same cartridge somebody else built", which no
amount of local verifying can. Put it in a build-cart request as
expectDigest and the build fails if it does not match.

Playtime counts the game, not the window, and lives in the conf

The launcher used to count from Play until its window closed. It now watches
for the game itself — a program running from the game's folder, on the
cartridge or wherever Steam installed it — and keeps counting after the window
is closed, leaving when the game does. The way GameplayTimeTracker and
GamingGaiden count.

It pauses when nobody is there: ten minutes (adjustable, or off) without
keyboard, mouse or controller input, and the idle stretch is taken back out.
Controllers count, which the desktop's own idle clock does not.

Every session is kept, and each game's figures — playtime, launches, first and
last played, the machine, and the last thirty sessions — are written into
cartridge.conf under that game, touching nothing else in the file.

How long to beat, optional and off: the wizard can look each game up on
HowLongToBeat while it writes the cartridge, once, and puts the figures in the
conf beside the playtime. The launcher's ⓘ shows both, and a skin can draw
progress through the main story from them — see
SKINNING.md. HowLongToBeat has no official API,
so this may stop finding anything when the site changes; that is a warning,
never a failed write.

The wizard asks less and gets more right

One screen, in the order the questions come: Games, Drive, Options,
Artwork. The choices that belong to one cartridge — copy the game, verify
the copy, eject when done, erase the drive first and to which filesystem — are
switches on that screen now, starting from the defaults in Settings and
forgotten after the write. They used to exist only in Settings, which made
"erase the drive" a preference that stayed on for every cartridge after it.
Erasing always starts off, and Write asks once more, naming the drive.

Settings lost most of its prose and one switch that did nothing ("Add the
cartridge to Steam's library list" — a copied Steam game is always registered,
or Steam could not play it).

Fixed along the way:

  • Ticking a second game threw an error, so a collection could not be made.
  • A Hero picture chosen on Create was never written to the cartridge.
  • Saving Settings switched every front-end back to its default.
  • After a write finished, the button still said Write and would write again. It
    now says Make another.
  • A game added by hand could not be given a drive: leaving that screen threw
    the entry away. It now has Done, and picks the likeliest program in the
    folder for you.
  • Artwork picked on SteamGridDB could land in the wrong slot. Each pick was
    filed under whichever tab was open when its download finished, so choosing
    quickly across Cover, Hero, Logo and Icon put the cover in the logo slot and
    so on. A pick now belongs to the tab it was clicked on, and Write waits for
    downloads still in flight.
  • Picking a logo, hero or icon no longer becomes the game's remembered cover.
  • The Edit tab and the drive lists name a cartridge by what its
    cartridge.conf says, not by the drive's volume label — which only changes
    when a drive is erased, so a drive rewritten from FTL to Cult of the Lamb
    still called itself FTL. Edit also re-reads a cartridge after it has been
    written over.
  • The Windows tuning buttons in Settings name the drive they will change, and
    fall back to the cartridge op...
Read more

v1.0.1

Choose a tag to compare

@github-actions github-actions released this 06 Sep 18:31

What's Changed

  • Flatpak: answer the open question by measuring it, and write the manifest by @HarryBMa in #19
  • Flatpak: vendor the crates, build it, and run it against the cartridge by @HarryBMa in #20

Full Changelog: v1.0.0...v1.0.1

v1.0.0

Choose a tag to compare

@github-actions github-actions released this 05 Sep 20:48

What's Changed

  • Make the Linux build installable: AUR packaging that works by @HarryBMa in #18

Full Changelog: v0.1.0...v1.0.0

v0.1.0

Choose a tag to compare

@github-actions github-actions released this 01 Sep 17:50

What's Changed

  • Add Tauri UI launcher with screenshot by @HarryBMa with @Copilot in #1
  • wip: cartridge launcher (Tauri) — core logic, UI, screenshots by @HarryBMa in #2
  • readme rewrite by @HarryBMa in #4
  • Implement analysis recommendations: autorun fix, Playnite size_on_disk, broad Proton scan, wizard UI, README fixes by @HarryBMa with @Copilot in #5
  • feat: multi-game cartridge bundles (game collections) by @HarryBMa with @Copilot in #6
  • Add SteamGridDB artwork search, caching, and wizard selection flow by @HarryBMa with @Copilot in #7
  • feat: switch cartridge filesystem from exFAT to btrfs (TRIM + zstd) by @HarryBMa with @Copilot in #8
  • Rename project references to PC Gamepak Cartridge by @HarryBMa with @Copilot in #9
  • Game bundles that carry their games, PC GamePak rename, and a rootless Linux install by @HarryBMa in #10
  • Add AUR, WinGet, and platform support badges to README.md

Co-authored-by: HarryBMa 163435833+HarryBMa@users.noreply.github.com by @HarryBMa in #11

  • Cartridge editing, controller navigation, a copy that checks itself, and a README you can navigate by @HarryBMa in #14
  • Open the launcher from an NFC tag as well as a drive by @HarryBMa in #15
  • Make the window a slot, and the cartridge the thing in it by @HarryBMa in #16
  • Reshoot the screenshots, and make the docs say what the app does by @HarryBMa in #17

New Contributors

  • @HarryBMa with @Copilot made their first contribution in #1

Full Changelog: https://github.com/HarryBMa/pc-gamepak/commits/v0.1.0