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; unofficial 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 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
- type Achievement
- type Cache
- type Def
- type Deps
- type EmuGame
- type EmuResult
- type Entry
- type Env
- type Icons
- type List
- type Net
- type Unlock
Constants ¶
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 = 2
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 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.
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", …).
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)
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.
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: 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
// 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 unofficial 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.