diff --git a/.github/workflows/prod_build_cli.yml b/.github/workflows/prod_build_cli.yml index 167a7e9..dac110e 100644 --- a/.github/workflows/prod_build_cli.yml +++ b/.github/workflows/prod_build_cli.yml @@ -90,7 +90,19 @@ jobs: if: matrix.os == 'windows-latest' run: | cd cli + node win_fix_binary.js cli + $env:PKG_PATCHED_BIN = '1' + $env:PKG_CACHE_PATH = './.pkg-cache/' + $env:PKG_IGNORE_TAG = '1' node_modules/.bin/pkg --compress GZip -t node16-win-x64 -c package.json -o dist/halocli.exe entry_cli.js + - name: Package HaLo Bridge tool (Windows) + if: matrix.os == 'windows-latest' + run: | + cd cli + node win_fix_binary.js bridge + $env:PKG_PATCHED_BIN = '1' + $env:PKG_CACHE_PATH = './.pkg-cache/' + $env:PKG_IGNORE_TAG = '1' node_modules/.bin/pkg --compress GZip -t node16-win-x64 -c package.json -o dist/halo-bridge.exe entry_bridge.js - name: Package HaLo CLI tool (MacOS) if: matrix.os == 'macos-latest' diff --git a/cli/.gitignore b/cli/.gitignore index 5330818..f958329 100644 --- a/cli/.gitignore +++ b/cli/.gitignore @@ -1 +1,2 @@ assets/static/ws_client.js* +.pkg-cache/ diff --git a/cli/arx.ico b/cli/arx.ico new file mode 100644 index 0000000..b7e2902 Binary files /dev/null and b/cli/arx.ico differ diff --git a/cli/package-lock.json b/cli/package-lock.json index d7dfc9c..bb42293 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -21,6 +21,8 @@ }, "devDependencies": { "pkg": "^5.8.0", + "pkg-fetch": "^3.4.2", + "resedit": "^2.0.0", "webpack": "^5.76.2", "webpack-cli": "^5.0.1" } @@ -2658,6 +2660,16 @@ "node": ">=8" } }, + "node_modules/pe-library": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-1.0.0.tgz", + "integrity": "sha512-yZ+4d3YHKUjO0BX03oXFfHRKLdYKDO2HmCt1RcApPxme/P5ASPbbKnuQkzFrmT482wi2kfO+sPgqasrz5QeU1w==", + "dev": true, + "engines": { + "node": ">=14", + "npm": ">=7" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -2976,6 +2988,19 @@ "node": ">=0.10.0" } }, + "node_modules/resedit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resedit/-/resedit-2.0.0.tgz", + "integrity": "sha512-vrrJCabKxAW4MT1QivtAAb0poGp8KT2qhnSzfN9tFIxb2rQu1hRHNn1VgGSZR7nmxGaW5Yz0YeW1bjgvRfNoKA==", + "dev": true, + "dependencies": { + "pe-library": "^1.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=7" + } + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", diff --git a/cli/package.json b/cli/package.json index 144935c..7764b12 100644 --- a/cli/package.json +++ b/cli/package.json @@ -29,7 +29,7 @@ "bin": "cli.js", "pkg": { "targets": [ - "node18" + "node16" ], "outputPath": "dist", "assets": [ @@ -51,6 +51,8 @@ }, "devDependencies": { "pkg": "^5.8.0", + "pkg-fetch": "^3.4.2", + "resedit": "^2.0.0", "webpack": "^5.76.2", "webpack-cli": "^5.0.1" } diff --git a/cli/win_fix_binary.js b/cli/win_fix_binary.js new file mode 100644 index 0000000..e24acd3 --- /dev/null +++ b/cli/win_fix_binary.js @@ -0,0 +1,114 @@ +const path = require("path"); +const fs = require("fs"); +const { readFileSync, writeFileSync } = require('fs'); +const { need, system } = require('pkg-fetch'); +const package_json = require('./package.json'); +const crypto = require("crypto"); + +const { + hostArch, + hostPlatform +} = system; + +function computeSha256(filePath) { + return new Promise((resolve, reject) => { + fs.createReadStream(filePath) + .pipe(crypto.createHash('sha256').setEncoding('hex')) + .on('error', function (err) { + reject(err); + }) + .on('finish', function () { + resolve(this.read()); + }); + }); +} + +async function fixBinary(name, bin_name) { + // unable to normally require, this is ES6 module + const ResEdit = await import('resedit'); + + if (package_json['pkg']['targets'].length !== 1) { + throw Error("Only one pkg target is supported"); + } + + const nodeBinPath = await need({ + dryRun: false, + forceBuild: false, + nodeRange: package_json['pkg']['targets'][0], + platform: hostPlatform, + arch: hostArch + }); + + const language = { + lang: 1033, // en-us + codepage: 1200 // UTF-16 + }; + + // Modify .exe w/ ResEdit + const data = readFileSync(nodeBinPath); + const executable = ResEdit.NtExecutable.from(data); + const res = ResEdit.NtExecutableResource.from(executable); + const vi = ResEdit.Resource.VersionInfo.fromEntries(res.entries)[0]; + + // Remove original filename + vi.removeStringValue(language, 'OriginalFilename'); + vi.removeStringValue(language, 'InternalName'); + + vi.setProductVersion(1, 0, 0, 0, language.lang); + vi.setFileVersion(1, 0, 0, 0, language.lang); + + vi.setStringValues(language, { + FileDescription: name, + ProductName: name, + LegalCopyright: 'Arx Research Inc.', + OriginalFilename: bin_name + }); + + vi.outputToResourceEntries(res.entries); + + // Add icon + const iconFile = ResEdit.Data.IconFile.from(readFileSync("arx.ico")); + ResEdit.Resource.IconGroupEntry.replaceIconsForResource( + res.entries, + 1, + language.lang, + iconFile.icons.map(item => item.data) + ); + + // Regenerate and write to .exe + res.outputResource(executable); + + if (!fs.existsSync(".pkg-cache")){ + fs.mkdirSync(".pkg-cache"); + } + + const nodeBinBase = path.basename(nodeBinPath); + const nodeHashKey = nodeBinBase.replace('fetched-', 'node-'); + const outPath = path.join(".pkg-cache", nodeBinBase); + writeFileSync(outPath, Buffer.from(executable.generate())); + const fileHash = await computeSha256(outPath); + + fs.appendFileSync('node_modules\\pkg-fetch\\lib-es5\\expected.js', '\n/** PATCHED **/ if (process.env.PKG_PATCHED_BIN === "1") {exports.EXPECTED_HASHES[\'' + nodeHashKey + '\'] = \'' + fileHash + '\';}'); +} + +let name = null; +let bin_name = null; + +if (process.argv.length < 3) { + throw Error("Binary type not specified in argv."); +} else if (process.argv[2] === "cli") { + name = 'HaLo CLI'; + bin_name = 'halocli.exe'; +} else if (process.argv[2] === "bridge") { + name = 'HaLo Bridge Server'; + bin_name = 'halo-bridge.exe'; +} else { + throw Error("Unknown binary type specified."); +} + +fixBinary(name, bin_name); + +// run pkg with: +// $env:PKG_PATCHED_BIN = 1 +// $env:PKG_CACHE_PATH = './.pkg-cache/' +// $env:PKG_IGNORE_TAG = 1