icarus

package
v1.30.0 Latest Latest
Warning

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

Go to latest
Published: Aug 8, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplyRowPatch

func ApplyRowPatch(baseJSON []byte, row ExmodRow) ([]byte, error)

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

func Compile(basePakPath, exmodzPath, outputPakPath string) (err error)

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

func ValidateSource(sourceFilePath string) error

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

func ParseExmod(data []byte) (*ExmodDiff, error)

type ExmodFileItem

type ExmodFileItem struct {
	Name   string
	Fields map[string]any
}

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

func New(httpClient *http.Client, projectID string) *Icarus

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) AuthURL

func (s *Icarus) AuthURL() string

AuthURL/ExchangeToken: unsupported — Firestore reads here are public.

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 (s *Icarus) ExchangeToken(ctx context.Context, code string) (*source.Token, error)

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) GetMod

func (s *Icarus) GetMod(ctx context.Context, queryGameID, modID string) (*domain.Mod, error)

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) ID

func (s *Icarus) ID() string

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) Name

func (s *Icarus) Name() string

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) TypeLabel

func (s *Icarus) TypeLabel() string

func (*Icarus) ValidateSource

func (s *Icarus) ValidateSource(sourceFilePath string) error

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.

Jump to

Keyboard shortcuts

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