@shockpkg/dir-projector
Version:
Package for creating Shockwave Director projectors
335 lines (270 loc) • 7.36 kB
JavaScript
import { basename as pathBasename, join as pathJoin } from 'path';
import { fsWalk } from "@shockpkg/archive-files/module.mjs";
import { patchWindowsS3dInstalledDisplayDriversSize } from "../patcher.mjs";
import { Projector } from "../projector.mjs";
import { defaultFalse, defaultNull, entryIsEmptyResourceFork, pathRelativeBase, pathRelativeBaseMatch, rcedit } from "../util.mjs";
/**
* ProjectorWindows constructor.
*
* @param options Options object.
*/
export class ProjectorWindows extends Projector {
/**
* Patch the Shockave 3D Xtra to have a larger buffer to avoid a crash.
* The buffer for resolving InstalledDisplayDrivers to a path is small.
* Changes to the values stored in InstalledDisplayDrivers cause issues.
* The value is now supposed to hold full paths on modern Windows.
* In particular, Nvidia drivers which do this need this patch.
*
* @default false
*/
/**
* Icon file, requires Windows or Wine.
*
* @default null
*/
/**
* Version strings, requires Windows or Wine.
*
* @default null
*/
/**
* Product version, requires Windows or Wine.
*
* @default null
*/
/**
* Version strings, requires Windows or Wine.
*
* @default null
*/
constructor(options) {
super(options);
this.patchShockwave3dInstalledDisplayDriversSize = void 0;
this.iconFile = void 0;
this.fileVersion = void 0;
this.productVersion = void 0;
this.versionStrings = void 0;
this.iconFile = defaultNull(options.iconFile);
this.fileVersion = defaultNull(options.fileVersion);
this.productVersion = defaultNull(options.productVersion);
this.versionStrings = defaultNull(options.versionStrings);
this.patchShockwave3dInstalledDisplayDriversSize = defaultFalse(options.patchShockwave3dInstalledDisplayDriversSize);
}
/**
* Projector file extension.
*
* @returns File extension.
*/
get projectorExtension() {
return '.exe';
}
/**
* Config file newline characters.
*
* @returns Newline characters.
*/
get configNewline() {
return '\r\n';
}
/**
* Config file newline characters.
*
* @returns Newline characters.
*/
get lingoNewline() {
return '\r\n';
}
/**
* Newline characters.
*
* @returns Newline characters.
*/
get newline() {
return '\r\n';
}
/**
* Splash image file extension.
*
* @returns File extension.
*/
get splashImageExtension() {
return '.BMP';
}
/**
* Get the SKL name.
*
* @returns File name.
*/
get sklName() {
return 'Projec32.skl';
}
/**
* Write out the projector.
*
* @param path Save path.
* @param name Save name.
*/
async write(path, name) {
await super.write(path, name);
await this._patch(path, name);
await this._updateResources(path, name);
}
/**
* Write the projector skeleton from archive.
*
* @param path Save path.
* @param name Save name.
*/
async _writeSkeleton(path, name) {
const {
shockwave,
sklName,
xtrasDirectoryName
} = this;
const xtrasPath = this.getXtrasPath(name);
const xtrasMappings = this.getIncludeXtrasMappings();
let foundProjectorSkl = false;
let foundXtras = false;
const xtrasHandler = async entry => {
// Check if Xtras path.
const xtrasRel = pathRelativeBase(entry.volumePath, xtrasDirectoryName, true);
if (xtrasRel === null) {
return false;
}
foundXtras = true; // Find output path if being included, else skip.
const dest = this.includeXtrasMappingsDest(xtrasMappings, xtrasRel);
if (!dest) {
return true;
}
await entry.extract(pathJoin(path, xtrasPath, dest));
return true;
};
const projectorSklHandler = async entry => {
const entryPath = entry.volumePath; // Should not be in sub directory.
if (entryPath.includes('/')) {
return false;
} // Check if skl path.
if (!pathRelativeBaseMatch(entryPath, sklName, true)) {
return false;
}
foundProjectorSkl = true;
await entry.extract(pathJoin(path, name));
return true;
};
const projectorDllHandler = async entry => {
const entryPath = entry.volumePath; // Should not be in sub directory.
if (entryPath.includes('/')) {
return false;
} // Check if dll path.
if (!/\.dll$/i.test(entryPath)) {
return false;
} // Exclude if shockwave projector.
if (shockwave) {
return true;
}
await entry.extract(pathJoin(path, entryPath));
return true;
};
const archive = await this.getSkeletonArchive();
await archive.read(async entry => {
// Skip empty resource forks (every file in DMG).
if (entryIsEmptyResourceFork(entry)) {
return;
}
if (await xtrasHandler(entry)) {
return;
}
if (await projectorSklHandler(entry)) {
return;
}
if (await projectorDllHandler(entry)) {
return;
}
});
if (!foundProjectorSkl) {
throw new Error(`Failed to locate: ${sklName}`);
}
if (!foundXtras) {
throw new Error(`Failed to locate: ${xtrasDirectoryName}`);
}
}
/**
* Patch projector.
*
* @param path Save path.
* @param name Save name.
*/
async _patch(path, name) {
await this._patchShockwave3dInstalledDisplayDriversSize(path, name);
}
/**
* Patch projector, Shockwave 3D InstalledDisplayDrivers size.
*
* @param path Save path.
* @param name Save name.
*/
async _patchShockwave3dInstalledDisplayDriversSize(path, name) {
if (!this.patchShockwave3dInstalledDisplayDriversSize) {
return;
}
const xtrasDir = pathJoin(path, this.getXtrasPath(name));
const search = 'Shockwave 3D Asset.x32';
const searchLower = search.toLowerCase();
let found = false;
await fsWalk(xtrasDir, async (path, stat) => {
if (!stat.isFile()) {
return;
}
const fn = pathBasename(path);
if (fn.toLowerCase() !== searchLower) {
return;
}
found = true;
await patchWindowsS3dInstalledDisplayDriversSize(pathJoin(xtrasDir, path));
}, {
ignoreUnreadableDirectories: true
});
if (!found) {
throw new Error(`Failed to locate for patching: ${search}`);
}
}
/**
* Update projector Windows resources.
*
* @param path Save path.
* @param name Save name.
*/
async _updateResources(path, name) {
const {
iconFile,
fileVersion,
productVersion,
versionStrings
} = this;
const options = {};
let optionsSet = false;
if (iconFile) {
options.iconPath = iconFile;
optionsSet = true;
}
if (fileVersion !== null) {
options.fileVersion = fileVersion;
optionsSet = true;
}
if (productVersion !== null) {
options.productVersion = productVersion;
optionsSet = true;
}
if (versionStrings !== null) {
options.versionStrings = versionStrings;
optionsSet = true;
} // Do not update if no changes are specified.
if (!optionsSet) {
return;
}
const file = pathJoin(path, name);
await rcedit(file, options);
}
}
//# sourceMappingURL=windows.mjs.map