UNPKG

@shockpkg/ria-packager

Version:

Package for creating Adobe AIR packages

676 lines (510 loc) 19.5 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.PackagerBundleWindows = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _path = require("path"); var _fsExtra = _interopRequireDefault(require("fs-extra")); var _portableExecutableSignature = require("portable-executable-signature"); var resedit = _interopRequireWildcard(require("resedit")); var rgbaImageCreateImage = _interopRequireWildcard(require("@rgba-image/create-image")); var rgbaImagePng = _interopRequireWildcard(require("@rgba-image/png")); var rgbaImageLanczos = _interopRequireWildcard(require("@rgba-image/lanczos")); var _archiveFiles = require("@shockpkg/archive-files"); var _iconEncoder = require("@shockpkg/icon-encoder"); var _util = require("../../util"); var _bundle = require("../bundle"); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } const ResEditNtExecutable = resedit.NtExecutable || resedit.default.NtExecutable; const ResEditNtExecutableResource = resedit.NtExecutableResource || resedit.default.NtExecutableResource; const ResEditResource = resedit.Resource || resedit.default.Resource; const ResEditData = resedit.Data || resedit.default.Data; const createImage = rgbaImageCreateImage.createImage || rgbaImageCreateImage.default.createImage; const fromPng = rgbaImagePng.fromPng || rgbaImagePng.default.fromPng; const toPng = rgbaImagePng.toPng || rgbaImagePng.default.toPng; const lanczos = rgbaImageLanczos.lanczos || rgbaImageLanczos.default.lanczos; /** * Helper function to resize images using lanczos algorithm. * * @param img Image object. * @param w Resized width. * @param h Resized height. * @returns Resized image. */ function resizeLanczos(img, w, h) { const r = createImage(w, h); lanczos(img, r, 0, 0, img.width, img.height, 0, 0, w, h); return r; } /** * PackagerBundleWindows constructor. * * @param path Output path. */ class PackagerBundleWindows extends _bundle.PackagerBundle { /** * Create modern application icon resource. * Enables higher resolution 256x256 icon with PNG compression. * Higher resolutions resized with lanczos from 512x512 or 1024x1024. * Default false uses the legacy formats of the official packager. */ /** * Create modern document type icon resource. * Enables higher resolution 256x256 icon with PNG compression. * Higher resolutions resized with lanczos from 512x512 or 1024x1024. * Default false uses the legacy formats of the official packager. */ /** * Remove unnecessary helper files from framework. * The official packages will include these. */ /** * Optionally preserve resource mtime. * The official packager does not preserve resource mtimes. */ /** * Optionally use specific architecture. */ /** * Version strings. * * @default null */ /** * Product version. * * @default null */ /** * Version strings. * * @default null */ constructor(path) { super(path); (0, _defineProperty2.default)(this, "applicationIconModern", false); (0, _defineProperty2.default)(this, "fileTypeIconModern", false); (0, _defineProperty2.default)(this, "frameworkCleanHelpers", false); (0, _defineProperty2.default)(this, "preserveResourceMtime", false); (0, _defineProperty2.default)(this, "architecture", null); (0, _defineProperty2.default)(this, "fileVersion", null); (0, _defineProperty2.default)(this, "productVersion", null); (0, _defineProperty2.default)(this, "versionStrings", null); } /** * Get app binary path. * * @returns Binary path. */ getAppBinaryPath() { return `${this._getFilename()}.exe`; } /** * Get app framework path. * * @returns Framework path. */ getAppFrameworkPath() { return 'Adobe AIR'; } /** * Get SDK binary path. * * @returns Binary path. */ getSdkBinaryPath() { const framework = this.getSdkFrameworkPath(); return `${framework}/Versions/1.0/Resources/CaptiveAppEntry.exe`; } /** * Get SDK framework path. * * @returns Framework path. */ getSdkFrameworkPath() { const win = this._getArchitecture() === 'x64' ? 'win64' : 'win'; return `runtimes/air-captive/${win}/Adobe AIR`; } /** * Get all version strings, if any. * * @returns Verion strings. */ getVersionStrings() { const { fileVersion, productVersion, versionStrings } = this; if (fileVersion === null && productVersion === null && versionStrings === null) { return null; } const values = { ...(versionStrings || {}) }; if (fileVersion !== null) { values.FileVersion = fileVersion; } if (productVersion !== null) { values.ProductVersion = productVersion; } return values; } /** * Get file mode value. * * @param executable Is the entry executable. * @returns File mode. */ _getFileMode(executable) { return executable ? 0b111100100 : 0b110100100; } /** * Open implementation. * * @param applicationData The application descriptor data. */ async _open(applicationData) { const { frameworkCleanHelpers } = this; const appBinaryPath = this.getAppBinaryPath(); const appFrameworkPath = this.getAppFrameworkPath(); const sdkBinaryPath = this.getSdkBinaryPath(); const sdkFrameworkPath = this.getSdkFrameworkPath(); const appBinaryPathFull = (0, _path.join)(this.path, appBinaryPath); const appFrameworkPathFull = (0, _path.join)(this.path, appFrameworkPath); let extractedBinary = false; let extractedFramework = false; let binaryInFrameworkPath = ''; // Extract everything needed from the SDK. const sdk = await this._openSdk(); await sdk.read(async entry => { // Ignore any resource forks. if (entry.type === _archiveFiles.PathType.RESOURCE_FORK) { return true; } const path = entry.volumePath; const sdkBinaryPathRel = (0, _util.pathRelativeBase)(path, sdkBinaryPath, true); const frameworkPathRel = (0, _util.pathRelativeBase)(path, sdkFrameworkPath, true); // Extract if the framework. if (frameworkPathRel !== null) { const dest = (0, _path.join)(appFrameworkPathFull, frameworkPathRel); extractedFramework = true; // If also the binary, remember it for later. if (sdkBinaryPathRel === null) { await entry.extract(dest); return true; } // If not removing framework binary, copy into framework. // Remember where to copy again after. // Otherwise let it be extracted to destination below. if (!frameworkCleanHelpers) { // Remember the shortest path, not empty. binaryInFrameworkPath = binaryInFrameworkPath || dest; if (dest.length < binaryInFrameworkPath.length) { binaryInFrameworkPath = dest; } await entry.extract(dest); return true; } } // Copy binary from framework if there. if (sdkBinaryPathRel !== null) { const dest = (0, _path.join)(appBinaryPathFull, sdkBinaryPathRel); await entry.extract(dest); extractedBinary = true; return true; } // Optimization to avoid walking unrelated directories if possible. return (0, _util.pathRelativeBaseMatch)(sdkFrameworkPath, path, true) || (0, _util.pathRelativeBaseMatch)(sdkBinaryPath, path, true) ? true : null; }); // If the binary is in framework, copy it. if (binaryInFrameworkPath) { await _fsExtra.default.copy(binaryInFrameworkPath, appBinaryPathFull, { preserveTimestamps: true }); extractedBinary = true; } // Check that required components were extracted. if (!extractedBinary) { throw new Error(`Failed to locate binary in SDK: ${sdkBinaryPath}`); } if (!extractedFramework) { throw new Error(`Failed to locate framework in SDK: ${sdkFrameworkPath}`); } } /** * Close implementation. */ async _close() { await this._updateResources(); } /** * Write resource with data implementation. * * @param destination Packaged file relative destination. * @param data Resource data. * @param options Resource options. */ async _writeResource(destination, data, options) { // Write resource to file. const mode = this._getFileMode(options.executable || false); const dest = this._getResourcePath(destination); await _fsExtra.default.outputFile(dest, data, { mode }); // Optionally preserve mtime information. if (this.preserveResourceMtime) { const { mtime } = options; if (mtime) { await _fsExtra.default.utimes(dest, mtime, mtime); } } } /** * Get path to a resource file. * * @param parts Path parts. * @returns Full path. */ _getResourcePath(...parts) { return (0, _path.join)(this.path, ...parts); } /** * Get the configured architecture. * Prefers the architecture option, descriptor file, then default of x86. * * @returns Architecture string. */ _getArchitecture() { return this.architecture || (this._applicationInfoArchitecture === '64' ? 'x64' : 'x86'); } /** * Update main executable resources. */ async _updateResources() { // Get any version strings. const versionStrings = this.getVersionStrings(); // Get all the icons. const applicationIcon = await this._encodeApplicationIcon(); const fileTypeIcons = await this._encodeFileTypeIcons(); // Assemble the icons into a list. const icons = [...(applicationIcon ? [applicationIcon] : []), ...(fileTypeIcons || [])]; // Skip if nothing to be changed. if (!versionStrings && !icons.length) { return; } // Read EXE file and parse resources. const appBinaryPath = this.getAppBinaryPath(); const appBinaryPathFull = (0, _path.join)(this.path, appBinaryPath); const exe = ResEditNtExecutable.from((0, _portableExecutableSignature.signatureSet)((0, _util.bufferToArrayBuffer)(await _fsExtra.default.readFile(appBinaryPathFull)), null, true, true)); const res = ResEditNtExecutableResource.from(exe); // Check that icons and version info not present. if (ResEditResource.IconGroupEntry.fromEntries(res.entries).length) { throw new Error('Executable resources contains unexpected icons'); } if (ResEditResource.VersionInfo.fromEntries(res.entries).length) { throw new Error('Executable resources contains unexpected version info'); } // The lang and codepage resource values. const lang = 1033; const codepage = 1252; // Add icons, resource ID 100 plus. let resIdsNext = 100; for (const iconData of icons) { // Parse ico. const ico = ResEditData.IconFile.from((0, _util.bufferToArrayBuffer)(iconData)); // Get the next icon group ID. const iconGroupId = resIdsNext++; // Add this group to the list. ResEditResource.IconGroupEntry.replaceIconsForResource(res.entries, iconGroupId, 0, ico.icons.map(icon => icon.data)); // List all the resources now in the list. const entriesById = new Map(res.entries.map((resource, index) => [resource.id, { index, resource }])); // Get icon group info. const entryInfo = entriesById.get(iconGroupId); if (!entryInfo) { throw new Error('Internal error'); } // Read icon group entry. const [iconGroup] = ResEditResource.IconGroupEntry.fromEntries([entryInfo.resource]); // Change individual icon resource id values. for (const icon of iconGroup.icons) { const iconInfo = entriesById.get(icon.iconID); if (!iconInfo) { throw new Error('Internal error'); } icon.iconID = iconInfo.resource.id = resIdsNext++; } // Update the group entry. res.entries[entryInfo.index] = iconGroup.generateEntry(); } // Add the version info if any. if (versionStrings) { const versionInfo = ResEditResource.VersionInfo.createEmpty(); versionInfo.setStringValues({ lang, codepage }, versionStrings); // Update integer values from parsed strings if possible. const { FileVersion, ProductVersion } = versionStrings; if (FileVersion) { const uints = this._peVersionInts(FileVersion); if (uints) { const [ms, ls] = uints; versionInfo.fixedInfo.fileVersionMS = ms; versionInfo.fixedInfo.fileVersionLS = ls; } } if (ProductVersion) { const uints = this._peVersionInts(ProductVersion); if (uints) { const [ms, ls] = uints; versionInfo.fixedInfo.productVersionMS = ms; versionInfo.fixedInfo.productVersionLS = ls; } } versionInfo.outputToResourceEntries(res.entries); } // Update the codepage on all resources, matches the official packager. for (const entry of res.entries) { entry.codepage = codepage; } // Update resources and write EXE file. res.outputResource(exe); await _fsExtra.default.writeFile(appBinaryPathFull, Buffer.from(exe.generate())); } /** * Parse PE version string to integers (MS then LS bits) or null. * * @param version Version string. * @returns Version integers ([MS, LS]) or null. */ _peVersionInts(version) { const parts = version.split(/[.,]/); const numbers = []; for (const part of parts) { const n = /^\d+$/.test(part) ? +part : NaN; if (!(n >= 0 && n <= 0xFFFF)) { return null; } numbers.push(n); } return numbers.length ? [// eslint-disable-next-line no-bitwise ((numbers[0] || 0) << 16 | (numbers[1] || 0)) >>> 0, // eslint-disable-next-line no-bitwise ((numbers[2] || 0) << 16 | (numbers[3] || 0)) >>> 0] : null; } /** * Calculate UID for icon, or null if none of required icons set. * * @param icon Icon info. * @returns UID string or null. */ _uidIcon(icon) { const paths = [icon.image16x16, icon.image32x32, icon.image48x48, icon.image128x128]; // If none set, skip. let has = false; for (const p of paths) { if (p) { has = true; break; } } // Compute a unique identifier for the used icon set paths. return has ? paths.map(s => `${s ? s.length : 0}:${s || ''}`).join('|') : null; } /** * Encode the application icon if specified. * * @returns Encoded icon. */ async _encodeApplicationIcon() { const icon = this._getIcon(); if (!icon || !this._uidIcon(icon)) { return null; } const modern = this.applicationIconModern; // Encode either a modern or a reference icon. return modern ? this._encodeIconModern(icon) : this._encodeIconReference(icon); } /** * Encode file type icons. * Avoids writting duplicate icons where the file/data is the same. * * @returns Encoded icons. */ async _encodeFileTypeIcons() { const fileIcons = this._getFileTypes(); if (!fileIcons) { return null; } const modern = this.fileTypeIconModern; const r = []; const did = new Set(); for (const [, { icon }] of fileIcons) { if (!icon) { continue; } // Compute a unique identifier for the used icon set paths. const uid = this._uidIcon(icon); if (!uid) { continue; } // Check if file was already generated for this icon set. if (did.has(uid)) { continue; } did.add(uid); // Write either a modern or a reference icon. // eslint-disable-next-line no-await-in-loop r.push(await (modern ? this._encodeIconModern(icon) : this._encodeIconReference(icon))); } return r; } /** * Encode icon matching official format. * * @param icon Icon info. * @returns Encoded icon. */ async _encodeIconReference(icon) { // Add icons in the same order official packager would use. const ico = new _iconEncoder.IconIco(); for (const path of [icon.image16x16, icon.image48x48, icon.image128x128, icon.image32x32]) { if (!path) { continue; } // eslint-disable-next-line no-await-in-loop const data = await _fsExtra.default.readFile(this._getResourcePath(path)); ico.addFromPng(data, false); } return ico.encode(); } /** * Encode icon using modern format. * * @param icon Icon info. * @returns Encoded icon. */ async _encodeIconModern(icon) { // Add icons in the same order official packager would use, plus extra. const ico = new _iconEncoder.IconIco(); for (const path of [icon.image16x16, icon.image48x48, icon.image128x128, icon.image32x32]) { if (!path) { continue; } // eslint-disable-next-line no-await-in-loop const data = await _fsExtra.default.readFile(this._getResourcePath(path)); ico.addFromPng(data, false); } // Add a 256x256 icon if possible. const icon256 = await this._getIcon256x256Data(icon); if (icon256) { ico.addFromPng(icon256, true); } return ico.encode(); } /** * Get 256x256 icon data from icon set. * Unfortuantely the icon set does not support this icon size. * This functions will resize a larger icon instead. * Uses the lanczos algorithm to resize icon down. * * @param icon Icon info. * @returns Encoded icon. */ async _getIcon256x256Data(icon) { const { image512x512, image1024x1024 } = icon; // Resize 512x512 icon down if available. if (image512x512) { const d = await _fsExtra.default.readFile(this._getResourcePath(image512x512)); return Buffer.from(toPng(resizeLanczos(fromPng(d), 256, 256))); } // Resize 1024x1024 icon down if available. // Do this in two half-res steps to minorly improve quality. if (image1024x1024) { const d = await _fsExtra.default.readFile(this._getResourcePath(image1024x1024)); return Buffer.from(toPng(resizeLanczos(resizeLanczos(fromPng(d), 512, 512), 256, 256))); } // Otherwise no icon to resize down. return null; } } exports.PackagerBundleWindows = PackagerBundleWindows; //# sourceMappingURL=windows.js.map