Add a first-party MDL mod-build system and reorganize modding/ under src/

modding/mods/ holds message edits as plain JSON (type, index, language,
plus only the changed fields - a long body can live in a sibling .txt to
avoid escaping in JSON), so mod authoring is reviewable in git instead of
living only in hacked's GUI. mdlp (modding/src/mdlp/, its own Docker image
mdlp-image/) turns them into the same mod folder/zip hacked's own Save
produces, reusing hacked's ss1/edit, ss1/world and ss1/content/text
packages via a go.mod replace - no GUI/GLFW/GTK needed. `make mods`
builds every mod into dist/mods/<name>/, scoped per-mod so it never
touches hacked's own build output; `make` (no target) now builds both.

Fixes a bug this surfaced: hacked's FileLocation.AbsolutePathFrom
silently writes into the current directory instead of the given output
folder when passed a relative path (the GUI never hits this since its
folder picker is always absolute) - mdlp now resolves its own paths
up front.

res/run-mods.sh/.bat are new companion launchers to run.sh/.bat: they
pass every dist/mods/*/mod folder to the engine as a separate absolute
argument, since ProcessModDirectory() only scans one directory level
deep. Wired into `make dist`/`dist-win` only, not the release archives.

modding/hacked/, its patches, sqweek-dialog/, its patches, and the new
mdlp/ move under modding/src/, separating source from the surrounding
Docker/script scaffolding; all path references updated to match.

NOTICE.txt gains a section for hacked's distributed build, listing hacked
(New BSD), sqweek/dialog (ISC - corrected from a wrong "New BSD" note),
and hacked's other direct Go dependencies.
This commit is contained in:
ml
2026-09-26 15:41:56 +02:00
parent c0952898ae
commit 389abd7de7
24 changed files with 839 additions and 35 deletions
+147 -20
View File
@@ -4,34 +4,59 @@ Tools and reference material for modding System Shock (1994) itself - distinct
from the rest of this repository, which is the [QuestShock](../README.md) VR
port of the game engine. Everything modding-related lives under this folder.
Both pieces here come from the [InkyBlackness](https://inkyblackness.github.io)
project and are vendored unmodified, each under its own upstream license (see
below) - neither is relicensed by this repository's own MIT license.
`hacked` and `ss-specs` come from the [InkyBlackness](https://inkyblackness.github.io)
project; `sqweek-dialog` is a separate third-party dependency of `hacked`.
All three are vendored unmodified under `src/`, each under its own upstream
license (see below) - none is relicensed by this repository's own MIT
license. `mdlp` (also under `src/`) is first-party code written for this
repository.
## 1. Layout
- `hacked/` - [InkyBlackness HackEd](https://github.com/inkyblackness/hacked),
the graphical mod editor. A vendored source snapshot (New BSD License),
taken from the `main` branch on 2026-09-26. To update it, replace this
directory with a fresh checkout and update that date.
- `ss-specs/` - [InkyBlackness ss-specs](https://github.com/inkyblackness/ss-specs),
the unofficial file-format specifications the editor (and any other modding
tool) is written against. Reference documentation only, not built. A
vendored snapshot (CC BY-NC-SA 4.0 - **non-commercial**, unlike the rest of
this repository), taken from the `main` branch on 2026-09-26.
- `src/` - every vendored and first-party source tree, kept apart from the
Docker/script scaffolding that builds them:
- `hacked/` - [InkyBlackness HackEd](https://github.com/inkyblackness/hacked),
the graphical mod editor. A vendored source snapshot (New BSD License),
taken from the `main` branch on 2026-09-26. To update it, replace this
directory with a fresh checkout and update that date.
- `hacked-patches/` - numbered `patch -p1` files applied to a scratch copy
of `hacked/` at build time (same pattern as the root project's
`android/engine-patches/`, see the repository root's
[AGENTS.md](../AGENTS.md) section 6) - `hacked/` itself is never edited
in place.
- `sqweek-dialog/` - `github.com/sqweek/dialog`, `hacked`'s native
file-dialog dependency. Vendored at the exact version `hacked/go.mod`
would otherwise pull from the module proxy (ISC License), so it can be
patched the same way as `hacked/` itself (see section 2's crash fix).
- `sqweek-dialog-patches/` - patches for `sqweek-dialog/`, same pattern as
`hacked-patches/`.
- `mdlp/` - the **Mod Definition Language Processor** (section 4). First-party
Go source, not vendored - edit it directly, no patch system needed.
- `ss-specs/` - [InkyBlackness ss-specs](https://github.com/inkyblackness/ss-specs),
the unofficial file-format specifications the editor (and any other
modding tool) is written against. Reference documentation only, not
built. A vendored snapshot (CC BY-NC-SA 4.0 - **non-commercial**, unlike
the rest of this repository), taken from the `main` branch on
2026-09-26.
- `hacked-build-image/` - the Dockerfile (and supporting scripts) for the
`hacked` build environment: the Go toolchain plus every dependency
`hacked/go.mod` pins, fetched once at image-build time so the actual build
needs no network access. Mirrors the main project's own `build-image/`
(see the repository root's [README.md](../README.md) section 4) one level
down, for this subproject.
`src/hacked/go.mod` pins, fetched once at image-build time so the actual
build needs no network access. Mirrors the main project's own
`build-image/` (see the repository root's [README.md](../README.md)
section 4) one level down, for this subproject.
- `hacked-build-image.sh` / `hacked-build-run-image.sh` /
`hacked-build-upload-image.sh` - build the image, run it to compile
`hacked/`, and push it to a registry, respectively. Same pattern as the
root project's own `build-image.sh`/`run-image.sh`/`upload-image.sh`.
- `Makefile` - `hacked` (default) builds `hacked/` via the image; `build-image`
(re)builds the image itself; `clean` removes `dist/`.
- `dist/` - build output (git-ignored), created by `make hacked`.
- `mdlp-image/`, `mdlp-image.sh` / `mdlp-run-image.sh` / `mdlp-upload-image.sh` -
the same pattern again, for `mdlp` (section 4.2).
- `mods/` - mod definitions in the Mod Definition Language (section 4).
- `Makefile` - `hacked` (default) builds `hacked/` via the image;
`build-image` (re)builds that image; `mdlp-image` builds the `mdlp` image;
`mods` processes every mod under `mods/` via that image; `clean` removes
`dist/`.
- `dist/` - build output (git-ignored): `make hacked` under `linux/`/`win/`,
`make mods` under `mods/`.
Shockolate (the engine `../engine/` vendors) already has its own modding
support (level/resource files, `res/data/`); this folder is about the
@@ -51,7 +76,7 @@ to install locally.
cd modding
# 1. Build the hacked-build-image (once, or after hacked-build-image/ or
# hacked/go.mod|go.sum changes)
# src/hacked/go.mod|go.sum changes)
./hacked-build-image.sh
# 2. Compile hacked/ for Linux and Windows
@@ -111,3 +136,105 @@ Rebecca Lansing's first email.
`ProcessModDirectory()` reports nothing on a bad path). When the path is
right, the terminal shows `ProcessModDirectory ...` / `Found resource
file: ...` / `Loading mod file ...` for confirmation.
## 4. Mod Definition Language (mods/)
The workflow above is manual and its result (whatever you clicked through in
`hacked`) isn't checked into git. `mods/` holds the same kind of edits as
plain, reviewable JSON instead, and `mdlp` (the **Mod Definition Language
Processor**, `mdlp/`) turns them into the same kind of mod folder and a zip,
reproducibly and offline.
### 4.1. Layout
```
mods/
<mod-name>/
messages/
<label>.json # one message's overrides
<label>.txt # optional: a text field's body, referenced by ...File
```
Each `messages/*.json` overrides one message (email, log, or fragment) by
type and index. Only fields present in the file are changed - everything
else is read from the game's own data (Static World Data), the same
inheritance the Messages window in `hacked` shows via its "(read-only)"
label:
| Field | Meaning |
|---|---|
| `type` | `Mails`, `Logs`, or `Fragments` |
| `index` | The message's index within that type |
| `language` | `Default`, `French`, or `German` - omit for `Default` |
| `title`, `sender`, `subject` | Plain strings |
| `verboseText` / `verboseTextFile` | The long body, inline or from a sibling file (avoids escaping newlines/quotes in JSON) |
| `terseText` / `terseTextFile` | The short body, inline or from a sibling file |
| `nextMessage`, `isInterrupt`, `colorIndex`, `leftDisplay`, `rightDisplay` | See `ElectronicMessage` in `src/hacked/ss1/content/text/ElectronicMessage.go` |
`mods/mymod/` is a worked example matching section 3's Rebecca's-mail edit,
expressed as MDL instead of a `hacked` session.
### 4.2. `mdlp`
`src/mdlp/` is first-party Go source (not vendored - edit it directly, no
patch system needed). It reuses `hacked`'s own resource read/write code
(`ss1/edit`, `ss1/world`, `ss1/content/text`) through a `go.mod` `replace`
pointing at the sibling `../hacked` (both under `src/`), instead of
reimplementing System Shock's file formats. It needs no GUI, GLFW, or GTK -
only the packages that model resource data.
`mdlp-image/` is its own Docker build/runtime environment, separate from
`hacked-build-image/`: mod processing is expected to grow further external
tool dependencies (image/audio conversion, etc.) of its own over time,
independent of `hacked`'s GTK/GLFW/mingw toolchain.
### 4.3. Building mods
```sh
cd modding
# 1. Build the mdlp-image (once, or after mdlp-image/ or
# src/mdlp/go.mod|go.sum changes)
./mdlp-image.sh
# 2. Process every mod under mods/
make mods
# ...or only some of them:
MODS="mymod" make mods
```
Needs the game's own data directory at `../res/assets/ss_ee/data` (see the
root [README.md](../README.md) section 3) as read-only reference data, so
overrides that touch only some fields of a message can inherit the rest.
Output lands in `dist/mods/<mod-name>/`: a `mod/` folder (the same shape
`hacked`'s own Project window Save produces) and `<mod-name>.zip`.
Test every built mod at once with `run-mods.sh` (Linux) / `run-mods.bat`
(Windows), copied into the root project's own `dist/`/`dist-win/` by
`make dist` (root [README.md](../README.md) section 4) alongside the plain
`run.sh`/`run.bat`:
```sh
../dist/run-mods.sh
```
It scans `dist/mods/*/mod` and passes each one it finds to `run.sh` as a
separate, absolute argument - the engine's `ProcessModDirectory()` only scans
one directory level deep (see `res/run-mods.sh`'s own comment), so it can't
simply be pointed at `dist/mods/` itself. To test a single mod instead, use
`run.sh` directly the same way as section 3's step 5:
```sh
../dist/run.sh "$(pwd)/dist/mods/mymod/mod"
```
### 4.4. Publishing the mdlp-image
Same as `hacked-build-image/` (section 2.1): `./mdlp-upload-image.sh` reads
`REGISTRY`/`REGISTRY_USER`/`REGISTRY_PASSWORD` and (optionally)
`MDLP_REGISTRY_IMAGE` from `../registry.env`. Don't bump
`mdlp-image/VERSION` just because the Dockerfile changed - only once a build
with the current version has actually succeeded, same policy as the root
project's own `build-image/VERSION`.