diff --git a/README.md b/README.md index 6457ce2..a6d824a 100644 --- a/README.md +++ b/README.md @@ -26,12 +26,14 @@ Currently only flat projectors are supported, not the kind that Director makes w ## Projector -### Windows +### Otto + +#### Windows ```js -import {ProjectorWindows} from '@shockpkg/dir-projector'; +import {ProjectorOttoWindows} from '@shockpkg/dir-projector'; -const projector = new ProjectorWindows('projector-windows/application.exe'); +const projector = new ProjectorOttoWindows('projector-windows/application.exe'); // Required skeleton. projector.skeleton = 'skeleton.zip'; @@ -79,12 +81,12 @@ projector.patchShockwave3dInstalledDisplayDriversSize = true; await projector.write(); ``` -### Mac +#### Mac ```js -import {ProjectorMac} from '@shockpkg/dir-projector'; +import {ProjectorOttoMac} from '@shockpkg/dir-projector'; -const projector = new ProjectorMac('projector-mac/application.app'); +const projector = new ProjectorOttoMac('projector-mac/application.app'); // Required skeleton. projector.skeleton = 'skeleton.zip'; @@ -135,12 +137,14 @@ await projector.write(); ## Bundle -### Windows +### Otto + +#### Windows ```js -import {BundleWindows} from '@shockpkg/dir-projector'; +import {BundleOttoWindows} from '@shockpkg/dir-projector'; -const bundle = new BundleWindows('bundle-windows/application.exe'); +const bundle = new BundleOttoWindows('bundle-windows/application.exe'); // Use projector property to set options. bundle.projector.skeleton = 'skeleton.zip'; @@ -154,12 +158,12 @@ await bundle.write(async b => { }); ``` -### Mac +#### Mac ```js -import {BundleMac} from '@shockpkg/dir-projector'; +import {BundleOttoMac} from '@shockpkg/dir-projector'; -const bundle = new BundleMac('bundle-mac/application.app'); +const bundle = new BundleOttoMac('bundle-mac/application.app'); // Use projector property to set options. bundle.projector.skeleton = 'skeleton.zip'; diff --git a/src/bundle.ts b/src/bundle.ts index 3c31e99..e5a9d61 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -557,7 +557,6 @@ export abstract class Bundle { * Close output. */ protected async _close() { - await this._writeLauncher(); await this._closeQueue.run(); } @@ -589,22 +588,10 @@ export abstract class Bundle { return dest; } - /** - * Main application file extension. - * - * @returns File extension. - */ - public abstract get extension(): string; - /** * Create projector instance for the bundle. * * @returns Projector instance. */ protected abstract _createProjector(): Projector; - - /** - * Write the launcher file. - */ - protected abstract _writeLauncher(): Promise; } diff --git a/src/bundle/index.ts b/src/bundle/index.ts index 4ef2a0e..0e2e885 100644 --- a/src/bundle/index.ts +++ b/src/bundle/index.ts @@ -1,2 +1,2 @@ -export * from './windows'; -export * from './mac'; +export * from './otto'; +export * from './otto/'; diff --git a/src/bundle.spec.ts b/src/bundle/otto.spec.ts similarity index 100% rename from src/bundle.spec.ts rename to src/bundle/otto.spec.ts diff --git a/src/bundle.test.ts b/src/bundle/otto.test.ts similarity index 88% rename from src/bundle.test.ts rename to src/bundle/otto.test.ts index bee5dba..bdab395 100644 --- a/src/bundle.test.ts +++ b/src/bundle/otto.test.ts @@ -1,5 +1,5 @@ import {describe, it} from 'node:test'; -import {notEqual, strictEqual} from 'node:assert'; +import {notEqual, ok, strictEqual} from 'node:assert'; import { chmod, lstat, @@ -13,13 +13,15 @@ import {join as pathJoin, basename, dirname} from 'node:path'; import {fsLchmod, fsLutimes} from '@shockpkg/archive-files'; -import {trimExtension} from './util'; -import {fixtureFile} from './util.spec'; -import {ProjectorDummy} from './projector.spec'; -import {Bundle} from './bundle'; -import {cleanBundlesDir} from './bundle.spec'; +import {trimExtension} from '../util'; +import {fixtureFile} from '../util.spec'; +import {Bundle} from '../bundle'; +import {ProjectorOttoDummy} from '../projector/otto.spec'; -const getDir = async (d: string) => cleanBundlesDir('dummy', d); +import {cleanBundlesDir} from './otto.spec'; +import {BundleOtto} from './otto'; + +const getDir = async (d: string) => cleanBundlesDir('otto', 'dummy', d); const supportsExecutable = !process.platform.startsWith('win'); const supportsSymlinks = !process.platform.startsWith('win'); @@ -28,8 +30,8 @@ const supportsSymlinkAttrs = process.platform.startsWith('darwin'); // eslint-disable-next-line no-bitwise const isUserExec = (mode: number) => !!(mode & 0b001000000); -class BundleDummy extends Bundle { - public readonly projector: ProjectorDummy; +class BundleOttoDummy extends BundleOtto { + public readonly projector: ProjectorOttoDummy; constructor(path: string) { super(path); @@ -47,7 +49,7 @@ class BundleDummy extends Bundle { if (directory === path) { throw new Error(`Output path must end with: ${extension}`); } - return new ProjectorDummy(pathJoin(directory, basename(path))); + return new ProjectorOttoDummy(pathJoin(directory, basename(path))); } protected async _writeLauncher() { @@ -56,13 +58,18 @@ class BundleDummy extends Bundle { } } -void describe('bundle', () => { - void describe('BundleDummy', () => { +void describe('bundle/otto', () => { + void describe('BundleOttoDummy', () => { + void it('instanceof', () => { + ok(BundleOttoDummy.prototype instanceof BundleOtto); + ok(BundleOttoDummy.prototype instanceof Bundle); + }); + void it('simple', async () => { const dir = await getDir('simple'); const dest = pathJoin(dir, 'application.exe'); - const b = new BundleDummy(dest); + const b = new BundleOttoDummy(dest); b.projector.skeleton = fixtureFile('dummy.zip'); b.projector.configFile = fixtureFile('config.ini.crlf.bin'); await b.write(); @@ -108,7 +115,7 @@ void describe('bundle', () => { await utimes(resources, dateA, dateA); - const b = new BundleDummy(dest); + const b = new BundleOttoDummy(dest); b.projector.skeleton = fixtureFile('dummy.zip'); b.projector.configFile = fixtureFile('config.ini.crlf.bin'); await b.write(async p => { @@ -252,7 +259,7 @@ void describe('bundle', () => { await mkdir(dirname(resourcesA), {recursive: true}); await writeFile(resourcesA, 'alpha'); - const b = new BundleDummy(dest); + const b = new BundleOttoDummy(dest); b.projector.skeleton = fixtureFile('dummy.zip'); b.projector.configFile = fixtureFile('config.ini.crlf.bin'); await b.write(async p => { diff --git a/src/bundle/otto.ts b/src/bundle/otto.ts new file mode 100644 index 0000000..500996d --- /dev/null +++ b/src/bundle/otto.ts @@ -0,0 +1,48 @@ +import {ProjectorOtto} from '../projector/otto'; +import {Bundle} from '../bundle'; + +/** + * BundleOtto object. + */ +export abstract class BundleOtto extends Bundle { + /** + * ProjectorOtto instance. + */ + public abstract readonly projector: ProjectorOtto; + + /** + * ProjectorOtto constructor. + * + * @param path Output path. + */ + constructor(path: string) { + super(path); + } + + /** + * @inheritdoc + */ + protected async _close(): Promise { + await this._writeLauncher(); + await super._close(); + } + + /** + * Main application file extension. + * + * @returns File extension. + */ + public abstract get extension(): string; + + /** + * Create projector instance for the bundle. + * + * @returns Projector instance. + */ + protected abstract _createProjector(): ProjectorOtto; + + /** + * Write the launcher file. + */ + protected abstract _writeLauncher(): Promise; +} diff --git a/src/bundle/otto/index.ts b/src/bundle/otto/index.ts new file mode 100644 index 0000000..4ef2a0e --- /dev/null +++ b/src/bundle/otto/index.ts @@ -0,0 +1,2 @@ +export * from './windows'; +export * from './mac'; diff --git a/src/bundle/mac.test.ts b/src/bundle/otto/mac.test.ts similarity index 72% rename from src/bundle/mac.test.ts rename to src/bundle/otto/mac.test.ts index d0005a4..da73d0e 100644 --- a/src/bundle/mac.test.ts +++ b/src/bundle/otto/mac.test.ts @@ -2,21 +2,22 @@ import {describe, it} from 'node:test'; import {ok} from 'node:assert'; import {join as pathJoin} from 'node:path'; -import {listSamples} from '../projector/mac.spec'; -import {cleanBundlesDir} from '../bundle.spec'; -import {fixtureFile, getPackageFile} from '../util.spec'; -import {Bundle} from '../bundle'; +import {listSamples} from '../../projector/otto/mac.spec'; +import {cleanBundlesDir} from '../otto.spec'; +import {fixtureFile, getPackageFile} from '../../util.spec'; +import {BundleOtto} from '../otto'; -import {BundleMac} from './mac'; +import {BundleOttoMac} from './mac'; -void describe('bundle/mac', () => { - void describe('BundleMac', () => { - void it('instanceof Bundle', () => { - ok(BundleMac.prototype instanceof Bundle); +void describe('bundle/otto/mac', () => { + void describe('BundleOttoMac', () => { + void it('instanceof', () => { + ok(BundleOttoMac.prototype instanceof BundleOtto); }); for (const {name} of listSamples()) { - const getDir = async (d: string) => cleanBundlesDir('mac', name, d); + const getDir = async (d: string) => + cleanBundlesDir('otto', 'mac', name, d); const getSkeleton = async () => getPackageFile(name); void describe(name, () => { @@ -24,7 +25,7 @@ void describe('bundle/mac', () => { const dir = await getDir('simple'); const dest = pathJoin(dir, 'application.app'); - const b = new BundleMac(dest); + const b = new BundleOttoMac(dest); b.projector.skeleton = await getSkeleton(); b.projector.configFile = fixtureFile('config.ini.lf.bin'); await b.write(async b => { @@ -39,7 +40,7 @@ void describe('bundle/mac', () => { const dir = await getDir('complex'); const dest = pathJoin(dir, 'application.app'); - const b = new BundleMac(dest); + const b = new BundleOttoMac(dest); const p = b.projector; p.skeleton = await getSkeleton(); p.configFile = fixtureFile('config.ini.lf.bin'); diff --git a/src/bundle/mac.ts b/src/bundle/otto/mac.ts similarity index 83% rename from src/bundle/mac.ts rename to src/bundle/otto/mac.ts index bb85b00..19bc602 100644 --- a/src/bundle/mac.ts +++ b/src/bundle/otto/mac.ts @@ -4,22 +4,22 @@ import {join as pathJoin, basename, dirname} from 'node:path'; import {fsLstatExists} from '@shockpkg/archive-files'; import {Plist, ValueDict, ValueString} from '@shockpkg/plist-dom'; -import {trimExtension} from '../util'; -import {machoTypesFile, machoAppLauncher} from '../util/mac'; -import {ProjectorMac} from '../projector/mac'; -import {Bundle} from '../bundle'; +import {trimExtension} from '../../util'; +import {machoTypesFile, machoAppLauncher} from '../../util/mac'; +import {ProjectorOttoMac} from '../../projector/otto/mac'; +import {BundleOtto} from '../otto'; /** - * BundleMac object. + * BundleOttoMac object. */ -export class BundleMac extends Bundle { +export class BundleOttoMac extends BundleOtto { /** - * ProjectorMac instance. + * ProjectorOttoMac instance. */ - public readonly projector: ProjectorMac; + public readonly projector: ProjectorOttoMac; /** - * BundleMac constructor. + * BundleOttoMac constructor. * * @param path Output path for the main application. */ @@ -30,9 +30,7 @@ export class BundleMac extends Bundle { } /** - * Main application file extension. - * - * @returns File extension. + * @inheritdoc */ public get extension() { return '.app'; @@ -48,18 +46,16 @@ export class BundleMac extends Bundle { } /** - * Create projector instance for the bundle. - * - * @returns Projector instance. + * @inheritdoc */ protected _createProjector() { const projName = `${this._getLauncherName()}${this.extension}`; const projPath = pathJoin(this.path, 'Contents', 'Resources', projName); - return new ProjectorMac(projPath); + return new ProjectorOttoMac(projPath); } /** - * Write the launcher file. + * @inheritdoc */ protected async _writeLauncher() { const {path, projector} = this; diff --git a/src/bundle/windows.test.ts b/src/bundle/otto/windows.test.ts similarity index 75% rename from src/bundle/windows.test.ts rename to src/bundle/otto/windows.test.ts index 69d4c7c..4e97174 100644 --- a/src/bundle/windows.test.ts +++ b/src/bundle/otto/windows.test.ts @@ -2,26 +2,25 @@ import {describe, it} from 'node:test'; import {ok} from 'node:assert'; import {join as pathJoin} from 'node:path'; -import {listSamples, versionStrings} from '../projector/windows.spec'; -import {cleanBundlesDir} from '../bundle.spec'; -import {fixtureFile, getPackageFile} from '../util.spec'; -import {Bundle} from '../bundle'; +import {listSamples, versionStrings} from '../../projector/otto/windows.spec'; +import {cleanBundlesDir} from '../otto.spec'; +import {fixtureFile, getPackageFile} from '../../util.spec'; +import {BundleOtto} from '../otto'; -import {BundleWindows} from './windows'; +import {BundleOttoWindows} from './windows'; -void describe('bundle/windows', () => { +void describe('bundle/otto/windows', () => { void describe('BundleWindows', () => { - void it('instanceof Bundle', () => { - ok(BundleWindows.prototype instanceof Bundle); + void it('instanceof', () => { + ok(BundleOttoWindows.prototype instanceof BundleOtto); }); for (const { name, - type, patchShockwave3dInstalledDisplayDriversSize } of listSamples()) { const getDir = async (d: string) => - cleanBundlesDir('windows', type, name, d); + cleanBundlesDir('otto', 'windows', name, d); const getSkeleton = async () => getPackageFile(name); void describe(name, () => { @@ -29,7 +28,7 @@ void describe('bundle/windows', () => { const dir = await getDir('simple'); const dest = pathJoin(dir, 'application.exe'); - const b = new BundleWindows(dest); + const b = new BundleOttoWindows(dest); b.projector.skeleton = await getSkeleton(); b.projector.configFile = fixtureFile('config.ini.crlf.bin'); await b.write(async b => { @@ -44,7 +43,7 @@ void describe('bundle/windows', () => { const dir = await getDir('complex'); const dest = pathJoin(dir, 'application.exe'); - const b = new BundleWindows(dest); + const b = new BundleOttoWindows(dest); const p = b.projector; p.skeleton = await getSkeleton(); b.projector.configFile = fixtureFile('config.ini.crlf.bin'); diff --git a/src/bundle/windows.ts b/src/bundle/otto/windows.ts similarity index 67% rename from src/bundle/windows.ts rename to src/bundle/otto/windows.ts index 9f66223..ec225c9 100644 --- a/src/bundle/windows.ts +++ b/src/bundle/otto/windows.ts @@ -1,22 +1,22 @@ import {mkdir, open, writeFile} from 'node:fs/promises'; import {join as pathJoin, basename, dirname} from 'node:path'; -import {trimExtension} from '../util'; -import {windowsLauncher} from '../util/windows'; -import {ProjectorWindows} from '../projector/windows'; -import {Bundle} from '../bundle'; +import {trimExtension} from '../../util'; +import {windowsLauncher} from '../../util/windows'; +import {ProjectorOttoWindows} from '../../projector/otto/windows'; +import {BundleOtto} from '../otto'; /** - * BundleWindows object. + * BundleOttoWindows object. */ -export class BundleWindows extends Bundle { +export class BundleOttoWindows extends BundleOtto { /** - * ProjectorWindows instance. + * ProjectorOttoWindows instance. */ - public readonly projector: ProjectorWindows; + public readonly projector: ProjectorOttoWindows; /** - * BundleWindows constructor. + * BundleOttoWindows constructor. * * @param path Output path for the main application. */ @@ -27,18 +27,14 @@ export class BundleWindows extends Bundle { } /** - * Main application file extension. - * - * @returns File extension. + * @inheritdoc */ public get extension() { return '.exe'; } /** - * Create projector instance for the bundle. - * - * @returns Projector instance. + * @inheritdoc */ protected _createProjector() { const {path, extension} = this; @@ -46,11 +42,11 @@ export class BundleWindows extends Bundle { if (directory === path) { throw new Error(`Output path must end with: ${extension}`); } - return new ProjectorWindows(pathJoin(directory, basename(path))); + return new ProjectorOttoWindows(pathJoin(directory, basename(path))); } /** - * Write the launcher file. + * @inheritdoc */ protected async _writeLauncher() { const {path, projector} = this; diff --git a/src/projector.ts b/src/projector.ts index 232523a..09c6dd5 100644 --- a/src/projector.ts +++ b/src/projector.ts @@ -1,106 +1,12 @@ -import {mkdir, readFile, writeFile} from 'node:fs/promises'; -import {join as pathJoin, dirname} from 'node:path'; - -import {fsLstatExists} from '@shockpkg/archive-files'; - -import {pathRelativeBase, trimExtension} from './util'; - -export interface IIncludeXtraMapping { - /** - * Source path, case insensitive. - * Does not need to match the full path. - */ - src: string; - - /** - * Destination path, case sensitive. - * Only matches same amount of the full path as src. - */ - dest: string | null; -} - -export interface IIncludeXtraMappingBest { - /** - * Map instance. - */ - map: IIncludeXtraMapping; - - /** - * Relative path. - */ - relative: string; -} - -export interface IIncludeXtras { - [key: string]: string | null; -} - /** * Projector constructor. */ export abstract class Projector { - /** - * Make a Shockwave projector. - */ - public shockwave = false; - - /** - * Splash image file. - */ - public splashImageFile: string | null = null; - - /** - * Splash image data. - */ - public splashImageData: Readonly | null = null; - - /** - * Lingo file. - */ - public lingoFile: string | null = null; - - /** - * Lingo data. - */ - public lingoData: Readonly | string | Readonly | null = - null; - - /** - * Xtras include map. - */ - public includeXtras: Readonly | null = null; - - /** - * Nest xtras in a Configuration directory. - */ - public nestXtrasConfiguration = false; - /** * Set the nobrowse option on mounted disk images. */ public nobrowse = false; - /** - * Skeleton path. - */ - public skeleton: string | null = null; - - /** - * Config data. - */ - public configData: - | Readonly - | string - | Readonly - | (() => Readonly | string | Readonly) - | (() => Promise | string | Readonly>) - | null = null; - - /** - * Config file. - */ - public configFile: string | null = null; - /** * Output path. */ @@ -115,346 +21,8 @@ export abstract class Projector { this.path = path; } - /** - * Config file extension. - * - * @returns File extension. - */ - public get configExtension() { - return '.INI'; - } - - /** - * Lingo file name. - * - * @returns File name. - */ - public get lingoName() { - return 'LINGO.INI'; - } - - /** - * Xtras directory name. - * - * @returns Directory encoding. - */ - public get xtrasName() { - return 'xtras'; - } - - /** - * Configuration directory name. - * - * @returns Directory encoding. - */ - public get configurationName() { - return 'Configuration'; - } - - /** - * Name of a projector trimming the extension, case insensitive. - * - * @returns Projector name without extension. - */ - public get name() { - return trimExtension(dirname(this.path), this.extension, true); - } - - /** - * Config file path. - * - * @returns Config path. - */ - public get configPath() { - const base = trimExtension(this.path, this.extension, true); - return `${base}${this.configExtension}`; - } - - /** - * Splash image file path. - * - * @returns Splash image path. - */ - public get splashImagePath() { - const base = trimExtension(this.path, this.extension, true); - return `${base}${this.splashImageExtension}`; - } - - /** - * Lingo file path. - * - * @returns Lingo file path. - */ - public get lingoPath() { - return pathJoin(dirname(this.path), this.lingoName); - } - - /** - * Get outout Xtras path. - * - * @returns Output path. - */ - public get xtrasPath() { - const cn = this.configurationName; - return this.nestXtrasConfiguration && cn - ? pathJoin(dirname(this.path), cn, this.xtrasName) - : pathJoin(dirname(this.path), this.xtrasName); - } - - /** - * Get config file data. - * - * @returns Config data or null. - */ - public async getConfigData() { - const {configData, configFile} = this; - if (configData) { - switch (typeof configData) { - case 'function': { - const d = await configData(); - if (typeof d === 'string') { - return new TextEncoder().encode(d); - } - if (Array.isArray(d)) { - return new TextEncoder().encode( - d.join(this.configNewline) - ); - } - return d as Readonly; - } - case 'string': { - return new TextEncoder().encode(configData); - } - default: { - // Fall through. - } - } - if (Array.isArray(configData)) { - return new TextEncoder().encode( - configData.join(this.configNewline) - ); - } - return configData as Readonly; - } - if (configFile) { - const d = await readFile(configFile); - return new Uint8Array(d.buffer, d.byteOffset, d.byteLength); - } - return null; - } - - /** - * Get splash image data if any specified, from data or file. - * - * @returns Splash image data or null. - */ - public async getSplashImageData() { - const {splashImageData, splashImageFile} = this; - return ( - splashImageData || - (splashImageFile ? readFile(splashImageFile) : null) - ); - } - - /** - * Get lingo data if any specified, from data or file. - * - * @returns Lingo data or null. - */ - public async getLingoData() { - const {lingoData, lingoFile} = this; - if (typeof lingoData === 'string') { - return Buffer.from(lingoData); - } - if (Array.isArray(lingoData)) { - return Buffer.from(lingoData.join(this.lingoNewline)); - } - if (lingoData) { - return lingoData as Readonly; - } - return lingoFile ? readFile(lingoFile) : null; - } - - /** - * Get include Xtras as a list of mappings. - * - * @returns Mappings list. - */ - public getIncludeXtrasMappings() { - const {includeXtras} = this; - const r: IIncludeXtraMapping[] = []; - if (includeXtras) { - for (const src of Object.keys(includeXtras)) { - const dest = includeXtras[src]; - r.push({ - src, - dest - }); - } - } - return r; - } - - /** - * Find the best match for a path in a list of Xtras mappings. - * Path search is case-insensitive. - * - * @param mappings Mappings list. - * @param path Path to search for. - * @returns Best match or null. - */ - public findIncludeXtrasMappingsBestMatch( - mappings: Readonly, - path: string - ) { - let best: IIncludeXtraMappingBest | null = null; - let bestScore = -1; - for (const map of mappings) { - const {src} = map; - const relative = - src === '' ? path : pathRelativeBase(path, src, true); - if (relative === null || bestScore >= src.length) { - continue; - } - best = { - map, - relative - }; - bestScore = src.length; - } - return best; - } - - /** - * Find output path for an Xtra. - * - * @param mappings Mappings list. - * @param path Path to search for. - * @returns Output path or null. - */ - public includeXtrasMappingsDest( - mappings: Readonly, - path: string - ) { - const best = this.findIncludeXtrasMappingsBestMatch(mappings, path); - if (!best) { - return null; - } - - const {map, relative} = best; - const base = map.dest || map.src; - // eslint-disable-next-line no-nested-ternary - return base ? (relative ? `${base}/${relative}` : base) : relative; - } - /** * Write out the projector. */ - public async write() { - const {skeleton} = this; - if (!skeleton) { - throw new Error('No projector skeleton configured'); - } - - await this._checkOutput(); - await this._writeSkeleton(skeleton); - await this._modifySkeleton(); - await this._writeConfig(); - await this._writeSplashImage(); - await this._writeLingo(); - } - - /** - * Check that output path is valid, else throws. - */ - protected async _checkOutput() { - await Promise.all( - [ - this.path, - this.configPath, - this.splashImagePath, - this.lingoPath - ].map(async p => { - if (await fsLstatExists(p)) { - throw new Error(`Output path already exists: ${p}`); - } - }) - ); - } - - /** - * Write out the projector config file. - */ - protected async _writeConfig() { - const data = await this.getConfigData(); - if (data) { - const {configPath} = this; - await mkdir(dirname(configPath), {recursive: true}); - await writeFile(configPath, data); - } - } - - /** - * Write out the projector splash image file. - */ - protected async _writeSplashImage() { - const data = await this.getSplashImageData(); - if (data) { - const {splashImagePath} = this; - await mkdir(dirname(splashImagePath), {recursive: true}); - await writeFile(splashImagePath, data); - } - } - - /** - * Write out the projector lingo file. - */ - protected async _writeLingo() { - const data = await this.getLingoData(); - if (data) { - const {lingoPath} = this; - await mkdir(dirname(lingoPath), {recursive: true}); - await writeFile(lingoPath, data); - } - } - - /** - * Projector file extension. - * - * @returns File extension. - */ - public abstract get extension(): string; - - /** - * Splash image file extension. - * - * @returns File extension. - */ - public abstract get splashImageExtension(): string; - - /** - * Config file newline characters. - * - * @returns Newline characters. - */ - public abstract get configNewline(): string; - - /** - * Lingo file newline characters. - * - * @returns Newline characters. - */ - public abstract get lingoNewline(): string; - - /** - * Write the projector skeleton from archive. - * - * @param skeleton Skeleton path. - */ - protected abstract _writeSkeleton(skeleton: string): Promise; - - /** - * Modify the projector skeleton. - */ - protected abstract _modifySkeleton(): Promise; + public abstract write(): Promise; } diff --git a/src/projector/index.ts b/src/projector/index.ts index 4ef2a0e..0e2e885 100644 --- a/src/projector/index.ts +++ b/src/projector/index.ts @@ -1,2 +1,2 @@ -export * from './windows'; -export * from './mac'; +export * from './otto'; +export * from './otto/'; diff --git a/src/projector.spec.ts b/src/projector/otto.spec.ts similarity index 80% rename from src/projector.spec.ts rename to src/projector/otto.spec.ts index e9fc699..18da745 100644 --- a/src/projector.spec.ts +++ b/src/projector/otto.spec.ts @@ -1,6 +1,6 @@ -import {Projector} from './projector'; +import {ProjectorOtto} from './otto'; -export class ProjectorDummy extends Projector { +export class ProjectorOttoDummy extends ProjectorOtto { constructor(path: string) { super(path); } diff --git a/src/projector.test.ts b/src/projector/otto.test.ts similarity index 68% rename from src/projector.test.ts rename to src/projector/otto.test.ts index 47cbfd1..7501334 100644 --- a/src/projector.test.ts +++ b/src/projector/otto.test.ts @@ -1,19 +1,28 @@ import {describe, it} from 'node:test'; +import {ok} from 'node:assert'; import {copyFile} from 'node:fs/promises'; import {join as pathJoin} from 'node:path'; -import {ProjectorDummy} from './projector.spec'; -import {cleanProjectorDir, fixtureFile} from './util.spec'; +import {cleanProjectorDir, fixtureFile} from '../util.spec'; +import {Projector} from '../projector'; -const getDir = async (d: string) => cleanProjectorDir('dummy', d); +import {ProjectorOtto} from './otto'; +import {ProjectorOttoDummy} from './otto.spec'; + +const getDir = async (d: string) => cleanProjectorDir('otto', 'dummy', d); + +void describe('projector/otto', () => { + void describe('ProjectorOttoDummy', () => { + void it('instanceof', () => { + ok(ProjectorOttoDummy.prototype instanceof ProjectorOtto); + ok(ProjectorOttoDummy.prototype instanceof Projector); + }); -void describe('projector', () => { - void describe('ProjectorDummy', () => { void it('simple', async () => { const dir = await getDir('simple'); const dest = pathJoin(dir, 'application.exe'); - const p = new ProjectorDummy(dest); + const p = new ProjectorOttoDummy(dest); p.skeleton = 'dummy'; p.configFile = fixtureFile('config.ini.crlf.bin'); await p.write(); @@ -25,7 +34,7 @@ void describe('projector', () => { const dir = await getDir('lingo'); const dest = pathJoin(dir, 'application.exe'); - const p = new ProjectorDummy(dest); + const p = new ProjectorOttoDummy(dest); p.skeleton = 'dummy'; p.configFile = fixtureFile('config.ini.crlf.bin'); p.lingoFile = fixtureFile('lingo.ini.crlf.bin'); @@ -38,7 +47,7 @@ void describe('projector', () => { const dir = await getDir('splash'); const dest = pathJoin(dir, 'application.exe'); - const p = new ProjectorDummy(dest); + const p = new ProjectorOttoDummy(dest); p.skeleton = 'dummy'; p.configFile = fixtureFile('config.ini.crlf.bin'); p.splashImageFile = fixtureFile('splash.bmp'); @@ -51,7 +60,7 @@ void describe('projector', () => { const dir = await getDir('complex'); const dest = pathJoin(dir, 'application.exe'); - const p = new ProjectorDummy(dest); + const p = new ProjectorOttoDummy(dest); p.skeleton = 'dummy'; p.configFile = fixtureFile('config.ini.crlf.bin'); p.lingoFile = fixtureFile('lingo.ini.crlf.bin'); diff --git a/src/projector/otto.ts b/src/projector/otto.ts new file mode 100644 index 0000000..27252d0 --- /dev/null +++ b/src/projector/otto.ts @@ -0,0 +1,451 @@ +import {mkdir, readFile, writeFile} from 'node:fs/promises'; +import {join as pathJoin, dirname} from 'node:path'; + +import {fsLstatExists} from '@shockpkg/archive-files'; + +import {Projector} from '../projector'; +import {pathRelativeBase, trimExtension} from '../util'; + +export interface IIncludeXtraMapping { + /** + * Source path, case insensitive. + * Does not need to match the full path. + */ + src: string; + + /** + * Destination path, case sensitive. + * Only matches same amount of the full path as src. + */ + dest: string | null; +} + +export interface IIncludeXtraMappingBest { + /** + * Map instance. + */ + map: IIncludeXtraMapping; + + /** + * Relative path. + */ + relative: string; +} + +export interface IIncludeXtras { + [key: string]: string | null; +} + +/** + * ProjectorOtto object. + */ +export abstract class ProjectorOtto extends Projector { + /** + * Make a Shockwave projector. + */ + public shockwave = false; + + /** + * Splash image file. + */ + public splashImageFile: string | null = null; + + /** + * Splash image data. + */ + public splashImageData: Readonly | null = null; + + /** + * Lingo file. + */ + public lingoFile: string | null = null; + + /** + * Lingo data. + */ + public lingoData: Readonly | string | Readonly | null = + null; + + /** + * Xtras include map. + */ + public includeXtras: Readonly | null = null; + + /** + * Nest xtras in a Configuration directory. + */ + public nestXtrasConfiguration = false; + + /** + * Skeleton path. + */ + public skeleton: string | null = null; + + /** + * Config data. + */ + public configData: + | Readonly + | string + | Readonly + | (() => Readonly | string | Readonly) + | (() => Promise | string | Readonly>) + | null = null; + + /** + * Config file. + */ + public configFile: string | null = null; + + /** + * ProjectorOtto constructor. + * + * @param path Output path. + */ + constructor(path: string) { + super(path); + } + + /** + * Config file extension. + * + * @returns File extension. + */ + public get configExtension() { + return '.INI'; + } + + /** + * Lingo file name. + * + * @returns File name. + */ + public get lingoName() { + return 'LINGO.INI'; + } + + /** + * Xtras directory name. + * + * @returns Directory encoding. + */ + public get xtrasName() { + return 'xtras'; + } + + /** + * Configuration directory name. + * + * @returns Directory encoding. + */ + public get configurationName() { + return 'Configuration'; + } + + /** + * Name of a projector trimming the extension, case insensitive. + * + * @returns Projector name without extension. + */ + public get name() { + return trimExtension(dirname(this.path), this.extension, true); + } + + /** + * Config file path. + * + * @returns Config path. + */ + public get configPath() { + const base = trimExtension(this.path, this.extension, true); + return `${base}${this.configExtension}`; + } + + /** + * Splash image file path. + * + * @returns Splash image path. + */ + public get splashImagePath() { + const base = trimExtension(this.path, this.extension, true); + return `${base}${this.splashImageExtension}`; + } + + /** + * Lingo file path. + * + * @returns Lingo file path. + */ + public get lingoPath() { + return pathJoin(dirname(this.path), this.lingoName); + } + + /** + * Get outout Xtras path. + * + * @returns Output path. + */ + public get xtrasPath() { + const cn = this.configurationName; + return this.nestXtrasConfiguration && cn + ? pathJoin(dirname(this.path), cn, this.xtrasName) + : pathJoin(dirname(this.path), this.xtrasName); + } + + /** + * Get config file data. + * + * @returns Config data or null. + */ + public async getConfigData() { + const {configData, configFile} = this; + if (configData) { + switch (typeof configData) { + case 'function': { + const d = await configData(); + if (typeof d === 'string') { + return new TextEncoder().encode(d); + } + if (Array.isArray(d)) { + return new TextEncoder().encode( + d.join(this.configNewline) + ); + } + return d as Readonly; + } + case 'string': { + return new TextEncoder().encode(configData); + } + default: { + // Fall through. + } + } + if (Array.isArray(configData)) { + return new TextEncoder().encode( + configData.join(this.configNewline) + ); + } + return configData as Readonly; + } + if (configFile) { + const d = await readFile(configFile); + return new Uint8Array(d.buffer, d.byteOffset, d.byteLength); + } + return null; + } + + /** + * Get splash image data if any specified, from data or file. + * + * @returns Splash image data or null. + */ + public async getSplashImageData() { + const {splashImageData, splashImageFile} = this; + return ( + splashImageData || + (splashImageFile ? readFile(splashImageFile) : null) + ); + } + + /** + * Get lingo data if any specified, from data or file. + * + * @returns Lingo data or null. + */ + public async getLingoData() { + const {lingoData, lingoFile} = this; + if (typeof lingoData === 'string') { + return Buffer.from(lingoData); + } + if (Array.isArray(lingoData)) { + return Buffer.from(lingoData.join(this.lingoNewline)); + } + if (lingoData) { + return lingoData as Readonly; + } + return lingoFile ? readFile(lingoFile) : null; + } + + /** + * Get include Xtras as a list of mappings. + * + * @returns Mappings list. + */ + public getIncludeXtrasMappings() { + const {includeXtras} = this; + const r: IIncludeXtraMapping[] = []; + if (includeXtras) { + for (const src of Object.keys(includeXtras)) { + const dest = includeXtras[src]; + r.push({ + src, + dest + }); + } + } + return r; + } + + /** + * Find the best match for a path in a list of Xtras mappings. + * Path search is case-insensitive. + * + * @param mappings Mappings list. + * @param path Path to search for. + * @returns Best match or null. + */ + public findIncludeXtrasMappingsBestMatch( + mappings: Readonly, + path: string + ) { + let best: IIncludeXtraMappingBest | null = null; + let bestScore = -1; + for (const map of mappings) { + const {src} = map; + const relative = + src === '' ? path : pathRelativeBase(path, src, true); + if (relative === null || bestScore >= src.length) { + continue; + } + best = { + map, + relative + }; + bestScore = src.length; + } + return best; + } + + /** + * Find output path for an Xtra. + * + * @param mappings Mappings list. + * @param path Path to search for. + * @returns Output path or null. + */ + public includeXtrasMappingsDest( + mappings: Readonly, + path: string + ) { + const best = this.findIncludeXtrasMappingsBestMatch(mappings, path); + if (!best) { + return null; + } + + const {map, relative} = best; + const base = map.dest || map.src; + // eslint-disable-next-line no-nested-ternary + return base ? (relative ? `${base}/${relative}` : base) : relative; + } + + /** + * @inheritdoc + */ + public async write() { + const {skeleton} = this; + if (!skeleton) { + throw new Error('No projector skeleton configured'); + } + + await this._checkOutput(); + await this._writeSkeleton(skeleton); + await this._modifySkeleton(); + await this._writeConfig(); + await this._writeSplashImage(); + await this._writeLingo(); + } + + /** + * Check that output path is valid, else throws. + */ + protected async _checkOutput() { + await Promise.all( + [ + this.path, + this.configPath, + this.splashImagePath, + this.lingoPath + ].map(async p => { + if (await fsLstatExists(p)) { + throw new Error(`Output path already exists: ${p}`); + } + }) + ); + } + + /** + * Write out the projector config file. + */ + protected async _writeConfig() { + const data = await this.getConfigData(); + if (data) { + const {configPath} = this; + await mkdir(dirname(configPath), {recursive: true}); + await writeFile(configPath, data); + } + } + + /** + * Write out the projector splash image file. + */ + protected async _writeSplashImage() { + const data = await this.getSplashImageData(); + if (data) { + const {splashImagePath} = this; + await mkdir(dirname(splashImagePath), {recursive: true}); + await writeFile(splashImagePath, data); + } + } + + /** + * Write out the projector lingo file. + */ + protected async _writeLingo() { + const data = await this.getLingoData(); + if (data) { + const {lingoPath} = this; + await mkdir(dirname(lingoPath), {recursive: true}); + await writeFile(lingoPath, data); + } + } + + /** + * Projector file extension. + * + * @returns File extension. + */ + public abstract get extension(): string; + + /** + * Splash image file extension. + * + * @returns File extension. + */ + public abstract get splashImageExtension(): string; + + /** + * Config file newline characters. + * + * @returns Newline characters. + */ + public abstract get configNewline(): string; + + /** + * Lingo file newline characters. + * + * @returns Newline characters. + */ + public abstract get lingoNewline(): string; + + /** + * Write the projector skeleton. + * + * @param skeleton Skeleton path. + */ + protected abstract _writeSkeleton(skeleton: string): Promise; + + /** + * Modify the projector skeleton. + */ + protected abstract _modifySkeleton(): Promise; +} diff --git a/src/projector/otto/index.ts b/src/projector/otto/index.ts new file mode 100644 index 0000000..4ef2a0e --- /dev/null +++ b/src/projector/otto/index.ts @@ -0,0 +1,2 @@ +export * from './windows'; +export * from './mac'; diff --git a/src/projector/mac.spec.ts b/src/projector/otto/mac.spec.ts similarity index 96% rename from src/projector/mac.spec.ts rename to src/projector/otto/mac.spec.ts index 7b4f0a5..03a439e 100644 --- a/src/projector/mac.spec.ts +++ b/src/projector/otto/mac.spec.ts @@ -2,7 +2,7 @@ import { platformIsMac, shouldTest, getInstalledPackagesSync -} from '../util.spec'; +} from '../../util.spec'; export function listSamples() { if (!shouldTest('mac')) { diff --git a/src/projector/mac.test.ts b/src/projector/otto/mac.test.ts similarity index 87% rename from src/projector/mac.test.ts rename to src/projector/otto/mac.test.ts index 73c197a..f0df0ba 100644 --- a/src/projector/mac.test.ts +++ b/src/projector/otto/mac.test.ts @@ -3,21 +3,21 @@ import {ok} from 'node:assert'; import {copyFile} from 'node:fs/promises'; import {join as pathJoin} from 'node:path'; -import {cleanProjectorDir, fixtureFile, getPackageFile} from '../util.spec'; -import {Projector} from '../projector'; +import {cleanProjectorDir, fixtureFile, getPackageFile} from '../../util.spec'; +import {ProjectorOtto} from '../otto'; -import {ProjectorMac} from './mac'; +import {ProjectorOttoMac} from './mac'; import {listSamples} from './mac.spec'; -void describe('projector/mac', () => { - void describe('ProjectorMac', () => { - void it('instanceof Projector', () => { - ok(ProjectorMac.prototype instanceof Projector); +void describe('projector/otto/mac', () => { + void describe('ProjectorOttoMac', () => { + void it('instanceof', () => { + ok(ProjectorOttoMac.prototype instanceof ProjectorOtto); }); for (const {name, nestXtrasContents, intel} of listSamples()) { const getDir = async (d: string) => - cleanProjectorDir('mac', name, d); + cleanProjectorDir('otto', 'mac', name, d); const getSkeleton = async () => getPackageFile(name); void describe(name, () => { @@ -25,7 +25,7 @@ void describe('projector/mac', () => { const dir = await getDir('simple'); const dest = pathJoin(dir, 'application.app'); - const p = new ProjectorMac(dest); + const p = new ProjectorOttoMac(dest); p.skeleton = await getSkeleton(); p.configFile = fixtureFile('config.ini.lf.bin'); await p.write(); @@ -40,7 +40,7 @@ void describe('projector/mac', () => { const dir = await getDir('xtras-all'); const dest = pathJoin(dir, 'application.app'); - const p = new ProjectorMac(dest); + const p = new ProjectorOttoMac(dest); p.skeleton = await getSkeleton(); p.configFile = fixtureFile('config.ini.lf.bin'); p.includeXtras = { @@ -59,7 +59,7 @@ void describe('projector/mac', () => { const dir = await getDir('xtras-selective'); const dest = pathJoin(dir, 'application.app'); - const p = new ProjectorMac(dest); + const p = new ProjectorOttoMac(dest); p.skeleton = await getSkeleton(); p.configFile = fixtureFile('config.ini.lf.bin'); p.includeXtras = { @@ -79,7 +79,7 @@ void describe('projector/mac', () => { const dir = await getDir('xtras-rename'); const dest = pathJoin(dir, 'application.app'); - const p = new ProjectorMac(dest); + const p = new ProjectorOttoMac(dest); p.skeleton = await getSkeleton(); p.configFile = fixtureFile('config.ini.lf.bin'); p.includeXtras = { @@ -99,7 +99,7 @@ void describe('projector/mac', () => { const dir = await getDir('shockwave'); const dest = pathJoin(dir, 'application.app'); - const p = new ProjectorMac(dest); + const p = new ProjectorOttoMac(dest); p.skeleton = await getSkeleton(); p.configFile = fixtureFile('config.ini.lf.bin'); p.shockwave = true; @@ -115,7 +115,7 @@ void describe('projector/mac', () => { const dir = await getDir('nestXtrasConfiguration'); const dest = pathJoin(dir, 'application.app'); - const p = new ProjectorMac(dest); + const p = new ProjectorOttoMac(dest); p.skeleton = await getSkeleton(); p.configFile = fixtureFile('config.ini.lf.bin'); p.nestXtrasConfiguration = true; @@ -136,7 +136,7 @@ void describe('projector/mac', () => { const dir = await getDir('nestXtrasContents'); const dest = pathJoin(dir, 'application.app'); - const p = new ProjectorMac(dest); + const p = new ProjectorOttoMac(dest); p.skeleton = await getSkeleton(); p.configFile = fixtureFile('config.ini.lf.bin'); p.nestXtrasContents = true; @@ -158,7 +158,7 @@ void describe('projector/mac', () => { const dir = await getDir('intel'); const dest = pathJoin(dir, 'application.app'); - const p = new ProjectorMac(dest); + const p = new ProjectorOttoMac(dest); p.skeleton = await getSkeleton(); p.configFile = fixtureFile('config.ini.lf.bin'); p.intel = true; @@ -175,7 +175,7 @@ void describe('projector/mac', () => { const dir = await getDir('complex'); const dest = pathJoin(dir, 'application.app'); - const p = new ProjectorMac(dest); + const p = new ProjectorOttoMac(dest); p.skeleton = await getSkeleton(); p.configFile = fixtureFile('config.ini.lf.bin'); p.lingoFile = fixtureFile('lingo.ini.lf.bin'); diff --git a/src/projector/mac.ts b/src/projector/otto/mac.ts similarity index 95% rename from src/projector/mac.ts rename to src/projector/otto/mac.ts index 94973a1..a273feb 100644 --- a/src/projector/mac.ts +++ b/src/projector/otto/mac.ts @@ -8,13 +8,17 @@ import { } from '@shockpkg/archive-files'; import {Plist, ValueDict, ValueString} from '@shockpkg/plist-dom'; -import {pathRelativeBase, pathRelativeBaseMatch, trimExtension} from '../util'; -import {Projector} from '../projector'; +import { + pathRelativeBase, + pathRelativeBaseMatch, + trimExtension +} from '../../util'; +import {ProjectorOtto} from '../otto'; /** - * ProjectorMac object. + * ProjectorOttoMac object. */ -export class ProjectorMac extends Projector { +export class ProjectorOttoMac extends ProjectorOtto { /** * Binary name. */ @@ -73,7 +77,7 @@ export class ProjectorMac extends Projector { public nestXtrasContents = false; /** - * ProjectorMac constructor. + * ProjectorOttoMac constructor. * * @param path Output path. */ @@ -82,36 +86,28 @@ export class ProjectorMac extends Projector { } /** - * Projector file extension. - * - * @returns File extension. + * @inheritdoc */ public get extension() { return '.app'; } /** - * Config file newline characters. - * - * @returns Newline characters. + * @inheritdoc */ public get configNewline() { return '\n'; } /** - * Config file newline characters. - * - * @returns Newline characters. + * @inheritdoc */ public get lingoNewline() { return '\n'; } /** - * Splash image file extension. - * - * @returns File extension. + * @inheritdoc */ public get splashImageExtension() { return '.pict'; @@ -459,9 +455,7 @@ export class ProjectorMac extends Projector { } /** - * Write the projector skeleton from archive. - * - * @param skeleton Skeleton path. + * @inheritdoc */ protected async _writeSkeleton(skeleton: string) { const { @@ -678,7 +672,7 @@ export class ProjectorMac extends Projector { } /** - * Modify the projector skeleton. + * @inheritdoc */ protected async _modifySkeleton() { await this._writeIcon(); diff --git a/src/projector/windows.spec.ts b/src/projector/otto/windows.spec.ts similarity index 91% rename from src/projector/windows.spec.ts rename to src/projector/otto/windows.spec.ts index 93c1fdb..3778ab9 100644 --- a/src/projector/windows.spec.ts +++ b/src/projector/otto/windows.spec.ts @@ -1,4 +1,4 @@ -import {shouldTest, getInstalledPackagesSync} from '../util.spec'; +import {shouldTest, getInstalledPackagesSync} from '../../util.spec'; export function listSamples() { if (!shouldTest('windows-i386')) { @@ -17,7 +17,6 @@ export function listSamples() { r.push({ name, version, - type: 'i386', patchShockwave3dInstalledDisplayDriversSize: version[0] > 8 || (version[0] === 8 && version[1] >= 5) }); diff --git a/src/projector/windows.test.ts b/src/projector/otto/windows.test.ts similarity index 87% rename from src/projector/windows.test.ts rename to src/projector/otto/windows.test.ts index 3018fbb..d1e7137 100644 --- a/src/projector/windows.test.ts +++ b/src/projector/otto/windows.test.ts @@ -3,25 +3,24 @@ import {ok} from 'node:assert'; import {copyFile} from 'node:fs/promises'; import {join as pathJoin} from 'node:path'; -import {cleanProjectorDir, fixtureFile, getPackageFile} from '../util.spec'; -import {Projector} from '../projector'; +import {cleanProjectorDir, fixtureFile, getPackageFile} from '../../util.spec'; +import {ProjectorOtto} from '../otto'; -import {ProjectorWindows} from './windows'; +import {ProjectorOttoWindows} from './windows'; import {listSamples, versionStrings} from './windows.spec'; -void describe('projector/windows', () => { - void describe('ProjectorWindows', () => { - void it('instanceof Projector', () => { - ok(ProjectorWindows.prototype instanceof Projector); +void describe('projector/otto/windows', () => { + void describe('ProjectorOttoWindows', () => { + void it('instanceof', () => { + ok(ProjectorOttoWindows.prototype instanceof ProjectorOtto); }); for (const { name, - type, patchShockwave3dInstalledDisplayDriversSize } of listSamples()) { const getDir = async (d: string) => - cleanProjectorDir('windows', type, name, d); + cleanProjectorDir('otto', 'windows', name, d); const getSkeleton = async () => getPackageFile(name); void describe(name, () => { @@ -29,7 +28,7 @@ void describe('projector/windows', () => { const dir = await getDir('simple'); const dest = pathJoin(dir, 'application.exe'); - const p = new ProjectorWindows(dest); + const p = new ProjectorOttoWindows(dest); p.skeleton = await getSkeleton(); p.configFile = fixtureFile('config.ini.crlf.bin'); await p.write(); @@ -44,7 +43,7 @@ void describe('projector/windows', () => { const dir = await getDir('xtras-all'); const dest = pathJoin(dir, 'application.exe'); - const p = new ProjectorWindows(dest); + const p = new ProjectorOttoWindows(dest); p.skeleton = await getSkeleton(); p.configFile = fixtureFile('config.ini.crlf.bin'); p.includeXtras = { @@ -65,7 +64,7 @@ void describe('projector/windows', () => { const dir = await getDir('xtras-selective'); const dest = pathJoin(dir, 'application.exe'); - const p = new ProjectorWindows(dest); + const p = new ProjectorOttoWindows(dest); p.skeleton = await getSkeleton(); p.configFile = fixtureFile('config.ini.crlf.bin'); p.includeXtras = { @@ -87,7 +86,7 @@ void describe('projector/windows', () => { const dir = await getDir('xtras-rename'); const dest = pathJoin(dir, 'application.exe'); - const p = new ProjectorWindows(dest); + const p = new ProjectorOttoWindows(dest); p.skeleton = await getSkeleton(); p.configFile = fixtureFile('config.ini.crlf.bin'); p.includeXtras = { @@ -109,7 +108,7 @@ void describe('projector/windows', () => { const dir = await getDir('shockwave'); const dest = pathJoin(dir, 'application.exe'); - const p = new ProjectorWindows(dest); + const p = new ProjectorOttoWindows(dest); p.skeleton = await getSkeleton(); p.configFile = fixtureFile('config.ini.crlf.bin'); p.shockwave = true; @@ -125,7 +124,7 @@ void describe('projector/windows', () => { const dir = await getDir('nestXtrasConfiguration'); const dest = pathJoin(dir, 'application.exe'); - const p = new ProjectorWindows(dest); + const p = new ProjectorOttoWindows(dest); p.skeleton = await getSkeleton(); p.configFile = fixtureFile('config.ini.crlf.bin'); p.nestXtrasConfiguration = true; @@ -147,7 +146,7 @@ void describe('projector/windows', () => { const dir = await getDir('complex'); const dest = pathJoin(dir, 'application.exe'); - const p = new ProjectorWindows(dest); + const p = new ProjectorOttoWindows(dest); p.skeleton = await getSkeleton(); p.configFile = fixtureFile('config.ini.crlf.bin'); p.lingoFile = fixtureFile('lingo.ini.crlf.bin'); diff --git a/src/projector/windows.ts b/src/projector/otto/windows.ts similarity index 89% rename from src/projector/windows.ts rename to src/projector/otto/windows.ts index d87b9e4..aee486c 100644 --- a/src/projector/windows.ts +++ b/src/projector/otto/windows.ts @@ -8,17 +8,17 @@ import { fsWalk } from '@shockpkg/archive-files'; -import {pathRelativeBase, pathRelativeBaseMatch} from '../util'; +import {pathRelativeBase, pathRelativeBaseMatch} from '../../util'; import { peResourceReplace, windowsPatchShockwave3dInstalledDisplayDriversSize -} from '../util/windows'; -import {Projector} from '../projector'; +} from '../../util/windows'; +import {ProjectorOtto} from '../otto'; /** - * ProjectorWindows object. + * ProjectorOttoWindows object. */ -export class ProjectorWindows extends Projector { +export class ProjectorOttoWindows extends ProjectorOtto { /** * Icon file. */ @@ -46,7 +46,7 @@ export class ProjectorWindows extends Projector { public patchShockwave3dInstalledDisplayDriversSize = false; /** - * ProjectorWindows constructor. + * ProjectorOttoWindows constructor. * * @param path Output path. */ @@ -55,36 +55,28 @@ export class ProjectorWindows extends Projector { } /** - * Projector file extension. - * - * @returns File extension. + * @inheritdoc */ public get extension() { return '.exe'; } /** - * Config file newline characters. - * - * @returns Newline characters. + * @inheritdoc */ public get configNewline() { return '\r\n'; } /** - * Lingo file newline characters. - * - * @returns Newline characters. + * @inheritdoc */ public get lingoNewline() { return '\r\n'; } /** - * Splash image file extension. - * - * @returns File extension. + * @inheritdoc */ public get splashImageExtension() { return '.BMP'; @@ -110,9 +102,7 @@ export class ProjectorWindows extends Projector { } /** - * Write the projector skeleton from archive. - * - * @param skeleton Skeleton path. + * @inheritdoc */ protected async _writeSkeleton(skeleton: string) { const {path, shockwave, sklName, xtrasName, xtrasPath} = this; @@ -235,7 +225,7 @@ export class ProjectorWindows extends Projector { } /** - * Modify the projector skeleton. + * @inheritdoc */ protected async _modifySkeleton() { const iconData = await this.getIconData();