File System
File System Intro
See File System Overview for details.
File System Management
1. Inject file system path mapping rules
Use injectFileSystemRule to inject a mapping for a protocol name to a file path.
API
gameHandle.injectFileSystemRule(maskPath, realPath)
Parameters
| Param | Type | Required | Description |
|---|---|---|---|
maskPath | String | Yes | Virtual path prefix |
realPath | String | Yes | Real file system path |
Returns
Promise<void>
Example
// Inject new file system rule
// Implementation uses string replacement inside.
// Add a mapping between a virtual path and a real path with this method.
// Built-in conversion rules:
// 1. tjfile://usr/ -> <host user data path>/fs/usr
// 2. tjfile://save_ -> <host user data path>/fs/store
// 3. tjfile://tmp_ -> <host temp file path>/fs
// Example:
// maskPath: tjfile://usr/
// realPath: /Users/username/Library/Application Support/myapp/userid/appid/
await gameHandle.injectFileSystemRule('tjfile://usr/', '/path/to/user/data/');
// After injection, convertRealPathToMaskPath will return tjfile://usr/
const maskPath = gameHandle.convertRealPathToMaskPath('/path/to/user/data/');
console.log('Mask path:', maskPath); // tjfile://usr/
Usage scenario
const path = require('path');
const os = require('os');
async function setupCustomFileSystem(gameHandle) {
// Define custom file system paths
const appDataPath = path.join(os.homedir(), '.myapp', 'data');
const cachePath = path.join(os.homedir(), '.myapp', 'cache');
const logPath = path.join(os.homedir(), '.myapp', 'logs');
// Inject custom rules
await gameHandle.injectFileSystemRule('myapp://data/', appDataPath);
await gameHandle.injectFileSystemRule('myapp://cache/', cachePath);
await gameHandle.injectFileSystemRule('myapp://logs/', logPath);
console.log('Custom file system rules injected');
}
2. Real path to virtual path
Convert a real file system path to a virtual (protocol) path.
API
gameHandle.convertRealPathToMaskPath(realPath)
Parameters
| Param | Type | Required | Description |
|---|---|---|---|
realPath | String | Yes | Real file path |
Returns
String - virtual (protocol) path
Example
// Convert real path to mask path
const realPath = '/Users/username/Library/Application Support/myapp/userid/appid/data.json';
const maskPath = gameHandle.convertRealPathToMaskPath(realPath);
console.log('Real path:', realPath);
console.log('Mask path:', maskPath); // e.g., tjfile://usr/data.json
3. Virtual path to real path
Convert a virtual (protocol) path to a real file system path.
API
gameHandle.convertMaskPathToRealPath(maskPath)
Parameters
| Param | Type | Required | Description |
|---|---|---|---|
maskPath | String | Yes | Virtual path prefix |
Returns
String - real file path
Example
// Convert mask path to real path
const maskPath = 'tjfile://usr/data.json';
const realPath = gameHandle.convertMaskPathToRealPath(maskPath);
console.log('Mask path:', maskPath);
console.log('Real path:', realPath); // e.g., /Users/username/Library/Application Support/myapp/userid/appid/data.json
File system path rules
Default mappings
The PC host registers these conversion rules by default:
| Virtual prefix | Real path | Note |
|---|---|---|
tjfile://usr/ | <user data path>/fs/usr | User data dir |
tjfile://save_ | <user data path>/fs/store | Save dir |
tjfile://tmp_ | <temp file path>/fs | Temp dir |
Path examples
macOS:
User data path: /Users/username/Library/Application Support/WebGLHost/userid/appid/
Temp file path: /Users/username/Library/Caches/WebGLHost/userid/appid/
Windows:
User data path: C:\Users\username\AppData\Roaming\WebGLHost\userid\appid\
Temp file path: C:\Users\username\AppData\Local\Temp\WebGLHost\userid\appid\
Full example
const { TJHostHandle } = require('webglhost-runtime-pc');
const path = require('path');
const os = require('os');
const fs = require('fs').promises;
class FileSystemManager {
constructor() {
this.gameHandle = null;
}
async initialize(gameHandle) {
this.gameHandle = gameHandle;
await this.setupCustomPaths();
}
async setupCustomPaths() {
// Define custom application paths
const appName = 'MyGame';
const baseDir = path.join(os.homedir(), `.${appName.toLowerCase()}`);
const paths = {
data: path.join(baseDir, 'data'),
save: path.join(baseDir, 'save'),
cache: path.join(baseDir, 'cache'),
logs: path.join(baseDir, 'logs'),
config: path.join(baseDir, 'config')
};
// Create directories if they don't exist
for (const [key, dirPath] of Object.entries(paths)) {
await fs.mkdir(dirPath, { recursive: true });
console.log(`Created directory: ${dirPath}`);
}
// Inject custom file system rules
await this.gameHandle.injectFileSystemRule('game://data/', paths.data);
await this.gameHandle.injectFileSystemRule('game://save/', paths.save);
await this.gameHandle.injectFileSystemRule('game://cache/', paths.cache);
await this.gameHandle.injectFileSystemRule('game://logs/', paths.logs);
await this.gameHandle.injectFileSystemRule('game://config/', paths.config);
console.log('File system rules injected successfully');
}
// Test path conversions
testPathConversions() {
// Test real path to mask path
const realDataPath = path.join(os.homedir(), '.mygame', 'data', 'player.json');
const maskDataPath = this.gameHandle.convertRealPathToMaskPath(realDataPath);
console.log('Real -> Mask:', realDataPath, '->', maskDataPath);
// Test mask path to real path
const maskSavePath = 'game://save/slot1.dat';
const realSavePath = this.gameHandle.convertMaskPathToRealPath(maskSavePath);
console.log('Mask -> Real:', maskSavePath, '->', realSavePath);
// Test built-in tjfile paths
const tjfilePath = 'tjfile://usr/userdata.json';
const tjfileRealPath = this.gameHandle.convertMaskPathToRealPath(tjfilePath);
console.log('tjfile -> Real:', tjfilePath, '->', tjfileRealPath);
}
// Example: Save player data
async savePlayerData(playerId, data) {
const maskPath = `game://save/player_${playerId}.json`;
const realPath = this.gameHandle.convertMaskPathToRealPath(maskPath);
await fs.writeFile(realPath, JSON.stringify(data, null, 2), 'utf-8');
console.log(`Player data saved to: ${realPath}`);
console.log(`Virtual path: ${maskPath}`);
}
// Example: Load player data
async loadPlayerData(playerId) {
const maskPath = `game://save/player_${playerId}.json`;
const realPath = this.gameHandle.convertMaskPathToRealPath(maskPath);
try {
const content = await fs.readFile(realPath, 'utf-8');
const data = JSON.parse(content);
console.log(`Player data loaded from: ${realPath}`);
return data;
} catch (error) {
console.error(`Failed to load player data:`, error);
return null;
}
}
// Example: Clear cache
async clearCache() {
const maskCachePath = 'game://cache/';
const realCachePath = this.gameHandle.convertMaskPathToRealPath(maskCachePath);
try {
const files = await fs.readdir(realCachePath);
for (const file of files) {
await fs.unlink(path.join(realCachePath, file));
}
console.log(`Cache cleared: ${realCachePath}`);
} catch (error) {
console.error('Failed to clear cache:', error);
}
}
// Example: Write log
async writeLog(message) {
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] ${message}\n`;
const maskLogPath = 'game://logs/game.log';
const realLogPath = this.gameHandle.convertMaskPathToRealPath(maskLogPath);
await fs.appendFile(realLogPath, logEntry, 'utf-8');
}
}
// Usage
async function main() {
// Initialize SDK and create game handle
const hostHandle = await new Promise((resolve, reject) => {
TJHostHandle.initialize('sdk-key', 'sdk-secret', (handle, error) => {
if (error) reject(error);
else resolve(handle);
});
});
const gameHandle = await hostHandle.createGameHandle();
// Setup file system manager
const fsManager = new FileSystemManager();
await fsManager.initialize(gameHandle);
// Test path conversions
fsManager.testPathConversions();
// Save player data
await fsManager.savePlayerData('player123', {
name: 'John',
level: 10,
score: 1000
});
// Load player data
const playerData = await fsManager.loadPlayerData('player123');
console.log('Loaded player data:', playerData);
// Write log
await fsManager.writeLog('Game started successfully');
// Clear cache
await fsManager.clearCache();
}
main().catch(console.error);
Tips
-
Path Separators: Use Node.js
pathutilities so the handling stays cross-platform compatible. -
Permissions: Ensure the app has access to the target file paths.
-
Path uniqueness: Each virtual path prefix should map to exactly one real path.
-
Path existence: Make sure the target directory exists before you use it.
-
String matching: Path conversion works via string replacement, so keep the virtual prefixes unique.
-
Performance: Frequent conversions can cost you—cache results when practical.