achievements

package
v1.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 3, 2026 License: AGPL-3.0 Imports: 28 Imported by: 0

Documentation

Overview

Package achievements reads a game's achievements: what it has (the schema: names, descriptions, icons) and which the player unlocked. Store installs are read from the store's own files or API; external copies from the unlock files their Steam emulator writes. Every file is treated as untrusted: reads are capped and parsers never panic.

Index

Constants

View Source
const FixUplayINI = "uplay-ini"

FixUplayINI: Seaglass can turn achievements on in the game's Uplay emulator ini (EnableUplay).

View Source
const IconPrefix = "/ach/"

IconPrefix is the path the interface loads stored icons from. They're kept apart from game art (/art/), whose pruning doesn't know them.

View Source
const RarityTTL = 7 * 24 * time.Hour

RarityTTL is how long global unlock percentages are kept.

View Source
const SchemaTTL = 30 * 24 * time.Hour

SchemaTTL is how long a downloaded schema (or rarity) is used before it's fetched again.

View Source
const SteamIconBase = "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/apps/"

SteamIconBase is where Steam's achievement icons are.

View Source
const Version = 4

Version is part of every cached result's key: raise it when what Resolve makes of the same files changes, so results are read again.

Variables

View Source
var ErrNone = errors.New("no achievements")

ErrNone means a store says the game has no achievements.

Functions

func EmuFiles

func EmuFiles(g EmuGame, env Env) []string

EmuFiles lists every file the game's emulator unlocks could be in.

func EnableUplay added in v1.9.0

func EnableUplay(g library.Game, env Env) (string, error)

EnableUplay sets Achievements = 1 in the game's Uplay emulator ini, keeping everything else in it as it was, and a copy of the ini from before next to it (<ini>.seaglass.bak, made once). It returns the ini.

func EpicEmuID

func EpicEmuID(g EmuGame) string

EpicEmuID finds the id Nemirtinga's emulator files its saves under, from its settings file next to the game.

func EpicSandbox

func EpicSandbox(epicApp string) string

EpicSandbox is an Epic game's sandbox: the namespace of "namespace:catalogItem:appName".

func Files

func Files(g library.Game, d Deps) []string

Files lists the local files a game's achievements are read from, for Stamp: a result made from the same files is still good.

func GOGClientID

func GOGClientID(dir, gogID string) string

GOGClientID reads a GOG game's client id from its goggame-<id>.info.

func SSEHash

func SSEHash(id string) string

SSEHash is the key SmartSteamEmu's stats.bin files an achievement ID under.

func Stamp

func Stamp(files []string, extra ...string) string

Stamp sums up what a result depends on: the size and time of each file that exists (so a result is reused while nothing changed), and extra words (the language, whether a key is set, …).

func SteamLanguage

func SteamLanguage() string

SteamLanguage is Steam's interface language ("english", "german", …), the language achievement names are shown in.

func SteamSchemaFile

func SteamSchemaFile(root string, appID int) string

SteamSchemaFile is Steam's cached schema for an app.

func SteamStatsFile

func SteamStatsFile(root, account string, appID int) string

SteamStatsFile is an account's cached stats for an app.

func Unknown

func Unknown(emulator string) bool

Unknown reports whether emulator is a group whose unlock files Seaglass can't read yet.

func UplayEmulator added in v1.7.0

func UplayEmulator(emulator string) bool

UplayEmulator reports whether emulator is one of the Uplay emulators the scan names.

func UplayFiles added in v1.7.0

func UplayFiles(g EmuGame, env Env) []string

UplayFiles lists the files and folders a Uplay game's achievements are read from.

Types

type Achievement

type Achievement struct {
	Def
	Unlocked   bool     `json:"unlocked"`
	UnlockedAt int64    `json:"unlockedAt,omitempty"` // unix seconds; 0 when unlocked at an unknown time
	Progress   float64  `json:"progress,omitempty"`
	Max        float64  `json:"max,omitempty"`
	Percent    *float64 `json:"percent,omitempty"` // share of all players who have it (Steam's global stats)
}

Achievement is one achievement as the interface shows it.

func Merge

func Merge(defs []Def, unlocks map[string]Unlock) []Achievement

Merge combines a schema with unlocks. Unlock IDs match schema IDs ignoring case (CODEX writes them in a different case than Steam). Unlocks the schema doesn't know are listed by their ID; so is every unlock when there's no schema.

type Cache

type Cache struct {
	Dir string
}

Cache keeps results per game and schemas fetched from the stores.

<Dir>\games\<gameID>.json            the last result and what it was made from
<Dir>\schema\<source>-<id>-<lang>.json  a store's schema, kept SchemaTTL

func (*Cache) Clear

func (c *Cache) Clear()

Clear forgets every cached game result (schemas stay).

func (*Cache) Load

func (c *Cache) Load(id int64) (Entry, bool)

Load returns a game's cached result.

func (*Cache) Save

func (c *Cache) Save(id int64, e Entry) error

Save stores a game's result.

type Def

type Def struct {
	ID       string `json:"id"`
	Name     string `json:"name"`
	Desc     string `json:"desc,omitempty"`
	Icon     string `json:"icon,omitempty"`     // https URL or a local file; the interface gets /ach/ URLs
	IconGray string `json:"iconGray,omitempty"` // the locked icon
	Hidden   bool   `json:"hidden,omitempty"`
}

Def is one achievement in a game's schema.

func GoldbergSchema

func GoldbergSchema(g EmuGame, lang string) ([]Def, string, error)

GoldbergSchema reads the schema Goldberg-style emulators keep next to the game (steam_settings\achievements.json). Icons become absolute paths of files inside the game folder; lang is Steam's language name ("english", "german", …).

func SteamLocal

func SteamLocal(root string, accounts []string, appID int, lang string) (defs []Def, unlocks map[string]Unlock, files []string, err error)

SteamLocal reads a Steam game's schema and the account's unlocks from Steam's cache. files are the files it depends on, for cache keys.

func SteamSchema

func SteamSchema(root string, appID int, lang string) ([]Def, error)

SteamSchema reads the schema Steam keeps for an app it has run.

func UplaySchema added in v1.7.0

func UplaySchema(g EmuGame) ([]Def, string)

UplaySchema reads achievements_schema.json next to the emulator: {"<key>": {"displayName": …, "description": …, "earned": 0}}.

type Deps

type Deps struct {
	Env           Env
	Lang          string // Steam's language name: "english", "german", …
	SteamRoot     string
	SteamAccounts []string // userdata account ids, the one in use first
	SteamKey      string   // the user's Steam Web API key ("" = none)
	SteamID       string   // steamID64 of the account in use
	Net           Net      // nil: nothing is asked online
	// EpicLocale is Epic's name for Lang ("de", "en-US", …).
	EpicLocale string
	// Epic returns a signed-in Epic account's access token; nil when
	// nobody is signed in.
	Epic func(ctx context.Context) (access, account string, err error)
	// GalaxyDB is GOG Galaxy's database ("" when there's none) and
	// GOGUnlocks reads a game's unlocks from it.
	GalaxyDB   string
	GOGUnlocks func(gogID string) (map[string]Unlock, error)
	// GOG returns a signed-in GOG account's access token and user id; nil
	// when nobody is signed in.
	GOG     func(ctx context.Context) (access, userID string, err error)
	Offline bool // don't go online now (a game is running)
	// UplayGames counts the library's games on a Uplay emulator: with only
	// one, the lone achievements folder there must be its.
	UplayGames int
	Cache      *Cache
	Icons      *Icons
}

Deps is everything Resolve works with besides the game.

type EmuGame

type EmuGame struct {
	Dir      string // install folder
	EmuDir   string // relative to Dir: where the emulator sits
	Emulator string // as the scan named it ("Goldberg", "RUNE", …)
	AppID    int    // Steam app id the emulator runs as
	EpicID   string // for Nemirtinga's Epic emulator
}

EmuGame is what the emulator lookup needs to know about a game.

type EmuResult

type EmuResult struct {
	Source  string            // emuSource name
	File    string            // the file read
	Unlocks map[string]Unlock // by ID, or by SSEHash(ID) when Hashed
	Hashed  bool
}

EmuResult is the unlock file found for a game.

func ReadEmu

func ReadEmu(g EmuGame, env Env) (res EmuResult, files []string, ok bool)

ReadEmu finds the game's unlock file and reads it. Every place is tried; when several have one (an earlier emulator setup's, or the launcher's and the game's), the newest file names the source and what any of them has unlocked counts. Files lists every candidate path looked at, for cache keys.

func ReadUplay added in v1.7.0

func ReadUplay(g EmuGame, env Env, known []string, soleGame bool) (res EmuResult, files []string, seen int, ok bool)

ReadUplay finds and reads the game's achievements.json. known are keys the game's achievements have (from a schema), to tell its folder from other games'; soleGame says this is the only game on a Uplay emulator, so a lone folder must be its. files lists what was looked at; seen counts the unlock files found, the game's or not.

type Entry

type Entry struct {
	Stamp string `json:"stamp"` // what the result was made from; see Stamp
	Net   bool   `json:"net"`   // part of it came from a store's servers
	List  List   `json:"list"`
}

Entry is a cached result.

type Env

type Env struct {
	Roaming, Local, Public, ProgramData, Documents string
}

Env holds the folders emulators write to. Tests point them at a temporary folder.

func DefaultEnv

func DefaultEnv() Env

DefaultEnv is this Windows account's folders.

type Icons

type Icons struct {
	Dir   string
	Fetch func(ctx context.Context, src, dir string) (string, error) // download, check, store; returns the file name
	Store func(dir string, data []byte) (string, error)              // check and store local data
	// contains filtered or unexported fields
}

Icons downloads achievement icons (and copies local ones) into Dir, checked and re-encoded, content-addressed. The interface only ever sees /ach/ URLs: the release CSP allows images from Seaglass itself only.

func (*Icons) Localize

func (ic *Icons) Localize(ctx context.Context, l *List, offline bool) (complete bool)

Localize replaces the list's icon sources with stored /ach/ URLs, storing what's missing, a few at a time. An icon that can't be had is left empty (the interface draws a generic one). With offline set nothing is downloaded. It reports false when ctx ended before every icon was tried, or a download failed for want of a connection: the list is then worth reading again later.

type List

type List struct {
	GameID    int64         `json:"gameId"`
	Source    string        `json:"source"` // "steam", "epic", "gog", "Goldberg", "CODEX", …; "" when none was found
	Total     int           `json:"total"`
	Unlocked  int           `json:"unlocked"`
	Items     []Achievement `json:"items"`
	UpdatedAt int64         `json:"updatedAt"`
	Hint      string        `json:"hint,omitempty"` // what's missing, and how to get it
	// Fix is something Seaglass can do about the hint: FixUplayINI.
	Fix string `json:"fix,omitempty"`
	// Partial: time ran out before every icon was stored; read it again later.
	Partial bool `json:"-"`
}

List is a game's achievements.

func Resolve

func Resolve(ctx context.Context, g library.Game, d Deps) (l *List, net bool)

Resolve reads a game's achievements from the best source it has:

  • an external copy (or a game from no store): its emulator's unlock files, named by steam_settings, Steam's cached schema or, with a key, Steam's Web API;
  • a Steam game: Steam's own cache, else the Web API (with a key).

Global rarity comes from Steam when the game's Steam app is known. net reports whether a store's servers were asked.

func (*List) Count

func (l *List) Count()

Count fills in Total and Unlocked from Items.

func (*List) SetRarity

func (l *List) SetRarity(pct map[string]float64)

SetRarity puts global unlock percentages (by achievement ID, any case) on the list's items.

type Net

type Net interface {
	SteamAchievementSchema(ctx context.Context, key string, appID int, lang string) ([]Def, error)
	SteamPlayerAchievements(ctx context.Context, key, steamID string, appID int) (map[string]Unlock, error)
	SteamRarity(ctx context.Context, appID int) (map[string]float64, error)
	EpicAchievements(ctx context.Context, sandbox, locale string) ([]Def, map[string]float64, error)
	EpicPlayerAchievements(ctx context.Context, access, account, sandbox string) (map[string]Unlock, error)
	GOGAchievements(ctx context.Context, access, game, userID string) ([]Def, map[string]Unlock, map[string]float64, error)
}

Net is what Resolve asks the stores' servers. owned.Client implements it.

type Unlock

type Unlock struct {
	Achieved bool
	At       int64 // unix seconds; 0 when unknown
	Progress float64
	Max      float64
}

Unlock is what an unlock file says about one achievement.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL