Repository navigation
v3.8.1 to v3.8.2 Experiment Development Notes
This page is an engineering record derived from the source code, tests, and experiment notes in three separate research workspaces. It documents implemented prototypes, observed behavior, technical hypotheses based on those observations, rejected approaches, and clues worth preserving.
All features described here remained experimental. They do not mean that arbitrary third-party mod state restoration is supported by a released build, and they are not part of the current v3.9.0 feature set.
The original request was not merely to remember which mod folders were enabled. The manager was expected to remember clothing, color, component, toggle, variant, and other internal choices for each mod, then restore those choices when the user returned to that mod or applied a multi-mod profile.
Three kinds of state must be distinguished:
-
Deployment state: whether a mod folder exists in the XXMI/EFMI
Modsdirectory. -
On-disk state: values already written into a mod-local
.inior the loader-leveld3dx_user.ini. - Runtime state: values currently active in the game/3DMigoto process, which may not yet be written to disk and may not be reloaded automatically after an external file edit.
The central difficulty is that the manager can read and write files reliably, but it cannot assume that “the file now contains the desired value” means “the running game has adopted the desired value.”
All three workspaces were derived from the untouched v3.8.0 codebase. Existing repository, profile, deployment, updater, and WinUI behavior was retained while each experiment was added as an isolated module.
| Experiment | Version identity | Core implementation | UI integration | Tests | Research note |
|---|---|---|---|---|---|
| Whole-file snapshot/hot replacement | 3.8.1-hot-injection-lab |
IntegratedModManager.Core/CompleteFileSnapshot.cs |
WinUI3/MainWindow.ModStatePresets.cs |
Tests/CompleteFileSnapshotTests.cs |
跨Mod预设测试说明.md |
Precise global persist state |
3.8.1-persistent-state-lab |
IntegratedModManager.Core/ThreeDmigotoPersistentState.cs |
WinUI3/MainWindow.PersistentModState.cs |
Tests/ThreeDmigotoPersistentStateTests.cs |
docs/跨Mod内部状态实验说明.md |
| EFMI-native resident controller | 3.8.2-efmi-resident-lab |
IntegratedModManager.Core/EfmiResidentMod.cs |
WinUI3/MainWindow.EfmiResident.cs |
Tests/EfmiResidentModTests.cs |
docs/EFMI原生常驻方案实验说明.md |
Complex format handling lives in IntegratedModManager.Core; WinUI event handling, prompts, path selection, and configuration persistence live in WinUI3; pure algorithms have unit tests. This separation makes file logic testable without launching the game, but a passing unit test cannot prove F10 ordering or an in-game visual result.
| Route | Working hypothesis | Implemented result | Final assessment |
|---|---|---|---|
v3.8.1 A: snapshot all of d3dx_user.ini
|
Avoid understanding mod variables; restore the opaque file and ask F10 to reload it | Backup, hashing, atomic replacement, and F10 simulation were implemented | File restoration is reliable; runtime reload direction and global blast radius are not controlled |
v3.8.1 B: capture per-mod global persist values |
Declarations in each mod can establish ownership of variables stored in the root config | Precise capture, offline merge, a temporary post bridge, and a two-F10 flow were implemented |
Offline restoration is well bounded; hot restoration is still constrained by 3DMigoto save/reload ordering |
| v3.8.2: resident loading plus activation gates | Keep namespaces registered and switch which mod is allowed to act instead of restoring values | Compatibility analysis, conservative INI transformation, controller generation, and isolated output were implemented | The architecture avoids root-config replacement, but arbitrary third-party INI cannot be transformed safely and generically; real performance/compatibility testing remains incomplete |
The first prototype prioritized format compatibility. Since different mods use different variable names and structures, the manager treated d3dx_user.ini as opaque bytes rather than interpreting it. This avoids damaging comments, ordering, line endings, or variable forms unknown to a simple parser.
The intended data flow was:
Change mod options in game
↓
XXMI/EFMI writes state to d3dx_user.ini
↓
The manager stores the complete byte sequence
↓
Applying a preset backs up the current file and atomically restores the snapshot
↓
F10 is sent in an attempt to reload the restored file
CompleteFileSnapshot operates only on complete byte arrays:
- Snapshot metadata includes file length and SHA-256.
- Both length and digest are checked before a restore.
- Data is written to a temporary sibling file first, then committed with
File.Replace; unsupported or failed replacement falls back to an overwrite move. - Temporary files are cleaned up in all cases.
The WinUI layer added:
- Automatic
d3dx_user.inidetection from the target Mods directory, its parent/grandparent, and the launcher directory/parent. - Snapshot storage under
presets/mod-statesin the application directory. - Timestamped current-file backups under
backups/mod-states. - Metadata for repository ID, preset name, original config path, snapshot filename, SHA-256, size, first-level deployed mod folders, and UTC timestamp.
- Detection of XXMI, EFMI, 3DMigoto, and 3DMLoader-related processes before applying.
- Best-effort rollback to the pre-restore bytes if replacement fails.
- A three-second delay followed by a Windows keyboard event for F10 when the foreground window is not the manager; otherwise the UI asks for a manual F10.
- Snapshot filename validation that rejects nested paths and confirms the resolved path remains under the snapshot root.
Unit tests verify that:
- A snapshot is accepted when all bytes, length, and SHA-256 match.
- A same-length mutation is rejected.
- A wrong length is rejected.
- Atomic replacement preserves every byte of the replacement content.
During the 2026-08-24 review, all 28 core tests and all 21 updater tests passed. The complete WinUI restore/build stage could not finish because the current environment could not access NuGet and therefore could not restore packages such as Microsoft.Data.Sqlite and Microsoft.WindowsAppSDK. This is an environment/dependency-restoration limitation in this review, not a newly observed failure in the snapshot algorithm tests.
Real-environment observation later showed that changing a global persist value in game causes the loader to rewrite d3dx_user.ini in real time. This proves a working runtime memory → disk file direction, but it does not prove a symmetric and reliable externally edited disk file → runtime memory direction.
That distinction created several fundamental problems:
- F10 may save before it reloads. If the current in-memory values are written first, they can overwrite the restored snapshot before the snapshot is read.
- The snapshot has a global blast radius. A preset intended for one mod also restores all other persistent mod values and unrelated settings stored in the same root file.
- The deployed-mod list is contextual metadata only. It does not isolate the variables owned by each mod.
- Sending F10 does not prove application. The manager can emit a key event, but cannot prove that the intended game window received it or that the loader completed its reload.
- The UI vocabulary was still evolving. The prototype contained both “hot injection” behavior and advice to restore with the game/XXMI closed, reflecting unresolved runtime semantics.
This route proved that complete-file preservation can be made robust. It did not prove that a running game will reliably adopt the restored file, so it was not promoted into a release.
The whole-file route failed because of granularity and reload ordering, not because copying bytes was difficult. The second v3.8.1 route therefore handles only variables that can be attributed to a specific mod: scan every .ini in an enabled mod, find global persist $variable declarations in [Constants], then read their current fully qualified values from [Constants] in the loader-level d3dx_user.ini.
The real test environment provided the following clues:
- Launcher:
G:\XXMI Launcher\Resources\Bin\XXMI Launcher.exe - Mods directory:
G:\XXMI Launcher\EFMI\Mods - Persistent user state:
G:\XXMI Launcher\EFMI\d3dx_user.ini - Game process:
Endfield.exe - Reload binding:
reload_config = no_modifiers VK_F10 - One inspected outfit mod could be associated with roughly 14 persistent variables, including component toggles and numeric values resembling
mx/mystate.
ThreeDmigotoPersistentState deliberately uses conservative rules:
- Read assignments only inside
[Constants]ind3dx_user.ini. - Accept only
$name = numeric-valueassignments. - Permit signs, decimals, and scientific notation, but require the value to parse as a finite invariant-culture
double; strings, vectors, commands, and non-finite values are rejected. - Recursively scan every
.iniin the mod and recognize onlyglobal persist $namedeclarations inside[Constants]. - Use an explicit
namespace = ...declared before the first section when present. Otherwise derive the default namespace fromMods\<relative INI path>. - Qualify a local
$nameas$\<namespace>\name; preserve an already fully qualified global name. - Normalize names to lowercase and reject unsafe namespaces or names containing traversal, assignment, control, or other invalid syntax.
- Return the intersection between values declared by the mod and values currently present in the user config.
This eliminated the largest blast-radius problem of the whole-file approach: unrelated values no longer had to be restored.
Capture does not send F10. Every 350 ms, the manager samples file length and last-write time. After two matching samples, it reads the text and verifies those attributes again. The default timeout is five seconds.
The reasoning follows directly from the observation that in-game changes already rewrite the file: the capture side only needs to wait for a stable write, and an extra F10 could itself alter the state being captured.
Captured state is stored by:
- repository ID;
- mod-relative source path;
- variable name/value list;
- update timestamp;
- optional
PersistentStatescollections inside multi-mod configuration profiles.
Only a mod currently deployed in the target Mods directory can be captured or restored, reducing the chance of mapping a repository entry to the wrong runtime folder.
Offline restore has the clearest technical boundary in this branch:
- Derive the game process from
target = ...in the siblingd3dx.ini, while also checkingEndfield.exeby default. - Refuse offline restoration while the game process is running.
- Read a stable
d3dx_user.ini. - Update only target assignments in
[Constants]; preserve other sections, values, and comments as far as possible. - Update every duplicate occurrence of a target variable so a later stale assignment cannot win.
- Create
[Constants]if missing and insert missing target variables in name order. - Preserve CRLF/LF style and terminal-newline behavior.
- Create a millisecond-timestamped backup under
IntegratedModManager Backups. - Commit through a temporary file and overwrite move.
- Reparse for up to two seconds and compare expected values numerically.
- Start XXMI only after verification; never press F10 in this flow.
The prerequisite is that the game is fully closed. Otherwise the runtime component may write stale memory values over the external edit again.
Because direct root-config replacement may be overwritten at the start of F10 processing, the runtime prototype creates a temporary mod file:
ZZZ_IntegratedModManager_StateBridge.ini
It contains a generated marker and assignments such as:
[Constants]
post $\namespace\variable = 2The intended sequence uses 3DMigoto config execution rather than only editing the root file:
- Write the temporary bridge.
- Ask the user to return to the game and manually press F10 once so the bridge is loaded and its
postassignments enter runtime state. - After the user confirms in the manager, delete the bridge.
- Ask for a second manual F10 so the temporary override disappears while the newly assigned persistent values are expected to remain saved.
- Poll
d3dx_user.inifor up to six seconds and verify the target numeric values.
This version deliberately does not synthesize keyboard input. It refuses to overwrite a same-named file without the generated marker and cleans up only a marked generated bridge after success or failure.
Core tests cover:
- Parsing numeric assignments only from
[Constants]. - Explicit and path-derived default namespaces.
- Capturing only the intersection of declarations and user-config values.
- Building
postbridges while rejecting command-like or unsafe values. - Rejecting malformed variable names and namespaces.
- Updating only target variables while preserving other content.
- Creating
[Constants]when absent. - Updating all duplicate assignments so the final assignment cannot restore an old value.
In the 2026-08-24 review, all 37 core tests passed, with 97.7% line coverage and 93.4% branch coverage. The later DataStore restore stage stopped because NuGet was inaccessible in the current environment, so the current test-all.ps1 run cannot be recorded as a complete pass.
The route is more precise than a whole-file snapshot, but it still cannot provide universal, automatic, reliable runtime restoration:
- It supports only parseable scalar numeric
global persistvalues, not strings, vectors, resource state, commands, side effects, or memory-only variables. - A third-party mod may keep assigning the same value in
[Present], a key section, or another command list; the bridge value can be overwritten in the same or a later frame. - F10 save/load ordering is loader runtime behavior and cannot be proven by file unit tests.
- Two manual F10 presses and window switching are not seamless automatic restoration.
- Fully qualified names may change when a mod folder or deployment layout changes.
- Correct values in
d3dx_user.iniprove the disk state only, not that clothing, materials, and rendered resources are already visually synchronized.
Offline restoration remains a potentially useful controlled tool. Runtime restoration did not meet the reliability bar for a released feature.
Instead of continuing to ask “how can old values be pushed back into a running mod?”, v3.8.2 asked:
If candidate mods remain in EFMI's loaded set, can their namespaces and
global persistregistrations remain alive while the manager controls only which mod may respond to keys, override resources, and draw?
This became the resident-loading plus activation-gate architecture:
Multiple managed mod copies remain in the Mods set
↓
Each standard runtime path receives an independent enable condition
↓
One controller persists $active_profile
↓
[Present] derives an enable flag for every mod from the selected profile
↓
Ctrl+Right / Ctrl+Left switches whole profiles in game without F10
The manager no longer needs to copy each mod's values into the root config. If the mod stays registered, XXMI/EFMI remains responsible for persisting the mod's own original variables.
Before transformation, EfmiResidentMod.Analyze recursively scans active .ini files, ignores filenames beginning with DISABLED, and classifies a mod as:
-
Supported: matches the conservatively recognized EFMI Tools structure and can be generated automatically. -
ReviewRequired: uses a nonstandard layout or contains a runtime path that cannot be proven gated. The current implementation also refuses automatic generation for this class. -
Unsupported: has a blocking issue, such as shipping a globald3dx.iniinside the mod or containing no active INI.
Recognition checks include:
-
CommandList\EFMIv1\RegisterModorCommandListRegisterModregistration paths; - a
$mod_enableddeclaration; - an
$object_detecteddeclaration; - counts of
global persistdeclarations; - counts of
[Key...]sections; - runtime effects such as
handling, draw commands,run,this, shader resource bindings, and vertex/index buffer assignments insideTextureOverride/ShaderOverridesections; - whether each effect-bearing override is already under a recognized
$mod_enabledor$object_detectedgate.
An effect-bearing override without a standard gate produces UNGUARDED_OVERRIDE and requires review. The policy intentionally rejects unknown layouts instead of rewriting something it cannot establish as safe.
For a Supported mod, the transformer makes only two structured changes:
-
Key gating
- An existing
condition = expressionin[Key...]becomes(expression) && $\IntegratedModManager\ResidentController\enable_<modId>. - A key section without a condition receives the enable condition at the end of the section.
- An existing
-
Standard runtime-path gating
- An
ifline using$mod_enabledor$object_detectedis conjoined with the resident enable flag. - For example:
- An
if $mod_enabled && DRAW_TYPE == 4becomes:
if $\IntegratedModManager\ResidentController\enable_mod_a && ($mod_enabled && DRAW_TYPE == 4)The source repository mod is never edited. A copy is made in an isolated output directory and only that copy is transformed. A stable mod ID combines an ASCII slug from the display name with the first four bytes of a SHA-256 over the normalized source path. If a non-ASCII name produces no slug, the prefix falls back to mod.
The fixed controller filename is:
000_IntegratedModManager_ResidentController.ini
Its namespace is:
IntegratedModManager\ResidentController
The generated design contains:
-
global persist $active_profile = <first profile ID>, allowing EFMI itself to remember the last selected profile. - A non-persistent global
$enable_<modId>for every controlled mod. -
[KeyResidentProfile]withtype = cycleandsmart = true. -
Ctrl+Rightfor next andCtrl+Leftfor previous by default. - A
[Present]block that derives each mod's enable value from$active_profileevery frame.
The manager orders the current repository's configuration profiles by update time, numbers them 1, 2, 3, and so on, collects all mods referenced by any profile, generates stable IDs, then maps each profile's mod-relative paths to the set of enabled IDs.
Generation deliberately remains isolated and experimental:
- The user chooses a parent directory; output is written to an
IntegratedModManager Resident Setchild folder. - A non-empty existing output folder is never overwritten.
- Every mod is reanalyzed immediately before generation, and only
Supportedis accepted. -
PathSafety.ResolveInsideDirectoryconstrains every resolved path to the output root. - The controller is written first; every mod is then copied to
Resident_<modId>and only the copied INIs are transformed. - Any failure deletes the newly created output tree to avoid leaving a partial deployment.
- The prototype never automatically writes to
G:\XXMI Launcher\EFMI\Mods, never modifies repository source files, and never reads or writesd3dx_user.ini.
Tests cover:
- Recognition of a standard EFMI Tools layout as
Supported. - Classification of an unguarded override as
ReviewRequired. - Rejection of a mod that ships
d3dx.ini. - Ignoring
DISABLED*.ini. - Adding activation gates to key sections with and without existing conditions.
- Combining activation gates with
$mod_enabledand$object_detectedruntime paths. - Generation of the persistent profile selector and per-profile enable flags.
- Rejection of a profile referencing an unknown mod ID.
- Deterministic, ASCII-safe stable mod IDs.
- Copy-and-transform deployment without source-file modification.
- Refusal to overwrite a non-empty output folder while preserving its content.
In the 2026-08-24 review, all 40 core tests passed, but the project coverage gate failed: line coverage was 81.3% against a 90% minimum, and branch coverage was 75.8% against an 85% minimum. The prototype has meaningful behavioral tests, but exceptional and boundary paths remain under-tested.
- Only a standard EFMI Tools pattern can be recognized conservatively; handwritten, older, or deeply nested INI layouts cannot be transformed generically.
- Static text analysis cannot prove that every draw, resource binding, command list, and side effect is controlled by the enable flag.
- Keeping multiple mods resident may add parsing, resource, memory, or VRAM cost; no real-game benchmark was completed.
- Same-character mods may share hashes, resources, or registration behavior. An enable flag of zero still requires real-environment validation that no conflict remains.
- The controller currently uses numeric profile IDs and provides no in-game visual profile names or UI.
- Regression testing across EFMI versions, reload behavior, crash recovery, and long-running profile switching was not completed.
- The prototype intentionally avoids automatic deployment. A release would still need a backup, rollback, upgrade, and uninstall lifecycle.
The architecture is a stronger long-term direction, but not a finished converter safe for every mod.
- In-game changes to
global persistcan caused3dx_user.inito update. - The manager can safely back up, hash, and atomically replace configuration files.
- Mod namespaces and
global persistdeclarations can attribute a subset of root-config values to a particular mod. - When the game is fully closed, a precise variable merge has a smaller risk surface than a whole-file replacement.
- A temporary
postbridge can express the intent to assign runtime values, but file tests alone cannot prove its execution order. - Resident gating avoids namespace disappearance caused by repeatedly deleting and copying mod folders, but requires complete and reliable transformation of third-party INI behavior.
- Whether every relevant XXMI/EFMI version always saves and reloads in the same F10 order.
- Whether a bridge assignment is overwritten by
[Present]or another command list in each target mod. - Whether a verified disk value guarantees immediate visual synchronization of all resources.
- Whether multiple same-character resident mods are free of hash, resource, performance, and registration conflicts in the real game.
- Whether a transformation rule can be broad enough for arbitrary third-party INI files without also making unsafe edits.
None of the prototypes reads or writes game-process memory. They do not inject DLLs, install hooks, bypass anti-cheat, or capture background keyboard input. Beyond account and security risk, such an approach is fragile across game updates, makes state ownership difficult to validate, creates a large maintenance burden, and would turn a file-oriented mod manager into a fundamentally different runtime tool.
The following directions remain useful, but should stay experimental and be tested in an isolated environment:
- Prefer offline restoration. Productize only the precise merge from v3.8.1 B as an explicit “game fully closed” operation; compare the root config before launch and after exit without promising runtime switching.
- Require a mod-provided state manifest. Let authors declare persistable variables, ranges, application order, and F10 requirements instead of asking the manager to infer arbitrary scripts.
- Build versioned adapters. Create a real syntax/AST model for known EFMI Tools generator versions rather than using generic regular expressions against all third-party INI files.
-
Add read-only diagnostics first. Report variable ownership, duplicate assignments,
[Present]write sites, unguarded overrides, and potentially conflicting hashes without changing files. - Benchmark resident sets. Measure launch time, F10 reload time, RAM/VRAM, frame time, and stability with 2/4/8 same-character mods resident.
- Create a reversible deployment manifest. Record each generated copy, source hash, transformation-rule version, and controller version for verification, upgrade, and complete uninstall.
-
Keep directory persistence separate from root-variable restoration. A directory junction preserves files the loader writes inside a mod folder; it does not automatically solve EFMI-root
d3dx_user.inior memory-only state.
The later released Windows directory-junction deployment solves the “deep copy” problem between repository mods and the loader's Mods directory: both paths refer to the same directory, so files written inside that linked mod folder can persist naturally.
It is not any of the experiments described on this page:
- It does not restore EFMI-root
d3dx_user.ini. - It does not parse or overwrite arbitrary mod
$variables. - It does not generate a
postbridge. - It does not press F10 automatically.
- It does not keep multiple mods resident and rewrite their INIs.
- It does not access game-process memory.
This distinction matters. Directory junctions are a released file-deployment mechanism; v3.8.1–v3.8.2 were internal-state experiments that did not reach universal reliability.
| Experiment | Current review result | What this does not prove |
|---|---|---|
| v3.8.1 Hot Injection | Core 28/28 and Updater 21/21, 49/49 total; complete WinUI build with zero warnings and zero errors | Reliable F10 hot replacement in the real game |
| v3.8.1 Persistent State | Core 37/37, DataStore 7/7, and Updater 26/26, 70/70 total; coverage gates passed | Reliable two-F10 bridges for every mod |
| v3.8.2 EFMI Resident | Core 40/40, DataStore 7/7, and Updater 26/26, 73/73 total; WinUI build passed; core coverage 81.3%/75.8%, below the 90%/85% gates | Safe resident conversion across real EFMI versions, arbitrary third-party mods, and long play sessions |
Passing unit tests establish only that pure parsing, merging, transformation, and path-protection behavior matches the test cases. Real-game validation, performance validation, and cross-version compatibility remain separate work.
The v3.8.1–v3.8.2 work was not simply a sequence of failed attempts. Each route narrowed the problem:
- Whole-file snapshots proved that the file layer can be preserved safely, but the granularity is too broad and runtime reload direction is uncontrolled.
- The
global persistbranch proved that a subset of values can be attributed and merged safely while offline, but runtime ordering and mod side effects remain outside the manager's control. - The resident EFMI branch removed the need to restore the root config, but moved the hard problem into complete third-party INI gating, compatibility, and performance validation.
The most accurate current conclusion is: universal, automatic, and reliable runtime persistence of internal state across arbitrary third-party XXMI/EFMI mods cannot currently be promised as a released feature. The verified code and negative results remain valuable starting points for offline precise restoration, author-declared state interfaces, or versioned EFMI transformers.
Wiki Home · v4.6.1 · Latest Release · Documentation · Licensing · Issues · Security
开始 / Start
使用手册 / Guides
- 快速上手 · Quick Start
- 用户手册 · User Guide
- 快捷键与浮窗 · Shortcut Panel
- 状态槽位 · State Slots
- 组合预设 · Combination Presets
- 浏览与下载 · Browse and Download
- 终末地启动 · Endfield Launch
- 组合与状态预设 · Combination and State Presets
发布记录 / Release Notes
- 4.6.1 默认槽位、浮窗保存与排版修复 / Default Slots, Overlay Saves and Layout Fixes
- 4.6.0 全部页面重新设计 / Every Page Redesigned
- 4.5.2 保存、刷新与提速 / Save, Refresh and Speed
- 4.5.1 浮窗保存 / Overlay Saving
- 4.5.0 头像、多选与槽位 / Avatars, Multi-select and Slots
- 4.4.0 快捷键面板 / Shortcut Panel
- 完整更新日志 / Full Changelog
历史页面与实验 / Historical Pages and Experiments
- 4.5.2 正式版(历史) · Stable 4.5.2 (history)
- 升级到 4.5.2(历史) · Upgrade to 4.5.2 (history)
- 4.0 正式版(历史) · Stable 4.0 (history)
- 4.0 预设(历史) · 4.0 Presets (history)
- 升级到 4.0(历史) · Upgrade to 4.0 (history)
- 4.0-beta · 3.9.5
- 3.9.0(历史) · 3.9.0 (history)
- 实验归档索引 · Experiment Archive
- 实验结论 · Experiment Findings
- 详细开发记录 · Development Notes
项目 / Project
- Source
- Documentation
- 软件结构 / Software Structure
- 4.6.1 Verification
- Licensing / 许可证
- Issues
- 反馈 QQ 群 / QQ group: 1041384622
- Security