@shockpkg/ria-packager
Version:
Package for creating Adobe AIR packages
676 lines (510 loc) • 19.5 kB
JavaScript
"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