Documentation
¶
Index ¶
- func ApplyRowPatch(baseJSON []byte, row ExmodRow) ([]byte, error)
- func Compile(basePakPath, exmodzPath, outputPakPath string) (err error)
- func MergeCompile(ctx context.Context, basePakPath string, sources []MergeSource, ...) (warnings []string, failed []source.MergeFailure, err error)
- func ValidateSource(sourceFilePath string) error
- type ExmodDiff
- type ExmodFileItem
- type ExmodRow
- type ExmodzBundle
- type Icarus
- func (s *Icarus) AuthURL() string
- func (s *Icarus) Capabilities() source.Capabilities
- func (s *Icarus) CheckUpdates(ctx context.Context, installed []domain.InstalledMod) ([]domain.Update, error)
- func (s *Icarus) ExchangeToken(ctx context.Context, code string) (*source.Token, error)
- func (s *Icarus) GetDependencies(ctx context.Context, mod *domain.Mod) ([]domain.ModReference, error)
- func (s *Icarus) GetDownloadURL(ctx context.Context, mod *domain.Mod, fileID string) (string, error)
- func (s *Icarus) GetMod(ctx context.Context, queryGameID, modID string) (*domain.Mod, error)
- func (s *Icarus) GetModFiles(ctx context.Context, mod *domain.Mod) ([]domain.DownloadableFile, error)
- func (s *Icarus) ID() string
- func (s *Icarus) MergeCompile(ctx context.Context, basePakPath string, sources []MergeSource, ...) ([]string, []source.MergeFailure, error)
- func (s *Icarus) Name() string
- func (s *Icarus) Search(ctx context.Context, query source.SearchQuery) (source.SearchResult, error)
- func (s *Icarus) TypeLabel() string
- func (s *Icarus) ValidateSource(sourceFilePath string) error
- type MergeSource
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ApplyRowPatch ¶
ApplyRowPatch applies row's File_Items to baseJSON, a real Icarus DataTable JSON export — the standard Unreal Engine shape {"RowStruct": "...", "Defaults": {...}, "Rows": [{"Name": "...", ...fields}, ...]}, confirmed against a real installed data.pak (task-7-report.md); not the flat {name: {fields}} map this function originally assumed, which never matched real game data and was only ever exercised against synthetic fixtures.
Each File_Item is an upsert, not a strict patch: if its Name matches an existing entry in Rows, that row's fields are shallow-merged with the item's fields (item fields win, everything else on the row survives untouched); if no row has that Name, the item is appended to Rows verbatim as a brand-new row. This matches what real .EXMOD content actually does — most rows patch existing base stats, but a content-adding mod (e.g. a new mountable species) introduces rows the base game doesn't have yet, and erroring on that (the original patch-only design) made every such mod uncompilable. All other top-level keys on the base document (RowStruct, Defaults, and anything else) pass through re-serialization unchanged, since only doc["Rows"] is ever modified. Output is deterministic: encoding/json sorts map keys.
func Compile ¶
Compile reads exmodzPath's .EXMOD diff, applies it to the game's base data tables, bundles in any pre-built assets the .EXMODZ carries, and writes the result as a new pak at outputPakPath ready to deploy as-is.
Base tables are read directly out of basePakPath — the installed game's own Content/Data/data.pak — so they are always week-correct by construction and the whole operation is offline. That pak stores 40 tables uncompressed and compresses the other 258 with Zlib, all of which go-unrealpak reads with the standard library (#175). basePakPath is also what resolves a bare, hyphen-flattened CurrentFile to a real mount path.
There is no ctx parameter: every step is local file I/O over a ~2 MB pak, with no network call and no long-running loop to cancel. The source.MergeCompiler interface still takes one, for implementations that need it (MergeCompile, this package's own N-mod entry point, is one).
The compiled pak's mount point and table-entry paths (icarusContentMountPoint, icarusDataTablePrefix below) are Icarus-specific and deliberately live here rather than in go-unrealpak, which stays game-agnostic — see unrealpak.Writer's WithMountPoint. They are not guessed: both were confirmed against two real, working prebuilt Icarus mod paks (#178; see docs/plans/2026-08-01-icarus-zlib-pivot.md's pak-divergence-report.md).
func MergeCompile ¶
func MergeCompile(ctx context.Context, basePakPath string, sources []MergeSource, outputPakPath string) (warnings []string, failed []source.MergeFailure, err error)
MergeCompile applies every source's .EXMOD row upserts, IN ORDER, against the same evolving base tables - a merge is just Compile with N diffs instead of 1. Table conflicts compose at the FIELD level for free: ApplyRowPatch always shallow-merges an item's fields into whatever the target row currently holds, so feeding mod A's patched bytes back in as the "base" for mod B's row (instead of re-reading the pristine base table each time) is the entire merge algorithm - two mods patching DIFFERENT fields of the same row, or entirely different rows of the same table, both survive; only a genuine same-row-same-field write is last-wins (an ordinary, expected upsert outcome, not something to warn about). Bundled ASSET files cannot compose this way - a same-path asset collision is necessarily last-wins, so it is reported as a warning instead.
ctx is accepted only to satisfy source.MergeCompiler and is never read - every step here is local file I/O over small files (mirrors Compile's own doc comment, internal/source/icarus/compile.go:23-25).
A non-nil error always means outputPakPath does not exist (or does not contain a fully-written pak) - see the removal defer below, mirroring Compile's own fail-clean contract.
func ValidateSource ¶
ValidateSource parses sourceFilePath without compiling anything - the ingest-time check. .exmodz archives fully parse (#197); .pak files (#221) open + enumerate only - full conversion is checked at merge time BY DESIGN (the result depends on the current base pak, which changes weekly).
Types ¶
type ExmodDiff ¶
type ExmodDiff struct {
Name string
Author string
Version string
Description string
Rows []ExmodRow
}
ExmodDiff is the parsed .EXMOD manifest — a diff against the base game's JSON data tables, not a binary/compiled-asset diff (confirmed against a real sample; see docs/plans/2026-07-29-icarus-exmod-pak-research.md).
func ParseExmod ¶
type ExmodFileItem ¶
ExmodFileItem upserts fields on the base row named Name — patching it if it already exists, adding it as a new row otherwise (see ApplyRowPatch). Fields holds every key from the source JSON except "Name" itself, generically — the real schema nests arbitrary game-data shapes here (see package doc comment), so this deliberately does not enumerate them.
type ExmodRow ¶
type ExmodRow struct {
CurrentFile string
FileItems []ExmodFileItem
}
ExmodRow targets one base data-table file (e.g. "AI-D_AIGrowth.json").
type ExmodzBundle ¶
type ExmodzBundle struct {
Diff *ExmodDiff
Assets map[string][]byte // zip-internal path -> raw content, manifest/readme/image excluded
}
ExmodzBundle is a parsed .EXMODZ: the diff manifest plus any pre-built asset files the mod author already compiled (placed as-is into the output pak — never recompiled by LMM).
func ParseExmodz ¶
func ParseExmodz(zipData []byte) (*ExmodzBundle, error)
ParseExmodz unpacks zipData (an in-memory .EXMODZ) into its manifest and bundled assets. The manifest lives at "Extracted Mods/<name>.EXMOD" in every sample seen so far; this looks for any "*.EXMOD" file under an "Extracted Mods/" prefix rather than hard-coding the mod name, since that varies per mod. Matching (both the manifest's prefix/suffix and the asset extensions below) is done on the entry name with backslashes normalized to forward slashes and case folded — some .EXMODZ producers are Windows tools and zip entry casing is not guaranteed — but stored asset keys keep their original case, only the slash direction is normalized.
type Icarus ¶
type Icarus struct {
// contains filtered or unexported fields
}
Icarus is a ModSource backed by the public, unauthenticated Firestore REST API described in docs/plans/2026-07-29-icarus-exmod-pak-research.md.
func New ¶
New constructs an Icarus source. projectID is the Firestore project ID (from the Firebase console) — passed explicitly rather than hard-coded so tests can point at an httptest server and so the real value lives in one place at the call site (Task 9), not buried in this package.
func (*Icarus) Capabilities ¶
func (s *Icarus) Capabilities() source.Capabilities
func (*Icarus) CheckUpdates ¶
func (s *Icarus) CheckUpdates(ctx context.Context, installed []domain.InstalledMod) ([]domain.Update, error)
CheckUpdates compares each installed mod's stored version against the catalog's current version string (semantic-ish, per modinfo.json's "recommended" versioning note — not guaranteed strictly semver, so this uses domain.IsNewerVersion the same way custom.API does).
func (*Icarus) ExchangeToken ¶
func (*Icarus) GetDependencies ¶
func (s *Icarus) GetDependencies(ctx context.Context, mod *domain.Mod) ([]domain.ModReference, error)
GetDependencies: the modinfo.json v2 schema has no dependency field.
func (*Icarus) GetDownloadURL ¶
func (s *Icarus) GetDownloadURL(ctx context.Context, mod *domain.Mod, fileID string) (string, error)
GetDownloadURL re-fetches the mod document and returns the stored URL for fileID ("pak" or "exmodz") directly — no signing, matching a static-URL catalog rather than an OAuth-gated one.
func (*Icarus) GetModFiles ¶
func (s *Icarus) GetModFiles(ctx context.Context, mod *domain.Mod) ([]domain.DownloadableFile, error)
GetModFiles returns the mod's downloadable files (pak and/or exmodz — see modinfo.json v2 schema). When exactly one file exists, it is marked primary. When both variants are published, exmodz is marked primary (#211) — the pak remains explicitly selectable. All returned files have a Description set: "mergeable EXMOD - recommended" for exmodz, "prebuilt PAK" for pak.
func (*Icarus) MergeCompile ¶
func (s *Icarus) MergeCompile(ctx context.Context, basePakPath string, sources []MergeSource, outputPakPath string) ([]string, []source.MergeFailure, error)
MergeCompile implements source.MergeCompiler by delegating to the package-level MergeCompile function. ctx is unused: merging is pure local file I/O against the installed game's own pak (#175/#197), with nothing to cancel.
func (*Icarus) Search ¶
func (s *Icarus) Search(ctx context.Context, query source.SearchQuery) (source.SearchResult, error)
Search fetches the whole mods collection and filters client-side — this catalog has no server-side query support to speak of, matching project_daedalus's own ModsController#find_mods approach.
func (*Icarus) ValidateSource ¶
ValidateSource implements source.MergeCompiler by delegating to the package-level ValidateSource function.
type MergeSource ¶
type MergeSource = source.MergeSource
MergeSource is a type alias (not a distinct type) for source.MergeSource (Step 3 above). internal/core must NOT import this icarus package directly (established #136/#196 precedent - see service_icarus_compile_test.go's fakeCompilerSource doc comment), so it can only ever construct/consume source.MergeSource values - aliasing it here, rather than defining a second, structurally-similar type, is what lets *Icarus's MergeCompile method (Step 6) satisfy source.MergeCompiler at all: Go interface satisfaction requires identical types, and a type alias IS the same type, not a look-alike.