Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,19 +168,32 @@ hashes are given where a change is easiest to understand by reading the commit.
(file, categories, host or guest clock). Network tab text says "guest"
rather than "Indy", since Indigo2s use it too. jitv2 builds also set the
compile thread count (`[jitv2] threads`), an August setting with no control.
- **Machine TOML files and private machine folders.** The GUI discovers and
saves `machines/<name>/<name>.toml` using the standalone emulator's schema.
`gui.json` retains GUI preferences and the active machine name. Each machine
runs from its own folder, with relative config paths, default disks under
`disks/`, and private `nvram.bin`/`nveeprom.bin` files. Existing JSON machines
migrate with a preferences backup; external disks stay in place. App Store
storage uses the container home, bookmarks cover external relative paths,
and CHD diffs migrate to each machine's folder. Renaming moves the folder and
TOML together. Import/export menu actions are removed. Directory changes wait
for emulator teardown and queued file operations, and startup seeds NVRAM
before machine construction.
- **NVRAM EEPROM gets the same stable-path treatment as NVRAM.** `nveeprom`
(Indigo2/IP28's motherboard EEPROM — where `eaddr` and PROM env actually live
on those profiles, not in the NVRAM file) used to default to a bare
`"nveeprom.bin"` with no anchoring, no migration, and no config-editor field,
unlike `nvram`. Since iris-gui never changes its working directory, that bare
default resolved against whatever CWD the OS happened to launch it with —
often not the same place twice between `cargo run` and a bundled `.app` — so
a machine could silently load a different (usually blank) EEPROM each
launch, and "Reset NVRAM"'s MAC write (which only ever touched `nvram`) had
no effect on Indigo2/IP28's actual `eaddr`. Now: a stable
`<config dir>/iris/nveeprom.bin` default (`GuiSettings::default_nveeprom_path`),
the same relative-path migration `nvram` gets on load, and a "NVRAM EEPROM
file" row on the General tab right below NVRAM file.
unlike `nvram`. At the time iris-gui never changed its working directory, so
that bare default resolved against whatever CWD the OS happened to launch it
with — often not the same place twice between `cargo run` and a bundled
`.app` — so a machine could silently load a different (usually blank)
EEPROM each launch, and "Reset NVRAM"'s MAC write (which only ever touched
`nvram`) had no effect on Indigo2/IP28's actual `eaddr`. Fixed first with a
stable `<config dir>/iris/nveeprom.bin` default and the same relative-path
migration `nvram` gets on load; machine folders (above) later superseded
that fixed path, so `nveeprom.bin` now resolves next to each machine's own
`nvram.bin`. Either way there's a "NVRAM EEPROM file" row on the General tab
right below NVRAM file.
- **"Reset NVRAM" and the no-MAC-yet pre-flight now also patch the NVRAM
EEPROM.** Both only ever touched the DS1386 `nvram` file — on Indigo2/IP28,
which read `eaddr` from `nveeprom` instead (see above), this had no effect
Expand Down
23 changes: 11 additions & 12 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ turn off those dependency defaults.

| GUI feature | Purpose | Enables |
|---|---|---|
| `bundled` | Distribution UI: hides TOML import/export; named machines remain in gui.json. | — |
| `bundled` | Distribution UI: hides source-checkout tools; machine TOML storage is shared by all builds. | — |
| `appstore` | Mac App Store UI, sandbox bookmarks, private-API patch, CHD diff redirection, and REX3 JIT disabled at startup; hides CI/Ultra64 controls. | `bundled` |
| `pcap` | Expose PCAP NIC selection and bridge support in the embedded core. | `iris/pcap` |
| `premiere` | Performance bundle for the embedded core; lightning plus idle parking. | `iris/lightning`, `iris/idle-pause` |
Expand Down Expand Up @@ -121,11 +121,11 @@ separate from the runtime environment inventory below.
## Shared machine configuration inventory

Source: [src/config.rs](src/config.rs), `MachineConfig` and nested structs.
The CLI reads `iris.toml` (or `--config FILE`); iris-gui stores the same schema
inside its named machines in `<config dir>/iris/gui.json`. TOML import/export
is available in GUI source builds. Relative core paths resolve from the process
working directory; the GUI anchors its default battery-backed paths in its
user config directory.
The CLI reads `iris.toml` (or `--config FILE`); iris-gui reads and saves the same
TOML schema in `<config dir>/iris/machines/<name>/<name>.toml`. `gui.json` holds
GUI preferences. The GUI uses the selected machine folder as the working
directory, stores config paths as relative paths, and defaults to `disks/` for
disk images and `nvram.bin`/`nveeprom.bin` for private battery-backed state.

Defaults below are **core schema/runtime defaults**, not this checkout's
sample iris.toml or the GUI New Machine dialog. GUI IP28 creation selects
Expand Down Expand Up @@ -376,17 +376,16 @@ not Cargo build features.
## iris-gui persisted settings inventory

Source: [iris-gui/src/settings.rs](iris-gui/src/settings.rs). These keys belong
to GUI JSON rather than iris.toml. View controls update the scales; the GUI
writes machine configs and sandbox access state for you.
to GUI JSON rather than machine TOML files. View controls update the scales;
the GUI saves machine TOMLs and sandbox access state for you.

| GUI JSON key | Normal load default | Purpose |
|---|---|---|
| `ui_scale` | `1.25` | egui controls scale; sanitized to supported range. |
| `vm_scale` | `0.75` | Maximum framebuffer magnification, independent of UI scale. |
| `machines` | empty map | Named shared MachineConfig objects. |
| `active_machine` | unset | Selected key in machines. |
| `recent_configs` | empty list | Recent TOML imports. |
| `last_config` | unset | Legacy one-shot TOML migration source. |
| `active_machine` | unset | Selected machine folder name. |
| `recent_configs` | empty list | Legacy recent TOML paths; retained for compatibility. |
| `last_config` | unset | Legacy TOML migration source; cleared after conversion. |
| `bookmarks` | empty map | macOS sandbox security-scoped bookmarks by absolute path. |
| `disk_folders` | empty list | Granted recursive sandbox disk-folder access. |

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ boots to a usable system: shell, networking, X11, the works.
in-process NFSv2/v3 server, TFTP for PROM network boot, and FTP/XDMCP helpers;
or PCAP bridging onto a real LAN
- Headless mode and a CI control socket (`iris-ci`) for automation
- Optional egui front-end (`iris-gui`) with machine management and a benchmark tab
- Optional egui front-end (`iris-gui`) with machine management and a benchmark tab;
machines use the CLI TOML schema in isolated folders with relative paths
- DaynaPort SCSI/Link Ethernet and the N64 development board (Ultra64), both enabled per machine in the config
- Other guests: Linux (Debian 7, Gentoo), NetBSD and OpenBSD have had SCSI,
interrupt and timer fixes land for them. They are not regularly tested, so
Expand Down
150 changes: 105 additions & 45 deletions iris-gui-README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ emulation will be noticeably slow.
| `hostgl` | Host OpenGL for IRIX programs (implies `hostcall`); IRIX's replacement libGL replays its GL calls on the host GPU. macOS (CGL) and Linux (EGL; needs libGL to build, libEGL and Mesa's EGL/DRI drivers at run time; see CHANGELOG) — builds elsewhere but registers no backend |
| `premiere` | `iris/lightning` + `iris/idle-pause` for maximum in-process speed |
| `perfetto` | Perfetto trace recording; the Debug tab gains Start/Stop for a `.pftrace` (see `docs/perfetto.md`) |
| `bundled` | Distributed build: hides the iris.toml import/export items. Set by the Release workflow |
| `bundled` | Distributed build: hides source-checkout tools such as premiere preparation. Machine TOML storage is shared by source, Release, and App Store builds. Set by the Release workflow |
| `appstore` | Mac App Store build: implies `bundled`, hides the CI tab, enables security-scoped bookmarks and folder grants |

DaynaPort, Ultra64, IP28/R10000, and the graphics board models are built in.
Expand Down Expand Up @@ -81,18 +81,19 @@ cargo tree -p iris | grep -E 'egui|eframe|rfd' # should print nothing

## 2. First run

On a cold start (no `gui.json`), the **New machine** dialog opens
When no machine TOML files exist, the **New machine** dialog opens
automatically. You'll be asked for:

- **Name** — used for the machine entry in `gui.json`. Conflicts get a
numeric suffix (`indy`, `indy-2`, …).
- **Name** — names the machine folder and its TOML file. Conflicts get a
numeric suffix (`indy`, `indy-2`, …). Names cannot contain path separators
or reserved filename characters.
- **Machine model** — Indy (IP24), Indigo2 (IP22), or Indigo2 IMPACT (IP28).
**Processor** offers R4400, R5000, and R10000. Selecting IP28 defaults to
R10000 and IMPACT Solid graphics.
- **PROM image** — defaults to "Use embedded PROM (bundled with iris)",
which lets iris fall back to its built-in PROM blob with no disk file
needed for IP24, IP22, or IP28. Each profile selects its own embedded image.
- **NVRAM file** — defaults to a stable per-user path (see Storage) and is
- **NVRAM file** — defaults to `nvram.bin` in the machine folder (see Storage) and is
seeded with a default NVRAM on first use. Indigo2 profiles also have a
**NVRAM EEPROM file**, which stores their PROM environment and MAC.
- **Total RAM** — preset totals. Tick **Advanced: configure individual banks**
Expand All @@ -102,7 +103,7 @@ automatically. You'll be asked for:
image afterwards from the SCSI menu (**Create blank HDD image…**).
- **CD-ROM (SCSI #4)** — optional install media.

Hit **Create**. The machine is saved to `gui.json` and becomes active. Start it
Click **Create**. The machine is saved to `machines/<name>/<name>.toml` and becomes active. Start it
from the **Machine** menu.

If the NVRAM or motherboard EEPROM has no Ethernet MAC, iris-gui writes one
Expand Down Expand Up @@ -132,7 +133,7 @@ light for the internal network).

| Menu | Contents |
| --- | --- |
| **File** | New machine… / Switch to machine / Import iris.toml… / Export current to iris.toml… / Prepare for premiere… (source builds) / Disk folder access (App Store) / Quit |
| **File** | New machine… / Switch to machine / Rename current… / Delete current machine / Prepare for premiere… (source builds) / Disk folder access (App Store) / Quit |
| **Machine** | Start / Stop / Reset / Reset NVRAM (fresh PRAM) / Processor (R4400, R5000, or R10000, applies at next Start) / Save and Restore state / Screenshot… / Serial console… |
| **Memory** | Total presets, plus per-bank submenus |
| **SCSI** | Per-ID submenu (SCSI #1 … #7) with context-appropriate actions, plus per-disk **Commit changes to disk** / **Discard changes** for COW overlays and CHD diffs while stopped |
Expand Down Expand Up @@ -195,58 +196,116 @@ PCAP-mode NFS IP).

### Where things live

- `<config dir>/iris/gui.json` — **the system of record.** Contains all saved
machines, the active machine pointer, UI scale, VM screen scale, and
fullscreen pref. `<config dir>` is `dirs::config_dir()`: `~/.config` on Linux,
`~/Library/Application Support` on macOS, `%APPDATA%` on Windows.
- `<config dir>/iris/nvram.bin` — the default NVRAM path. It is absolute on
purpose, so the NVRAM is the same however the app was launched; older relative
`nvram.bin` entries are migrated.
- `<config dir>/iris/nveeprom.bin` — the default NVRAM EEPROM path (Indigo2/IP28's
motherboard EEPROM, where `eaddr` and PROM env actually live on those profiles —
Indy has no such chip). Same absolute-path and migration treatment as `nvram.bin`
above. Editable on the General tab, right below NVRAM file.
- `iris.toml` — the **standalone iris CLI's** config format. iris-gui
treats it as *import/export only* via the File menu (hidden in `bundled`
builds), so a machine configured in the GUI can still be booted with
`cargo run -- --config exported.toml`.
The storage root is `<config dir>/iris`: `~/Library/Application Support/iris`
on macOS, `$XDG_CONFIG_HOME/iris` (default `~/.config/iris`) on Linux, including
AppImage builds, and `%APPDATA%/iris` on Windows. Mac App Store builds use
Foundation's container home, so the root is inside
`~/Library/Containers/<bundle id>/Data/Library/Application Support/iris`.

```text
iris/
├── gui.json
└── machines/
├── indy/
│ ├── indy.toml
│ ├── nvram.bin
│ ├── nveeprom.bin
│ └── disks/
│ └── scsi1.raw
└── indigo2/
├── indigo2.toml
└── disks/
```

`gui.json` holds GUI preferences and the active machine name. Each machine's
TOML file is its configuration source, using the same `MachineConfig` schema
as the standalone `iris` CLI. At launch, the GUI lists
`machines/<name>/<name>.toml` and selects the last opened machine. If that
machine is unavailable, it selects the first available machine or opens
**New machine** when the list is empty. Invalid TOML files produce visible
errors and remain untouched.

The selected machine folder is the emulator's working directory. Config paths
stay relative to that folder, including files selected with **Browse**:

```toml
prom = "(embedded)"
nvram = "nvram.bin"
nveeprom = "nveeprom.bin"

[scsi.1]
path = "disks/scsi1.raw"
```

New disk images default to `disks/scsiN.raw`. External images stay in place;
the TOML references them with relative paths such as `../../../../media/root.chd`.
The same resolution applies to disc changers, NFS shares, TFTP folders, serial
logs, ELF binaries, test dumps, filesystem CI sockets, and JIT cache folders.
TCP CI addresses and network interface names retain their original spelling.
On Windows, external paths must be on the same volume as the machine folder
to be expressible as relative paths.

NVRAM and motherboard EEPROM default to separate files in each machine folder.
App Store CHD diffs use that machine's `chd-diffs/` folder. Security-scoped
bookmarks remain in `gui.json` and use resolved absolute paths, including
external resources reached through `..`.

To run the same machine with the CLI, change to its folder and pass its TOML:

```sh
cd "$HOME/Library/Application Support/iris/machines/indy"
/path/to/iris --config indy.toml
```

### `gui.json` shape

```json
{
"ui_scale": 1.15,
"fullscreen": false,
"ui_scale": 1.25,
"vm_scale": 0.75,
"active_machine": "indy",
"machines": {
"indy": { "prom": "(embedded)", "nvram": "/home/me/.config/iris/nvram.bin", ... },
"irix-65": { ... }
},
"recent_configs": [...],
"last_config": null
"recent_configs": [],
"last_config": null,
"bookmarks": {},
"disk_folders": []
}
```

`MachineConfig` (defined in `src/config.rs`) is serde-serialized directly, so the
schema follows the canonical iris config. Existing `gui.json` files from earlier
iris-gui builds upgrade automatically (missing fields default).
The legacy `recent_configs` and `last_config` keys remain compatible with
older preferences. Machine configurations are never serialized into this JSON.
The File menu has no TOML import or export actions: place a machine TOML in its
matching folder instead. **Prepare for premiere** uses the existing machine
TOML; it no longer creates a separate exported config.

### Autosave
### Autosave and machine management

Every form field, dialog result, and menu action that mutates the config
calls `App::mark_dirty()`. Each frame, `App::maybe_autosave()` flushes after
**~600 ms of inactivity** — debouncing keystrokes without leaving you in a
"did it save?" state.
Config edits save to the active TOML after **~600 ms of inactivity**. Hard
flushes also occur before **Start**, **Quit**, **Switch to machine**, and
**Rename current**. Preference saves do not rewrite machine TOMLs. Selecting
or starting a machine rereads its file, so external edits take effect too.

Hard flushes also occur before **Start**, on **Quit**, on **Switch to machine**,
and on **Import iris.toml…**.
Switching, creating, renaming, and deleting machines require the emulator to
be stopped and its queued file operations to finish. Renaming moves the
machine folder and renames its TOML together, retaining its disks and
battery-backed state. Deleting removes the TOML registration and retains the
folder's disk and NVRAM files. Retained folders reserve their names to prevent
accidental reuse.

### Migration

If you had an older iris-gui that pointed `last_config` at an
`iris.toml`, the first launch of the new build will import that TOML as
a named machine (using the file stem), clear the legacy pointer, and
adopt it as `active_machine`. No manual steps required.
On the first launch with machines embedded in `gui.json`, the GUI keeps
`gui.json.pre-toml.bak`, writes each machine to its TOML folder, and removes
only the machine payloads from the preferences. It copies battery-backed
state into each machine's folder, preserving PROM settings and MAC addresses.
Existing disks stay in place and their paths become relative. App Store CHD
diffs are copied from the shared redirect into the machine's redirect, and
the original files remain available for rollback. Names that cannot be used
as folder names receive deterministic safe names.

A legacy `last_config` pointer also migrates once into a machine folder; its
source TOML stays untouched. If migration fails, the original JSON and its
backup remain intact, and preference writes stop until the error is resolved.


---

Expand Down Expand Up @@ -351,7 +410,8 @@ iris/
├── bench_ui.rs Benchmark tab
├── scsi_menu.rs SCSI menu (per-ID actions, disc pickers)
├── safe_stop.rs Stop-safety evaluator
├── settings.rs GuiSettings: gui.json, NVRAM seeding and MAC helpers
├── settings.rs GUI preferences, migration, NVRAM seeding and MAC helpers
├── machines.rs TOML folders, relative paths, discovery, and rename
├── ram.rs RAM presets shared by menus and tabs
├── netplan.rs subnet math for the Networking tab
├── netfix.rs "check / fix guest networking" logic
Expand Down
10 changes: 3 additions & 7 deletions iris-gui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,8 @@ assets = [
]

[features]
# Pre-compiled / distributed build. Hides developer-only affordances that don't
# make sense for users running an official binary — currently the iris.toml
# import/export menu items (the GUI's gui.json machine store is the system of
# record; iris.toml is only useful alongside the standalone `iris` CLI, i.e. a
# source checkout). Set by the Release and App Store workflows. A no-op for
# source builds, where the iris.toml workflow stays available.
# Distributed build. Hides source-checkout tools such as premiere preparation.
# Machine TOML storage is shared by source, Release, and App Store builds.
bundled = []
# Mac App Store distribution build. Implies `bundled`, and additionally hides
# the CI/Automation config tab (the iris-ci socket is a developer automation
Expand Down Expand Up @@ -124,7 +120,7 @@ if-addrs = "0.15"
[target.'cfg(target_os = "macos")'.dependencies]
objc2 = "0.6"
objc2-foundation = { version = "0.3", features = [
"NSURL", "NSData", "NSError", "NSString", "NSArray",
"NSURL", "NSData", "NSError", "NSString", "NSArray", "NSPathUtilities",
] }

[[bin]]
Expand Down
3 changes: 2 additions & 1 deletion iris-gui/src/bench_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -800,10 +800,11 @@ mod dev {
fn locate(rel: &str) -> Option<PathBuf> {
let exe_dir = std::env::current_exe().ok().and_then(|p| p.parent().map(PathBuf::from));
let name = Path::new(rel).file_name()?.to_owned();
let mut candidates = vec![PathBuf::from(rel), PathBuf::from("..").join(rel)];
let mut candidates = vec![crate::launch_dir().join(rel), crate::launch_dir().join("..").join(rel)];
if let Some(d) = exe_dir {
candidates.push(d.join(&name));
candidates.push(d.join(rel));
if let Some(root) = d.parent().and_then(Path::parent) { candidates.push(root.join(rel)); }
}
candidates.into_iter().find(|p| p.exists())
}
Expand Down
Loading
Loading