Syncromesh/Assets
Virtual filesystem access and custom module loading.
Provides script-level access to the virtual filesystem and handles ES module resolution for both JS and native modules.
Import #
import * as Assets from 'Syncromesh/Assets';Functions #
- Path is resolved by the internal asset system (e.g.
/rom/...). - Throws if the asset does not exist.
getText(path: string): Promise<string>- Path is resolved by the internal asset system (e.g.
/rom/...). - Returns an empty array if the asset does not exist or is empty.
getBinary(path: string): Promise<number[]>- The lookup is recursive for nested subdirectories.
- Useful for discovering available assets at runtime.
readDir(path: string): Promise<string[]>- The destination defaults to
/rom. - Relative sources use the process working directory.
- The Promise rejects when the source or virtual destination is invalid.
mount(source: string, destination?: string): Promise<boolean>- The source must be relative and cannot contain parent traversal.
- Use it from the embedded bootstrap to bring in game data packs distributed beside the runtime.
mountAdjacent(source: string, destination?: string): Promise<boolean>Mount sources and packaged assets #
Syncromesh automatically mounts an unmodified ZIP appended to its own executable at /rom. That archive can contain only a minimal index.js orchestrator which mounts game data packs distributed beside the executable. The -m option remains available for explicit development or override mounts.
# /rom/index.js is embedded; game-core.zip remains replaceable
./my-game- ZIP64 archives, empty files, and long member names are supported.
- Absolute and parent-traversal archive members are rejected rather than exposed in the virtual filesystem.
- Native hosts can mount a precise ZIP byte range with
Helix::Assets::mountArchiveRange, including when the containing file has trailing data. - Later mounts take precedence when multiple sources provide the same virtual path.
Packaging a Syncromesh application #
Embed a small application archive containing /rom/index.js into the Syncromesh executable and distribute larger game content as sidecar ZIPs. This keeps the launcher cohesive while allowing maps, episodes, server data, or patches to be replaced independently.
source/
├── application/
│ └── index.js
└── packs/
├── game-core/
│ ├── maps/
│ ├── scripts/
│ └── textures/
└── episode-1/
├── maps/
└── scripts/Minimal embedded bootstrap #
// application/index.js
import * as Assets from 'Syncromesh/Assets';
export default async function main(args)
{
await Assets.mountAdjacent('packs/game-core.zip', '/game');
await Assets.mountAdjacent('packs/episode-1.zip', '/game');
// Imports from a data pack must be dynamic because mounting happens at runtime.
const game = await import('/game/scripts/main.js');
await game.start(args);
}Build the Linux distribution #
set -eu
GAME_ROOT=/path/to/source
SYNCROMESH_RUNTIME=/path/to/syncromesh
DIST=/path/to/dist/MyGame
PACKAGE_TMP=$(mktemp -d)
APP_ZIP=$PACKAGE_TMP/application.zip
mkdir -p "$DIST/packs"
(
cd "$GAME_ROOT/application"
zip -q -r "$APP_ZIP" .
)
install -m755 "$SYNCROMESH_RUNTIME" "$DIST/my-game"
dd if="$APP_ZIP" of="$DIST/my-game" oflag=append conv=notrunc status=none
(
cd "$GAME_ROOT/packs/game-core"
zip -q -r "$DIST/packs/game-core.zip" .
)
(
cd "$GAME_ROOT/packs/episode-1"
zip -q -r "$DIST/packs/episode-1.zip" .
)
rm -rf "$PACKAGE_TMP"Build the Windows executable #
Create application.zip with index.js at its root, then append it to the completed Windows runtime from Command Prompt. Assemble the combined executable before applying any Authenticode signature.
mkdir MyGame\packs
copy /b syncromesh.exe+application.zip MyGame\my-game.exe
copy game-core.zip MyGame\packs\game-core.zip
copy episode-1.zip MyGame\packs\episode-1.zipMyGame/
├── my-game # or my-game.exe on Windows
├── libhx-game.so # optional Linux native module
├── libhx-game.dll # optional Windows native module
└── packs/
├── game-core.zip
└── episode-1.zip- The application ZIP root maps to
/rom; the default bootstrap must therefore be namedindex.jsat the ZIP root. - Each data-pack ZIP root maps to the destination supplied to
mountAdjacent. - Use dynamic
import()for modules provided by packs mounted during bootstrap; static imports are resolved before the bootstrap function runs. - Native modules must remain filesystem files beside the executable or in an explicit
-ndirectory. - Test the final directory from an unrelated working directory on every target platform.
Module resolution #
When a JS import references a path, the module loader resolves it against the virtual filesystem. Files ending in .js or .json are evaluated as ES modules.
Imports starting with Module/ are resolved as native shared libraries. The loader searches for libhx-<name>.so in the -n directory (if specified), then falls back to the executable's directory. The native library must export an helix_plugin_integrate(ctx, name, host, instance) entry point.
Examples #
import * as Assets from 'Syncromesh/Assets';
// Mount replaceable game data distributed beside the executable.
await Assets.mountAdjacent('packs/game-core.zip', '/game');
await Assets.mountAdjacent('packs/episode-1.zip', '/game');
// Later packs override earlier files at the same virtual path.
const mapJson = await Assets.getText('/game/maps/overworld.json');
const map = JSON.parse(mapJson);
const files = await Assets.readDir('/game/maps');
// Import a native module
import * as WS from 'Module/ws';