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
- Variables
- func EmuFiles(g EmuGame, env Env) []string
- func EnableUplay(g library.Game, env Env) (string, error)
- func EpicEmuID(g EmuGame) string
- func EpicSandbox(epicApp string) string
- func Files(g library.Game, d Deps) []string
- func GOGClientID(dir, gogID string) string
- func SSEHash(id string) string
- func Stamp(files []string, extra ...string) string
- func SteamLanguage() string
- func SteamSchemaFile(root string, appID int) string
- func SteamStatsFile(root, account string, appID int) string
- func Unknown(emulator string) bool
- func UplayEmulator(emulator string) bool
- func UplayFiles(g EmuGame, env Env) []string
- type Achievement
- type Cache
- type Def
- func GoldbergSchema(g EmuGame, lang string) ([]Def, string, error)
- func SteamLocal(root string, accounts []string, appID int, lang string) (defs []Def, unlocks map[string]Unlock, files []string, err error)
- func SteamSchema(root string, appID int, lang string) ([]Def, error)
- func UplaySchema(g EmuGame) ([]Def, string)
- type Deps
- type EmuGame
- type EmuResult
- type Entry
- type Env
- type Icons
- type List
- type Net
- type Unlock
Constants ¶
const FixUplayINI = "uplay-ini"
FixUplayINI: Seaglass can turn achievements on in the game's Uplay emulator ini (EnableUplay).
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.
const RarityTTL = 7 * 24 * time.Hour
RarityTTL is how long global unlock percentages are kept.
const SchemaTTL = 30 * 24 * time.Hour
SchemaTTL is how long a downloaded schema (or rarity) is used before it's fetched again.
const SteamIconBase = "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/apps/"
SteamIconBase is where Steam's achievement icons are.
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 ¶
var ErrNone = errors.New("no achievements")
ErrNone means a store says the game has no achievements.
Functions ¶
func EnableUplay ¶ added in v1.9.0
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 ¶
EpicEmuID finds the id Nemirtinga's emulator files its saves under, from its settings file next to the game.
func EpicSandbox ¶
EpicSandbox is an Epic game's sandbox: the namespace of "namespace:catalogItem:appName".
func Files ¶
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 ¶
GOGClientID reads a GOG game's client id from its goggame-<id>.info.
func Stamp ¶
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 ¶
SteamSchemaFile is Steam's cached schema for an app.
func SteamStatsFile ¶
SteamStatsFile is an account's cached stats for an app.
func Unknown ¶
Unknown reports whether emulator is a group whose unlock files Seaglass can't read yet.
func UplayEmulator ¶ added in v1.7.0
UplayEmulator reports whether emulator is one of the Uplay emulators the scan names.
func UplayFiles ¶ added in v1.7.0
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).
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 ¶
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 ¶
SteamSchema reads the schema Steam keeps for an app it has run.
func UplaySchema ¶ added in v1.7.0
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 ¶
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.
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 ¶
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 ¶
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.
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.