storage

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Aug 22, 2026 License: GPL-3.0 Imports: 11 Imported by: 0

Documentation

Index

Constants

View Source
const DefaultRumbleScale = 1.0

DefaultRumbleScale is the rumble intensity assigned to new profiles.

View Source
const MaxInputPlayers = 2

MaxInputPlayers is the maximum number of player slots stored in config.

Variables

View Source
var FontSizePresets = []int{10, 12, 14, 16, 18, 20, 24, 28, 32}

FontSizePresets lists the available font size options

View Source
var ValidAspectRatios = []string{"dar", "4:3", "1:1", "stretch"}

ValidAspectRatios lists the allowed aspect ratio mode values

Functions

func ApplyMissingDefaults

func ApplyMissingDefaults(config *Config, presentKeys map[string]bool)

ApplyMissingDefaults sets default values for config fields that are absent from the JSON file. This replaces ensureConfigDefaults with key-presence awareness: only truly missing fields get defaults, preserving intentional zero values (e.g., volume=0).

func AspectRatioDisplayName

func AspectRatioDisplayName(mode string) string

AspectRatioDisplayName returns a user-facing label for the given mode.

func AtomicWriteJSON

func AtomicWriteJSON(path string, data interface{}) error

AtomicWriteJSON writes data to a JSON file atomically. It writes to a temporary file first, then renames to the target path. This ensures the file is never in a partially-written state.

func CorrectInputConfig

func CorrectInputConfig(config *Config, isValidKey, isValidPad func(string) bool)

CorrectInputConfig removes invalid entries from input override maps and profile/player configuration. Profiles missing identity fields are dropped, duplicate IDs and duplicate names per controller model are dropped, invalid bindings are removed, out-of-range rumble scales are reset to the default, the players list is clamped, and dangling player profile references are cleared. isValidKey checks if a key name is valid, isValidPad checks if a pad name is valid.

func CreateConfigIfMissing

func CreateConfigIfMissing() error

CreateConfigIfMissing creates a default config.json if it doesn't exist

func CreateLibraryIfMissing

func CreateLibraryIfMissing() error

CreateLibraryIfMissing creates a default library.json if it doesn't exist

func DeleteConfig

func DeleteConfig() error

DeleteConfig removes the config.json file

func DeleteLibrary

func DeleteLibrary() error

DeleteLibrary removes the library.json file

func EnsureDirectories

func EnsureDirectories() error

EnsureDirectories creates all necessary directories for the application

func GetArtworkDir

func GetArtworkDir() (string, error)

GetArtworkDir returns the full path to the artwork directory

func GetBaseDir

func GetBaseDir() (string, error)

GetBaseDir returns the base directory for application data. The directory name is set by Init(). Example paths: - macOS: ~/Library/Application Support/<appName> - Linux: ~/.local/share/<appName> - Windows: %APPDATA%/<appName>

func GetConfigPath

func GetConfigPath() (string, error)

GetConfigPath returns the full path to config.json

func GetGameArtworkPath

func GetGameArtworkPath(gameCRC string) (string, error)

GetGameArtworkPath returns the path to a game's box art

func GetGameCHTRumblePath added in v0.5.0

func GetGameCHTRumblePath(gameCRC string) (string, error)

GetGameCHTRumblePath returns the path to a game's rumble CHT file

func GetGameRumblePath

func GetGameRumblePath(gameID string) (string, error)

GetGameRumblePath returns the path to a game's .erumble file

func GetGameSaveDir

func GetGameSaveDir(gameCRC string) (string, error)

GetGameSaveDir returns the save directory for a specific game (by CRC32)

func GetLibraryPath

func GetLibraryPath() (string, error)

GetLibraryPath returns the full path to library.json

func GetMetadataDir

func GetMetadataDir() (string, error)

GetMetadataDir returns the full path to the metadata directory

func GetRumbleDir

func GetRumbleDir() (string, error)

GetRumbleDir returns the full path to the rumble directory

func GetSavesDir

func GetSavesDir() (string, error)

GetSavesDir returns the full path to the saves directory

func GetScreenshotDir

func GetScreenshotDir() (string, error)

GetScreenshotDir returns the full path to the screenshots directory

func Init

func Init(dataDirName string)

Init sets the application data directory name. Must be called before any storage operations.

func ReadJSON

func ReadJSON(path string, data interface{}) error

ReadJSON reads and unmarshals a JSON file

func SanitizeLibraryEntries

func SanitizeLibraryEntries(lib *Library)

SanitizeLibraryEntries silently corrects invalid game entry fields. This runs on load so invalid values never reach the UI or emulator.

func SaveConfig

func SaveConfig(config *Config) error

SaveConfig saves the configuration to config.json atomically

func SaveLibrary

func SaveLibrary(library *Library) error

SaveLibrary saves the library to library.json atomically

func ValidFontSize

func ValidFontSize(size int) int

ValidFontSize returns the nearest valid preset font size.

func ValidPadMapping added in v0.5.0

func ValidPadMapping(guid, line string) bool

ValidPadMapping reports whether guid and line form a well-formed pad mapping entry: the GUID is 32 lowercase hex characters, the line starts with the GUID, and the line is a single line.

func ValidateConfig

func ValidateConfig(config *Config, validThemes []string) []string

ValidateConfig checks all config fields against valid ranges and returns human-readable error descriptions. An empty slice means the config is valid. validThemes should be the list of known theme names.

func ValidateInputConfig

func ValidateInputConfig(config *Config, isValidKey, isValidPad func(string) bool) []string

ValidateInputConfig checks input config overrides using the provided validators. Returns error descriptions for invalid entries. isValidKey checks if a key name is valid, isValidPad checks if a pad name is valid.

func ValidateLibrary

func ValidateLibrary(lib *Library) []string

ValidateLibrary checks library-level fields against valid ranges and returns human-readable error descriptions. An empty slice means the library is valid.

Types

type AudioConfig

type AudioConfig struct {
	Volume          float64 `json:"volume"`
	Muted           bool    `json:"muted"`
	FastForwardMute bool    `json:"fastForwardMute"` // Mute audio during fast-forward (default: true)
}

AudioConfig contains audio-related settings

type BIOSConfig

type BIOSConfig struct {
	Active string            `json:"active,omitempty"` // Active variant label, empty = none
	Files  map[string]string `json:"files,omitempty"`  // variant label -> file path
}

BIOSConfig stores the user's BIOS configuration for one BIOSOption.

type Config

type Config struct {
	Version           int                     `json:"version"`
	Theme             string                  `json:"theme"`    // Theme name: "Default", "Dark", "Light", "Retro"
	FontSize          int                     `json:"fontSize"` // 10-32, default 14
	Video             VideoConfig             `json:"video"`
	Audio             AudioConfig             `json:"audio"`
	Window            WindowConfig            `json:"window"`
	Library           LibraryView             `json:"library"`
	Shaders           ShaderConfig            `json:"shaders"`
	Rewind            RewindConfig            `json:"rewind"`
	Input             InputConfig             `json:"input"`
	CoreOptions       map[string]string       `json:"coreOptions,omitempty"`
	BIOS              map[string]BIOSConfig   `json:"bios,omitempty"`
	RetroAchievements RetroAchievementsConfig `json:"retroAchievements"`
}

Config represents the application configuration stored in config.json

func CorrectConfig

func CorrectConfig(config *Config, validThemes []string) *Config

CorrectConfig resets any invalid fields to their defaults from DefaultConfig(). Valid fields are preserved. validThemes should be the list of known theme names.

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig returns a new Config with default values

func LoadConfig

func LoadConfig() (*Config, error)

LoadConfig loads the configuration from config.json. If the file doesn't exist, it returns default configuration. If the file is corrupted, it returns an error. Missing fields (absent from JSON) are silently defaulted.

type ControllerProfile added in v0.5.0

type ControllerProfile struct {
	ID          string            `json:"id"`
	SDLID       string            `json:"sdlId"`
	Controller  string            `json:"controller"`
	Name        string            `json:"name"`
	RumbleScale float64           `json:"rumbleScale"`        // intensity multiplier 0.0-3.0 in 0.05 steps, 0=off
	Bindings    map[string]string `json:"bindings,omitempty"` // button name -> pad button name override
}

ControllerProfile is a named controller button mapping for a controller model. The model is identified by SDLID+Controller; multiple profiles can exist for the same model (e.g. third-party pads reported as the same device). ID is immutable and referenced by PlayerConfig; Name is the user-editable display name, unique per model.

func (*ControllerProfile) MatchesPad added in v0.5.0

func (p *ControllerProfile) MatchesPad(sdlID, name string) bool

MatchesPad reports whether this profile's controller model matches the given pad identity (SDL ID and name).

func (*ControllerProfile) UnmarshalJSON added in v0.5.0

func (p *ControllerProfile) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a profile with RumbleScale pre-set to the default, so a profile saved without the key loads at the default intensity instead of 0 (off). An explicit 0 in the file is preserved.

type GameDisc added in v0.3.0

type GameDisc struct {
	Index int    `json:"index"` // 0-based disc index (DiscNumber-1)
	File  string `json:"file"`  // Path to this disc's image on disk
	Name  string `json:"name"`  // Per-disc display name
}

GameDisc is one disc of a multi-disc disc-based game.

type GameEntry

type GameEntry struct {
	CRC32           string       `json:"crc32"`
	File            string       `json:"file,omitempty"`   // Cartridge ROM/archive path; disc games leave this empty and use Discs
	Name            string       `json:"name"`             // Full No-Intro name from RDB
	DisplayName     string       `json:"displayName"`      // Cleaned name for display (region info removed)
	Region          string       `json:"region"`           // "us", "eu", "jp" (from RDB)
	Serial          string       `json:"serial,omitempty"` // Disc/product serial (game ID), when known
	Developer       string       `json:"developer,omitempty"`
	Publisher       string       `json:"publisher,omitempty"`
	Genre           string       `json:"genre,omitempty"`
	Franchise       string       `json:"franchise,omitempty"`
	ESRBRating      string       `json:"esrbRating,omitempty"`
	ReleaseDate     string       `json:"releaseDate,omitempty"` // "Month / Year" format
	System          string       `json:"system,omitempty"`      // Variant name (e.g. "Neo Geo Pocket") - set when >1 RDB variant
	ConsoleID       int          `json:"consoleID,omitempty"`   // RetroAchievements console ID; 0 = use system default
	Favorite        bool         `json:"favorite"`              // User marked as favorite
	Missing         bool         `json:"missing"`               // true if ROM file not found
	PlayTimeSeconds int64        `json:"playTimeSeconds"`       // Total play time
	LastPlayed      int64        `json:"lastPlayed"`            // Unix timestamp
	Added           int64        `json:"added"`                 // Unix timestamp when added to library
	Settings        GameSettings `json:"settings"`              // Per-game settings

	// Discs lists the discs of a disc-based game (one entry even for a
	// single-disc game), ordered by Index. Empty for cartridge games,
	// which use File.
	Discs []GameDisc `json:"discs,omitempty"`
	// SelectedDisc is the 0-based slice index into Discs to launch.
	// Persisted so the chosen disc is remembered. Ignored when Discs is
	// empty.
	SelectedDisc int `json:"selectedDisc,omitempty"`
}

GameEntry represents a single game in the library

type GameSettings

type GameSettings struct {
	SaveSlot int `json:"saveSlot,omitempty"` // Last-used save state slot (0-9)
}

GameSettings contains per-game configuration overrides

type InputConfig

type InputConfig struct {
	P1Keyboard         map[string]string   `json:"p1Keyboard,omitempty"`         // button name -> key name override
	DisableAnalogStick bool                `json:"disableAnalogStick,omitempty"` // disable analog stick mirroring d-pad
	Profiles           []ControllerProfile `json:"profiles,omitempty"`           // named controller mappings, in creation order
	Players            []PlayerConfig      `json:"players,omitempty"`            // player slot -> profile assignment
	PadMappings        map[string]string   `json:"padMappings,omitempty"`        // SDL GUID -> generated standard-layout mapping line
}

InputConfig contains input binding overrides and controller configuration. Empty/nil maps mean "use adaptor defaults." Only user overrides are stored.

func (*InputConfig) NewProfileID added in v0.5.0

func (c *InputConfig) NewProfileID() string

NewProfileID returns a random 8-hex-char profile ID unique within this input config.

func (*InputConfig) PlayerProfile added in v0.5.0

func (c *InputConfig) PlayerProfile(player int) *ControllerProfile

PlayerProfile returns the profile assigned to the given player slot, or nil if the slot is unassigned or the reference is dangling.

func (*InputConfig) PlayerRumbleScale added in v0.5.0

func (c *InputConfig) PlayerRumbleScale(player int) float64

PlayerRumbleScale returns the rumble intensity for the given player slot from the assigned profile, or 0 (off) when the slot has no profile.

func (*InputConfig) ProfileByID added in v0.5.0

func (c *InputConfig) ProfileByID(id string) *ControllerProfile

ProfileByID returns the profile with the given ID, or nil if not found.

func (*InputConfig) RumbleEnabled added in v0.5.0

func (c *InputConfig) RumbleEnabled() bool

RumbleEnabled reports whether any player slot has a profile with a non-zero rumble intensity.

type Library

type Library struct {
	Version         int                   `json:"version"`
	ScanDirectories []ScanDirectory       `json:"scanDirectories"`
	ExcludedPaths   []string              `json:"excludedPaths"`
	Games           map[string]*GameEntry `json:"games"` // CRC32 hex string -> entry
}

Library represents the game library stored in library.json

func CorrectLibrary

func CorrectLibrary(lib *Library) *Library

CorrectLibrary resets any invalid library-level fields to their defaults.

func DefaultLibrary

func DefaultLibrary() *Library

DefaultLibrary returns a new Library with default values

func LoadLibrary

func LoadLibrary() (*Library, error)

LoadLibrary loads the library from library.json. If the file doesn't exist, it returns an empty library. If the file is corrupted, it returns an error.

func (*Library) AddExcludedPath

func (lib *Library) AddExcludedPath(path string)

AddExcludedPath adds a path to the exclusion list

func (*Library) AddGame

func (lib *Library) AddGame(entry *GameEntry)

AddGame adds or updates a game entry in the library

func (*Library) AddScanDirectory

func (lib *Library) AddScanDirectory(path string, recursive bool)

AddScanDirectory adds a directory to scan for ROMs

func (*Library) GameCount

func (lib *Library) GameCount() int

GameCount returns the number of games in the library

func (*Library) GetGame

func (lib *Library) GetGame(gameCRC string) *GameEntry

GetGame retrieves a game by CRC32

func (*Library) GetGamesSorted

func (lib *Library) GetGamesSorted(sortBy string, favoritesOnly bool) []*GameEntry

GetGamesSorted returns a sorted slice of game entries

func (*Library) GetGamesSortedFiltered

func (lib *Library) GetGamesSortedFiltered(sortBy string, favoritesOnly bool, searchText string) []*GameEntry

GetGamesSortedFiltered returns a sorted slice of game entries filtered by search text. Search is case-insensitive and matches against DisplayName and Name fields. Empty searchText returns all games (same as GetGamesSorted).

func (*Library) IsPathExcluded

func (lib *Library) IsPathExcluded(path string) bool

IsPathExcluded checks if a path is in the exclusion list

func (*Library) RemoveExcludedPath

func (lib *Library) RemoveExcludedPath(path string)

RemoveExcludedPath removes a path from the exclusion list

func (*Library) RemoveGame

func (lib *Library) RemoveGame(gameCRC string)

RemoveGame removes a game from the library

func (*Library) RemoveScanDirectory

func (lib *Library) RemoveScanDirectory(path string)

RemoveScanDirectory removes a directory from the scan list

func (*Library) UpdatePlayTime

func (lib *Library) UpdatePlayTime(gameCRC string, secondsPlayed int64)

UpdatePlayTime adds play time to a game and updates last played

type LibraryView

type LibraryView struct {
	ViewMode        string `json:"viewMode"`        // "icon" or "list"
	SortBy          string `json:"sortBy"`          // "title", "lastPlayed", "playTime"
	FavoritesFilter bool   `json:"favoritesFilter"` // Show only favorites
}

LibraryView contains library display preferences

type PlayerConfig added in v0.5.0

type PlayerConfig struct {
	Profile string `json:"profile,omitempty"` // ControllerProfile.ID
}

PlayerConfig holds the controller assignment for one player slot. An empty Profile means no controller is assigned to that player.

type RetroAchievementsConfig

type RetroAchievementsConfig struct {
	Enabled                 bool   `json:"enabled"`
	EncoreMode              bool   `json:"encoreMode"`              // Allow re-triggering unlocked achievements
	UnlockSound             bool   `json:"unlockSound"`             // Play sound on achievement unlock
	ShowNotification        bool   `json:"showNotification"`        // Show popup notification on achievement unlock
	AutoScreenshot          bool   `json:"autoScreenshot"`          // Take screenshot on achievement unlock
	SuppressHardcoreWarning bool   `json:"suppressHardcoreWarning"` // Hide "Unknown Emulator" hardcore warning
	SpectatorMode           bool   `json:"spectatorMode"`           // Watch achievements without submitting unlocks
	Username                string `json:"username,omitempty"`
	Token                   string `json:"token,omitempty"` // Auth token (password is never stored)
}

RetroAchievementsConfig contains RetroAchievements integration settings

type RewindConfig

type RewindConfig struct {
	Enabled      bool `json:"enabled"`      // Default: false (off due to RAM usage)
	BufferSizeMB int  `json:"bufferSizeMB"` // Default: 40
	FrameStep    int  `json:"frameStep"`    // Default: 1 (capture every frame)
}

RewindConfig contains rewind feature settings

type ScanDirectory

type ScanDirectory struct {
	Path      string `json:"path"`
	Recursive bool   `json:"recursive"`
}

ScanDirectory represents a directory to scan for ROMs

type ShaderConfig

type ShaderConfig struct {
	UIShaders   []string `json:"uiShaders"`   // Ordered list of shader IDs for UI context
	GameShaders []string `json:"gameShaders"` // Ordered list of shader IDs for Game context
}

ShaderConfig contains shader effect settings

type VideoConfig

type VideoConfig struct {
	AspectRatio string `json:"aspectRatio"`
}

VideoConfig contains video-related settings

type WindowConfig

type WindowConfig struct {
	Width      int  `json:"width"`
	Height     int  `json:"height"`
	X          *int `json:"x,omitempty"` // nil = OS decides position
	Y          *int `json:"y,omitempty"`
	Fullscreen bool `json:"fullscreen"`
}

WindowConfig contains window position and size

Jump to

Keyboard shortcuts

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