├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── main.yaml ├── .gitignore ├── .npmignore ├── .nvmrc ├── .prettierignore ├── .prettierrc.yaml ├── LICENSE.txt ├── README.md ├── babel.config.mjs ├── eslint.config.mjs ├── launchers ├── download.mjs └── manifest.json ├── package-lock.json ├── package.json ├── spec ├── asm │ ├── linux │ │ ├── menu-i386 │ │ │ ├── 10.0-menu-1-a.asm │ │ │ ├── 10.0-menu-1-b.asm │ │ │ ├── 10.0-menu-2-a.asm │ │ │ ├── 10.0-menu-2-b.asm │ │ │ ├── 10.0-widget-a.asm │ │ │ ├── 10.0-widget-b.asm │ │ │ ├── 10.1-menu-a.asm │ │ │ ├── 10.1-menu-b.asm │ │ │ ├── 10.1-widget-a.asm │ │ │ ├── 10.1-widget-b.asm │ │ │ ├── 11.0-menu-a.asm │ │ │ ├── 11.0-menu-b.asm │ │ │ ├── 11.0-widget-a.asm │ │ │ ├── 11.0-widget-b.asm │ │ │ ├── 11.2-menu-a.asm │ │ │ ├── 11.2-menu-b.asm │ │ │ ├── 11.2-widget-a.asm │ │ │ ├── 11.2-widget-b.asm │ │ │ ├── 6-widget-a.asm │ │ │ ├── 6-widget-b.asm │ │ │ ├── 9-menu-a.asm │ │ │ ├── 9-menu-b.asm │ │ │ ├── 9-widget-a.asm │ │ │ └── 9-widget-b.asm │ │ ├── menu-x86_64 │ │ │ ├── 24-menu-a.asm │ │ │ ├── 24-menu-b.asm │ │ │ ├── 24-widget-a.asm │ │ │ ├── 24-widget-b.asm │ │ │ ├── 32-menu-a.asm │ │ │ ├── 32-menu-b.asm │ │ │ ├── 32-widget-a.asm │ │ │ └── 32-widget-b.asm │ │ ├── offset-x86_64 │ │ │ ├── 24-a.asm │ │ │ ├── 24-b.asm │ │ │ ├── 25-a.asm │ │ │ ├── 25-b.asm │ │ │ ├── 32-a.asm │ │ │ └── 32-b.asm │ │ ├── patch-i386 │ │ │ └── ebx.asm │ │ ├── path-i386 │ │ │ ├── 10.0.asm │ │ │ ├── 10.1.asm │ │ │ ├── 11.0.asm │ │ │ ├── 11.2.asm │ │ │ ├── 6.asm │ │ │ └── 9.asm │ │ ├── path-x86_64 │ │ │ └── 24.asm │ │ ├── title-i386 │ │ │ ├── 10.1.asm │ │ │ ├── 11.2.asm │ │ │ ├── 6.asm │ │ │ └── 9.asm │ │ └── title-x86_64 │ │ │ └── 24.asm │ ├── mac │ │ ├── title-arm64 │ │ │ ├── 35-debug.asm │ │ │ └── 35-release.asm │ │ ├── title-i386 │ │ │ ├── 11.asm │ │ │ ├── 13.asm │ │ │ └── 23.asm │ │ └── title-x86_64 │ │ │ ├── 11-1.asm │ │ │ ├── 11-2.asm │ │ │ ├── 13.asm │ │ │ ├── 23.asm │ │ │ └── 35.asm │ └── windows │ │ ├── ood-i386 │ │ ├── 30.asm │ │ ├── 31.asm │ │ └── ret4.asm │ │ └── ood-x86_64 │ │ ├── 26.asm │ │ ├── 30.asm │ │ └── ret.asm └── fixtures │ ├── Info.plist │ ├── PkgInfo │ ├── dummy │ ├── dummy.app │ └── Contents │ │ ├── Info.plist │ │ ├── MacOS │ │ └── main │ │ ├── PkgInfo │ │ └── Resources │ │ └── icon.icns │ ├── dummy.exe │ ├── dummy.exe.zip │ ├── icon-to-icns.sh │ ├── icon-to-ico.sh │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ ├── image.jpg │ ├── image.png │ ├── image.swf │ ├── swf14-lzma.swf │ ├── swf14-zlib.swf │ ├── swf14.swf │ ├── swf3.swf │ ├── swf4-loadmovie.as │ ├── swf4-loadmovie.sh │ ├── swf4-loadmovie.swf │ ├── swf6-loadmovie.as │ ├── swf6-loadmovie.sh │ ├── swf6-loadmovie.swf │ ├── swf6-showmenu-false.as │ ├── swf6-showmenu-false.sh │ ├── swf6-showmenu-false.swf │ ├── swf6-zlib.swf │ └── swf6.swf ├── src ├── bundle.ts ├── bundle │ ├── html.test.ts │ ├── html.ts │ ├── index.ts │ ├── sa.spec.ts │ ├── sa.test.ts │ ├── sa.ts │ └── sa │ │ ├── index.ts │ │ ├── linux.test.ts │ │ ├── linux.ts │ │ ├── mac.test.ts │ │ ├── mac.ts │ │ ├── windows.test.ts │ │ └── windows.ts ├── index.ts ├── launchers.ts ├── loader.test.ts ├── loader.ts ├── meta.test.ts ├── meta.ts ├── projector.ts ├── projector │ ├── html.test.ts │ ├── html.ts │ ├── index.ts │ ├── sa.spec.ts │ ├── sa.test.ts │ ├── sa.ts │ └── sa │ │ ├── index.ts │ │ ├── linux.spec.ts │ │ ├── linux.test.ts │ │ ├── linux.ts │ │ ├── mac.spec.ts │ │ ├── mac.test.ts │ │ ├── mac.ts │ │ ├── windows.spec.ts │ │ ├── windows.test.ts │ │ └── windows.ts ├── queue.test.ts ├── queue.ts ├── swf │ ├── data.ts │ ├── fixed8.ts │ ├── rect.ts │ ├── swf.ts │ ├── tag.ts │ └── util.ts ├── util.spec.ts ├── util.test.ts ├── util.ts └── util │ ├── index.ts │ ├── internal │ ├── data.ts │ ├── linux │ │ ├── asm.ts │ │ ├── elf.ts │ │ ├── menu.ts │ │ ├── menu32.ts │ │ ├── menu64.ts │ │ ├── offset64.ts │ │ ├── patch.ts │ │ ├── path.ts │ │ ├── path32.ts │ │ ├── path64.ts │ │ ├── title.ts │ │ ├── title32.ts │ │ └── title64.ts │ ├── mac │ │ ├── asm.ts │ │ ├── constants.ts │ │ └── title.ts │ ├── patch.ts │ └── windows │ │ ├── asm.ts │ │ ├── constants.ts │ │ ├── exe.ts │ │ ├── ood32.ts │ │ ├── ood64.ts │ │ ├── rsrc.ts │ │ └── title.ts │ ├── linux.ts │ ├── mac.test.ts │ ├── mac.ts │ ├── windows.test.ts │ └── windows.ts ├── tsconfig.eslint.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | indent_style = tab 8 | indent_size = 4 9 | charset = utf8 10 | 11 | [*.{yml,yaml}] 12 | indent_style = space 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.bin binary 3 | spec/fixtures/dummy binary 4 | spec/fixtures/dummy.exe binary 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS files 2 | .DS_Store 3 | .DS_Store? 4 | ._* 5 | .Spotlight-V100 6 | .Trashes 7 | Icon? 8 | ehthumbs.db 9 | Thumbs.db 10 | 11 | # Node files 12 | node_modules 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | report.*.json 17 | *.tgz 18 | yarn.lock 19 | 20 | # IDE config 21 | .vscode 22 | 23 | # Launcher binaries 24 | /launchers/data 25 | 26 | # Build files 27 | /dts 28 | /esm 29 | /cjs 30 | 31 | # Test files 32 | /shockpkg 33 | /spec/projectors 34 | /spec/bundles 35 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # OS files 2 | .DS_Store 3 | .DS_Store? 4 | ._* 5 | .Spotlight-V100 6 | .Trashes 7 | Icon? 8 | ehthumbs.db 9 | Thumbs.db 10 | 11 | # Node files 12 | node_modules 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | report.*.json 17 | *.tgz 18 | yarn.lock 19 | .nvmrc 20 | 21 | # IDE config 22 | .vscode 23 | 24 | /package-lock.json 25 | /.gitattributes 26 | /.gitignore 27 | /.github 28 | /.editorconfig 29 | /.prettierrc.yaml 30 | /.prettierignore 31 | /babel.config.mjs 32 | /eslint.config.mjs 33 | /tsconfig.json 34 | /tsconfig.eslint.json 35 | /spec 36 | /src 37 | 38 | /dts/**/*.test.d.ts 39 | /esm/**/*.test.mjs 40 | /esm/**/*.test.mjs.map 41 | /cjs/**/*.test.js 42 | /cjs/**/*.test.js.map 43 | 44 | /dts/**/*.spec.d.ts 45 | /esm/**/*.spec.mjs 46 | /esm/**/*.spec.mjs.map 47 | /cjs/**/*.spec.js 48 | /cjs/**/*.spec.js.map 49 | 50 | /launchers 51 | /shockpkg 52 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.16.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /package-lock.json 2 | /dts 3 | /esm 4 | /cjs 5 | /spec/fixtures 6 | /spec/projectors 7 | /spec/bundles 8 | /shockpkg 9 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | tabWidth: 4 2 | printWidth: 80 3 | useTabs: true 4 | semi: true 5 | singleQuote: true 6 | quoteProps: as-needed 7 | trailingComma: none 8 | bracketSpacing: false 9 | arrowParens: avoid 10 | overrides: 11 | - files: 12 | - '*.yaml' 13 | - '*.yml' 14 | options: 15 | tabWidth: 2 16 | useTabs: false 17 | -------------------------------------------------------------------------------- /babel.config.mjs: -------------------------------------------------------------------------------- 1 | import {readFileSync, readdirSync} from 'node:fs'; 2 | import {deflateRawSync} from 'node:zlib'; 3 | 4 | const {name, version, engines} = JSON.parse(readFileSync('./package.json')); 5 | 6 | const node = engines.node 7 | .split(/[^\d.]+/) 8 | .filter(s => s.length) 9 | .map(s => [...s.split('.').map(s => +s || 0), 0, 0].slice(0, 3)) 10 | .sort((a, b) => a[2] - b[2]) 11 | .sort((a, b) => a[1] - b[1]) 12 | .sort((a, b) => a[0] - b[0])[0] 13 | .join('.'); 14 | 15 | const launchers = (manifest => { 16 | const r = {}; 17 | for (const {name, file} of manifest) { 18 | r[name] = deflateRawSync(readFileSync(`launchers/data/${file}`), { 19 | level: 9 20 | }).toString('base64'); 21 | } 22 | return r; 23 | })(JSON.parse(readFileSync('./launchers/manifest.json'))); 24 | 25 | const asms = (dirs => { 26 | const parse = s => { 27 | const b = []; 28 | for (const line of s.split(/[\n\r]+/)) { 29 | const [s] = line.split(/[#;]/)[0].trim().split(' '); 30 | if (!s) { 31 | continue; 32 | } 33 | for (const h of s.split(' ')) { 34 | // eslint-disable-next-line unicorn/prefer-number-properties 35 | b.push(/^[\da-f]{2}$/i.test(h) ? parseInt(h, 16) : -1); 36 | } 37 | } 38 | return b; 39 | }; 40 | const r = []; 41 | for (const dir of dirs) { 42 | const d = {}; 43 | for (const f of readdirSync(dir).sort()) { 44 | const m = f.match(/^([^.].*)\.asm$/); 45 | if (m) { 46 | d[m[1]] = parse(readFileSync(`${dir}/${f}`, 'utf8')); 47 | } 48 | } 49 | r.push({ 50 | search: `#{${dir}}`, 51 | replace: d 52 | }); 53 | } 54 | return r; 55 | })([ 56 | 'spec/asm/linux/menu-i386', 57 | 'spec/asm/linux/menu-x86_64', 58 | 'spec/asm/linux/offset-x86_64', 59 | 'spec/asm/linux/patch-i386', 60 | 'spec/asm/linux/path-i386', 61 | 'spec/asm/linux/path-x86_64', 62 | 'spec/asm/linux/title-i386', 63 | 'spec/asm/linux/title-x86_64', 64 | 'spec/asm/mac/title-i386', 65 | 'spec/asm/mac/title-x86_64', 66 | 'spec/asm/mac/title-arm64', 67 | 'spec/asm/windows/ood-i386', 68 | 'spec/asm/windows/ood-x86_64' 69 | ]); 70 | 71 | export default api => { 72 | const env = api.env(); 73 | api.cache(() => env); 74 | const modules = env === 'esm' ? false : 'commonjs'; 75 | const ext = modules ? '.js' : '.mjs'; 76 | const presets = []; 77 | const plugins = []; 78 | presets.push( 79 | [ 80 | '@babel/preset-env', 81 | { 82 | modules, 83 | exclude: ['proposal-dynamic-import'], 84 | targets: { 85 | node 86 | } 87 | } 88 | ], 89 | ['@babel/preset-typescript'] 90 | ); 91 | plugins.push( 92 | [ 93 | 'module-replace', 94 | { 95 | replace: [[/^(\.\.?\/.+)\.(m|c)?tsx?$/i, `$1${ext}`]] 96 | } 97 | ], 98 | [ 99 | 'search-and-replace', 100 | { 101 | rules: [ 102 | { 103 | search: '#{NAME}', 104 | replace: name 105 | }, 106 | { 107 | search: '#{VERSION}', 108 | replace: version 109 | }, 110 | { 111 | search: '#{LAUNCHERS}', 112 | replace: launchers 113 | }, 114 | ...asms 115 | ] 116 | } 117 | ] 118 | ); 119 | if (modules === 'commonjs') { 120 | plugins.push([ 121 | '@babel/plugin-transform-modules-commonjs', 122 | { 123 | importInterop: 'node' 124 | } 125 | ]); 126 | } 127 | return { 128 | presets, 129 | plugins 130 | }; 131 | }; 132 | -------------------------------------------------------------------------------- /launchers/download.mjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsdoc/require-jsdoc */ 2 | import {fileURLToPath} from 'node:url'; 3 | import {join as pathJoin} from 'node:path'; 4 | import {mkdir, readFile, rm, writeFile} from 'node:fs/promises'; 5 | import {createHash} from 'node:crypto'; 6 | 7 | const DIR = fileURLToPath(new URL('.', import.meta.url)); 8 | const OUT = pathJoin(DIR, 'data'); 9 | const MANIFEST = pathJoin(DIR, 'manifest.json'); 10 | 11 | function sha256(data) { 12 | return createHash('sha256').update(data).digest('hex'); 13 | } 14 | 15 | async function ensure({file, url, hash}) { 16 | const out = pathJoin(OUT, file); 17 | { 18 | const existing = await readFile(out).catch(() => null); 19 | if (existing) { 20 | if (sha256(await readFile(out)) === hash) { 21 | return; 22 | } 23 | await rm(out, {force: true}); 24 | } 25 | } 26 | const response = await fetch(url); 27 | if (response.status !== 200) { 28 | throw new Error(`Status ${response.status}: ${url}`); 29 | } 30 | const body = new Uint8Array(await response.arrayBuffer()); 31 | { 32 | const hashed = sha256(body); 33 | if (hashed !== hash) { 34 | throw new Error(`Invalid hash: ${hashed}`); 35 | } 36 | } 37 | await writeFile(out, body); 38 | } 39 | 40 | async function main() { 41 | const manifest = JSON.parse(await readFile(MANIFEST, 'utf8')); 42 | await mkdir(OUT, {recursive: true}); 43 | await Promise.all(manifest.map(ensure)); 44 | } 45 | main().catch(err => { 46 | // eslint-disable-next-line no-console 47 | console.error(err); 48 | process.exitCode = 1; 49 | }); 50 | -------------------------------------------------------------------------------- /launchers/manifest.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "windows-i686", 4 | "url": "https://github.com/shockpkg/projector-launcher-windows/releases/download/1.0.0/main.i686.exe", 5 | "file": "windows-i686.exe", 6 | "hash": "166e5cb9228842e98e59d0cae1578fd0d97c9754944dae6533678716f7fd1c1c" 7 | }, 8 | { 9 | "name": "windows-x86_64", 10 | "url": "https://github.com/shockpkg/projector-launcher-windows/releases/download/1.0.0/main.x86_64.exe", 11 | "file": "windows-x86_64.exe", 12 | "hash": "6a8e15452b1049ed9727eee65e1f8c81a6ff496f7e452c75268e2c3193dd61b1" 13 | }, 14 | { 15 | "name": "mac-app-ppc", 16 | "url": "https://github.com/shockpkg/projector-launcher-mac-app/releases/download/1.1.0/main.ppc", 17 | "file": "mac-app-ppc", 18 | "hash": "17414c123fe82ac74a89fad9c80e36d8b612ded5a520e35f3c33eabe75a023a7" 19 | }, 20 | { 21 | "name": "mac-app-ppc64", 22 | "url": "https://github.com/shockpkg/projector-launcher-mac-app/releases/download/1.1.0/main.ppc64", 23 | "file": "mac-app-ppc64", 24 | "hash": "9e159161fc21b72de6fddb5fb9c60c0e34e649e4660248778219e58198adfb3d" 25 | }, 26 | { 27 | "name": "mac-app-i386", 28 | "url": "https://github.com/shockpkg/projector-launcher-mac-app/releases/download/1.1.0/main.i386", 29 | "file": "mac-app-i386", 30 | "hash": "e52e19fce336130824dcfd4731bf85db7e8e96628ef8c6a49769dc5247ef6ed0" 31 | }, 32 | { 33 | "name": "mac-app-x86_64", 34 | "url": "https://github.com/shockpkg/projector-launcher-mac-app/releases/download/1.1.0/main.x86_64", 35 | "file": "mac-app-x86_64", 36 | "hash": "f5b7625da819324f442cea1f3af83ea4b2bf0af1d185a7747d81b698a6168562" 37 | }, 38 | { 39 | "name": "mac-app-arm64", 40 | "url": "https://github.com/shockpkg/projector-launcher-mac-app/releases/download/1.1.0/main.arm64", 41 | "file": "mac-app-arm64", 42 | "hash": "a20cbe0ffd3c3ad6b5ee2a58af81ea33145903847785382e7ef3424f08556312" 43 | }, 44 | { 45 | "name": "linux-i386", 46 | "url": "https://github.com/shockpkg/projector-launcher-linux/releases/download/2.0.0/main.i386", 47 | "file": "linux-i386", 48 | "hash": "5bc49257a1bbe5f86a0068de0a783c669f10f88d44650e9451d3a1926277ab4c" 49 | }, 50 | { 51 | "name": "linux-x86_64", 52 | "url": "https://github.com/shockpkg/projector-launcher-linux/releases/download/2.0.0/main.x86_64", 53 | "file": "linux-x86_64", 54 | "hash": "4d5ae1aca3cee75732be68eec6136d5fe64c4648973123c98bb0f65492825199" 55 | } 56 | ] 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@shockpkg/swf-projector", 3 | "description": "Package for creating Flash Player projectors", 4 | "version": "6.0.9", 5 | "keywords": [ 6 | "shockpkg", 7 | "flash", 8 | "animate", 9 | "swf", 10 | "projector" 11 | ], 12 | "main": "cjs/index", 13 | "exports": { 14 | ".": [ 15 | { 16 | "types": "./dts/index.d.ts", 17 | "import": "./esm/index.mjs", 18 | "require": "./cjs/index.js", 19 | "default": "./cjs/index.js" 20 | }, 21 | "./cjs/index.js" 22 | ] 23 | }, 24 | "types": "dts/index.d.ts", 25 | "module": "esm/index.mjs", 26 | "sideEffects": false, 27 | "engines": { 28 | "node": ">=18.12.0" 29 | }, 30 | "scripts": { 31 | "shockpkg": "shockpkg", 32 | "launchers": "node launchers/download.mjs", 33 | "clean": "rimraf dts esm cjs spec/bundles spec/projectors", 34 | "lint": "eslint .", 35 | "format": "prettier -w .", 36 | "formatted": "prettier -c .", 37 | "build:dts": "tsc", 38 | "build:esm": "babel --env-name esm -x .ts -s true -d esm --out-file-extension .mjs src", 39 | "build:cjs": "babel --env-name cjs -x .ts -s true -d cjs --out-file-extension .js src", 40 | "build": "npm run build:dts && npm run build:esm && npm run build:cjs", 41 | "test-legacy:esm": "node -r source-map-support/register --test esm", 42 | "test-legacy:cjs": "node -r source-map-support/register --test cjs", 43 | "test-legacy": "npm run test-legacy:esm && npm run test-legacy:cjs", 44 | "test:esm": "node -r source-map-support/register --test 'esm/**/*.test.mjs'", 45 | "test:cjs": "node -r source-map-support/register --test 'cjs/**/*.test.js'", 46 | "test": "npm run test:esm && npm run test:cjs", 47 | "all-legacy:esm": "npm run clean && npm run build:esm && npm run test-legacy:esm && npm run lint && npm run formatted", 48 | "all-legacy:cjs": "npm run clean && npm run build:cjs && npm run test-legacy:cjs && npm run lint && npm run formatted", 49 | "all-legacy": "npm run clean && npm run build && npm run test-legacy && npm run lint && npm run formatted", 50 | "all:esm": "npm run clean && npm run build:esm && npm run test:esm && npm run lint && npm run formatted", 51 | "all:cjs": "npm run clean && npm run build:cjs && npm run test:cjs && npm run lint && npm run formatted", 52 | "all": "npm run clean && npm run build && npm run test && npm run lint && npm run formatted", 53 | "prepack": "npm run launchers && npm run clean && npm run build" 54 | }, 55 | "repository": "https://github.com/shockpkg/swf-projector.git", 56 | "bugs": "https://github.com/shockpkg/swf-projector/issues", 57 | "author": "JrMasterModelBuilder", 58 | "copyright": "Copyright (c) 2019-2024 JrMasterModelBuilder", 59 | "license": "MPL-2.0", 60 | "dependencies": { 61 | "@shockpkg/archive-files": "^3.2.5", 62 | "@shockpkg/plist-dom": "^4.0.7", 63 | "@shockpkg/resedit": "^2.0.2", 64 | "macho-unsign": "^2.0.6", 65 | "portable-executable-signature": "^2.0.6" 66 | }, 67 | "devDependencies": { 68 | "@babel/cli": "^7.24.8", 69 | "@babel/core": "^7.25.2", 70 | "@babel/preset-env": "^7.25.3", 71 | "@babel/preset-typescript": "^7.24.7", 72 | "@eslint/js": "^9.8.0", 73 | "@shockpkg/cli": "^3.0.9", 74 | "@shockpkg/core": "^3.0.8", 75 | "@stylistic/eslint-plugin": "^2.6.1", 76 | "@types/node": "^22.1.0", 77 | "babel-plugin-module-replace": "^1.0.1", 78 | "babel-plugin-search-and-replace": "^1.1.1", 79 | "eslint": "^9.8.0", 80 | "eslint-config-prettier": "^9.1.0", 81 | "eslint-plugin-jsdoc": "^48.11.0", 82 | "eslint-plugin-unicorn": "^55.0.0", 83 | "prettier": "^3.3.3", 84 | "rimraf": "^6.0.1", 85 | "source-map-support": "^0.5.21", 86 | "typescript": "^5.5.4", 87 | "typescript-eslint": "^8.0.1" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/10.0-menu-1-a.asm: -------------------------------------------------------------------------------- 1 | 89 04 24 mov DWORD PTR [esp], eax 2 | E8 -- -- -- -- call ... 3 | C7 44 24 08 03 00 00 00 mov DWORD PTR [esp+0x8], 0x3 4 | 89 74 24 04 mov DWORD PTR [esp+0x4], esi 5 | 89 04 24 mov DWORD PTR [esp], eax 6 | E8 -- -- -- -- call _gtk_menu_shell_insert 7 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/10.0-menu-1-b.asm: -------------------------------------------------------------------------------- 1 | 89 04 24 mov DWORD PTR [esp], eax 2 | E8 -- -- -- -- call ... 3 | C7 44 24 08 03 00 00 00 mov DWORD PTR [esp+0x8], 0x3 4 | 89 74 24 04 mov DWORD PTR [esp+0x4], esi 5 | 89 04 24 mov DWORD PTR [esp], eax 6 | 90 90 90 90 90 nop 5 7 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/10.0-menu-2-a.asm: -------------------------------------------------------------------------------- 1 | 8B 45 0C mov eax, DWORD PTR [ebp+0xC] 2 | 89 04 24 mov DWORD PTR [esp], eax 3 | E8 -- -- -- -- call ... 4 | C7 44 24 08 03 00 00 00 mov DWORD PTR [esp+0x8], 0x3 5 | 89 5C 24 04 mov DWORD PTR [esp+0x4], ebx 6 | 89 04 24 mov DWORD PTR [esp], eax 7 | E8 -- -- -- -- call _gtk_menu_shell_insert 8 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/10.0-menu-2-b.asm: -------------------------------------------------------------------------------- 1 | 8B 45 0C mov eax, DWORD PTR [ebp+0xC] 2 | 89 04 24 mov DWORD PTR [esp], eax 3 | E8 -- -- -- -- call ... 4 | C7 44 24 08 03 00 00 00 mov DWORD PTR [esp+0x8], 0x3 5 | 89 5C 24 04 mov DWORD PTR [esp+0x4], ebx 6 | 89 04 24 mov DWORD PTR [esp], eax 7 | 90 90 90 90 90 nop 5 8 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/10.0-widget-a.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 89 86 -- 02 00 00 mov DWORD PTR [esi+...], eax 3 | 89 04 24 mov DWORD PTR [esp], eax 4 | E8 -- -- -- -- call _gtk_widget_show 5 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/10.0-widget-b.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 89 86 -- 02 00 00 mov DWORD PTR [esi+...], eax 3 | 89 04 24 mov DWORD PTR [esp], eax 4 | 90 90 90 90 90 nop 5 5 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/10.1-menu-a.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 8B 55 10 mov edx, DWORD PTR [ebp+0x10] 3 | 8B 4D -- mov ecx, DWORD PTR [ebp-...] 4 | 89 54 24 08 mov DWORD PTR [esp+0x8], edx 5 | 89 4C 24 04 mov DWORD PTR [esp+0x4], ecx 6 | 89 04 24 mov DWORD PTR [esp], eax 7 | E8 -- -- -- -- call _gtk_menu_shell_insert 8 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/10.1-menu-b.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 8B 55 10 mov edx, DWORD PTR [ebp+0x10] 3 | 8B 4D -- mov ecx, DWORD PTR [ebp-...] 4 | 89 54 24 08 mov DWORD PTR [esp+0x8], edx 5 | 89 4C 24 04 mov DWORD PTR [esp+0x4], ecx 6 | 89 04 24 mov DWORD PTR [esp], eax 7 | 90 90 90 90 90 nop 5 8 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/10.1-widget-a.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 8B 55 DC mov edx, DWORD PTR [ebp-0x24] 3 | 8B 42 60 mov eax, DWORD PTR [edx+0x60] 4 | 89 04 24 mov DWORD PTR [esp], eax 5 | E8 -- -- -- -- call _gtk_widget_show 6 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/10.1-widget-b.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 8B 55 DC mov edx, DWORD PTR [ebp-0x24] 3 | 8B 42 60 mov eax, DWORD PTR [edx+0x60] 4 | 89 04 24 mov DWORD PTR [esp], eax 5 | 90 90 90 90 90 nop 5 6 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/11.0-menu-a.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 8B 55 14 mov edx, DWORD PTR [ebp+0x14] 3 | 89 74 24 04 mov DWORD PTR [esp+0x4], esi 4 | 89 54 24 08 mov DWORD PTR [esp+0x8], edx 5 | 89 04 24 mov DWORD PTR [esp], eax 6 | E8 -- -- -- -- call _gtk_menu_shell_insert 7 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/11.0-menu-b.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 8B 55 14 mov edx, DWORD PTR [ebp+0x14] 3 | 89 74 24 04 mov DWORD PTR [esp+0x4], esi 4 | 89 54 24 08 mov DWORD PTR [esp+0x8], edx 5 | 89 04 24 mov DWORD PTR [esp], eax 6 | 90 90 90 90 90 nop 5 7 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/11.0-widget-a.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 8B 55 D4 mov edx, DWORD PTR [ebp-0x2C] 3 | 8B 42 60 mov eax, DWORD PTR [edx+0x60] 4 | 89 04 24 mov DWORD PTR [esp], eax 5 | E8 -- -- -- -- call _gtk_widget_show 6 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/11.0-widget-b.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 8B 55 D4 mov edx, DWORD PTR [ebp-0x2C] 3 | 8B 42 60 mov eax, DWORD PTR [edx+0x60] 4 | 89 04 24 mov DWORD PTR [esp], eax 5 | 90 90 90 90 90 nop 5 6 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/11.2-menu-a.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 8B 55 14 mov edx, DWORD PTR [ebp+0x14] 3 | 89 7C 24 04 mov DWORD PTR [esp+0x4], edi 4 | 89 54 24 08 mov DWORD PTR [esp+0x8], edx 5 | 89 04 24 mov DWORD PTR [esp], eax 6 | E8 -- -- -- -- call _gtk_menu_shell_insert 7 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/11.2-menu-b.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 8B 55 14 mov edx, DWORD PTR [ebp+0x14] 3 | 89 7C 24 04 mov DWORD PTR [esp+0x4], edi 4 | 89 54 24 08 mov DWORD PTR [esp+0x8], edx 5 | 89 04 24 mov DWORD PTR [esp], eax 6 | 90 90 90 90 90 nop 5 7 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/11.2-widget-a.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 8B 55 08 mov edx, DWORD PTR [ebp+0x8] 3 | 8B 42 60 mov eax, DWORD PTR [edx+0x60] 4 | 89 04 24 mov DWORD PTR [esp], eax 5 | E8 -- -- -- -- call _gtk_widget_show 6 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/11.2-widget-b.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 8B 55 08 mov edx, DWORD PTR [ebp+0x8] 3 | 8B 42 60 mov eax, DWORD PTR [edx+0x60] 4 | 89 04 24 mov DWORD PTR [esp], eax 5 | 90 90 90 90 90 nop 5 6 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/6-widget-a.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 89 83 08 01 00 00 mov DWORD PTR [ebx+0x108], eax 3 | 89 04 24 mov DWORD PTR [esp], eax 4 | E8 -- -- -- -- call _gtk_widget_show 5 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/6-widget-b.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 89 83 08 01 00 00 mov DWORD PTR [ebx+0x108], eax 3 | 89 04 24 mov DWORD PTR [esp], eax 4 | 90 90 90 90 90 nop 5 5 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/9-menu-a.asm: -------------------------------------------------------------------------------- 1 | 89 04 24 mov DWORD PTR [esp], eax 2 | E8 -- -- -- -- call ... 3 | BA 03 00 00 00 mov edx, 0x3 4 | 89 54 24 08 mov DWORD PTR [esp+0x8], edx 5 | 89 -- 24 04 mov DWORD PTR [esp+0x4], ... 6 | 89 04 24 mov DWORD PTR [esp], eax 7 | E8 -- -- -- -- call _gtk_menu_shell_insert 8 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/9-menu-b.asm: -------------------------------------------------------------------------------- 1 | 89 04 24 mov DWORD PTR [esp], eax 2 | E8 -- -- -- -- call ... 3 | BA 03 00 00 00 mov edx, 0x3 4 | 89 54 24 08 mov DWORD PTR [esp+0x8], edx 5 | 89 -- 24 04 mov DWORD PTR [esp+0x4], ... 6 | 89 04 24 mov DWORD PTR [esp], eax 7 | 90 90 90 90 90 nop 5 8 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/9-widget-a.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 89 87 -- 02 00 00 mov DWORD PTR [edi+...], eax 3 | 89 04 24 mov DWORD PTR [esp], eax 4 | E8 -- -- -- -- call _gtk_widget_show 5 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-i386/9-widget-b.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 89 87 -- 02 00 00 mov DWORD PTR [edi+...], eax 3 | 89 04 24 mov DWORD PTR [esp], eax 4 | 90 90 90 90 90 nop 5 5 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-x86_64/24-menu-a.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 44 89 EA mov edx, r13d 3 | 48 89 DE mov rsi, rbx 4 | 48 89 C7 mov rdi, rax 5 | E8 -- -- -- -- call _gtk_menu_shell_insert 6 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-x86_64/24-menu-b.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 44 89 EA mov edx, r13d 3 | 48 89 DE mov rsi, rbx 4 | 48 89 C7 mov rdi, rax 5 | 90 90 90 90 90 nop 5 6 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-x86_64/24-widget-a.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 49 8B BC 24 90 00 00 00 mov rdi, QWORD PTR [r12+0x90] 3 | E8 -- -- -- -- call _gtk_widget_show 4 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-x86_64/24-widget-b.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 49 8B BC 24 90 00 00 00 mov rdi, QWORD PTR [r12+0x90] 3 | 90 90 90 90 90 nop 5 4 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-x86_64/32-menu-a.asm: -------------------------------------------------------------------------------- 1 | 48 89 C7 mov rdi, rax 2 | E8 -- -- -- -- call ... 3 | 44 89 EA mov edx, r13d 4 | 48 89 EE mov rsi, rbp 5 | 48 89 C7 mov rdi, rax 6 | E8 -- -- -- -- call _gtk_menu_shell_insert 7 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-x86_64/32-menu-b.asm: -------------------------------------------------------------------------------- 1 | 48 89 C7 mov rdi, rax 2 | E8 -- -- -- -- call ... 3 | 44 89 EA mov edx, r13d 4 | 48 89 EE mov rsi, rbp 5 | 48 89 C7 mov rdi, rax 6 | 90 90 90 90 90 nop 5 7 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-x86_64/32-widget-a.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 49 8B BC 24 90 00 00 00 mov rdi, QWORD PTR [r12+0x90] 3 | E8 -- -- -- -- call _gtk_widget_show 4 | -------------------------------------------------------------------------------- /spec/asm/linux/menu-x86_64/32-widget-b.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 49 8B BC 24 90 00 00 00 mov rdi, QWORD PTR [r12+0x90] 3 | 90 90 90 90 90 nop 5 4 | -------------------------------------------------------------------------------- /spec/asm/linux/offset-x86_64/24-a.asm: -------------------------------------------------------------------------------- 1 | 48 8D B4 24 80 00 00 00 lea rsi, [rsp+0x80] 2 | BA 34 00 00 00 mov edx, 0x34 3 | 4C 89 FF mov rdi, r15 4 | 4C 89 E1 mov rcx, r12 5 | FF 50 28 call QWORD PTR [rax+0x28] 6 | 84 C0 test al, al 7 | 75 45 jne 0x5D 8 | 49 8B 07 mov rax, QWORD PTR [r15] 9 | 4C 89 FF mov rdi, r15 10 | FF 50 08 call QWORD PTR [rax+0x8] 11 | 41 0F B6 5D 00 movzx ebx, BYTE PTR [r13+0x0] 12 | 48 89 EF mov rdi, rbp 13 | E8 -- -- -- -- call ... 14 | 48 8B 8C 24 B8 00 00 00 mov rcx, QWORD PTR [rsp+0xB8] 15 | 64 48 33 0C 25 28 00 00 00 xor rcx, QWORD PTR fs:0x28 16 | 89 D8 mov eax, ebx 17 | 0F 85 -- -- -- -- jne ... 18 | 48 81 C4 C8 00 00 00 add rsp, 0xC8 19 | 5B pop rbx 20 | 5D pop rbp 21 | 41 5C pop r12 22 | 41 5D pop r13 23 | 41 5E pop r14 24 | 41 5F pop r15 25 | C3 ret 26 | -- -- -- -- ... 27 | 48 83 7C 24 30 34 cmp QWORD PTR [rsp+0x30], 0x34 28 | 75 -- jne ... 29 | 8B B4 24 A0 00 00 00 mov esi, DWORD PTR [rsp+0xA0] 30 | BA 01 00 00 00 mov edx, 0x1 31 | 4C 89 FF mov rdi, r15 32 | E8 -- -- -- -- call ... 33 | 84 C0 test al, al 34 | 74 -- je ... 35 | 45 31 F6 xor r14d, r14d 36 | 66 83 BC 24 B0 00 00 00 00 cmp WORD PTR [rsp+0xB0], 0x0 37 | C7 44 24 0C 00 00 00 00 mov DWORD PTR [rsp+0xC], 0x0 38 | 74 -- je ... 39 | -------------------------------------------------------------------------------- /spec/asm/linux/offset-x86_64/24-b.asm: -------------------------------------------------------------------------------- 1 | 48 8D B4 24 78 00 00 00 lea rsi, [rsp+0x78] 2 | BA 40 00 00 00 mov edx, 0x40 3 | 4C 89 FF mov rdi, r15 4 | 4C 89 E1 mov rcx, r12 5 | FF 50 28 call QWORD PTR [rax+0x28] 6 | 84 C0 test al, al 7 | 75 45 jne 0x5B 8 | 49 8B 07 mov rax, QWORD PTR [r15] 9 | 4C 89 FF mov rdi, r15 10 | FF 50 08 call QWORD PTR [rax+0x8] 11 | 41 0F B6 5D 00 movzx ebx, BYTE PTR [r13+0x0] 12 | 48 89 EF mov rdi, rbp 13 | E8 -- -- -- -- call ... 14 | 48 8B 8C 24 B8 00 00 00 mov rcx, QWORD PTR [rsp+0xB8] 15 | 64 48 33 0C 25 28 00 00 00 xor rcx, QWORD PTR fs:0x28 16 | 89 D8 mov eax, ebx 17 | 0F 85 -- -- -- -- jne ... 18 | 48 81 C4 C8 00 00 00 add rsp, 0xC8 19 | 5B pop rbx 20 | 5D pop rbp 21 | 41 5C pop r12 22 | 41 5D pop r13 23 | 41 5E pop r14 24 | 41 5F pop r15 25 | C3 ret 26 | -- -- -- -- ... 27 | 48 83 7C 24 30 40 cmp QWORD PTR [rsp+0x30], 0x40 28 | 75 -- jne ... 29 | 8B B4 24 A0 00 00 00 mov esi, DWORD PTR [rsp+0xA0] 30 | 41 89 F6 mov r14d, esi 31 | 0F B7 84 24 B4 00 00 00 movzx eax, WORD PTR [rsp+0xB4] 32 | C1 E0 06 shl eax, 0x6 33 | 41 01 C6 add r14d, eax 34 | 90 90 90 90 nop 4 35 | 90 90 90 90 nop 4 36 | 90 90 90 90 nop 4 37 | 90 90 90 90 nop 4 38 | 90 90 90 90 nop 4 39 | EB -- jmp ... 40 | -------------------------------------------------------------------------------- /spec/asm/linux/offset-x86_64/25-a.asm: -------------------------------------------------------------------------------- 1 | 48 8D 74 24 70 lea rsi, [rsp+0x70] 2 | BA 34 00 00 00 mov edx, 0x34 3 | 4C 89 FF mov rdi, r15 4 | 4C 89 E1 mov rcx, r12 5 | FF 50 28 call QWORD PTR [rax+0x28] 6 | 84 C0 test al, al 7 | 75 48 jne 0x5F 8 | 49 8B 07 mov rax, QWORD PTR [r15] 9 | 4C 89 FF mov rdi, r15 10 | FF 50 08 call QWORD PTR [rax+0x8] 11 | 41 0F B6 5D 00 movzx ebx, BYTE PTR [r13+0x0] 12 | 48 89 EF mov rdi, rbp 13 | E8 -- -- -- -- call ... 14 | 48 8B 8C 24 A8 00 00 00 mov rcx, QWORD PTR [rsp+0xA8] 15 | 64 48 33 0C 25 28 00 00 00 xor rcx, QWORD PTR fs:0x28 16 | 89 D8 mov eax, ebx 17 | 0F 85 -- -- -- -- jne ... 18 | 48 81 C4 B8 00 00 00 add rsp, 0xB8 19 | 5B pop rbx 20 | 5D pop rbp 21 | 41 5C pop r12 22 | 41 5D pop r13 23 | 41 5E pop r14 24 | 41 5F pop r15 25 | C3 ret 26 | -- -- -- -- -- -- -- ... 27 | 48 83 7C 24 30 34 cmp QWORD PTR [rsp+0x30], 0x34 28 | 75 -- jne ... 29 | 8B B4 24 90 00 00 00 mov esi, DWORD PTR [rsp+0x90] 30 | BA 01 00 00 00 mov edx, 0x1 31 | 4C 89 FF mov rdi, r15 32 | E8 -- -- -- -- call ... 33 | 84 C0 test al, al 34 | 74 -- je ... 35 | 45 31 F6 xor r14d, r14d 36 | 66 83 BC 24 A0 00 00 00 00 cmp WORD PTR [rsp+0xA0], 0x0 37 | C7 44 24 0C 00 00 00 00 mov DWORD PTR [rsp+0xC], 0x0 38 | 74 -- je ... 39 | -------------------------------------------------------------------------------- /spec/asm/linux/offset-x86_64/25-b.asm: -------------------------------------------------------------------------------- 1 | 48 8D 74 24 68 lea rsi, [rsp+0x68] 2 | BA 40 00 00 00 mov edx, 0x40 3 | 4C 89 FF mov rdi, r15 4 | 4C 89 E1 mov rcx, r12 5 | FF 50 28 call QWORD PTR [rax+0x28] 6 | 84 C0 test al, al 7 | 75 48 jne 0x5F 8 | 49 8B 07 mov rax, QWORD PTR [r15] 9 | 4C 89 FF mov rdi, r15 10 | FF 50 08 call QWORD PTR [rax+0x8] 11 | 41 0F B6 5D 00 movzx ebx, BYTE PTR [r13+0x0] 12 | 48 89 EF mov rdi, rbp 13 | E8 -- -- -- -- call ... 14 | 48 8B 8C 24 A8 00 00 00 mov rcx, QWORD PTR [rsp+0xA8] 15 | 64 48 33 0C 25 28 00 00 00 xor rcx, QWORD PTR fs:0x28 16 | 89 D8 mov eax, ebx 17 | 0F 85 -- -- -- -- jne ... 18 | 48 81 C4 B8 00 00 00 add rsp, 0xB8 19 | 5B pop rbx 20 | 5D pop rbp 21 | 41 5C pop r12 22 | 41 5D pop r13 23 | 41 5E pop r14 24 | 41 5F pop r15 25 | C3 ret 26 | -- -- -- -- -- -- -- ... 27 | 48 83 7C 24 30 40 cmp QWORD PTR [rsp+0x30], 0x40 28 | 75 -- jne ... 29 | 8B B4 24 90 00 00 00 mov esi, DWORD PTR [rsp+0x90] 30 | 41 89 F6 mov r14d, esi 31 | 0F B7 84 24 A4 00 00 00 movzx eax, WORD PTR [rsp+0xA4] 32 | C1 E0 06 shl eax, 0x6 33 | 41 01 C6 add r14d, eax 34 | 90 90 90 90 nop 4 35 | 90 90 90 90 nop 4 36 | 90 90 90 90 nop 4 37 | 90 90 90 90 nop 4 38 | 90 90 90 90 nop 4 39 | EB -- jmp ... 40 | -------------------------------------------------------------------------------- /spec/asm/linux/offset-x86_64/32-a.asm: -------------------------------------------------------------------------------- 1 | 48 8D 74 24 70 lea rsi, [rsp+0x70] 2 | BA 34 00 00 00 mov edx, 0x34 3 | 48 89 DF mov rdi, rbx 4 | 4C 89 E9 mov rcx, r13 5 | FF 50 28 call QWORD PTR [rax+0x28] 6 | 84 C0 test al, al 7 | 75 4E jne 0x50 8 | 48 8B 03 mov rax, QWORD PTR [rbx] 9 | 48 89 DF mov rdi, rbx 10 | FF 50 08 call QWORD PTR [rax+0x8] 11 | 48 8B 44 24 08 mov rax, QWORD PTR [rsp+0x8] 12 | 4C 89 E7 mov rdi, r12 13 | 0F B6 18 movzx ebx, BYTE PTR [rax] 14 | E8 -- -- -- -- call ... 15 | 48 8B 8C 24 A8 00 00 00 mov rcx, QWORD PTR [rsp+0xA8] 16 | 64 48 33 0C 25 28 00 00 00 xor rcx, QWORD PTR fs:0x28 17 | 89 D8 mov eax, ebx 18 | 0F 85 -- -- -- -- jne ... 19 | 48 81 C4 B8 00 00 00 add rsp, 0xB8 20 | 5B pop rbx 21 | 5D pop rbp 22 | 41 5C pop r12 23 | 41 5D pop r13 24 | 41 5E pop r14 25 | 41 5F pop r15 26 | C3 ret 27 | -- -- -- -- -- -- -- -- -- -- ... 28 | 48 83 7C 24 30 34 cmp QWORD PTR [rsp+0x30], 0x34 29 | 75 -- jne ... 30 | 8B B4 24 90 00 00 00 mov esi, DWORD PTR [rsp+0x90] 31 | BA 01 00 00 00 mov edx, 0x1 32 | 48 89 DF mov rdi, rbx 33 | E8 -- -- -- -- call ... 34 | 84 C0 test al, al 35 | 74 92 36 | 66 83 BC 24 A0 00 00 00 00 cmp WORD PTR [rsp+0xA0], 0x0 37 | 0F 84 -- -- -- -- je ... 38 | 45 31 F6 xor r14d, r14d 39 | 45 31 FF xor r15d, r15d 40 | 0F 1F 00 nop DWORD PTR [rax] 41 | 48 8B 03 mov rax, QWORD PTR [rbx] 42 | 4C 89 E9 mov rcx, r13 43 | BA 28 00 00 00 mov edx, 0x28 44 | 48 89 EE mov rsi, rbp 45 | 48 89 DF mov rdi, rbx 46 | FF 50 28 call QWORD PTR [rax+0x28] 47 | 84 C0 test al, al 48 | 0F 85 -- -- -- -- jne ... 49 | -------------------------------------------------------------------------------- /spec/asm/linux/offset-x86_64/32-b.asm: -------------------------------------------------------------------------------- 1 | 48 8D 74 24 68 lea rsi, [rsp+0x68] 2 | BA 40 00 00 00 mov edx, 0x40 3 | 48 89 DF mov rdi, rbx 4 | 4C 89 E9 mov rcx, r13 5 | FF 50 28 call QWORD PTR [rax+0x28] 6 | 84 C0 test al, al 7 | 75 4E jne 0x50 8 | 48 8B 03 mov rax, QWORD PTR [rbx] 9 | 48 89 DF mov rdi, rbx 10 | FF 50 08 call QWORD PTR [rax+0x8] 11 | 48 8B 44 24 08 mov rax, QWORD PTR [rsp+0x8] 12 | 4C 89 E7 mov rdi, r12 13 | 0F B6 18 movzx ebx, BYTE PTR [rax] 14 | E8 -- -- -- -- call ... 15 | 48 8B 8C 24 A8 00 00 00 mov rcx, QWORD PTR [rsp+0xA8] 16 | 64 48 33 0C 25 28 00 00 00 xor rcx, QWORD PTR fs:0x28 17 | 89 D8 mov eax, ebx 18 | 0F 85 -- -- -- -- jne ... 19 | 48 81 C4 B8 00 00 00 add rsp, 0xB8 20 | 5B pop rbx 21 | 5D pop rbp 22 | 41 5C pop r12 23 | 41 5D pop r13 24 | 41 5E pop r14 25 | 41 5F pop r15 26 | C3 ret 27 | -- -- -- -- -- -- -- -- -- -- ... 28 | 48 83 7C 24 30 40 cmp QWORD PTR [rsp+0x30], 0x40 29 | 75 -- jne ... 30 | 8B B4 24 90 00 00 00 mov esi, DWORD PTR [rsp+0x90] 31 | 41 89 F7 mov r15d, esi 32 | 0F B7 84 24 A4 00 00 00 movzx eax, WORD PTR [rsp+0xA4] 33 | C1 E0 06 shl eax, 0x6 34 | 41 01 C7 add r15d, eax 35 | 90 90 90 90 nop 4 36 | 90 90 90 90 nop 4 37 | 90 90 90 90 nop 4 38 | 90 90 90 90 nop 4 39 | 90 90 90 90 nop 4 40 | 90 90 90 90 nop 4 41 | 90 90 90 90 nop 4 42 | 90 90 90 90 nop 4 43 | 90 90 90 90 nop 4 44 | 90 90 90 90 nop 4 45 | 90 90 90 90 nop 4 46 | 90 90 90 90 nop 4 47 | 90 90 90 90 nop 4 48 | -------------------------------------------------------------------------------- /spec/asm/linux/patch-i386/ebx.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call ... 2 | 81 C3 -- -- -- -- add ebx, ... 3 | -------------------------------------------------------------------------------- /spec/asm/linux/path-i386/10.0.asm: -------------------------------------------------------------------------------- 1 | 0F 84 -- -- -- -- je ... 2 | 8D 9D E0 EF FF FF lea ebx, [ebp-0x1020] 3 | C7 44 24 04 -- -- -- -- mov DWORD PTR [esp+0x4], ... 4 | 89 1C 24 mov DWORD PTR [esp], ebx 5 | E8 -- -- -- -- call ... 6 | -------------------------------------------------------------------------------- /spec/asm/linux/path-i386/10.1.asm: -------------------------------------------------------------------------------- 1 | 0F 84 -- -- -- -- je ... 2 | 8D 45 E4 lea eax, [ebp-0x1C] 3 | 89 04 24 mov DWORD PTR [esp], eax 4 | C7 44 24 04 -- -- -- -- mov DWORD PTR [esp+0x4], ... 5 | E8 -- -- -- -- call ... 6 | 8B 55 08 mov edx, DWORD PTR [ebp+0x8] 7 | -------------------------------------------------------------------------------- /spec/asm/linux/path-i386/11.0.asm: -------------------------------------------------------------------------------- 1 | 0F 84 -- -- -- -- je ... 2 | 8D 45 E4 lea eax, [ebp-0x1C] 3 | 31 DB xor ebx, ebx 4 | 89 04 24 mov DWORD PTR [esp], eax 5 | C7 44 24 04 -- -- -- -- mov DWORD PTR [esp+0x4], ... 6 | E8 -- -- -- -- call ... 7 | -------------------------------------------------------------------------------- /spec/asm/linux/path-i386/11.2.asm: -------------------------------------------------------------------------------- 1 | 8D 83 -- -- -- -- lea eax, [ebx+...] 2 | 31 F6 xor esi, esi 3 | 89 44 24 04 mov DWORD PTR [esp+0x4], eax 4 | -------------------------------------------------------------------------------- /spec/asm/linux/path-i386/6.asm: -------------------------------------------------------------------------------- 1 | 55 push ebp 2 | 57 push edi 3 | 56 push esi 4 | 53 push ebx 5 | 83 EC 24 sub esp, 0x24 6 | BB 00 10 00 00 mov ebx, 0x1000 7 | 8B 6C 24 40 mov ebp, DWORD PTR [esp+0x40] 8 | 53 push ebx 9 | 68 -- -- -- -- push ... 10 | C6 05 -- -- -- -- 00 mov BYTE PTR ds:..., 0x0 11 | E8 -- -- -- -- call ... 12 | 89 C7 mov edi, eax 13 | 83 C4 10 add esp, 0x10 14 | 85 FF test edi, edi 15 | 0F 84 -- -- -- -- je ... 16 | 85 ED test ebp, ebp 17 | 0F 84 -- -- -- -- je ... 18 | 83 EC 08 sub esp, 0x8 19 | 53 push ebx 20 | 57 push edi 21 | E8 -- -- -- -- call ... 22 | 83 C4 10 add esp, 0x10 23 | 84 C0 test al, al 24 | 0F 84 -- -- -- -- je ... 25 | 83 EC 08 sub esp, 0x8 26 | 68 -- -- -- -- push ... 27 | 8D 5C 24 0C lea ebx, [esp+0xC] 28 | 53 push ebx 29 | E8 -- -- -- -- call ... 30 | 5A pop edx 31 | 59 pop ecx 32 | 57 push edi 33 | 53 push ebx 34 | E8 -- -- -- -- call ... 35 | 89 2C 24 mov DWORD PTR [esp], ebp 36 | E8 -- -- -- -- call ... 37 | 83 C4 10 add esp, 0x10 38 | 83 F8 05 cmp eax, 0x5 39 | 7E -- jle ... 40 | 83 EC 0C sub esp, 0xC 41 | 57 push edi 42 | -------------------------------------------------------------------------------- /spec/asm/linux/path-i386/9.asm: -------------------------------------------------------------------------------- 1 | 0F 84 -- -- -- -- je ... 2 | 8D 5D E8 lea ebx, [ebp-0x18] 3 | BE -- -- -- -- mov esi, ... 4 | 89 74 24 04 mov DWORD PTR [esp+0x4], esi 5 | 89 1C 24 mov DWORD PTR [esp], ebx 6 | E8 -- -- -- -- call ... 7 | -------------------------------------------------------------------------------- /spec/asm/linux/path-x86_64/24.asm: -------------------------------------------------------------------------------- 1 | 49 89 F4 mov r12, rsi 2 | 48 8D 35 -- -- -- -- lea rsi, [rip + -- -- -- --] 3 | -------------------------------------------------------------------------------- /spec/asm/linux/title-i386/10.1.asm: -------------------------------------------------------------------------------- 1 | C7 44 24 08 -- -- -- -- mov DWORD PTR [esp+0x8], ... 2 | C7 44 24 04 -- -- -- -- mov DWORD PTR [esp+0x4], ... 3 | 89 -- 24 mov DWORD PTR [esp], ... 4 | E8 -- -- -- -- call ... 5 | -------------------------------------------------------------------------------- /spec/asm/linux/title-i386/11.2.asm: -------------------------------------------------------------------------------- 1 | 8D 83 -- -- -- -- lea eax, [ebx+...] 2 | 89 44 24 04 mov DWORD PTR [esp+0x4], eax 3 | C7 44 24 08 -- -- -- -- mov DWORD PTR [esp+0x8], ... 4 | 89 34 24 mov DWORD PTR [esp], esi 5 | E8 -- -- -- -- call ... 6 | -------------------------------------------------------------------------------- /spec/asm/linux/title-i386/6.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call _gtk_widget_show 2 | 5F pop edi 3 | 58 pop eax 4 | FF 35 -- -- -- -- push DWORD PTR ds:... 5 | -- -- -- ... 6 | -- -- -- ... 7 | E8 -- -- -- -- call _gdk_window_set_title 8 | -------------------------------------------------------------------------------- /spec/asm/linux/title-i386/9.asm: -------------------------------------------------------------------------------- 1 | E8 -- -- -- -- call _gtk_widget_show 2 | A1 -- -- -- -- mov eax, DWORD PTR ds:.... 3 | 89 44 24 04 mov DWORD PTR [esp+0x4], eax 4 | -- -- -- -- -- -- ... 5 | -- -- -- ... 6 | 89 -- 24 mov DWORD PTR [esp], eax 7 | E8 -- -- -- -- call _gtk_window_set_title 8 | -------------------------------------------------------------------------------- /spec/asm/linux/title-x86_64/24.asm: -------------------------------------------------------------------------------- 1 | 48 8D 35 -- -- -- -- lea rsi, [rip+...] 2 | BA -- -- -- -- mov edx, ... 3 | -------------------------------------------------------------------------------- /spec/asm/mac/title-arm64/35-debug.asm: -------------------------------------------------------------------------------- 1 | FF 43 01 D1 sub sp, sp, #0x50 2 | FD 7B 04 A9 stp x29, x30, [sp, #0x40] 3 | FD 03 01 91 add x29, sp, #0x40 4 | A0 83 1F F8 stur x0, [x29, #-8] 5 | A1 03 1F F8 stur x1, [x29, #-0x10] 6 | A8 83 5F F8 ldur x8, [x29, #-8] 7 | -- -- -- -- adrp x9, #_kCFAllocatorDefault_ptr@PAGE 8 | 29 -- -- F9 ldr x9, [x9, #_kCFAllocatorDefault_ptr@PAGEOFF] 9 | 20 01 40 F9 ldr x0, [x9] 10 | A9 03 5F F8 ldur x9, [x29, #-0x10] 11 | E0 13 00 F9 str x0, [sp, #0x20] 12 | E0 03 09 AA mov x0, x9 13 | E8 0F 00 F9 str x8, [sp, #0x18] 14 | -- -- -- -- bl ... 15 | A8 03 5F F8 ldur x8, [x29, #-0x10] 16 | E0 0B 00 F9 str x0, [sp, #0x10] 17 | E0 03 08 AA mov x0, x8 18 | -- -- -- -- bl ... 19 | E8 13 40 F9 ldr x8, [sp, #0x20] 20 | E0 07 00 F9 str x0, [sp, #8] 21 | E0 03 08 AA mov x0, x8 22 | E1 0B 40 F9 ldr x1, [sp, #0x10] 23 | E2 07 40 F9 ldr x2, [sp, #8] 24 | -- -- -- -- bl _CFStringCreateWithCharacters 25 | -------------------------------------------------------------------------------- /spec/asm/mac/title-arm64/35-release.asm: -------------------------------------------------------------------------------- 1 | F4 4F BE A9 stp x20, x19, [sp, #-0x20]! 2 | FD 7B 01 A9 stp x29, x30, [sp, #0x10] 3 | FD 43 00 91 add x29, sp, #0x10 4 | F3 03 00 AA mov x19, x0 5 | -- -- -- -- adrp x8, #_kCFAllocatorDefault_ptr@PAGE 6 | 08 -- -- F9 ldr x8, [x8, #_kCFAllocatorDefault_ptr@PAGEOFF] 7 | 00 01 40 F9 ldr x0, [x8] 8 | 22 20 40 A9 ldp x2, x8, [x1] 9 | -- -- -- -- adrp x9, ... 10 | 29 -- -- 91 add x9, x9, ... 11 | 1F 01 00 F1 cmp x8, #0 12 | 21 01 88 9A csel x1, x9, x8, eq 13 | -- -- -- -- bl _CFStringCreateWithCharacters 14 | -------------------------------------------------------------------------------- /spec/asm/mac/title-i386/11.asm: -------------------------------------------------------------------------------- 1 | 55 push ebp 2 | 89 E5 mov ebp, esp 3 | 56 push esi 4 | 53 push ebx 5 | 83 EC 10 sub esp, 0x10 6 | 8B 75 08 mov esi, DWORD PTR [ebp+0x8] 7 | 8B 45 0C mov eax, DWORD PTR [ebp+0xc] 8 | 8B 18 mov ebx, DWORD PTR [eax] 9 | 8B 48 04 mov ecx, DWORD PTR [eax+0x4] 10 | 85 C9 test ecx, ecx 11 | 0F 44 0D -- -- -- -- cmove ecx, DWORD PTR ds:... 12 | 89 5C 24 08 mov DWORD PTR [esp+0x8], ebx 13 | 89 4C 24 04 mov DWORD PTR [esp+0x4], ecx 14 | 8B 15 -- -- -- -- mov edx, DWORD PTR ds:... 15 | 8B 02 mov eax, DWORD PTR [edx] 16 | 89 04 24 mov DWORD PTR [esp], eax 17 | E8 -- -- -- -- call _CFStringCreateWithCharacters 18 | -------------------------------------------------------------------------------- /spec/asm/mac/title-i386/13.asm: -------------------------------------------------------------------------------- 1 | 55 push ebp 2 | 89 E5 mov ebp, esp 3 | 57 push edi 4 | 56 push esi 5 | 83 EC 10 sub esp, 0x10 6 | E8 00 00 00 00 call 0xd 7 | 5F pop edi 8 | 8B 45 0C mov eax, DWORD PTR [ebp+0xc] 9 | 8B 08 mov ecx, DWORD PTR [eax] 10 | 8B 40 04 mov eax, DWORD PTR [eax+0x4] 11 | 89 4C 24 08 mov DWORD PTR [esp+0x8], ecx 12 | 85 C0 test eax, eax 13 | 0F 44 87 -- -- -- -- cmove eax, DWORD PTR [edi+...] 14 | 89 44 24 04 mov DWORD PTR [esp+0x4], eax 15 | 8B 87 -- -- -- -- mov eax, DWORD PTR [edi+...] 16 | 8B 00 mov eax, DWORD PTR [eax] 17 | 89 04 24 mov DWORD PTR [esp], eax 18 | E8 -- -- -- -- call _CFStringCreateWithCharacters 19 | -------------------------------------------------------------------------------- /spec/asm/mac/title-i386/23.asm: -------------------------------------------------------------------------------- 1 | 55 push ebp 2 | 89 E5 mov ebp, esp 3 | 57 push edi 4 | 56 push esi 5 | 83 EC 10 sub esp, 0x10 6 | E8 00 00 00 00 call 0xd 7 | 5F pop edi 8 | 8B 45 0C mov eax, DWORD PTR [ebp+0xc] 9 | 8B 8F -- -- -- -- mov ecx, DWORD PTR [edi+...] 10 | 8B 09 mov ecx, DWORD PTR [ecx] 11 | 8B 10 mov edx, DWORD PTR [eax] 12 | 8B 40 04 mov eax, DWORD PTR [eax+0x4] 13 | 85 C0 test eax, eax 14 | 0F 44 87 -- -- -- -- cmove eax, DWORD PTR [edi+...] 15 | 89 54 24 08 mov DWORD PTR [esp+0x8], edx 16 | 89 44 24 04 mov DWORD PTR [esp+0x4], eax 17 | 89 0C 24 mov DWORD PTR [esp], ecx 18 | E8 -- -- -- -- call _CFStringCreateWithCharacters 19 | -------------------------------------------------------------------------------- /spec/asm/mac/title-x86_64/11-1.asm: -------------------------------------------------------------------------------- 1 | 55 push rbp 2 | 48 89 E5 mov rbp, rsp 3 | 41 54 push r12 4 | 53 push rbx 5 | 49 89 FC mov r12, rdi 6 | 48 8B 16 mov rdx, QWORD PTR [rsi] 7 | 48 8B 76 08 mov rsi, QWORD PTR [rsi+0x8] 8 | 48 85 F6 test rsi, rsi 9 | 74 -- je -- 10 | 48 8B 05 -- -- -- -- mov rax, QWORD PTR [rip+...] 11 | 48 8B 38 mov rdi, QWORD PTR [rax] 12 | E8 -- -- -- -- call _CFStringCreateWithCharacters 13 | -------------------------------------------------------------------------------- /spec/asm/mac/title-x86_64/11-2.asm: -------------------------------------------------------------------------------- 1 | 48 8D 35 -- -- -- -- lea rsi, [rip+...] 2 | -------------------------------------------------------------------------------- /spec/asm/mac/title-x86_64/13.asm: -------------------------------------------------------------------------------- 1 | 55 push rbp 2 | 48 89 E5 mov rbp, rsp 3 | 41 56 push r14 4 | 53 push rbx 5 | 49 89 FE mov r14, rdi 6 | 48 8B 16 mov rdx, QWORD PTR [rsi] 7 | 48 8B 76 08 mov rsi, QWORD PTR [rsi+0x8] 8 | 48 85 F6 test rsi, rsi 9 | 48 0F 44 35 -- -- -- -- cmove rsi, QWORD PTR [rip+...] 10 | 48 8B 05 -- -- -- -- mov rax, QWORD PTR [rip+...] 11 | 48 8B 38 mov rdi, QWORD PTR [rax] 12 | E8 -- -- -- -- call _CFStringCreateWithCharacters 13 | -------------------------------------------------------------------------------- /spec/asm/mac/title-x86_64/23.asm: -------------------------------------------------------------------------------- 1 | 55 push rbp 2 | 48 89 E5 mov rbp, rsp 3 | 41 56 push r14 4 | 53 push rbx 5 | 49 89 FE mov r14, rdi 6 | 48 8B 05 -- -- -- -- mov rax, QWORD PTR [rip+...] 7 | 48 8B 38 mov rdi, QWORD PTR [rax] 8 | 48 8B 16 mov rdx, QWORD PTR [rsi] 9 | 48 8B 76 08 mov rsi, QWORD PTR [rsi+0x8] 10 | 48 85 F6 test rsi, rsi 11 | 48 0F 44 35 -- -- -- -- cmove rsi, QWORD PTR [rip+...] 12 | E8 -- -- -- -- call _CFStringCreateWithCharacters 13 | -------------------------------------------------------------------------------- /spec/asm/mac/title-x86_64/35.asm: -------------------------------------------------------------------------------- 1 | 55 push rbp 2 | 48 89 E5 mov rbp, rsp 3 | 41 56 push r14 4 | 53 push rbx 5 | 49 89 FE mov r14, rdi 6 | 48 8B 05 -- -- -- -- mov rax, QWORD PTR [rip+...] 7 | 48 8B 38 mov rdi, QWORD PTR [rax] 8 | 48 8B 16 mov rdx, QWORD PTR [rsi] 9 | 48 8B 76 08 mov rsi, QWORD PTR [rsi+0x8] 10 | 48 85 F6 test rsi, rsi 11 | 75 07 jne 0x1d 12 | 48 8D 35 -- -- -- -- lea rsi, [rip+...] 13 | E8 -- -- -- -- call _CFStringCreateWithCharacters 14 | -------------------------------------------------------------------------------- /spec/asm/windows/ood-i386/30.asm: -------------------------------------------------------------------------------- 1 | 55 push ebp 2 | 8B EC mov ebp, esp 3 | 56 push esi 4 | 8B F1 mov esi, ecx 5 | 57 push edi 6 | 8B 7D 08 mov edi, DWORD PTR [ebp+0x8] 7 | 57 push edi 8 | 8D 4E 04 lea ecx, [esi+0x4] 9 | E8 -- -- -- -- call ... 10 | 8B 06 mov eax, DWORD PTR [esi] 11 | 83 F8 0E cmp eax, 0xE 12 | 77 55 ja 0x6F 13 | FF 24 85 -- -- -- -- jmp DWORD PTR [eax*4+...] 14 | 57 push edi 15 | 8B CE mov ecx, esi 16 | E8 -- -- -- -- call ... 17 | EB 44 jmp 0x6F 18 | 57 push edi 19 | 8B CE mov ecx, esi 20 | E8 -- -- -- -- call ... 21 | EB 3A jmp 0x6F 22 | 57 push edi 23 | 8B CE mov ecx, esi 24 | E8 -- -- -- -- call ... 25 | EB 30 jmp 0x6F 26 | 57 push edi 27 | 8B CE mov ecx, esi 28 | E8 -- -- -- -- call ... 29 | EB 26 jmp 0x6F 30 | 57 push edi 31 | 8B CE mov ecx, esi 32 | E8 -- -- -- -- call ... 33 | EB 1C jmp 0x6F 34 | 57 push edi 35 | 8B CE mov ecx, esi 36 | E8 -- -- -- -- call ... 37 | EB 12 jmp 0x6F 38 | 57 push edi 39 | 8B CE mov ecx, esi 40 | E8 -- -- -- -- call ... 41 | EB 08 jmp 0x6F 42 | 57 push edi 43 | 8B CE mov ecx, esi 44 | E8 -- -- -- -- call ... 45 | 5F pop edi 46 | 5E pop esi 47 | 5D pop ebp 48 | C2 04 00 ret 0x4 49 | -------------------------------------------------------------------------------- /spec/asm/windows/ood-i386/31.asm: -------------------------------------------------------------------------------- 1 | 55 push ebp 2 | 8B EC mov ebp, esp 3 | 56 push esi 4 | 8B F1 mov esi, ecx 5 | 57 push edi 6 | 8B 7D 08 mov edi, DWORD PTR [ebp+0x8] 7 | 57 push edi 8 | 8D 4E 04 lea ecx, [esi+0x4] 9 | E8 -- -- -- -- call ... 10 | 8B 06 mov eax, DWORD PTR [esi] 11 | 48 dec eax 12 | 83 F8 07 cmp eax, 0x7 13 | 77 55 ja 0x70 14 | FF 24 85 -- -- -- -- jmp DWORD PTR [eax*4+...] 15 | 57 push edi 16 | 8B CE mov ecx, esi 17 | E8 -- -- -- -- call ... 18 | EB 44 jmp 0x70 19 | 57 push edi 20 | 8B CE mov ecx, esi 21 | E8 -- -- -- -- call ... 22 | EB 3A jmp 0x70 23 | 57 push edi 24 | 8B CE mov ecx, esi 25 | E8 -- -- -- -- call ... 26 | EB 30 jmp 0x70 27 | 57 push edi 28 | 8B CE mov ecx, esi 29 | E8 -- -- -- -- call ... 30 | EB 26 jmp 0x70 31 | 57 push edi 32 | 8B CE mov ecx, esi 33 | E8 -- -- -- -- call ... 34 | EB 1C jmp 0x70 35 | 57 push edi 36 | 8B CE mov ecx, esi 37 | E8 -- -- -- -- call ... 38 | EB 12 jmp 0x70 39 | 57 push edi 40 | 8B CE mov ecx, esi 41 | E8 -- -- -- -- call ... 42 | EB 08 jmp 0x70 43 | 57 push edi 44 | 8B CE mov ecx, esi 45 | E8 -- -- -- -- call ... 46 | 5F pop edi 47 | 5E pop esi 48 | 5D pop ebp 49 | C2 04 00 ret 0x4 50 | -------------------------------------------------------------------------------- /spec/asm/windows/ood-i386/ret4.asm: -------------------------------------------------------------------------------- 1 | C2 04 00 ret 0x4 2 | -------------------------------------------------------------------------------- /spec/asm/windows/ood-x86_64/26.asm: -------------------------------------------------------------------------------- 1 | 48 89 5C 24 08 mov QWORD PTR [rsp+0x8], rbx 2 | 57 push rdi 3 | 48 83 EC 20 sub rsp, 0x20 4 | 48 8B D9 mov rbx, rcx 5 | 48 8B FA mov rdi, rdx 6 | 48 83 C1 08 add rcx, 0x8 7 | E8 -- -- -- -- call ... 8 | 44 8B 03 mov r8d, DWORD PTR [rbx] 9 | 41 83 E8 01 sub r8d, 0x1 10 | 0F 84 85 00 00 00 je 0xAB 11 | 41 83 E8 01 sub r8d, 0x1 12 | 74 72 je 0x9E 13 | 41 83 E8 01 sub r8d, 0x1 14 | 74 5F je 0x91 15 | 41 83 E8 01 sub r8d, 0x1 16 | 74 4C je 0x84 17 | 41 83 E8 01 sub r8d, 0x1 18 | 74 39 je 0x77 19 | 41 83 E8 01 sub r8d, 0x1 20 | 74 26 je 0x6A 21 | 41 83 E8 01 sub r8d, 0x1 22 | 74 13 je 0x5D 23 | 41 83 F8 01 cmp r8d, 0x1 24 | 75 66 jne 0xB6 25 | 48 8B D7 mov rdx, rdi 26 | 48 8B CB mov rcx, rbx 27 | E8 -- -- -- -- call ... 28 | EB 59 jmp 0xB6 29 | 48 8B D7 mov rdx, rdi 30 | 48 8B CB mov rcx, rbx 31 | E8 -- -- -- -- call ... 32 | EB 4C jmp 0xB6 33 | 48 8B D7 mov rdx, rdi 34 | 48 8B CB mov rcx, rbx 35 | E8 -- -- -- -- call ... 36 | EB 3F jmp 0xB6 37 | 48 8B D7 mov rdx, rdi 38 | 48 8B CB mov rcx, rbx 39 | E8 -- -- -- -- call ... 40 | EB 32 jmp 0xB6 41 | 48 8B D7 mov rdx, rdi 42 | 48 8B CB mov rcx, rbx 43 | E8 -- -- -- -- call ... 44 | EB 25 jmp 0xB6 45 | 48 8B D7 mov rdx, rdi 46 | 48 8B CB mov rcx, rbx 47 | E8 -- -- -- -- call ... 48 | EB 18 jmp 0xB6 49 | 48 8B D7 mov rdx, rdi 50 | 48 8B CB mov rcx, rbx 51 | E8 -- -- -- -- call ... 52 | EB 0B jmp 0xB6 53 | 48 8B D7 mov rdx, rdi 54 | 48 8B CB mov rcx, rbx 55 | E8 -- -- -- -- call ... 56 | 48 8B 5C 24 30 mov rbx, QWORD PTR [rsp+0x30] 57 | 48 83 C4 20 add rsp, 0x20 58 | 5F pop rdi 59 | C3 ret 60 | -------------------------------------------------------------------------------- /spec/asm/windows/ood-x86_64/30.asm: -------------------------------------------------------------------------------- 1 | 48 89 5C 24 08 mov QWORD PTR [rsp+0x8], rbx 2 | 57 push rdi 3 | 48 83 EC 20 sub rsp, 0x20 4 | 48 8B D9 mov rbx, rcx 5 | 48 8B FA mov rdi, rdx 6 | 48 83 C1 08 add rcx, 0x8 7 | E8 -- -- -- -- call ... 8 | 44 8B 03 mov r8d, DWORD PTR [rbx] 9 | 41 83 F8 05 cmp r8d, 0x5 10 | 7F 64 jg 0x81 11 | 74 55 je 0x74 12 | 45 85 C0 test r8d, r8d 13 | 0F 84 90 00 00 00 je 0xB8 14 | 41 83 E8 01 sub r8d, 0x1 15 | 74 39 je 0x67 16 | 41 83 E8 01 sub r8d, 0x1 17 | 74 26 je 0x5A 18 | 41 83 E8 01 sub r8d, 0x1 19 | 74 13 je 0x4D 20 | 41 83 F8 01 cmp r8d, 0x1 21 | 75 78 jne 0xB8 22 | 48 8B D7 mov rdx, rdi 23 | 48 8B CB mov rcx, rbx 24 | E8 -- -- -- -- call ... 25 | EB 6B jmp 0xB3 26 | 48 8B D7 mov rdx, rdi 27 | 48 8B CB mov rcx, rbx 28 | E8 -- -- -- -- call ... 29 | EB 5E jmp 0xAE 30 | 48 8B D7 mov rdx, rdi 31 | 48 8B CB mov rcx, rbx 32 | E8 -- -- -- -- call ... 33 | EB 51 jmp 0xA9 34 | 48 8B D7 mov rdx, rdi 35 | 48 8B CB mov rcx, rbx 36 | E8 -- -- -- -- call ... 37 | EB 44 jmp 0xA4 38 | 48 8B D7 mov rdx, rdi 39 | 48 8B CB mov rcx, rbx 40 | E8 -- -- -- -- call ... 41 | EB 37 jmp 0x9F 42 | 41 83 F8 06 cmp r8d, 0x6 43 | 74 26 je 0x94 44 | 41 83 F8 07 cmp r8d, 0x7 45 | 74 13 je 0x87 46 | 41 83 F8 08 cmp r8d, 0x8 47 | 75 25 jne 0x9F 48 | 48 8B D7 mov rdx, rdi 49 | 48 8B CB mov rcx, rbx 50 | E8 -- -- -- -- call ... 51 | EB 18 jmp 0x9A 52 | 48 8B D7 mov rdx, rdi 53 | 48 8B CB mov rcx, rbx 54 | E8 -- -- -- -- call ... 55 | EB 0B jmp 0x95 56 | 48 8B D7 mov rdx, rdi 57 | 48 8B CB mov rcx, rbx 58 | E8 -- -- -- -- call ... 59 | 48 8B 5C 24 30 mov rbx, QWORD PTR [rsp+0x30] 60 | 48 83 C4 20 add rsp, 0x20 61 | 5F pop rdi 62 | C3 ret 63 | -------------------------------------------------------------------------------- /spec/asm/windows/ood-x86_64/ret.asm: -------------------------------------------------------------------------------- 1 | C3 ret 2 | -------------------------------------------------------------------------------- /spec/fixtures/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en-US 7 | CFBundleExecutable 8 | application 9 | CFBundleGetInfoString 10 | Custom Info 11 | CFBundleIconFile 12 | application.icns 13 | CFBundleIdentifier 14 | com.example.id 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleLongVersionString 18 | Version 3.14.15.92 19 | CFBundleName 20 | application 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | 3.14.15.92 25 | CFBundleSignature 26 | ???? 27 | CFBundleSupportedPlatforms 28 | 29 | MacOSX 30 | 31 | CFBundleVersion 32 | 3.14.15.92 33 | CSResourcesFileMapped 34 | 35 | LSPrefersCarbon 36 | YES 37 | NSAppTransportSecurity 38 | 39 | NSAllowsArbitraryLoads 40 | 41 | 42 | NSAppleScriptEnabled 43 | YES 44 | NSHighResolutionCapable 45 | 46 | NSHumanReadableCopyright 47 | Custom Copyright 48 | NSMainNibFile 49 | MainMenu 50 | NSPrincipalClass 51 | NSApplication 52 | 53 | 54 | -------------------------------------------------------------------------------- /spec/fixtures/PkgInfo: -------------------------------------------------------------------------------- 1 | APPL???? -------------------------------------------------------------------------------- /spec/fixtures/dummy: -------------------------------------------------------------------------------- 1 | ELF____________DUMMY_ELF_BINARY -------------------------------------------------------------------------------- /spec/fixtures/dummy.app/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en-US 7 | CFBundleExecutable 8 | main 9 | CFBundleGetInfoString 10 | Custom Info 11 | CFBundleIconFile 12 | icon.icns 13 | CFBundleIdentifier 14 | com.example.id 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleLongVersionString 18 | Version 3.14.15.92 19 | CFBundleName 20 | dummy 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | 3.14.15.92 25 | CFBundleSignature 26 | ???? 27 | CFBundleSupportedPlatforms 28 | 29 | MacOSX 30 | 31 | CFBundleVersion 32 | 3.14.15.92 33 | CSResourcesFileMapped 34 | 35 | LSPrefersCarbon 36 | YES 37 | NSAppTransportSecurity 38 | 39 | NSAllowsArbitraryLoads 40 | 41 | 42 | NSAppleScriptEnabled 43 | YES 44 | NSHighResolutionCapable 45 | 46 | NSHumanReadableCopyright 47 | Custom Copyright 48 | NSMainNibFile 49 | MainMenu 50 | NSPrincipalClass 51 | NSApplication 52 | 53 | 54 | -------------------------------------------------------------------------------- /spec/fixtures/dummy.app/Contents/MacOS/main: -------------------------------------------------------------------------------- 1 | DUMMY_MACHO 2 | -------------------------------------------------------------------------------- /spec/fixtures/dummy.app/Contents/PkgInfo: -------------------------------------------------------------------------------- 1 | APPL???? -------------------------------------------------------------------------------- /spec/fixtures/dummy.app/Contents/Resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockpkg/swf-projector/b499f081159e34753472ce28e75e17d7991939e6/spec/fixtures/dummy.app/Contents/Resources/icon.icns -------------------------------------------------------------------------------- /spec/fixtures/dummy.exe: -------------------------------------------------------------------------------- 1 | DUMMY_PE_EXE 2 | -------------------------------------------------------------------------------- /spec/fixtures/dummy.exe.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockpkg/swf-projector/b499f081159e34753472ce28e75e17d7991939e6/spec/fixtures/dummy.exe.zip -------------------------------------------------------------------------------- /spec/fixtures/icon-to-icns.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf icon.iconset 4 | mkdir icon.iconset 5 | 6 | convert -strip -resize 1024x1024 icon.png icon.iconset/icon_512x512@2x.png 7 | convert -strip -resize 512x512 icon.png icon.iconset/icon_512x512.png 8 | convert -strip -resize 512x512 icon.png icon.iconset/icon_256x256@2x.png 9 | convert -strip -resize 256x256 icon.png icon.iconset/icon_256x256.png 10 | convert -strip -resize 256x256 icon.png icon.iconset/icon_128x128@2x.png 11 | convert -strip -resize 128x128 icon.png icon.iconset/icon_128x128.png 12 | convert -strip -resize 64x64 icon.png icon.iconset/icon_32x32@2x.png 13 | convert -strip -resize 32x32 icon.png icon.iconset/icon_32x32.png 14 | convert -strip -resize 32x32 icon.png icon.iconset/icon_16x16@2x.png 15 | convert -strip -resize 16x16 icon.png icon.iconset/icon_16x16.png 16 | 17 | iconutil -c icns icon.iconset 18 | rm -rf icon.iconset 19 | -------------------------------------------------------------------------------- /spec/fixtures/icon-to-ico.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf icon.icoset 4 | mkdir icon.icoset 5 | 6 | convert icon.png -strip -resize 256x256 icon.icoset/icon_256x256.png 7 | convert icon.png -strip -resize 128x128 icon.icoset/icon_128x128.bmp 8 | convert icon.png -strip -resize 64x64 icon.icoset/icon_64x64.bmp 9 | convert icon.png -strip -resize 48x48 icon.icoset/icon_48x38.bmp 10 | convert icon.png -strip -resize 48x48 -flatten -colors 256 -type palette bmp3:icon.icoset/icon_48x38i.bmp 11 | convert icon.png -strip -resize 48x48 -flatten -colors 16 -type palette -depth 4 bmp3:icon.icoset/icon_48x38d4.bmp 12 | convert icon.png -strip -resize 32x32 icon.icoset/icon_32x32.bmp 13 | convert icon.png -strip -resize 32x32 -flatten -colors 256 -type palette bmp3:icon.icoset/icon_32x32i.bmp 14 | convert icon.png -strip -resize 32x32 -flatten -colors 16 -type palette -depth 4 bmp3:icon.icoset/icon_32x32d4.bmp 15 | convert icon.png -strip -resize 24x24 icon.icoset/icon_24x24.bmp 16 | convert icon.png -strip -resize 24x24 -flatten -colors 256 -type palette bmp3:icon.icoset/icon_24x24i.bmp 17 | convert icon.png -strip -resize 24x24 -flatten -colors 16 -type palette -depth 4 bmp3:icon.icoset/icon_24x24d4.bmp 18 | convert icon.png -strip -resize 16x16 icon.icoset/icon_16x16.bmp 19 | convert icon.png -strip -resize 16x16 -flatten -colors 256 -type palette bmp3:icon.icoset/icon_16x16i.bmp 20 | convert icon.png -strip -resize 16x16 -flatten -colors 16 -type palette -depth 4 bmp3:icon.icoset/icon_16x16d4.bmp 21 | 22 | convert icon.icoset/*.png icon.icoset/*.bmp icon.ico 23 | rm -rf icon.icoset 24 | -------------------------------------------------------------------------------- /spec/fixtures/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockpkg/swf-projector/b499f081159e34753472ce28e75e17d7991939e6/spec/fixtures/icon.icns -------------------------------------------------------------------------------- /spec/fixtures/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockpkg/swf-projector/b499f081159e34753472ce28e75e17d7991939e6/spec/fixtures/icon.ico -------------------------------------------------------------------------------- /spec/fixtures/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockpkg/swf-projector/b499f081159e34753472ce28e75e17d7991939e6/spec/fixtures/icon.png -------------------------------------------------------------------------------- /spec/fixtures/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockpkg/swf-projector/b499f081159e34753472ce28e75e17d7991939e6/spec/fixtures/image.jpg -------------------------------------------------------------------------------- /spec/fixtures/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockpkg/swf-projector/b499f081159e34753472ce28e75e17d7991939e6/spec/fixtures/image.png -------------------------------------------------------------------------------- /spec/fixtures/image.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockpkg/swf-projector/b499f081159e34753472ce28e75e17d7991939e6/spec/fixtures/image.swf -------------------------------------------------------------------------------- /spec/fixtures/swf14-lzma.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockpkg/swf-projector/b499f081159e34753472ce28e75e17d7991939e6/spec/fixtures/swf14-lzma.swf -------------------------------------------------------------------------------- /spec/fixtures/swf14-zlib.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockpkg/swf-projector/b499f081159e34753472ce28e75e17d7991939e6/spec/fixtures/swf14-zlib.swf -------------------------------------------------------------------------------- /spec/fixtures/swf14.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockpkg/swf-projector/b499f081159e34753472ce28e75e17d7991939e6/spec/fixtures/swf14.swf -------------------------------------------------------------------------------- /spec/fixtures/swf3.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockpkg/swf-projector/b499f081159e34753472ce28e75e17d7991939e6/spec/fixtures/swf3.swf -------------------------------------------------------------------------------- /spec/fixtures/swf4-loadmovie.as: -------------------------------------------------------------------------------- 1 | loadMovie("image.swf", "/"); 2 | -------------------------------------------------------------------------------- /spec/fixtures/swf4-loadmovie.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit 3 | set -o nounset 4 | set -o pipefail 5 | 6 | makeswf \ 7 | -c -1 \ 8 | -v 4 \ 9 | -s 600x400 \ 10 | -r 30 \ 11 | -b ffffff \ 12 | -o swf4-loadmovie.swf \ 13 | swf4-loadmovie.as 14 | rm swf4-loadmovie.swf.*.pp 15 | -------------------------------------------------------------------------------- /spec/fixtures/swf4-loadmovie.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockpkg/swf-projector/b499f081159e34753472ce28e75e17d7991939e6/spec/fixtures/swf4-loadmovie.swf -------------------------------------------------------------------------------- /spec/fixtures/swf6-loadmovie.as: -------------------------------------------------------------------------------- 1 | createEmptyMovieClip("img", 1); 2 | img.loadMovie("image.jpg"); 3 | 4 | createTextField("txt", 2, 0, 0, 600, 400); 5 | txt.text = _root._url; 6 | -------------------------------------------------------------------------------- /spec/fixtures/swf6-loadmovie.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit 3 | set -o nounset 4 | set -o pipefail 5 | 6 | makeswf \ 7 | -c -1 \ 8 | -v 6 \ 9 | -s 600x400 \ 10 | -r 30 \ 11 | -b ffffff \ 12 | -o swf6-loadmovie.swf \ 13 | swf6-loadmovie.as 14 | rm swf6-loadmovie.swf.*.pp 15 | -------------------------------------------------------------------------------- /spec/fixtures/swf6-loadmovie.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockpkg/swf-projector/b499f081159e34753472ce28e75e17d7991939e6/spec/fixtures/swf6-loadmovie.swf -------------------------------------------------------------------------------- /spec/fixtures/swf6-showmenu-false.as: -------------------------------------------------------------------------------- 1 | createEmptyMovieClip("mc", 1); 2 | mc.lineStyle(2, 0x00FF00, 100); 3 | mc.beginFill(0x0000FF, 100); 4 | mc.moveTo(0, 0); 5 | mc.lineTo(600, 0); 6 | mc.lineTo(600, 400); 7 | mc.lineTo(0, 400); 8 | mc.lineTo(0, 0); 9 | getURL("FSCommand:showmenu", "false"); 10 | -------------------------------------------------------------------------------- /spec/fixtures/swf6-showmenu-false.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit 3 | set -o nounset 4 | set -o pipefail 5 | 6 | makeswf \ 7 | -c -1 \ 8 | -v 6 \ 9 | -s 600x400 \ 10 | -r 30 \ 11 | -b ffffff \ 12 | -o swf6-showmenu-false.swf \ 13 | swf6-showmenu-false.as 14 | rm swf6-showmenu-false.swf.*.pp 15 | -------------------------------------------------------------------------------- /spec/fixtures/swf6-showmenu-false.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockpkg/swf-projector/b499f081159e34753472ce28e75e17d7991939e6/spec/fixtures/swf6-showmenu-false.swf -------------------------------------------------------------------------------- /spec/fixtures/swf6-zlib.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockpkg/swf-projector/b499f081159e34753472ce28e75e17d7991939e6/spec/fixtures/swf6-zlib.swf -------------------------------------------------------------------------------- /spec/fixtures/swf6.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shockpkg/swf-projector/b499f081159e34753472ce28e75e17d7991939e6/spec/fixtures/swf6.swf -------------------------------------------------------------------------------- /src/bundle/html.test.ts: -------------------------------------------------------------------------------- 1 | import {describe, it} from 'node:test'; 2 | import {ok} from 'node:assert'; 3 | import {join as pathJoin} from 'node:path'; 4 | 5 | import {cleanBundlesDir, fixtureFile} from '../util.spec.ts'; 6 | import {Bundle} from '../bundle.ts'; 7 | 8 | import {BundleHtml} from './html.ts'; 9 | 10 | const getDir = async (d: string) => cleanBundlesDir('html', d); 11 | 12 | void describe('bundle/html', () => { 13 | void describe('BundleHtml', () => { 14 | void it('instanceof', () => { 15 | ok(BundleHtml.prototype instanceof Bundle); 16 | }); 17 | 18 | void it('simple', async () => { 19 | const dir = await getDir('simple'); 20 | const dest = pathJoin(dir, 'application.html'); 21 | 22 | const b = new BundleHtml(dest); 23 | const p = b.projector; 24 | p.src = 'movie.swf'; 25 | p.width = 600; 26 | p.height = 400; 27 | await b.write(async b => { 28 | await b.copyResource('movie.swf', fixtureFile('swf3.swf')); 29 | }); 30 | }); 31 | 32 | void it('complex', async () => { 33 | const dir = await getDir('complex'); 34 | const dest = pathJoin(dir, 'application.html'); 35 | 36 | const b = new BundleHtml(dest); 37 | const p = b.projector; 38 | p.src = 'movie.swf'; 39 | p.width = 600; 40 | p.height = 400; 41 | p.lang = 'en-US'; 42 | p.title = 'A "special" title with characters'; 43 | p.background = '#000000'; 44 | p.color = '#999999'; 45 | p.bgcolor = '#000000'; 46 | p.id = 'element-id'; 47 | p.name = 'element-name'; 48 | p.codebase = 49 | 'https://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=32,0,0,0'; 50 | p.pluginspage = 'https://www.adobe.com/go/getflashplayer'; 51 | p.play = true; 52 | p.loop = true; 53 | p.menu = true; 54 | p.quality = 'high'; 55 | p.scale = 'default'; 56 | p.align = 'l'; 57 | p.salign = 'l'; 58 | p.wmode = 'opaque'; 59 | p.base = '.'; 60 | p.allowFullScreen = true; 61 | p.allowFullScreenInteractive = true; 62 | p.allowScriptAccess = 'always'; 63 | p.allowNetworking = 'all'; 64 | p.fullScreenAspectRatio = 'landscape'; 65 | p.flashvars = 'param1=value1¶m2=value2'; 66 | p.browserzoom = 'scale'; 67 | p.devicefont = false; 68 | p.swliveconnect = true; 69 | p.expressinstall = 'expressinstall.swf'; 70 | p.swfversion = 32; 71 | await b.write(async b => { 72 | await b.copyResource('movie.swf', fixtureFile('swf3.swf')); 73 | }); 74 | }); 75 | 76 | void it('flat', async () => { 77 | const dir = await getDir('flat'); 78 | const dest = pathJoin(dir, 'application.html'); 79 | 80 | const b = new BundleHtml(dest, true); 81 | const p = b.projector; 82 | p.src = 'movie.swf'; 83 | p.width = 600; 84 | p.height = 400; 85 | await b.write(async b => { 86 | await b.copyResource('movie.swf', fixtureFile('swf3.swf')); 87 | }); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /src/bundle/html.ts: -------------------------------------------------------------------------------- 1 | import {basename, dirname, join as pathJoin} from 'node:path'; 2 | import {mkdir, writeFile} from 'node:fs/promises'; 3 | 4 | import {Bundle} from '../bundle.ts'; 5 | import {ProjectorHtml} from '../projector/html.ts'; 6 | import {htmlEncode, trimExtension} from '../util.ts'; 7 | 8 | /** 9 | * BundleHtml object. 10 | */ 11 | export class BundleHtml extends Bundle { 12 | /** 13 | * ProjectorHtml instance. 14 | */ 15 | public readonly projector: ProjectorHtml; 16 | 17 | /** 18 | * BundleHtml constructor. 19 | * 20 | * @param path Output path. 21 | * @param flat Flat bundle. 22 | */ 23 | constructor(path: string, flat = false) { 24 | super(path, flat); 25 | 26 | this.projector = this._createProjector(); 27 | } 28 | 29 | /** 30 | * Main application file extension. 31 | * 32 | * @returns File extension. 33 | */ 34 | public get extension() { 35 | const a = basename(this.path).split('.'); 36 | const ext = a.length > 1 ? a.pop() : ''; 37 | if (!ext) { 38 | throw new Error('Failed to extract extension'); 39 | } 40 | return `.${ext}`; 41 | } 42 | 43 | /** 44 | * Get the nested subdirectory. 45 | * 46 | * @returns Directory name. 47 | */ 48 | public get subdir() { 49 | return trimExtension(basename(this.path), this.extension); 50 | } 51 | 52 | /** 53 | * Get the nested index. 54 | * 55 | * @returns File name. 56 | */ 57 | public get index() { 58 | return `index${this.extension}`; 59 | } 60 | 61 | /** 62 | * Get launcher HTML code. 63 | * 64 | * @returns HTML code. 65 | */ 66 | public getLauncher() { 67 | const {projector, subdir, index} = this; 68 | const {lang, title} = projector; 69 | const path = `${subdir}/${index}`; 70 | const url = path.split(/[/\\]/).map(encodeURIComponent).join('/'); 71 | const hAttr = lang === null ? '' : ` lang="${htmlEncode(lang, true)}"`; 72 | 73 | return [ 74 | '', 75 | ``, 76 | ' ', 77 | ' ', 78 | ' ', 79 | ` `, 83 | ...(title === null 84 | ? [] 85 | : [` ${htmlEncode(title)}`]), 86 | ' ', 87 | ' ', 88 | '', 89 | '' 90 | ] 91 | .map(s => s.replace(/^\s+/, s => '\t'.repeat(s.length))) 92 | .join('\n'); 93 | } 94 | 95 | /** 96 | * @inheritdoc 97 | */ 98 | protected async _close(): Promise { 99 | if (!this.flat) { 100 | await this._writeLauncher(); 101 | } 102 | await super._close(); 103 | } 104 | 105 | /** 106 | * @inheritdoc 107 | */ 108 | protected _getProjectorPathNested(): string { 109 | return pathJoin(dirname(this.path), this.subdir, this.index); 110 | } 111 | 112 | /** 113 | * Create projector instance for the bundle. 114 | * 115 | * @returns Projector instance. 116 | */ 117 | protected _createProjector() { 118 | return new ProjectorHtml(this._getProjectorPath()); 119 | } 120 | 121 | /** 122 | * Write the launcher file. 123 | */ 124 | protected async _writeLauncher() { 125 | const {path} = this; 126 | await mkdir(dirname(path), {recursive: true}); 127 | await writeFile(path, this.getLauncher()); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/bundle/index.ts: -------------------------------------------------------------------------------- 1 | export * from './html.ts'; 2 | export * from './sa.ts'; 3 | export * from './sa/index.ts'; 4 | -------------------------------------------------------------------------------- /src/bundle/sa.spec.ts: -------------------------------------------------------------------------------- 1 | import {mkdir, writeFile} from 'node:fs/promises'; 2 | import {join as pathJoin, basename, dirname} from 'node:path'; 3 | 4 | import {trimExtension} from '../util.ts'; 5 | import {ProjectorSaDummy} from '../projector/sa.spec.ts'; 6 | 7 | import {BundleSa} from './sa.ts'; 8 | 9 | export class BundleSaDummy extends BundleSa { 10 | public readonly projector: ProjectorSaDummy; 11 | 12 | constructor(path: string, flat = false) { 13 | super(path, flat); 14 | 15 | this.projector = this._createProjector(); 16 | } 17 | 18 | public get extension() { 19 | return '.exe'; 20 | } 21 | 22 | protected _getProjectorPathNested(): string { 23 | const {path, extension} = this; 24 | const directory = trimExtension(path, extension, true); 25 | if (directory === path) { 26 | throw new Error(`Output path must end with: ${extension}`); 27 | } 28 | return pathJoin(directory, basename(path)); 29 | } 30 | 31 | protected _createProjector() { 32 | return new ProjectorSaDummy(this._getProjectorPath()); 33 | } 34 | 35 | protected async _writeLauncher() { 36 | await mkdir(dirname(this.path), {recursive: true}); 37 | await writeFile(this.path, 'DUMMY_PE_LAUNCHER_EXE\n', 'utf8'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/bundle/sa.ts: -------------------------------------------------------------------------------- 1 | import {ProjectorSa} from '../projector/sa.ts'; 2 | import {Bundle} from '../bundle.ts'; 3 | 4 | /** 5 | * BundleSa object. 6 | */ 7 | export abstract class BundleSa extends Bundle { 8 | /** 9 | * ProjectorSa instance. 10 | */ 11 | public abstract readonly projector: ProjectorSa; 12 | 13 | /** 14 | * BundleSa constructor. 15 | * 16 | * @param path Output path. 17 | * @param flat Flat bundle. 18 | */ 19 | constructor(path: string, flat = false) { 20 | super(path, flat); 21 | } 22 | 23 | /** 24 | * @inheritdoc 25 | */ 26 | protected async _close(): Promise { 27 | if (!this.flat) { 28 | await this._writeLauncher(); 29 | } 30 | await super._close(); 31 | } 32 | 33 | /** 34 | * Main application file extension. 35 | * 36 | * @returns File extension. 37 | */ 38 | public abstract get extension(): string; 39 | 40 | /** 41 | * Create projector instance for the bundle. 42 | * 43 | * @returns Projector instance. 44 | */ 45 | protected abstract _createProjector(): ProjectorSa; 46 | 47 | /** 48 | * Write the launcher file. 49 | */ 50 | protected abstract _writeLauncher(): Promise; 51 | } 52 | -------------------------------------------------------------------------------- /src/bundle/sa/index.ts: -------------------------------------------------------------------------------- 1 | export * from './windows.ts'; 2 | export * from './mac.ts'; 3 | export * from './linux.ts'; 4 | -------------------------------------------------------------------------------- /src/bundle/sa/linux.test.ts: -------------------------------------------------------------------------------- 1 | import {describe, it} from 'node:test'; 2 | import {ok} from 'node:assert'; 3 | import {join as pathJoin} from 'node:path'; 4 | 5 | import {listSamples} from '../../projector/sa/linux.spec.ts'; 6 | import { 7 | cleanBundlesDir, 8 | fixtureFile, 9 | getPackageFile, 10 | simpleSwf 11 | } from '../../util.spec.ts'; 12 | import {loader} from '../../loader.ts'; 13 | import {BundleSa} from '../sa.ts'; 14 | 15 | import {BundleSaLinux} from './linux.ts'; 16 | 17 | void describe('bundle/sa/linux', () => { 18 | void describe('BundleSaLinux', () => { 19 | void it('instanceof', () => { 20 | ok(BundleSaLinux.prototype instanceof BundleSa); 21 | }); 22 | 23 | for (const pkg of listSamples()) { 24 | const getDir = async (d: string) => 25 | cleanBundlesDir('sa', 'linux', pkg.type, pkg.name, d); 26 | const getPlayer = async () => getPackageFile(pkg.name); 27 | const simple = fixtureFile(simpleSwf(pkg.zlib, pkg.lzma)); 28 | 29 | void describe(pkg.name, () => { 30 | void it('simple', async () => { 31 | const dir = await getDir('simple'); 32 | const dest = pathJoin(dir, 'application'); 33 | 34 | const b = new BundleSaLinux(dest); 35 | b.projector.patchProjectorOffset = pkg.patchProjectorOffset; 36 | b.projector.player = await getPlayer(); 37 | b.projector.movieFile = simple; 38 | await b.write(); 39 | }); 40 | 41 | if (pkg.version[0] < 6) { 42 | return; 43 | } 44 | 45 | void it('complex', async () => { 46 | const dir = await getDir('complex'); 47 | const dest = pathJoin(dir, 'application'); 48 | 49 | const b = new BundleSaLinux(dest); 50 | b.projector.patchProjectorOffset = pkg.patchProjectorOffset; 51 | b.projector.patchProjectorPath = true; 52 | b.projector.player = await getPlayer(); 53 | b.projector.movieData = loader( 54 | 6, 55 | 600, 56 | 400, 57 | 30, 58 | 0xffffff, 59 | 'main.swf', 60 | 30 / 2 61 | ); 62 | await b.write(async b => { 63 | await b.copyResource( 64 | 'main.swf', 65 | fixtureFile('swf6-loadmovie.swf') 66 | ); 67 | await b.copyResource( 68 | 'image.jpg', 69 | fixtureFile('image.jpg') 70 | ); 71 | }); 72 | }); 73 | 74 | void it('flat', async () => { 75 | const dir = await getDir('flat'); 76 | const dest = pathJoin(dir, 'application'); 77 | 78 | const b = new BundleSaLinux(dest, true); 79 | b.projector.patchProjectorOffset = pkg.patchProjectorOffset; 80 | b.projector.patchProjectorPath = true; 81 | b.projector.player = await getPlayer(); 82 | b.projector.movieData = loader( 83 | 6, 84 | 600, 85 | 400, 86 | 30, 87 | 0xffffff, 88 | 'main.swf', 89 | 30 / 2 90 | ); 91 | await b.write(async b => { 92 | await b.copyResource( 93 | 'main.swf', 94 | fixtureFile('swf6-loadmovie.swf') 95 | ); 96 | await b.copyResource( 97 | 'image.jpg', 98 | fixtureFile('image.jpg') 99 | ); 100 | }); 101 | }); 102 | }); 103 | } 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /src/bundle/sa/linux.ts: -------------------------------------------------------------------------------- 1 | import {mkdir, open, writeFile} from 'node:fs/promises'; 2 | import {join as pathJoin, basename, dirname} from 'node:path'; 3 | 4 | import {linuxLauncher} from '../../util/linux.ts'; 5 | import {EM_386, EM_X86_64} from '../../util/internal/linux/elf.ts'; 6 | import {ProjectorSaLinux} from '../../projector/sa/linux.ts'; 7 | import {BundleSa} from '../sa.ts'; 8 | 9 | /** 10 | * BundleSaLinux object. 11 | */ 12 | export class BundleSaLinux extends BundleSa { 13 | /** 14 | * ProjectorSaLinux instance. 15 | */ 16 | public readonly projector: ProjectorSaLinux; 17 | 18 | /** 19 | * BundleSaLinux constructor. 20 | * 21 | * @param path Output path for the main application. 22 | * @param flat Flat bundle. 23 | */ 24 | constructor(path: string, flat = false) { 25 | super(path, flat); 26 | 27 | this.projector = this._createProjector(); 28 | } 29 | 30 | /** 31 | * @inheritdoc 32 | */ 33 | public get extension() { 34 | return ''; 35 | } 36 | 37 | /** 38 | * @inheritdoc 39 | */ 40 | protected _getProjectorPathNested(): string { 41 | const {path} = this; 42 | return pathJoin(`${path}.data`, basename(path)); 43 | } 44 | 45 | /** 46 | * @inheritdoc 47 | */ 48 | protected _createProjector() { 49 | return new ProjectorSaLinux(this._getProjectorPath()); 50 | } 51 | 52 | /** 53 | * @inheritdoc 54 | */ 55 | protected async _writeLauncher() { 56 | const {path, projector} = this; 57 | 58 | let stat = null; 59 | const d = new Uint8Array(2); 60 | const v = new DataView(d.buffer, d.byteOffset, d.byteLength); 61 | const f = await open(projector.path, 'r'); 62 | try { 63 | stat = await f.stat(); 64 | const r = await f.read(d, 0, 2, 18); 65 | if (r.bytesRead < 2) { 66 | throw new Error('Unknown format'); 67 | } 68 | } finally { 69 | await f.close(); 70 | } 71 | 72 | const machine = v.getUint16(0, true); 73 | let launcher = null; 74 | switch (machine) { 75 | case EM_386: { 76 | launcher = await linuxLauncher('i386'); 77 | break; 78 | } 79 | case EM_X86_64: { 80 | launcher = await linuxLauncher('x86_64'); 81 | break; 82 | } 83 | default: { 84 | throw new Error(`Unknown machine type: ${machine}`); 85 | } 86 | } 87 | 88 | await mkdir(dirname(path), {recursive: true}); 89 | await writeFile(path, launcher, { 90 | mode: stat.mode 91 | }); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/bundle/sa/mac.test.ts: -------------------------------------------------------------------------------- 1 | import {describe, it} from 'node:test'; 2 | import {ok} from 'node:assert'; 3 | import {join as pathJoin} from 'node:path'; 4 | 5 | import {listSamples, customWindowTitle} from '../../projector/sa/mac.spec.ts'; 6 | import { 7 | cleanBundlesDir, 8 | fixtureFile, 9 | getPackageFile, 10 | simpleSwf 11 | } from '../../util.spec.ts'; 12 | import {loader} from '../../loader.ts'; 13 | import {BundleSa} from '../sa.ts'; 14 | 15 | import {BundleSaMac} from './mac.ts'; 16 | 17 | void describe('bundle/sa/mac', () => { 18 | void describe('BundleSaMac', () => { 19 | void it('instanceof', () => { 20 | ok(BundleSaMac.prototype instanceof BundleSa); 21 | }); 22 | 23 | for (const pkg of listSamples()) { 24 | const getDir = async (d: string) => 25 | cleanBundlesDir('sa', 'mac', pkg.name, d); 26 | const getPlayer = async () => getPackageFile(pkg.name); 27 | const simple = fixtureFile(simpleSwf(pkg.zlib, pkg.lzma)); 28 | 29 | void describe(pkg.name, () => { 30 | void it('simple', async () => { 31 | const dir = await getDir('simple'); 32 | const dest = pathJoin(dir, 'application.app'); 33 | 34 | const b = new BundleSaMac(dest); 35 | b.projector.removeCodeSignature = true; 36 | b.projector.player = await getPlayer(); 37 | b.projector.movieFile = simple; 38 | await b.write(); 39 | }); 40 | 41 | if (pkg.version[0] < 6) { 42 | return; 43 | } 44 | 45 | void it('complex', async () => { 46 | const dir = await getDir('complex'); 47 | const dest = pathJoin(dir, 'application.app'); 48 | 49 | const b = new BundleSaMac(dest); 50 | b.projector.removeCodeSignature = true; 51 | b.projector.iconFile = fixtureFile('icon.icns'); 52 | b.projector.infoPlistFile = fixtureFile('Info.plist'); 53 | b.projector.pkgInfoFile = fixtureFile('PkgInfo'); 54 | b.projector.binaryName = 'application'; 55 | b.projector.bundleName = 'App Bundle Name'; 56 | b.projector.removeInfoPlistStrings = true; 57 | if (pkg.version[0] >= 11) { 58 | b.projector.patchWindowTitle = customWindowTitle; 59 | } 60 | b.projector.player = await getPlayer(); 61 | b.projector.movieData = loader( 62 | 6, 63 | 600, 64 | 400, 65 | 30, 66 | 0xffffff, 67 | 'main.swf' 68 | ); 69 | await b.write(async b => { 70 | await b.copyResource( 71 | 'main.swf', 72 | fixtureFile('swf6-loadmovie.swf') 73 | ); 74 | await b.copyResource( 75 | 'image.jpg', 76 | fixtureFile('image.jpg') 77 | ); 78 | }); 79 | }); 80 | 81 | void it('flat', async () => { 82 | const dir = await getDir('flat'); 83 | const dest = pathJoin(dir, 'application.app'); 84 | 85 | const b = new BundleSaMac(dest, true); 86 | b.projector.removeCodeSignature = true; 87 | b.projector.player = await getPlayer(); 88 | b.projector.movieData = loader( 89 | 6, 90 | 600, 91 | 400, 92 | 30, 93 | 0xffffff, 94 | 'main.swf' 95 | ); 96 | await b.write(async b => { 97 | await b.copyResource( 98 | 'main.swf', 99 | fixtureFile('swf6-loadmovie.swf') 100 | ); 101 | await b.copyResource( 102 | 'image.jpg', 103 | fixtureFile('image.jpg') 104 | ); 105 | }); 106 | }); 107 | }); 108 | } 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /src/bundle/sa/mac.ts: -------------------------------------------------------------------------------- 1 | import {copyFile, mkdir, readFile, stat, writeFile} from 'node:fs/promises'; 2 | import {join as pathJoin, basename, dirname} from 'node:path'; 3 | 4 | import {fsLstatExists} from '@shockpkg/archive-files'; 5 | import {Plist, ValueDict, ValueString} from '@shockpkg/plist-dom'; 6 | 7 | import {trimExtension} from '../../util.ts'; 8 | import {machoTypesFile, machoAppLauncher} from '../../util/mac.ts'; 9 | import {ProjectorSaMac} from '../../projector/sa/mac.ts'; 10 | import {BundleSa} from '../sa.ts'; 11 | 12 | /** 13 | * BundleSaMac object. 14 | */ 15 | export class BundleSaMac extends BundleSa { 16 | /** 17 | * ProjectorSaMac instance. 18 | */ 19 | public readonly projector: ProjectorSaMac; 20 | 21 | /** 22 | * BundleSaMac constructor. 23 | * 24 | * @param path Output path for the main application. 25 | * @param flat Flat bundle. 26 | */ 27 | constructor(path: string, flat = false) { 28 | super(path, flat); 29 | 30 | this.projector = this._createProjector(); 31 | } 32 | 33 | /** 34 | * @inheritdoc 35 | */ 36 | public get extension() { 37 | return '.app'; 38 | } 39 | 40 | /** 41 | * Get the launcher name. 42 | * 43 | * @returns Launcher name. 44 | */ 45 | protected _getLauncherName() { 46 | return trimExtension(basename(this.path), this.extension, true); 47 | } 48 | 49 | /** 50 | * @inheritdoc 51 | */ 52 | protected _getProjectorPathNested(): string { 53 | const projName = `${this._getLauncherName()}${this.extension}`; 54 | return pathJoin(this.path, 'Contents', 'Resources', projName); 55 | } 56 | 57 | /** 58 | * @inheritdoc 59 | */ 60 | protected _createProjector() { 61 | return new ProjectorSaMac(this._getProjectorPath()); 62 | } 63 | 64 | /** 65 | * @inheritdoc 66 | */ 67 | protected async _writeLauncher() { 68 | const {path, projector} = this; 69 | 70 | // Create paths to things to create. 71 | const appContents = pathJoin(path, 'Contents'); 72 | const appMacOS = pathJoin(appContents, 'MacOS'); 73 | const appResources = pathJoin(appContents, 'Resources'); 74 | const appInfoPlist = pathJoin(appContents, 'Info.plist'); 75 | const appPkgInfo = pathJoin(appContents, 'PkgInfo'); 76 | 77 | // Read the projector Info.plist. 78 | const plist = new Plist(); 79 | plist.fromXml(await readFile(projector.infoPlistPath, 'utf8')); 80 | const dict = plist.getValue().castAs(ValueDict); 81 | 82 | // Get the binary path and read the types. 83 | const projBinaryName = dict 84 | .getValue('CFBundleExecutable') 85 | .castAs(ValueString).value; 86 | const projBinaryPath = projector.getBinaryPath(projBinaryName); 87 | const projBinaryTypes = await machoTypesFile(projBinaryPath); 88 | 89 | // Get the icon path. 90 | const projIconName = dict 91 | .getValue('CFBundleIconFile') 92 | .castAs(ValueString).value; 93 | const projIconPath = projector.getIconPath(projIconName); 94 | 95 | // Get the PkgInfo path. 96 | const projPkgInfoPath = projector.pkgInfoPath; 97 | 98 | // Create the launcher binary with the same types and mode. 99 | const launcherName = this._getLauncherName(); 100 | const launcherPath = pathJoin(appMacOS, launcherName); 101 | await mkdir(dirname(launcherPath), {recursive: true}); 102 | await writeFile(launcherPath, await machoAppLauncher(projBinaryTypes), { 103 | mode: (await stat(projBinaryPath)).mode 104 | }); 105 | 106 | // Copy the projector icon if present. 107 | const pathIcon = pathJoin(appResources, projIconName); 108 | if (await fsLstatExists(projIconPath)) { 109 | await copyFile(projIconPath, pathIcon); 110 | } 111 | 112 | // Copy PkgInfo if present. 113 | if (await fsLstatExists(projPkgInfoPath)) { 114 | await copyFile(projPkgInfoPath, appPkgInfo); 115 | } 116 | 117 | // Update the executable name in the plist for the launcher. 118 | dict.set('CFBundleExecutable', new ValueString(launcherName)); 119 | 120 | // Write the updated Info.plist. 121 | await mkdir(dirname(appInfoPlist), {recursive: true}); 122 | await writeFile(appInfoPlist, plist.toXml(), 'utf8'); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/bundle/sa/windows.test.ts: -------------------------------------------------------------------------------- 1 | import {describe, it} from 'node:test'; 2 | import {ok} from 'node:assert'; 3 | import {join as pathJoin} from 'node:path'; 4 | 5 | import { 6 | listSamples, 7 | customWindowTitle, 8 | versionStrings 9 | } from '../../projector/sa/windows.spec.ts'; 10 | import { 11 | cleanBundlesDir, 12 | fixtureFile, 13 | getPackageFile, 14 | simpleSwf 15 | } from '../../util.spec.ts'; 16 | import {loader} from '../../loader.ts'; 17 | import {BundleSa} from '../sa.ts'; 18 | 19 | import {BundleSaWindows} from './windows.ts'; 20 | 21 | void describe('bundle/sa/windows', () => { 22 | void describe('BundleSaWindows', () => { 23 | void it('instanceof', () => { 24 | ok(BundleSaWindows.prototype instanceof BundleSa); 25 | }); 26 | 27 | for (const pkg of listSamples()) { 28 | const getDir = async (d: string) => 29 | cleanBundlesDir('sa', 'windows', pkg.type, pkg.name, d); 30 | const getPlayer = async () => getPackageFile(pkg.name); 31 | const simple = fixtureFile(simpleSwf(pkg.zlib, pkg.lzma)); 32 | 33 | void describe(pkg.name, () => { 34 | void it('simple', async () => { 35 | const dir = await getDir('simple'); 36 | const dest = pathJoin(dir, 'application.exe'); 37 | 38 | const b = new BundleSaWindows(dest); 39 | b.projector.removeCodeSignature = true; 40 | b.projector.patchOutOfDateDisable = 41 | pkg.patchOutOfDateDisable; 42 | b.projector.player = await getPlayer(); 43 | b.projector.movieFile = simple; 44 | await b.write(); 45 | }); 46 | 47 | if (pkg.version[0] < 4) { 48 | return; 49 | } 50 | 51 | const swfv = pkg.version[0] < 5 ? 4 : 5; 52 | const movies = 53 | pkg.version[0] < 6 54 | ? ['swf4-loadmovie.swf', 'image.swf'] 55 | : ['swf6-loadmovie.swf', 'image.jpg']; 56 | 57 | void it('complex', async () => { 58 | const dir = await getDir('complex'); 59 | const dest = pathJoin(dir, 'application.exe'); 60 | 61 | const b = new BundleSaWindows(dest); 62 | b.projector.iconFile = fixtureFile('icon.ico'); 63 | b.projector.versionStrings = versionStrings; 64 | b.projector.patchWindowTitle = customWindowTitle; 65 | b.projector.removeCodeSignature = true; 66 | b.projector.patchOutOfDateDisable = 67 | pkg.patchOutOfDateDisable; 68 | b.projector.player = await getPlayer(); 69 | b.projector.movieData = loader( 70 | swfv, 71 | 600, 72 | 400, 73 | 30, 74 | 0xffffff, 75 | 'main.swf' 76 | ); 77 | await b.write(async b => { 78 | await b.copyResource( 79 | 'main.swf', 80 | fixtureFile(movies[0]) 81 | ); 82 | await b.copyResource(movies[1], fixtureFile(movies[1])); 83 | }); 84 | }); 85 | 86 | void it('flat', async () => { 87 | const dir = await getDir('flat'); 88 | const dest = pathJoin(dir, 'application.exe'); 89 | 90 | const b = new BundleSaWindows(dest, true); 91 | b.projector.removeCodeSignature = true; 92 | b.projector.patchOutOfDateDisable = 93 | pkg.patchOutOfDateDisable; 94 | b.projector.player = await getPlayer(); 95 | b.projector.movieData = loader( 96 | swfv, 97 | 600, 98 | 400, 99 | 30, 100 | 0xffffff, 101 | 'main.swf' 102 | ); 103 | await b.write(async b => { 104 | await b.copyResource( 105 | 'main.swf', 106 | fixtureFile(movies[0]) 107 | ); 108 | await b.copyResource(movies[1], fixtureFile(movies[1])); 109 | }); 110 | }); 111 | }); 112 | } 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /src/bundle/sa/windows.ts: -------------------------------------------------------------------------------- 1 | import {mkdir, open, readFile, writeFile} from 'node:fs/promises'; 2 | import {join as pathJoin, basename, dirname} from 'node:path'; 3 | 4 | import {trimExtension} from '../../util.ts'; 5 | import {windowsLauncher} from '../../util/windows.ts'; 6 | import {ProjectorSaWindows} from '../../projector/sa/windows.ts'; 7 | import {BundleSa} from '../sa.ts'; 8 | 9 | /** 10 | * BundleSaWindows object. 11 | */ 12 | export class BundleSaWindows extends BundleSa { 13 | /** 14 | * ProjectorSaWindows instance. 15 | */ 16 | public readonly projector: ProjectorSaWindows; 17 | 18 | /** 19 | * BundleSaWindows constructor. 20 | * 21 | * @param path Output path for the main application. 22 | * @param flat Flat bundle. 23 | */ 24 | constructor(path: string, flat = false) { 25 | super(path, flat); 26 | 27 | this.projector = this._createProjector(); 28 | } 29 | 30 | /** 31 | * @inheritdoc 32 | */ 33 | public get extension() { 34 | return '.exe'; 35 | } 36 | 37 | /** 38 | * @inheritdoc 39 | */ 40 | protected _getProjectorPathNested(): string { 41 | const {path, extension} = this; 42 | const directory = trimExtension(path, extension, true); 43 | if (directory === path) { 44 | throw new Error(`Output path must end with: ${extension}`); 45 | } 46 | return pathJoin(directory, basename(path)); 47 | } 48 | 49 | /** 50 | * @inheritdoc 51 | */ 52 | protected _createProjector() { 53 | return new ProjectorSaWindows(this._getProjectorPath()); 54 | } 55 | 56 | /** 57 | * @inheritdoc 58 | */ 59 | protected async _writeLauncher() { 60 | const {path, projector} = this; 61 | 62 | const d = new Uint8Array(4); 63 | const v = new DataView(d.buffer, d.byteOffset, d.byteLength); 64 | const f = await open(projector.path, 'r'); 65 | try { 66 | let r = await f.read(d, 0, 4, 60); 67 | if (r.bytesRead < 4) { 68 | throw new Error('Unknown format'); 69 | } 70 | r = await f.read(d, 0, 2, v.getUint32(0, true) + 4); 71 | if (r.bytesRead < 2) { 72 | throw new Error('Unknown format'); 73 | } 74 | } finally { 75 | await f.close(); 76 | } 77 | 78 | const machine = v.getUint16(0, true); 79 | // eslint-disable-next-line jsdoc/require-jsdoc 80 | const res = async () => readFile(projector.path); 81 | let launcher = null; 82 | switch (machine) { 83 | case 0x14c: { 84 | launcher = await windowsLauncher('i686', res); 85 | break; 86 | } 87 | case 0x8664: { 88 | launcher = await windowsLauncher('x86_64', res); 89 | break; 90 | } 91 | default: { 92 | throw new Error(`Unknown machine type: ${machine}`); 93 | } 94 | } 95 | 96 | await mkdir(dirname(path), {recursive: true}); 97 | await writeFile(path, launcher); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './meta.ts'; 2 | export * from './launchers.ts'; 3 | export * from './util.ts'; 4 | export * from './util/index.ts'; 5 | export * from './queue.ts'; 6 | export * from './loader.ts'; 7 | export * from './projector.ts'; 8 | export * from './projector/index.ts'; 9 | export * from './bundle.ts'; 10 | export * from './bundle/index.ts'; 11 | -------------------------------------------------------------------------------- /src/launchers.ts: -------------------------------------------------------------------------------- 1 | // This file contains compile-time defined variables. 2 | 3 | export const LAUNCHERS = '#{LAUNCHERS}' as unknown as Readonly<{ 4 | [key: string]: string; 5 | }>; 6 | -------------------------------------------------------------------------------- /src/loader.test.ts: -------------------------------------------------------------------------------- 1 | import {describe, it} from 'node:test'; 2 | import {strictEqual} from 'node:assert'; 3 | 4 | import {loader} from './loader.ts'; 5 | 6 | function hex(buffer: Uint8Array) { 7 | return [...buffer] 8 | .map(b => b.toString(16).padStart(2, '0').toUpperCase()) 9 | .join(' '); 10 | } 11 | 12 | const swf4 = [ 13 | // Magic 'SWF' 14 | '46 57 53', 15 | // Version 16 | '04', 17 | // Size 18 | '39 00 00 00', 19 | // Frame size 20 | '78 00 05 DC 00 00 0F A0 00', 21 | // Frame rate 22 | '00 1E', 23 | // Frame count 24 | '01 00', 25 | // SetBackgroundColor 26 | '43 02 33 66 99', 27 | // DoAction 28 | '19 03', 29 | // > push 'other.swf' 30 | '96 0B 00 00 6F 74 68 65 72 2E 73 77 66 00', 31 | // > push '/' 32 | '96 03 00 00 2F 00', 33 | // > loadMovie 34 | '9A 01 00 40', 35 | // > end 36 | '00', 37 | // ShowFrame 38 | '40 00', 39 | // End 40 | '00 00' 41 | ].join(' '); 42 | 43 | const swf5 = [ 44 | // Magic 'SWF' 45 | '46 57 53', 46 | // Version 47 | '05', 48 | // Size 49 | '43 00 00 00', 50 | // Frame size 51 | '78 00 05 DC 00 00 0F A0 00', 52 | // Frame rate 53 | '00 1E', 54 | // Frame count 55 | '01 00', 56 | // SetBackgroundColor 57 | '43 02 33 66 99', 58 | // DoAction 59 | '23 03', 60 | // > constants 'other.swf', '_level0' 61 | '88 14 00 02 00 6F 74 68 65 72 2E 73 77 66 00 5F 6C 65 76 65 6C 30 00', 62 | // > push c:0, c:1 63 | '96 04 00 08 00 08 01', 64 | // > loadMovie 65 | '9A 01 00 40', 66 | // > end 67 | '00', 68 | // ShowFrame 69 | '40 00', 70 | // End 71 | '00 00' 72 | ].join(' '); 73 | 74 | const swf5Complex = [ 75 | // Magic 'SWF' 76 | '46 57 53', 77 | // Version 78 | '05', 79 | // Size 80 | '47 00 00 00', 81 | // Frame size 82 | '78 00 05 DD 40 00 0F A5 00', 83 | // Frame rate 84 | '80 1E', 85 | // Frame count 86 | '03 00', 87 | // SetBackgroundColor 88 | '43 02 33 66 99', 89 | // ShowFrame 90 | '40 00', 91 | // ShowFrame 92 | '40 00', 93 | // DoAction 94 | '23 03', 95 | // > constants 'other.swf', '_level0' 96 | '88 14 00 02 00 6F 74 68 65 72 2E 73 77 66 00 5F 6C 65 76 65 6C 30 00', 97 | // > push c:0, c:1 98 | '96 04 00 08 00 08 01', 99 | // > loadMovie 100 | '9A 01 00 40', 101 | // > end 102 | '00', 103 | // ShowFrame 104 | '40 00', 105 | // End 106 | '00 00' 107 | ].join(' '); 108 | 109 | void describe('loader', () => { 110 | void describe('loader', () => { 111 | void it('swfv: 4', () => { 112 | strictEqual( 113 | hex(loader(4, 600, 400, 30, 0x336699, 'other.swf')), 114 | swf4 115 | ); 116 | }); 117 | 118 | void it('swfv: 5', () => { 119 | strictEqual( 120 | hex(loader(5, 600, 400, 30, 0x336699, 'other.swf')), 121 | swf5 122 | ); 123 | }); 124 | 125 | void it('swfv: 5 complex', () => { 126 | strictEqual( 127 | hex(loader(5, 600.5, 400.5, 30.5, 0x336699, 'other.swf', 2)), 128 | swf5Complex 129 | ); 130 | }); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /src/loader.ts: -------------------------------------------------------------------------------- 1 | import {stringEncode} from './swf/util.ts'; 2 | import {Tag} from './swf/tag.ts'; 3 | import {Swf} from './swf/swf.ts'; 4 | import {concat} from './util/internal/data.ts'; 5 | 6 | /** 7 | * Type string. 8 | * 9 | * @param str The string. 10 | * @returns Bytecode data. 11 | */ 12 | function asvm1TypeString(str: string) { 13 | return concat([new Uint8Array(1), stringEncode(str)]); 14 | } 15 | 16 | /** 17 | * Action: ConstantPoolEnd. 18 | * 19 | * @param strs Constant strings. 20 | * @returns Bytecode data. 21 | */ 22 | function asmv1ActionConstantPool(strs: string[]) { 23 | const data = concat([new Uint8Array(5), ...strs.map(stringEncode)]); 24 | const v = new DataView(data.buffer, data.byteOffset, data.byteLength); 25 | data[0] = 0x88; 26 | v.setUint16(1, data.length - 3, true); 27 | v.setUint16(3, strs.length, true); 28 | return data; 29 | } 30 | 31 | /** 32 | * Type constant 8-bit. 33 | * 34 | * @param i Constant index. 35 | * @returns Bytecode data. 36 | */ 37 | function asvm1TypeConstant8(i: number) { 38 | const data = new Uint8Array(2); 39 | data[0] = 0x08; 40 | data[1] = i; 41 | return data; 42 | } 43 | 44 | /** 45 | * Action: Push. 46 | * 47 | * @param pushed Pushed data. 48 | * @returns Bytecode data. 49 | */ 50 | function asmv1ActionPush(pushed: Uint8Array[]) { 51 | const data = concat([new Uint8Array(3), ...pushed]); 52 | const v = new DataView(data.buffer, data.byteOffset, data.byteLength); 53 | data[0] = 0x96; 54 | v.setUint16(1, data.length - 3, true); 55 | return data; 56 | } 57 | 58 | /** 59 | * Action: LoadMovie. 60 | * 61 | * @returns Bytecode data. 62 | */ 63 | function asvm1ActionLoadMovie() { 64 | const data = new Uint8Array(4); 65 | const v = new DataView(data.buffer, data.byteOffset, data.byteLength); 66 | data[0] = 0x9a; 67 | v.setUint16(1, data.length - 3, true); 68 | v.setUint8(3, 0x40); 69 | return data; 70 | } 71 | 72 | /** 73 | * Action: End. 74 | * 75 | * @returns Bytecode data. 76 | */ 77 | function asvm1ActionEnd() { 78 | return new Uint8Array(1); 79 | } 80 | 81 | /** 82 | * Bytecode in SWF4 format. 83 | * 84 | * @param url The URL to load. 85 | * @returns Bytecode data. 86 | */ 87 | function bytecodeLoadMovieSwf4(url: string) { 88 | return concat([ 89 | asmv1ActionPush([asvm1TypeString(url)]), 90 | asmv1ActionPush([asvm1TypeString('/')]), 91 | asvm1ActionLoadMovie(), 92 | asvm1ActionEnd() 93 | ]); 94 | } 95 | 96 | /** 97 | * Bytecode in SWF5 format. 98 | * 99 | * @param url The URL to load. 100 | * @returns Bytecode data. 101 | */ 102 | function bytecodeLoadMovieSwf5(url: string) { 103 | return concat([ 104 | asmv1ActionConstantPool([url, '_level0']), 105 | asmv1ActionPush([asvm1TypeConstant8(0), asvm1TypeConstant8(1)]), 106 | asvm1ActionLoadMovie(), 107 | asvm1ActionEnd() 108 | ]); 109 | } 110 | 111 | /** 112 | * Generate a loader stub SWF movie. 113 | * Optionally include a delay to give player a change to initialize first. 114 | * 115 | * @param swfv SWF format version number. 116 | * @param width Frame width. 117 | * @param height Frame height. 118 | * @param fps Frames-per-second. 119 | * @param color Background color. 120 | * @param url The URL to load. 121 | * @param delay Number of frame to delay loading. 122 | * @returns Movie data. 123 | */ 124 | export function loader( 125 | swfv: number, 126 | width: number, 127 | height: number, 128 | fps: number, 129 | color: number, 130 | url: string, 131 | delay = 0 132 | ) { 133 | if (swfv < 4) { 134 | throw new Error('SWF format version must be 4+'); 135 | } 136 | delay = delay < 0 ? 0 : delay; 137 | 138 | const swf = new Swf(); 139 | swf.version = swfv; 140 | swf.frameSize.xMax = Math.round(width * 20); 141 | swf.frameSize.yMax = Math.round(height * 20); 142 | swf.frameRate.value = fps; 143 | swf.frameCount = delay + 1; 144 | 145 | const setBackgroundColor = new Tag(); 146 | setBackgroundColor.code = 9; 147 | setBackgroundColor.data = new Uint8Array(3); 148 | // eslint-disable-next-line no-bitwise 149 | setBackgroundColor.data[2] = color & 0xff; 150 | // eslint-disable-next-line no-bitwise 151 | setBackgroundColor.data[1] = (color >> 8) & 0xff; 152 | // eslint-disable-next-line no-bitwise 153 | setBackgroundColor.data[0] = (color >> 16) & 0xff; 154 | swf.tags.push(setBackgroundColor); 155 | 156 | const showFrame = new Tag(); 157 | showFrame.code = 1; 158 | for (let i = 0; i < delay; i++) { 159 | swf.tags.push(showFrame); 160 | } 161 | 162 | const doAction = new Tag(); 163 | doAction.code = 12; 164 | doAction.data = 165 | swfv < 5 ? bytecodeLoadMovieSwf4(url) : bytecodeLoadMovieSwf5(url); 166 | swf.tags.push(doAction, showFrame); 167 | 168 | const end = new Tag(); 169 | end.code = 0; 170 | swf.tags.push(end); 171 | 172 | return swf.encode(); 173 | } 174 | -------------------------------------------------------------------------------- /src/meta.test.ts: -------------------------------------------------------------------------------- 1 | import {describe, it} from 'node:test'; 2 | import {match} from 'node:assert'; 3 | 4 | import {NAME, VERSION} from './meta.ts'; 5 | 6 | void describe('meta', () => { 7 | void it('NAME', () => { 8 | match(NAME, /^(@[\d._a-z-]+\/)?[\d._a-z-]+/); 9 | }); 10 | 11 | void it('VERSION', () => { 12 | match(VERSION, /^\d+\.\d+\.\d+/); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/meta.ts: -------------------------------------------------------------------------------- 1 | // This file contains compile-time defined variables. 2 | 3 | export const NAME: string = '#{NAME}'; 4 | 5 | export const VERSION: string = '#{VERSION}'; 6 | -------------------------------------------------------------------------------- /src/projector.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Projector object. 3 | */ 4 | export abstract class Projector { 5 | /** 6 | * Set the nobrowse option on mounted disk images. 7 | */ 8 | public nobrowse = false; 9 | 10 | /** 11 | * Output path. 12 | */ 13 | public readonly path: string; 14 | 15 | /** 16 | * Projector constructor. 17 | * 18 | * @param path Output path. 19 | */ 20 | constructor(path: string) { 21 | this.path = path; 22 | } 23 | 24 | /** 25 | * Write out the projector. 26 | */ 27 | public abstract write(): Promise; 28 | } 29 | -------------------------------------------------------------------------------- /src/projector/html.test.ts: -------------------------------------------------------------------------------- 1 | import {describe, it} from 'node:test'; 2 | import {ok} from 'node:assert'; 3 | import {copyFile} from 'node:fs/promises'; 4 | import {join as pathJoin} from 'node:path'; 5 | 6 | import {cleanProjectorDir, fixtureFile} from '../util.spec.ts'; 7 | import {Projector} from '../projector.ts'; 8 | 9 | import {ProjectorHtml} from './html.ts'; 10 | 11 | const getDir = async (d: string) => cleanProjectorDir('html', d); 12 | 13 | void describe('projector/html', () => { 14 | void describe('ProjectorHtml', () => { 15 | void it('instanceof', () => { 16 | ok(ProjectorHtml.prototype instanceof Projector); 17 | }); 18 | 19 | void it('simple', async () => { 20 | const dir = await getDir('simple'); 21 | const dest = pathJoin(dir, 'page.html'); 22 | 23 | const p = new ProjectorHtml(dest); 24 | p.src = 'movie.swf'; 25 | p.width = 600; 26 | p.height = 400; 27 | await p.write(); 28 | 29 | await copyFile(fixtureFile('swf3.swf'), pathJoin(dir, 'movie.swf')); 30 | }); 31 | 32 | void it('complex', async () => { 33 | const dir = await getDir('complex'); 34 | const dest = pathJoin(dir, 'page.html'); 35 | 36 | const p = new ProjectorHtml(dest); 37 | p.src = 'movie.swf'; 38 | p.width = 600; 39 | p.height = 400; 40 | p.lang = 'en-US'; 41 | p.title = 'A "special" title with characters'; 42 | p.background = '#000000'; 43 | p.color = '#999999'; 44 | p.bgcolor = '#000000'; 45 | p.id = 'element-id'; 46 | p.name = 'element-name'; 47 | p.codebase = 48 | 'https://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=32,0,0,0'; 49 | p.pluginspage = 'https://www.adobe.com/go/getflashplayer'; 50 | p.play = true; 51 | p.loop = true; 52 | p.menu = true; 53 | p.quality = 'high'; 54 | p.scale = 'default'; 55 | p.align = 'l'; 56 | p.salign = 'l'; 57 | p.wmode = 'opaque'; 58 | p.base = '.'; 59 | p.allowFullScreen = true; 60 | p.allowFullScreenInteractive = true; 61 | p.allowScriptAccess = 'always'; 62 | p.allowNetworking = 'all'; 63 | p.fullScreenAspectRatio = 'landscape'; 64 | p.flashvars = 'param1=value1¶m2=value2'; 65 | p.browserzoom = 'scale'; 66 | p.devicefont = false; 67 | p.swliveconnect = true; 68 | p.expressinstall = 'expressinstall.swf'; 69 | p.swfversion = 32; 70 | await p.write(); 71 | 72 | await copyFile(fixtureFile('swf3.swf'), pathJoin(dir, 'movie.swf')); 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /src/projector/index.ts: -------------------------------------------------------------------------------- 1 | export * from './html.ts'; 2 | export * from './sa.ts'; 3 | export * from './sa/index.ts'; 4 | -------------------------------------------------------------------------------- /src/projector/sa.spec.ts: -------------------------------------------------------------------------------- 1 | import {mkdir, readFile, writeFile} from 'node:fs/promises'; 2 | import {dirname} from 'node:path'; 3 | 4 | import {concat} from '../util/internal/data.ts'; 5 | 6 | import {ProjectorSa} from './sa.ts'; 7 | 8 | export class ProjectorSaDummy extends ProjectorSa { 9 | constructor(path: string) { 10 | super(path); 11 | } 12 | 13 | public get extension(): string { 14 | return '.exe'; 15 | } 16 | 17 | protected async _writePlayer(player: string) { 18 | let data: Uint8Array = await readFile(player); 19 | const movieData = await this.getMovieData(); 20 | if (movieData) { 21 | data = concat([data, this._encodeMovieData(movieData, 'dms')]); 22 | } 23 | await mkdir(dirname(this.path), {recursive: true}); 24 | await writeFile(this.path, data); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/projector/sa.test.ts: -------------------------------------------------------------------------------- 1 | import {describe, it} from 'node:test'; 2 | import {join as pathJoin} from 'node:path'; 3 | 4 | import {cleanProjectorDir, fixtureFile} from '../util.spec.ts'; 5 | 6 | import {ProjectorSaDummy} from './sa.spec.ts'; 7 | 8 | const getDir = async (d: string) => cleanProjectorDir('sa', 'dummy', d); 9 | 10 | void describe('projector/sa', () => { 11 | void describe('ProjectorSaDummy', () => { 12 | void it('simple', async () => { 13 | const dir = await getDir('simple'); 14 | const dest = pathJoin(dir, 'application.exe'); 15 | 16 | const p = new ProjectorSaDummy(dest); 17 | p.player = fixtureFile('dummy.exe'); 18 | p.movieFile = fixtureFile('swf3.swf'); 19 | await p.write(); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/projector/sa/index.ts: -------------------------------------------------------------------------------- 1 | export * from './windows.ts'; 2 | export * from './mac.ts'; 3 | export * from './linux.ts'; 4 | -------------------------------------------------------------------------------- /src/projector/sa/linux.spec.ts: -------------------------------------------------------------------------------- 1 | import {shouldTest, getInstalledPackagesInfoSync} from '../../util.spec.ts'; 2 | 3 | export function listSamples() { 4 | const platforms = new Set(); 5 | if (shouldTest('linux-i386')) { 6 | platforms.add('linux'); 7 | platforms.add('linux-i386'); 8 | } 9 | if (shouldTest('linux-x86_64')) { 10 | platforms.add('linux-x86_64'); 11 | } 12 | return getInstalledPackagesInfoSync() 13 | .filter(o => platforms.has(o.platform)) 14 | .map(o => ({ 15 | ...o, 16 | type: o.platform === 'linux-x86_64' ? 'x86_64' : 'i386', 17 | patchProjectorOffset: o.platform === 'linux-x86_64' 18 | })); 19 | } 20 | 21 | export const customWindowTitle = 22 | 'Custom Window Title (Longer Than The Original Window Title Was)'; 23 | -------------------------------------------------------------------------------- /src/projector/sa/linux.test.ts: -------------------------------------------------------------------------------- 1 | import {describe, it} from 'node:test'; 2 | import {ok} from 'node:assert'; 3 | import {copyFile} from 'node:fs/promises'; 4 | import {join as pathJoin} from 'node:path'; 5 | 6 | import { 7 | cleanProjectorDir, 8 | fixtureFile, 9 | getPackageFile, 10 | simpleSwf, 11 | testShowMenu 12 | } from '../../util.spec.ts'; 13 | import {ProjectorSa} from '../sa.ts'; 14 | 15 | import {ProjectorSaLinux} from './linux.ts'; 16 | import {customWindowTitle, listSamples} from './linux.spec.ts'; 17 | 18 | void describe('projector/sa/linux', () => { 19 | void describe('ProjectorSaLinux', () => { 20 | void it('instanceof', () => { 21 | ok(ProjectorSaLinux.prototype instanceof ProjectorSa); 22 | }); 23 | 24 | void describe('dummy', () => { 25 | // eslint-disable-next-line unicorn/consistent-function-scoping 26 | const getDir = async (d: string) => 27 | cleanProjectorDir('sa', 'linux', 'dummy', d); 28 | 29 | void it('simple', async () => { 30 | const dir = await getDir('simple'); 31 | const dest = pathJoin(dir, 'application'); 32 | 33 | const p = new ProjectorSaLinux(dest); 34 | p.player = fixtureFile('dummy'); 35 | p.movieFile = fixtureFile('swf3.swf'); 36 | await p.write(); 37 | }); 38 | }); 39 | 40 | for (const pkg of listSamples()) { 41 | const getDir = async (d: string) => 42 | cleanProjectorDir('sa', 'linux', pkg.type, pkg.name, d); 43 | const getPlayer = async () => getPackageFile(pkg.name); 44 | const simple = fixtureFile(simpleSwf(pkg.zlib, pkg.lzma)); 45 | 46 | void describe(pkg.name, () => { 47 | void it('simple', async () => { 48 | const dir = await getDir('simple'); 49 | const dest = pathJoin(dir, 'application'); 50 | 51 | const p = new ProjectorSaLinux(dest); 52 | p.patchProjectorOffset = pkg.patchProjectorOffset; 53 | p.player = await getPlayer(); 54 | p.movieFile = simple; 55 | await p.write(); 56 | }); 57 | 58 | void it('complex', async () => { 59 | const dir = await getDir('complex'); 60 | const dest = pathJoin(dir, 'application'); 61 | 62 | const p = new ProjectorSaLinux(dest); 63 | p.patchProjectorOffset = pkg.patchProjectorOffset; 64 | p.patchProjectorPath = true; 65 | p.patchWindowTitle = customWindowTitle; 66 | p.patchMenuRemove = true; 67 | p.player = await getPlayer(); 68 | 69 | if (pkg.version[0] < 6) { 70 | p.movieFile = fixtureFile('swf3.swf'); 71 | await p.write(); 72 | } else { 73 | p.movieFile = fixtureFile('swf6-loadmovie.swf'); 74 | await p.write(); 75 | await copyFile( 76 | fixtureFile('image.jpg'), 77 | pathJoin(dir, 'image.jpg') 78 | ); 79 | } 80 | }); 81 | 82 | if (pkg.version[0] >= 6 && testShowMenu) { 83 | void it('showmenu-false', async () => { 84 | const dir = await getDir('showmenu-false'); 85 | const dest = pathJoin(dir, 'application'); 86 | 87 | const p = new ProjectorSaLinux(dest); 88 | p.patchProjectorOffset = pkg.patchProjectorOffset; 89 | p.player = await getPlayer(); 90 | p.movieFile = fixtureFile('swf6-showmenu-false.swf'); 91 | await p.write(); 92 | }); 93 | } 94 | }); 95 | } 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /src/projector/sa/mac.spec.ts: -------------------------------------------------------------------------------- 1 | import {shouldTest, getInstalledPackagesInfoSync} from '../../util.spec.ts'; 2 | 3 | export function listSamples() { 4 | if (!shouldTest('mac')) { 5 | return []; 6 | } 7 | return getInstalledPackagesInfoSync().filter(o => 8 | o.platform.startsWith('mac') 9 | ); 10 | } 11 | 12 | export const customWindowTitle = 13 | 'Custom Window Title (Longer Than Possible Existing Unused Strings)'; 14 | -------------------------------------------------------------------------------- /src/projector/sa/mac.test.ts: -------------------------------------------------------------------------------- 1 | import {describe, it} from 'node:test'; 2 | import {ok} from 'node:assert'; 3 | import {copyFile} from 'node:fs/promises'; 4 | import {join as pathJoin} from 'node:path'; 5 | 6 | import { 7 | cleanProjectorDir, 8 | fixtureFile, 9 | getPackageFile, 10 | simpleSwf, 11 | testShowMenu 12 | } from '../../util.spec.ts'; 13 | import {ProjectorSa} from '../sa.ts'; 14 | 15 | import {ProjectorSaMac} from './mac.ts'; 16 | import {customWindowTitle, listSamples} from './mac.spec.ts'; 17 | 18 | void describe('projector/sa/mac', () => { 19 | void describe('ProjectorSaMac', () => { 20 | void it('instanceof', () => { 21 | ok(ProjectorSaMac.prototype instanceof ProjectorSa); 22 | }); 23 | 24 | void describe('dummy', () => { 25 | // eslint-disable-next-line unicorn/consistent-function-scoping 26 | const getDir = async (d: string) => 27 | cleanProjectorDir('sa', 'mac', 'dummy', d); 28 | 29 | void it('simple', async () => { 30 | const dir = await getDir('simple'); 31 | const dest = pathJoin(dir, 'application.app'); 32 | 33 | const p = new ProjectorSaMac(dest); 34 | p.player = fixtureFile('dummy.app'); 35 | p.movieFile = fixtureFile('swf3.swf'); 36 | await p.write(); 37 | }); 38 | }); 39 | 40 | for (const pkg of listSamples()) { 41 | const getDir = async (d: string) => 42 | cleanProjectorDir('sa', 'mac', pkg.name, d); 43 | const getPlayer = async () => getPackageFile(pkg.name); 44 | const simple = fixtureFile(simpleSwf(pkg.zlib, pkg.lzma)); 45 | 46 | void describe(pkg.name, () => { 47 | void it('simple', async () => { 48 | const dir = await getDir('simple'); 49 | const dest = pathJoin(dir, 'application.app'); 50 | 51 | const p = new ProjectorSaMac(dest); 52 | p.removeCodeSignature = true; 53 | p.player = await getPlayer(); 54 | p.movieFile = simple; 55 | await p.write(); 56 | }); 57 | 58 | void it('removeFileAssociations', async () => { 59 | const dir = await getDir('removeFileAssociations'); 60 | const dest = pathJoin(dir, 'application.app'); 61 | 62 | const p = new ProjectorSaMac(dest); 63 | p.removeFileAssociations = true; 64 | p.removeCodeSignature = true; 65 | p.player = await getPlayer(); 66 | p.movieFile = fixtureFile('swf3.swf'); 67 | await p.write(); 68 | }); 69 | 70 | void it('complex', async () => { 71 | const dir = await getDir('complex'); 72 | const dest = pathJoin(dir, 'application.app'); 73 | 74 | const p = new ProjectorSaMac(dest); 75 | p.iconFile = fixtureFile('icon.icns'); 76 | p.infoPlistFile = fixtureFile('Info.plist'); 77 | p.pkgInfoFile = fixtureFile('PkgInfo'); 78 | p.binaryName = 'application'; 79 | p.bundleName = 'App Bundle Name'; 80 | p.removeInfoPlistStrings = true; 81 | p.removeCodeSignature = true; 82 | if (pkg.version[0] >= 11) { 83 | p.patchWindowTitle = customWindowTitle; 84 | } 85 | p.player = await getPlayer(); 86 | if (pkg.version[0] < 6) { 87 | p.movieFile = fixtureFile('swf3.swf'); 88 | await p.write(); 89 | } else { 90 | p.movieFile = fixtureFile('swf6-loadmovie.swf'); 91 | await p.write(); 92 | await copyFile( 93 | fixtureFile('image.jpg'), 94 | pathJoin(dir, 'image.jpg') 95 | ); 96 | } 97 | }); 98 | 99 | if (pkg.version[0] >= 6 && testShowMenu) { 100 | void it('showmenu-false', async () => { 101 | const dir = await getDir('showmenu-false'); 102 | const dest = pathJoin(dir, 'application.app'); 103 | 104 | const p = new ProjectorSaMac(dest); 105 | p.player = await getPlayer(); 106 | p.movieFile = fixtureFile('swf6-showmenu-false.swf'); 107 | await p.write(); 108 | }); 109 | } 110 | }); 111 | } 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /src/projector/sa/windows.spec.ts: -------------------------------------------------------------------------------- 1 | import {shouldTest, getInstalledPackagesInfoSync} from '../../util.spec.ts'; 2 | 3 | export function listSamples() { 4 | const platforms = new Set(); 5 | if (shouldTest('windows-i386')) { 6 | platforms.add('windows'); 7 | platforms.add('windows-32bit'); 8 | platforms.add('windows-i386'); 9 | } 10 | if (shouldTest('windows-x86_64')) { 11 | platforms.add('windows-x86_64'); 12 | } 13 | return getInstalledPackagesInfoSync() 14 | .filter(o => platforms.has(o.platform)) 15 | .map(o => ({ 16 | ...o, 17 | type: o.platform === 'windows-x86_64' ? 'x86_64' : 'i386', 18 | patchOutOfDateDisable: o.version[0] >= 30 19 | })); 20 | } 21 | 22 | export const customWindowTitle = 23 | 'Custom Window Title (Longer Than The Original Window Title Was)'; 24 | 25 | export const versionStrings = { 26 | FileVersion: '3.14.15.92', 27 | ProductVersion: '3.1.4.1', 28 | CompanyName: 'Custom Company Name', 29 | FileDescription: 'Custom File Description', 30 | LegalCopyright: 'Custom Legal Copyright', 31 | ProductName: 'Custom Product Name', 32 | LegalTrademarks: 'Custom Legal Trademarks', 33 | OriginalFilename: 'CustomOriginalFilename.exe', 34 | InternalName: 'CustomInternalName', 35 | Comments: 'Custom Comments' 36 | }; 37 | -------------------------------------------------------------------------------- /src/projector/sa/windows.test.ts: -------------------------------------------------------------------------------- 1 | import {describe, it} from 'node:test'; 2 | import {ok} from 'node:assert'; 3 | import {copyFile} from 'node:fs/promises'; 4 | import {join as pathJoin} from 'node:path'; 5 | 6 | import { 7 | cleanProjectorDir, 8 | fixtureFile, 9 | getPackageFile, 10 | simpleSwf, 11 | testShowMenu 12 | } from '../../util.spec.ts'; 13 | import {ProjectorSa} from '../sa.ts'; 14 | 15 | import {ProjectorSaWindows} from './windows.ts'; 16 | import { 17 | customWindowTitle, 18 | listSamples, 19 | versionStrings 20 | } from './windows.spec.ts'; 21 | 22 | void describe('projector/sa/windows', () => { 23 | void describe('ProjectorSaWindows', () => { 24 | void it('instanceof', () => { 25 | ok(ProjectorSaWindows.prototype instanceof ProjectorSa); 26 | }); 27 | 28 | void describe('dummy', () => { 29 | // eslint-disable-next-line unicorn/consistent-function-scoping 30 | const getDir = async (d: string) => 31 | cleanProjectorDir('sa', 'windows', 'dummy', d); 32 | 33 | void it('simple', async () => { 34 | const dir = await getDir('simple'); 35 | const dest = pathJoin(dir, 'application.exe'); 36 | 37 | const p = new ProjectorSaWindows(dest); 38 | p.player = fixtureFile('dummy.exe'); 39 | p.movieFile = fixtureFile('swf3.swf'); 40 | await p.write(); 41 | }); 42 | 43 | void it('archived', async () => { 44 | const dir = await getDir('archived'); 45 | const dest = pathJoin(dir, 'application.exe'); 46 | 47 | const p = new ProjectorSaWindows(dest); 48 | p.player = fixtureFile('dummy.exe.zip'); 49 | p.movieFile = fixtureFile('swf3.swf'); 50 | await p.write(); 51 | }); 52 | }); 53 | 54 | for (const pkg of listSamples()) { 55 | const getDir = async (d: string) => 56 | cleanProjectorDir('sa', 'windows', pkg.type, pkg.name, d); 57 | const getPlayer = async () => getPackageFile(pkg.name); 58 | const simple = fixtureFile(simpleSwf(pkg.zlib, pkg.lzma)); 59 | 60 | void describe(pkg.name, () => { 61 | void it('simple', async () => { 62 | const dir = await getDir('simple'); 63 | const dest = pathJoin(dir, 'application.exe'); 64 | 65 | const p = new ProjectorSaWindows(dest); 66 | p.removeCodeSignature = true; 67 | p.patchOutOfDateDisable = pkg.patchOutOfDateDisable; 68 | p.player = await getPlayer(); 69 | p.movieFile = simple; 70 | await p.write(); 71 | }); 72 | 73 | void it('complex', async () => { 74 | const dir = await getDir('complex'); 75 | const dest = pathJoin(dir, 'application.exe'); 76 | 77 | const p = new ProjectorSaWindows(dest); 78 | p.iconFile = fixtureFile('icon.ico'); 79 | p.versionStrings = versionStrings; 80 | p.removeCodeSignature = true; 81 | p.patchWindowTitle = customWindowTitle; 82 | p.patchOutOfDateDisable = pkg.patchOutOfDateDisable; 83 | p.player = await getPlayer(); 84 | 85 | if (pkg.version[0] < 6) { 86 | p.movieFile = fixtureFile('swf3.swf'); 87 | await p.write(); 88 | } else { 89 | p.movieFile = fixtureFile('swf6-loadmovie.swf'); 90 | await p.write(); 91 | await copyFile( 92 | fixtureFile('image.jpg'), 93 | pathJoin(dir, 'image.jpg') 94 | ); 95 | } 96 | }); 97 | 98 | if (pkg.version[0] >= 6 && testShowMenu) { 99 | void it('showmenu-false', async () => { 100 | const dir = await getDir('showmenu-false'); 101 | const dest = pathJoin(dir, 'application.exe'); 102 | 103 | const p = new ProjectorSaWindows(dest); 104 | p.removeCodeSignature = true; 105 | p.patchOutOfDateDisable = pkg.patchOutOfDateDisable; 106 | p.player = await getPlayer(); 107 | p.movieFile = fixtureFile('swf6-showmenu-false.swf'); 108 | await p.write(); 109 | }); 110 | } 111 | }); 112 | } 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /src/queue.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable unicorn/no-array-push-push */ 2 | import {describe, it} from 'node:test'; 3 | import {strictEqual} from 'node:assert'; 4 | 5 | import {Queue} from './queue.ts'; 6 | 7 | void describe('queue', () => { 8 | void describe('Queue', () => { 9 | void it('priority: 0', async () => { 10 | const order: string[] = []; 11 | const q = new Queue(); 12 | 13 | q.push(async () => { 14 | order.push('a'); 15 | }); 16 | q.push(async () => { 17 | order.push('b'); 18 | }); 19 | q.push(async () => { 20 | order.push('c'); 21 | }); 22 | q.push(async () => { 23 | order.push('d'); 24 | }); 25 | q.push(async () => { 26 | order.push('e'); 27 | }); 28 | q.push(async () => { 29 | order.push('f'); 30 | }); 31 | 32 | await q.run(); 33 | 34 | strictEqual(order.join(','), 'a,b,c,d,e,f'); 35 | }); 36 | 37 | void it('priority: incremental', async () => { 38 | const order: string[] = []; 39 | const q = new Queue(); 40 | 41 | q.push(async () => { 42 | order.push('f'); 43 | }, 1); 44 | q.push(async () => { 45 | order.push('e'); 46 | }, 2); 47 | q.push(async () => { 48 | order.push('d'); 49 | }, 3); 50 | q.push(async () => { 51 | order.push('c'); 52 | }, 4); 53 | q.push(async () => { 54 | order.push('b'); 55 | }, 5); 56 | q.push(async () => { 57 | order.push('a'); 58 | }, 6); 59 | 60 | await q.run(); 61 | 62 | strictEqual(order.join(','), 'a,b,c,d,e,f'); 63 | }); 64 | 65 | void it('priority: decremental', async () => { 66 | const order: string[] = []; 67 | const q = new Queue(); 68 | 69 | q.push(async () => { 70 | order.push('a'); 71 | }, 6); 72 | q.push(async () => { 73 | order.push('b'); 74 | }, 5); 75 | q.push(async () => { 76 | order.push('c'); 77 | }, 4); 78 | q.push(async () => { 79 | order.push('d'); 80 | }, 3); 81 | q.push(async () => { 82 | order.push('e'); 83 | }, 2); 84 | q.push(async () => { 85 | order.push('f'); 86 | }, 1); 87 | 88 | await q.run(); 89 | 90 | strictEqual(order.join(','), 'a,b,c,d,e,f'); 91 | }); 92 | 93 | void it('priority: mixed', async () => { 94 | const order: string[] = []; 95 | const q = new Queue(); 96 | 97 | q.push(async () => { 98 | order.push('d'); 99 | }, 0); 100 | q.push(async () => { 101 | order.push('a'); 102 | }, 2); 103 | q.push(async () => { 104 | order.push('f'); 105 | }, -1); 106 | q.push(async () => { 107 | order.push('b'); 108 | }, 2); 109 | q.push(async () => { 110 | order.push('c'); 111 | }, 1); 112 | q.push(async () => { 113 | order.push('e'); 114 | }, 0); 115 | 116 | await q.run(); 117 | 118 | strictEqual(order.join(','), 'a,b,c,d,e,f'); 119 | }); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /src/queue.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple queue with optional priority. 3 | */ 4 | export class Queue { 5 | /** 6 | * Queue data. 7 | */ 8 | private _queue_: { 9 | priority: number; 10 | handler: () => Promise; 11 | }[] = []; 12 | 13 | /** 14 | * Queue constructor. 15 | */ 16 | constructor() {} 17 | 18 | /** 19 | * Get size of queue. 20 | * 21 | * @returns Total callbacks in queue. 22 | */ 23 | public get size() { 24 | return this._queue_.length; 25 | } 26 | 27 | /** 28 | * Clear queue. 29 | */ 30 | public clear() { 31 | this._queue_ = []; 32 | } 33 | 34 | /** 35 | * Enqueue callback. 36 | * 37 | * @param handler Callback function. 38 | * @param priority Callback priority. 39 | */ 40 | public push(handler: () => Promise, priority = 0) { 41 | const queue = this._queue_; 42 | let index = 0; 43 | for (let i = queue.length; i--; ) { 44 | if (queue[i].priority < priority) { 45 | index = i + 1; 46 | break; 47 | } 48 | } 49 | queue.splice(index, 0, { 50 | priority, 51 | handler 52 | }); 53 | } 54 | 55 | /** 56 | * Pop callback off queue. 57 | * 58 | * @returns Callback function or null if empty. 59 | */ 60 | public pop() { 61 | const entry = this._queue_.pop(); 62 | return entry ? entry.handler : null; 63 | } 64 | 65 | /** 66 | * Shift callback off queue. 67 | * 68 | * @returns Callback function or null if empty. 69 | */ 70 | public shift() { 71 | const entry = this._queue_.shift(); 72 | return entry ? entry.handler : null; 73 | } 74 | 75 | /** 76 | * Run queue. 77 | */ 78 | public async run() { 79 | for (;;) { 80 | const entry = this.pop(); 81 | if (!entry) { 82 | break; 83 | } 84 | 85 | // eslint-disable-next-line no-await-in-loop 86 | await entry(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/swf/data.ts: -------------------------------------------------------------------------------- 1 | import {subview} from './util.ts'; 2 | 3 | /** 4 | * Data object. 5 | */ 6 | export abstract class Data { 7 | /** 8 | * Data constructor. 9 | */ 10 | constructor() {} 11 | 12 | /** 13 | * Encode size. 14 | * 15 | * @returns The size. 16 | */ 17 | public abstract get size(): number; 18 | 19 | /** 20 | * Encode into data. 21 | * 22 | * @param data Data encode target. 23 | */ 24 | public abstract encoder(data: Uint8Array): void; 25 | 26 | /** 27 | * Encode into data at offset, or new data. 28 | * 29 | * @param data The buffer to use. 30 | * @param offset Offset in the buffer. 31 | * @returns Encoded data. 32 | */ 33 | public encode(data: Uint8Array | null = null, offset = 0) { 34 | const {size} = this; 35 | data = data ? subview(data, offset, size) : new Uint8Array(size); 36 | this.encoder(data); 37 | return data; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/swf/fixed8.ts: -------------------------------------------------------------------------------- 1 | import {Data} from './data.ts'; 2 | 3 | /** 4 | * Fixed8 object. 5 | */ 6 | export class Fixed8 extends Data { 7 | /** 8 | * Float numerator. 9 | */ 10 | public numerator: number = 0; 11 | 12 | /** 13 | * Float denominator. 14 | */ 15 | public denominator: number = 0; 16 | 17 | /** 18 | * Fixed8 constructor. 19 | */ 20 | constructor() { 21 | super(); 22 | } 23 | 24 | /** 25 | * @inheritdoc 26 | */ 27 | public get size() { 28 | return 2; 29 | } 30 | 31 | /** 32 | * Value. 33 | * 34 | * @param value The value. 35 | */ 36 | public set value(value) { 37 | const i16 = Math.floor(value * 256); 38 | // eslint-disable-next-line no-bitwise 39 | this.numerator = (i16 >> 8) & 0xff; 40 | // eslint-disable-next-line no-bitwise 41 | this.denominator = i16 & 0xff; 42 | } 43 | 44 | /** 45 | * Value. 46 | * 47 | * @returns The value. 48 | */ 49 | public get value() { 50 | // eslint-disable-next-line no-bitwise 51 | const i16 = (this.numerator << 8) | this.denominator; 52 | return i16 / 256; 53 | } 54 | 55 | /** 56 | * @inheritdoc 57 | */ 58 | public encoder(data: Uint8Array) { 59 | data[0] = this.denominator; 60 | data[1] = this.numerator; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/swf/rect.ts: -------------------------------------------------------------------------------- 1 | import {Data} from './data.ts'; 2 | import {bitCountS, bitCountToBytes, bitWriter} from './util.ts'; 3 | 4 | /** 5 | * Rect object. 6 | */ 7 | export class Rect extends Data { 8 | /** 9 | * Minimum X. 10 | */ 11 | public xMin: number = 0; 12 | 13 | /** 14 | * Maximum X. 15 | */ 16 | public xMax: number = 0; 17 | 18 | /** 19 | * Minimum Y. 20 | */ 21 | public yMin: number = 0; 22 | 23 | /** 24 | * Maximum Y. 25 | */ 26 | public yMax: number = 0; 27 | 28 | /** 29 | * Rect constructor. 30 | */ 31 | constructor() { 32 | super(); 33 | } 34 | 35 | /** 36 | * Number of bits need to encode the values. 37 | * 38 | * @returns Bit count. 39 | */ 40 | public get nBits() { 41 | return Math.max( 42 | bitCountS(this.xMin), 43 | bitCountS(this.xMax), 44 | bitCountS(this.yMin), 45 | bitCountS(this.yMax) 46 | ); 47 | } 48 | 49 | /** 50 | * @inheritdoc 51 | */ 52 | public get size() { 53 | return bitCountToBytes(5 + this.nBits * 4); 54 | } 55 | 56 | /** 57 | * @inheritdoc 58 | */ 59 | public encoder(data: Uint8Array) { 60 | const {nBits} = this; 61 | const bW = bitWriter(data, 0); 62 | let b = 0; 63 | bW(nBits, 5, b); 64 | b += 5; 65 | for (const value of [this.xMin, this.xMax, this.yMin, this.yMax]) { 66 | bW(value, nBits, b); 67 | b += nBits; 68 | } 69 | const over = b % 8; 70 | if (over) { 71 | bW(0, 8 - over, b); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/swf/swf.ts: -------------------------------------------------------------------------------- 1 | import {Data} from './data.ts'; 2 | import {Rect} from './rect.ts'; 3 | import {Fixed8} from './fixed8.ts'; 4 | import {Tag} from './tag.ts'; 5 | 6 | /** 7 | * Swf object. 8 | */ 9 | export class Swf extends Data { 10 | /** 11 | * Format version. 12 | */ 13 | public version: number = 0; 14 | 15 | /** 16 | * Frame size. 17 | */ 18 | public frameSize: Rect = new Rect(); 19 | 20 | /** 21 | * Frame rate. 22 | */ 23 | public frameRate: Fixed8 = new Fixed8(); 24 | 25 | /** 26 | * Frame count. 27 | */ 28 | public frameCount: number = 0; 29 | 30 | /** 31 | * Movie tags. 32 | */ 33 | public tags: Tag[] = []; 34 | 35 | /** 36 | * Swf constructor. 37 | */ 38 | constructor() { 39 | super(); 40 | } 41 | 42 | /** 43 | * @inheritdoc 44 | */ 45 | public get size() { 46 | return this.tags.reduce( 47 | (v, t) => t.size + v, 48 | 3 + 1 + 4 + this.frameSize.size + this.frameRate.size + 2 49 | ); 50 | } 51 | 52 | /** 53 | * @inheritdoc 54 | */ 55 | public encoder(data: Uint8Array) { 56 | let i = 0; 57 | data[i++] = 0x46; 58 | data[i++] = 0x57; 59 | data[i++] = 0x53; 60 | data[i++] = this.version; 61 | 62 | const v = new DataView(data.buffer, data.byteOffset, data.byteLength); 63 | v.setUint32(i, this.size, true); 64 | i += 4; 65 | 66 | i += this.frameSize.encode(data, i).length; 67 | 68 | i += this.frameRate.encode(data, i).length; 69 | 70 | v.setUint16(i, this.frameCount, true); 71 | i += 2; 72 | 73 | for (const tag of this.tags) { 74 | i += tag.encode(data, i).length; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/swf/tag.ts: -------------------------------------------------------------------------------- 1 | import {Data} from './data.ts'; 2 | 3 | /** 4 | * Tag object. 5 | */ 6 | export class Tag extends Data { 7 | /** 8 | * Tag code. 9 | */ 10 | public code: number = 0; 11 | 12 | /** 13 | * Tag data. 14 | */ 15 | public data: Uint8Array = new Uint8Array(0); 16 | 17 | /** 18 | * Tag constructor. 19 | */ 20 | constructor() { 21 | super(); 22 | } 23 | 24 | /** 25 | * Is the tag a long tag. 26 | * 27 | * @returns True if long tag. 28 | */ 29 | public get long() { 30 | return this.data.length >= 0b111111; 31 | } 32 | 33 | /** 34 | * @inheritdoc 35 | */ 36 | public get size() { 37 | return 2 + (this.long ? 4 : 0) + this.data.length; 38 | } 39 | 40 | /** 41 | * @inheritdoc 42 | */ 43 | public encoder(data: Uint8Array) { 44 | let i = 0; 45 | const {code, data: d, long} = this; 46 | // eslint-disable-next-line no-bitwise 47 | const head = (code << 6) | (long ? 0b111111 : d.length); 48 | const v = new DataView(data.buffer, data.byteOffset, data.byteLength); 49 | v.setUint16(i, head, true); 50 | i += 2; 51 | if (long) { 52 | v.setUint32(i, d.length, true); 53 | i += 4; 54 | } 55 | data.set(d, i); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/swf/util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Set subview of an existing buffer. 3 | * 4 | * @param data Data to get a subview of. 5 | * @param start Start offset. 6 | * @param size Subview size. 7 | * @returns Data subview. 8 | */ 9 | export function subview(data: Uint8Array, start: number, size = -1) { 10 | size = size < 0 ? data.length - start : size; 11 | const r = data.subarray(start, start + size); 12 | if (r.length < size) { 13 | throw new Error(`Data is too small: ${r.length} < ${size}`); 14 | } 15 | return r; 16 | } 17 | 18 | /** 19 | * Encode a string as a C-String. 20 | * 21 | * @param str String to be encoded. 22 | * @returns Encoded string. 23 | */ 24 | export function stringEncode(str: string) { 25 | return new TextEncoder().encode(`${str}\0`); 26 | } 27 | 28 | /** 29 | * Get number of bits needed to encode an unsigned integer. 30 | * 31 | * @param i Integer, unsigned. 32 | * @returns The number of bits. 33 | */ 34 | export function bitCountU(i: number) { 35 | let n = 0; 36 | // eslint-disable-next-line no-bitwise 37 | for (; i; i >>= 1) { 38 | n++; 39 | } 40 | return n; 41 | } 42 | 43 | /** 44 | * Get number of bits needed to encode a signed integer. 45 | * 46 | * @param i Integer, signed. 47 | * @returns The number of bits. 48 | */ 49 | export function bitCountS(i: number) { 50 | // eslint-disable-next-line no-bitwise 51 | return bitCountU(i < 0 ? i ^ -1 : i) + 1; 52 | } 53 | 54 | /** 55 | * Get the number of bytes needed to store N bit. 56 | * 57 | * @param bits The number of bits. 58 | * @returns The number of bytes. 59 | */ 60 | export function bitCountToBytes(bits: number) { 61 | const over = bits % 8; 62 | return (bits - over) / 8 + (over ? 1 : 0); 63 | } 64 | 65 | /** 66 | * Create a bit writer function for a buffer at offset. 67 | * 68 | * @param data The buffer to write bits into. 69 | * @param start Start offset. 70 | * @returns Writter function (value, count, offset). 71 | */ 72 | export function bitWriter(data: Uint8Array, start = 0) { 73 | const view = new DataView(data.buffer, data.byteOffset, data.byteLength); 74 | return (v: number, c: number, b: number) => { 75 | for (let i = 0; i < c; i++) { 76 | const bI = b + i; 77 | const bitI = bI % 8; 78 | const byteI = (bI - bitI) / 8; 79 | let byteV = view.getUint8(start + byteI); 80 | // eslint-disable-next-line no-bitwise 81 | const flag = 1 << (7 - bitI); 82 | // eslint-disable-next-line no-bitwise 83 | if ((v >> (c - 1 - i)) & 1) { 84 | // eslint-disable-next-line no-bitwise 85 | byteV |= flag; 86 | } else { 87 | // eslint-disable-next-line no-bitwise 88 | byteV &= ~flag; 89 | } 90 | view.setUint8(start + byteI, byteV); 91 | } 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /src/util.spec.ts: -------------------------------------------------------------------------------- 1 | import {join as pathJoin} from 'node:path'; 2 | import {mkdir, rm} from 'node:fs/promises'; 3 | import {readdirSync, statSync} from 'node:fs'; 4 | 5 | import {Manager} from '@shockpkg/core'; 6 | 7 | // eslint-disable-next-line no-process-env 8 | const envTest = process.env.SWF_PROJECTOR_TEST || null; 9 | 10 | export function shouldTest(name: string) { 11 | return ( 12 | !envTest || 13 | envTest.toLowerCase().split(',').includes(name.toLowerCase()) 14 | ); 15 | } 16 | 17 | // eslint-disable-next-line no-process-env 18 | export const testShowMenu = process.env.SWF_PROJECTOR_SHOWMENU === '1'; 19 | 20 | export const specFixturesPath = pathJoin('spec', 'fixtures'); 21 | export const specProjectorsPath = pathJoin('spec', 'projectors'); 22 | export const specBundlesPath = pathJoin('spec', 'bundles'); 23 | 24 | export function fixtureFile(name: string) { 25 | return pathJoin(specFixturesPath, name); 26 | } 27 | 28 | export async function getPackageFile(pkg: string) { 29 | return new Manager().file(pkg); 30 | } 31 | 32 | export async function cleanProjectorDir(...path: string[]) { 33 | const dir = pathJoin(specProjectorsPath, ...path); 34 | await rm(dir, {recursive: true, force: true}); 35 | await mkdir(dir, {recursive: true}); 36 | return dir; 37 | } 38 | 39 | export async function cleanBundlesDir(...path: string[]) { 40 | const dir = pathJoin(specBundlesPath, ...path); 41 | await rm(dir, {recursive: true, force: true}); 42 | await mkdir(dir, {recursive: true}); 43 | return dir; 44 | } 45 | 46 | let getInstalledPackagesCache: string[] | null = null; 47 | export function getInstalledPackagesSync() { 48 | if (!getInstalledPackagesCache) { 49 | getInstalledPackagesCache = []; 50 | try { 51 | const dir = 'shockpkg'; 52 | for (const d of readdirSync(dir, {withFileTypes: true})) { 53 | if (d.name.startsWith('.') || !d.isDirectory()) { 54 | continue; 55 | } 56 | try { 57 | const st = statSync( 58 | `${dir}/${d.name}/.shockpkg/package.json` 59 | ); 60 | if (st.isFile()) { 61 | getInstalledPackagesCache.push(d.name); 62 | } 63 | } catch (err) { 64 | if (!(err && (err as {code: string}).code === 'ENOENT')) { 65 | throw err; 66 | } 67 | } 68 | } 69 | } catch (err) { 70 | if (!(err && (err as {code: string}).code === 'ENOENT')) { 71 | throw err; 72 | } 73 | } 74 | } 75 | return getInstalledPackagesCache; 76 | } 77 | 78 | export function versionZlib(version: number[]) { 79 | return version[0] >= 6; 80 | } 81 | 82 | export function versionLzma(version: number[]) { 83 | return version[0] > 11 || (version[0] === 11 && version[1] >= 1); 84 | } 85 | 86 | export function simpleSwf(zlib: boolean, lzma: boolean) { 87 | if (lzma) { 88 | return 'swf14-lzma.swf'; 89 | } 90 | if (zlib) { 91 | return 'swf6-zlib.swf'; 92 | } 93 | return 'swf3.swf'; 94 | } 95 | 96 | export function packageInfo(name: string) { 97 | const m = name.match(/^flash-player-([\d.]+)-(.*)-sa(-debug)?(-.*)?$/); 98 | if (!m) { 99 | return null; 100 | } 101 | 102 | const version = m[1].split('.').map(Number); 103 | const zlib = versionZlib(version); 104 | const lzma = versionLzma(version); 105 | return { 106 | name, 107 | version, 108 | platform: m[2], 109 | debug: !!m[3], 110 | zlib, 111 | lzma 112 | }; 113 | } 114 | 115 | export function getInstalledPackagesInfoSync() { 116 | const r = []; 117 | for (const name of getInstalledPackagesSync()) { 118 | const info = packageInfo(name); 119 | if (info) { 120 | r.push(info); 121 | } 122 | } 123 | 124 | r.sort((a, b) => +a.debug - +b.debug); 125 | for (let i = 4; i--; ) { 126 | r.sort((a, b) => (a.version[i] || 0) - (b.version[i] || 0)); 127 | } 128 | 129 | return r; 130 | } 131 | -------------------------------------------------------------------------------- /src/util.test.ts: -------------------------------------------------------------------------------- 1 | import {describe, it} from 'node:test'; 2 | import {strictEqual} from 'node:assert'; 3 | 4 | import {pathRelativeBase, trimExtension} from './util.ts'; 5 | 6 | void describe('util', () => { 7 | void describe('pathRelativeBase', () => { 8 | void it('file', () => { 9 | strictEqual(pathRelativeBase('test', 'test'), ''); 10 | strictEqual(pathRelativeBase('test/', 'test'), ''); 11 | strictEqual(pathRelativeBase('test', 'Test'), null); 12 | }); 13 | 14 | void it('file nocase', () => { 15 | strictEqual(pathRelativeBase('test', 'Test', true), ''); 16 | }); 17 | 18 | void it('dir', () => { 19 | strictEqual(pathRelativeBase('test/123', 'test'), '123'); 20 | strictEqual(pathRelativeBase('test/123', 'Test'), null); 21 | }); 22 | 23 | void it('dir nocase', () => { 24 | strictEqual(pathRelativeBase('test/123', 'Test', true), '123'); 25 | }); 26 | }); 27 | 28 | void describe('trimExtension', () => { 29 | void it('case', () => { 30 | strictEqual(trimExtension('test.txt', '.txt'), 'test'); 31 | strictEqual(trimExtension('test.bin', '.txt'), 'test.bin'); 32 | strictEqual(trimExtension('test.TXT', '.txt'), 'test.TXT'); 33 | strictEqual(trimExtension('test.txt', '.TXT'), 'test.txt'); 34 | }); 35 | 36 | void it('nocase', () => { 37 | strictEqual(trimExtension('test.txt', '.TXT', true), 'test'); 38 | strictEqual(trimExtension('test.TXT', '.txt', true), 'test'); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import {inflateRaw} from 'node:zlib'; 2 | 3 | import {LAUNCHERS} from './launchers.ts'; 4 | 5 | /** 6 | * HTML encode. 7 | * 8 | * @param s Raw strings. 9 | * @param dq Double quotes. 10 | * @param sq Single quotes. 11 | * @returns Encoded strings. 12 | */ 13 | export function htmlEncode(s: string, dq = false, sq = false) { 14 | s = s.replace(/&/g, '&').replace(//g, '>'); 15 | if (dq) { 16 | s = s.replace(/"/g, '"'); 17 | } 18 | if (sq) { 19 | s = s.replace(/'/g, '''); 20 | } 21 | return s; 22 | } 23 | 24 | /** 25 | * Trim dot slash from head of path. 26 | * 27 | * @param path Path string. 28 | * @returns Trimmed path. 29 | */ 30 | export function trimDotSlash(path: string) { 31 | return path.replace(/^(\.\/)+/, ''); 32 | } 33 | 34 | /** 35 | * Find path relative from base, if base matches. 36 | * 37 | * @param path Path to match against. 38 | * @param start Search start. 39 | * @param nocase Match case-insensitive. 40 | * @returns Returns path, or null. 41 | */ 42 | export function pathRelativeBase(path: string, start: string, nocase = false) { 43 | const p = trimDotSlash(nocase ? path.toLowerCase() : path); 44 | const s = trimDotSlash(nocase ? start.toLowerCase() : start); 45 | if (p === s) { 46 | return ''; 47 | } 48 | if (p.startsWith(`${s}/`)) { 49 | return path.slice(s.length + 1); 50 | } 51 | return null; 52 | } 53 | 54 | /** 55 | * Same as pathRelativeBase, but retuns true on a match, else false. 56 | * 57 | * @param path Path to match against. 58 | * @param start Search start. 59 | * @param nocase Match case-insensitive. 60 | * @returns Returns true on match, else false. 61 | */ 62 | export function pathRelativeBaseMatch( 63 | path: string, 64 | start: string, 65 | nocase = false 66 | ) { 67 | return pathRelativeBase(path, start, nocase) !== null; 68 | } 69 | 70 | /** 71 | * Trim a file extenion. 72 | * 73 | * @param path File path. 74 | * @param ext File extension. 75 | * @param nocase Match case-insensitive. 76 | * @returns Path without file extension. 77 | */ 78 | export function trimExtension(path: string, ext: string, nocase = false) { 79 | const p = nocase ? path.toLowerCase() : path; 80 | const e = nocase ? ext.toLowerCase() : ext; 81 | return p.endsWith(e) ? path.slice(0, p.length - e.length) : path; 82 | } 83 | 84 | /** 85 | * Get launcher data for an ID. 86 | * 87 | * @param id Laucher ID. 88 | * @returns Launcher data. 89 | */ 90 | export async function launcher(id: string) { 91 | const b64 = LAUNCHERS[id]; 92 | if (typeof b64 !== 'string') { 93 | // eslint-disable-next-line unicorn/prefer-type-error 94 | throw new Error(`Invalid launcher id: ${id}`); 95 | } 96 | 97 | return new Promise((resolve, reject) => { 98 | inflateRaw(Buffer.from(b64, 'base64'), (err, data) => { 99 | if (err) { 100 | reject(err); 101 | return; 102 | } 103 | resolve( 104 | new Uint8Array(data.buffer, data.byteOffset, data.byteLength) 105 | ); 106 | }); 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /src/util/index.ts: -------------------------------------------------------------------------------- 1 | export * from './windows.ts'; 2 | export * from './mac.ts'; 3 | export * from './linux.ts'; 4 | -------------------------------------------------------------------------------- /src/util/internal/data.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Encode integer as 4 byte hex. 3 | * 4 | * @param i The integer to encode. 5 | * @returns Hex string. 6 | */ 7 | export function hex4(i: number) { 8 | return i.toString(16).toUpperCase().padStart(8, '0'); 9 | } 10 | 11 | /** 12 | * Align integer. 13 | * 14 | * @param i Integer value. 15 | * @param align Alignment amount. 16 | * @returns Aligned integer. 17 | */ 18 | export function align(i: number, align: number) { 19 | const o = i % align; 20 | return o ? align - o + i : i; 21 | } 22 | 23 | /** 24 | * Concat data chunks together. 25 | * 26 | * @param pieces The pieces to merge. 27 | * @returns Merged data. 28 | */ 29 | export function concat(pieces: readonly Readonly[]) { 30 | let l = 0; 31 | for (const piece of pieces) { 32 | l += piece.length; 33 | } 34 | const r = new Uint8Array(l); 35 | l = 0; 36 | for (const piece of pieces) { 37 | r.set(piece, l); 38 | l += piece.length; 39 | } 40 | return r; 41 | } 42 | 43 | /** 44 | * Get buffer. 45 | * 46 | * @param data Data view. 47 | * @param offset The offset. 48 | * @param size The size. 49 | * @returns ArrayBuffer slice. 50 | */ 51 | export function getBuffer(data: Readonly, offset: number, size = -1) { 52 | const {byteOffset, byteLength} = data; 53 | const o = byteOffset + offset; 54 | const l = byteLength - byteOffset; 55 | if (size > l) { 56 | throw new Error(`Size out of bounds`); 57 | } 58 | return data.buffer.slice(o, o + (size < 0 ? l : size)); 59 | } 60 | 61 | /** 62 | * Set buffer. 63 | * 64 | * @param data Data view. 65 | * @param offset The offset. 66 | * @param buffer The ArrayBuffer. 67 | * @param size The size. 68 | */ 69 | export function setBuffer( 70 | data: DataView, 71 | offset: number, 72 | buffer: Readonly, 73 | size = -1 74 | ) { 75 | const {byteOffset, byteLength} = data; 76 | const o = byteOffset + offset; 77 | new Uint8Array(data.buffer, o, byteLength - o).set( 78 | new Uint8Array(buffer, 0, size < 0 ? buffer.byteLength : size) 79 | ); 80 | } 81 | 82 | /** 83 | * Find exact match in data. 84 | * 85 | * @param data Data to search. 86 | * @param find Search for. 87 | * @param from Search from. 88 | * @returns Index. 89 | */ 90 | export function findIndex( 91 | data: Readonly, 92 | find: Readonly, 93 | from = 0 94 | ) { 95 | const l = find.length; 96 | if (l) { 97 | const e = data.length - l; 98 | const [f] = find; 99 | for (let i = from; ; i++) { 100 | i = data.indexOf(f, i); 101 | if (i < 0 || i > e) { 102 | break; 103 | } 104 | let m = true; 105 | for (let j = 1; j < l; j++) { 106 | if (data[i + j] !== find[j]) { 107 | m = false; 108 | break; 109 | } 110 | } 111 | if (m) { 112 | return i; 113 | } 114 | } 115 | } 116 | return -1; 117 | } 118 | 119 | /** 120 | * Get C-String with a max length. 121 | * 122 | * @param data Data buffer. 123 | * @param i Integer offset. 124 | * @param l Max length. 125 | * @returns ASCII string. 126 | */ 127 | export function getCstrN(data: Readonly, i: number, l: number) { 128 | let c = 0; 129 | for (; c < l; c++) { 130 | if (!data[i + c]) { 131 | break; 132 | } 133 | } 134 | // eslint-disable-next-line unicorn/prefer-code-point 135 | return String.fromCharCode(...data.subarray(i, i + c)); 136 | } 137 | 138 | /** 139 | * Encode string as UTF-16. 140 | * 141 | * @param str The string to encode. 142 | * @param le Little endian. 143 | * @returns Encoded data. 144 | */ 145 | export function encodeUtf16(str: string, le = false) { 146 | const l = str.length; 147 | const d = new ArrayBuffer(l * 2); 148 | const v = new DataView(d); 149 | for (let i = 0; i < l; i++) { 150 | // eslint-disable-next-line unicorn/prefer-code-point 151 | v.setUint16(i * 2, str.charCodeAt(i), le); 152 | } 153 | return new Uint8Array(d); 154 | } 155 | 156 | /** 157 | * Get UTF16 string from data buffer. 158 | * 159 | * @param data Data buffer. 160 | * @param i Start index. 161 | * @param le Little endian. 162 | * @returns Decoded string or null if never null terminated. 163 | */ 164 | export function getUtf16(data: Readonly, i: number, le = false) { 165 | const v = new DataView(data.buffer, data.byteOffset, data.byteLength); 166 | const e = v.byteLength - 1; 167 | for (const a = []; i < e; i += 2) { 168 | const c = v.getUint16(i, le); 169 | if (!c) { 170 | // eslint-disable-next-line unicorn/prefer-code-point 171 | return String.fromCharCode(...a); 172 | } 173 | a.push(c); 174 | } 175 | return null; 176 | } 177 | -------------------------------------------------------------------------------- /src/util/internal/linux/asm.ts: -------------------------------------------------------------------------------- 1 | // This file contains compile-time defined variables. 2 | 3 | export const MENU_I386 = '#{spec/asm/linux/menu-i386}' as unknown as { 4 | [p: string]: number[]; 5 | }; 6 | 7 | export const MENU_X8664 = '#{spec/asm/linux/menu-x86_64}' as unknown as { 8 | [p: string]: number[]; 9 | }; 10 | 11 | export const OFFSET_X8664 = '#{spec/asm/linux/offset-x86_64}' as unknown as { 12 | [p: string]: number[]; 13 | }; 14 | 15 | export const PATCH_I386 = '#{spec/asm/linux/patch-i386}' as unknown as { 16 | [p: string]: number[]; 17 | }; 18 | 19 | export const PATH_I386 = '#{spec/asm/linux/path-i386}' as unknown as { 20 | [p: string]: number[]; 21 | }; 22 | 23 | export const PATH_X8664 = '#{spec/asm/linux/path-x86_64}' as unknown as { 24 | [p: string]: number[]; 25 | }; 26 | 27 | export const TITLE_I386 = '#{spec/asm/linux/title-i386}' as unknown as { 28 | [p: string]: number[]; 29 | }; 30 | 31 | export const TITLE_X8664 = '#{spec/asm/linux/title-x86_64}' as unknown as { 32 | [p: string]: number[]; 33 | }; 34 | -------------------------------------------------------------------------------- /src/util/internal/linux/menu.ts: -------------------------------------------------------------------------------- 1 | import {writeFuzzy} from '../patch.ts'; 2 | 3 | import {Elf32, Elf64} from './elf.ts'; 4 | import {Patch} from './patch.ts'; 5 | 6 | /** 7 | * Patch menu spec. 8 | */ 9 | export interface IPatchMenuSpec { 10 | /** 11 | * Expected number of replacements. 12 | */ 13 | count: number; 14 | 15 | /** 16 | * Fuzzy find. 17 | */ 18 | find: number[]; 19 | 20 | /** 21 | * Fuzzy replace. 22 | */ 23 | replace: number[]; 24 | } 25 | 26 | /** 27 | * PatchMenu object. 28 | */ 29 | export abstract class PatchMenu extends Patch { 30 | /** 31 | * Patch spec. 32 | */ 33 | protected abstract _spec: IPatchMenuSpec[]; 34 | 35 | private _replace_ = [] as [Uint8Array, number, number[]][]; 36 | 37 | /** 38 | * @inheritDoc 39 | */ 40 | public check() { 41 | this._replace_ = []; 42 | const rep = [] as [Uint8Array, number, number[]][]; 43 | for (const {count, find, replace} of this._spec) { 44 | let found = 0; 45 | for (const [, i, d] of this._findFuzzyCode(find)) { 46 | found++; 47 | rep.push([d, i, replace]); 48 | if (found > count) { 49 | return false; 50 | } 51 | } 52 | if (found !== count) { 53 | return false; 54 | } 55 | } 56 | this._replace_ = rep; 57 | return !!rep.length; 58 | } 59 | 60 | /** 61 | * @inheritDoc 62 | */ 63 | public patch() { 64 | for (const [d, i, f] of this._replace_) { 65 | writeFuzzy(d, i, f); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/util/internal/linux/menu32.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-classes-per-file */ 2 | 3 | import {MENU_I386} from './asm.ts'; 4 | import {Elf32} from './elf.ts'; 5 | import {PatchMenu} from './menu.ts'; 6 | 7 | /** 8 | * PatchMenu32 object. 9 | */ 10 | export abstract class PatchMenu32 extends PatchMenu {} 11 | 12 | /** 13 | * Patch objects. 14 | */ 15 | export const menu32 = [ 16 | /** 17 | * 6.0.79.0 i386. 18 | */ 19 | class extends PatchMenu32 { 20 | /** 21 | * @inheritDoc 22 | */ 23 | protected _spec = [ 24 | { 25 | count: 1, 26 | find: MENU_I386['6-widget-a'], 27 | replace: MENU_I386['6-widget-b'] 28 | } 29 | ]; 30 | }, 31 | 32 | /** 33 | * 9.0.115.0 i386. 34 | */ 35 | class extends PatchMenu32 { 36 | /** 37 | * @inheritDoc 38 | */ 39 | protected _spec = [ 40 | { 41 | count: 2, 42 | find: MENU_I386['9-widget-a'], 43 | replace: MENU_I386['9-widget-b'] 44 | }, 45 | { 46 | count: 2, 47 | find: MENU_I386['9-menu-a'], 48 | replace: MENU_I386['9-menu-b'] 49 | } 50 | ]; 51 | }, 52 | 53 | /** 54 | * 10.0.12.36 i386. 55 | */ 56 | class extends PatchMenu32 { 57 | /** 58 | * @inheritDoc 59 | */ 60 | protected _spec = [ 61 | { 62 | count: 2, 63 | find: MENU_I386['10.0-widget-a'], 64 | replace: MENU_I386['10.0-widget-b'] 65 | }, 66 | { 67 | count: 1, 68 | find: MENU_I386['10.0-menu-1-a'], 69 | replace: MENU_I386['10.0-menu-1-b'] 70 | }, 71 | { 72 | count: 1, 73 | find: MENU_I386['10.0-menu-2-a'], 74 | replace: MENU_I386['10.0-menu-2-b'] 75 | } 76 | ]; 77 | }, 78 | 79 | /** 80 | * 10.1.53.64 i386. 81 | */ 82 | class extends PatchMenu32 { 83 | /** 84 | * @inheritDoc 85 | */ 86 | protected _spec = [ 87 | { 88 | count: 1, 89 | find: MENU_I386['10.1-widget-a'], 90 | replace: MENU_I386['10.1-widget-b'] 91 | }, 92 | { 93 | count: 1, 94 | find: MENU_I386['10.1-menu-a'], 95 | replace: MENU_I386['10.1-menu-b'] 96 | } 97 | ]; 98 | }, 99 | 100 | /** 101 | * 11.0.1.152 i386. 102 | */ 103 | class extends PatchMenu32 { 104 | /** 105 | * @inheritDoc 106 | */ 107 | protected _spec = [ 108 | { 109 | count: 1, 110 | find: MENU_I386['11.0-widget-a'], 111 | replace: MENU_I386['11.0-widget-b'] 112 | }, 113 | { 114 | count: 1, 115 | find: MENU_I386['11.0-menu-a'], 116 | replace: MENU_I386['11.0-menu-b'] 117 | } 118 | ]; 119 | }, 120 | 121 | /** 122 | * 11.2.202.228 i386. 123 | */ 124 | class extends PatchMenu32 { 125 | /** 126 | * @inheritDoc 127 | */ 128 | protected _spec = [ 129 | { 130 | count: 1, 131 | find: MENU_I386['11.2-widget-a'], 132 | replace: MENU_I386['11.2-widget-b'] 133 | }, 134 | { 135 | count: 1, 136 | find: MENU_I386['11.2-menu-a'], 137 | replace: MENU_I386['11.2-menu-b'] 138 | } 139 | ]; 140 | } 141 | ] as (new (elf: Elf32) => PatchMenu32)[]; 142 | -------------------------------------------------------------------------------- /src/util/internal/linux/menu64.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-classes-per-file */ 2 | 3 | import {MENU_X8664} from './asm.ts'; 4 | import {Elf64} from './elf.ts'; 5 | import {PatchMenu} from './menu.ts'; 6 | 7 | /** 8 | * PatchMenu64 object. 9 | */ 10 | export abstract class PatchMenu64 extends PatchMenu {} 11 | 12 | /** 13 | * Patch objects. 14 | */ 15 | export const menu64 = [ 16 | /** 17 | * 24.0.0.186 x86_64. 18 | */ 19 | class extends PatchMenu64 { 20 | /** 21 | * @inheritDoc 22 | */ 23 | protected _spec = [ 24 | { 25 | count: 1, 26 | find: MENU_X8664['24-widget-a'], 27 | replace: MENU_X8664['24-widget-b'] 28 | }, 29 | { 30 | count: 1, 31 | find: MENU_X8664['24-menu-a'], 32 | replace: MENU_X8664['24-menu-b'] 33 | } 34 | ]; 35 | }, 36 | 37 | /** 38 | * 32.0.0.293 x86_64. 39 | */ 40 | class extends PatchMenu64 { 41 | /** 42 | * @inheritDoc 43 | */ 44 | protected _spec = [ 45 | { 46 | count: 1, 47 | find: MENU_X8664['32-widget-a'], 48 | replace: MENU_X8664['32-widget-b'] 49 | }, 50 | { 51 | count: 1, 52 | find: MENU_X8664['32-menu-a'], 53 | replace: MENU_X8664['32-menu-b'] 54 | } 55 | ]; 56 | } 57 | ] as (new (elf: Elf64) => PatchMenu64)[]; 58 | -------------------------------------------------------------------------------- /src/util/internal/linux/offset64.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-classes-per-file */ 2 | 3 | import {writeFuzzy} from '../patch.ts'; 4 | 5 | import {OFFSET_X8664} from './asm.ts'; 6 | import {Elf64} from './elf.ts'; 7 | import {Patch} from './patch.ts'; 8 | 9 | /** 10 | * Patch offset 64-bit spec. 11 | */ 12 | export interface IPatchOffset64Spec { 13 | /** 14 | * Fuzzy find. 15 | */ 16 | find: number[]; 17 | 18 | /** 19 | * Fuzzy replace. 20 | */ 21 | replace: number[]; 22 | } 23 | 24 | /** 25 | * PatchOffset64 object. 26 | * Replace the bad ELF header reading logic with new logic. 27 | * The code was never updated from the old 32-bit code and is not accurate. 28 | */ 29 | export abstract class PatchOffset64 extends Patch { 30 | /** 31 | * Patch spec. 32 | */ 33 | protected abstract _spec: IPatchOffset64Spec; 34 | 35 | private _replace_ = [] as [Uint8Array, number, number[]][]; 36 | 37 | /** 38 | * @inheritDoc 39 | */ 40 | public check() { 41 | this._replace_ = []; 42 | const {find, replace} = this._spec; 43 | const rep = [] as [Uint8Array, number, number[]][]; 44 | for (const [, i, d] of this._findFuzzyCode(find)) { 45 | if (rep.length) { 46 | return false; 47 | } 48 | rep.push([d, i, replace]); 49 | } 50 | if (rep.length !== 1) { 51 | return false; 52 | } 53 | this._replace_ = rep; 54 | return true; 55 | } 56 | 57 | /** 58 | * @inheritDoc 59 | */ 60 | public patch() { 61 | for (const [d, i, f] of this._replace_) { 62 | writeFuzzy(d, i, f); 63 | } 64 | } 65 | } 66 | 67 | /** 68 | * Patch objects. 69 | */ 70 | export const offset64 = [ 71 | /** 72 | * 24.0.0.186 x86_64. 73 | */ 74 | class extends PatchOffset64 { 75 | /** 76 | * @inheritDoc 77 | */ 78 | protected _spec = { 79 | find: OFFSET_X8664['24-a'], 80 | replace: OFFSET_X8664['24-b'] 81 | }; 82 | }, 83 | 84 | /** 85 | * 25.0.0.127 x86_64. 86 | */ 87 | class extends PatchOffset64 { 88 | /** 89 | * @inheritDoc 90 | */ 91 | protected _spec = { 92 | find: OFFSET_X8664['25-a'], 93 | replace: OFFSET_X8664['25-b'] 94 | }; 95 | }, 96 | 97 | /** 98 | * 32.0.0.293 x86_64. 99 | */ 100 | class extends PatchOffset64 { 101 | /** 102 | * @inheritDoc 103 | */ 104 | protected _spec = { 105 | find: OFFSET_X8664['32-a'], 106 | replace: OFFSET_X8664['32-b'] 107 | }; 108 | } 109 | ] as (new (elf: Elf64) => PatchOffset64)[]; 110 | -------------------------------------------------------------------------------- /src/util/internal/linux/patch.ts: -------------------------------------------------------------------------------- 1 | import {findFuzzy} from '../patch.ts'; 2 | 3 | import {PATCH_I386} from './asm.ts'; 4 | import {Elf32, Elf32Shdr, Elf64, EM_386} from './elf.ts'; 5 | 6 | type Unpacked = T extends (infer U)[] ? U : T; 7 | 8 | /** 9 | * Patch object. 10 | */ 11 | export abstract class Patch { 12 | /** 13 | * ELF object. 14 | */ 15 | protected _elf: T; 16 | 17 | /** 18 | * Patch constructor. 19 | * 20 | * @param elf ELF object. 21 | */ 22 | constructor(elf: T) { 23 | this._elf = elf; 24 | } 25 | 26 | /** 27 | * Get shdr for address. 28 | * 29 | * @param addr The address. 30 | * @returns The shdr or null. 31 | */ 32 | protected _getShdrForAddress(addr: number | bigint) { 33 | for (const shdr of this._elf.sectionHeaders) { 34 | const {shAddr} = shdr; 35 | if ( 36 | addr >= shAddr && 37 | addr < (shAddr as number) + (shdr.shSize as number) 38 | ) { 39 | return shdr as Unpacked; 40 | } 41 | } 42 | return null; 43 | } 44 | 45 | /** 46 | * The shdr for address or throw. 47 | * 48 | * @param addr The address. 49 | * @returns The shdr. 50 | */ 51 | protected _theShdrForAddress(addr: number | bigint) { 52 | const shdr = this._getShdrForAddress(addr); 53 | if (!shdr) { 54 | throw new Error(`No section at address: ${addr.toString()}`); 55 | } 56 | return shdr; 57 | } 58 | 59 | /** 60 | * Fuzzy find in code. 61 | * 62 | * @param find Fuzzy find. 63 | * @yields The shdr and index in shdr. 64 | */ 65 | protected *_findFuzzyCode(find: number[]) { 66 | const shdr = this._getShdrForAddress(this._elf.elfHeader.eEntry); 67 | if (!shdr) { 68 | return; 69 | } 70 | const d = new Uint8Array(shdr.data); 71 | for (const i of findFuzzy(d, find)) { 72 | yield [shdr, i, d] as [ 73 | Unpacked, 74 | number, 75 | Uint8Array 76 | ]; 77 | } 78 | } 79 | 80 | /** 81 | * Get ebx for code at address. 82 | * 83 | * @param addr The address. 84 | * @returns The value of ebd or null if value not found. 85 | */ 86 | protected _findEbx(addr: number) { 87 | if (this._elf.bits !== 32 || this._elf.elfHeader.eMachine !== EM_386) { 88 | throw new Error('Unsupported architecture'); 89 | } 90 | const shdr = this._theShdrForAddress(addr) as Elf32Shdr; 91 | const d = new Uint8Array(shdr.data); 92 | const v = new DataView(shdr.data); 93 | const before = addr - shdr.shAddr; 94 | for (const i of findFuzzy(d, PATCH_I386['ebx'], 0, before, true)) { 95 | return shdr.shAddr + i + 5 + v.getUint32(i + 7, true); 96 | } 97 | return null; 98 | } 99 | 100 | /** 101 | * Read C-String from address. 102 | * 103 | * @param addr String address. 104 | * @returns The C-String or null if invalid. 105 | */ 106 | protected _readCstr(addr: number | bigint) { 107 | const shdr = this._getShdrForAddress(addr); 108 | if (!shdr) { 109 | return null; 110 | } 111 | const d = new Uint8Array(shdr.data); 112 | const s = Number(addr) - Number(shdr.shAddr); 113 | const e = d.length; 114 | for (let i = s; i < e; i++) { 115 | if (!d[i]) { 116 | // eslint-disable-next-line unicorn/prefer-code-point 117 | return String.fromCharCode(...d.subarray(s, i)); 118 | } 119 | } 120 | return null; 121 | } 122 | 123 | /** 124 | * Check patch. 125 | * 126 | * @returns True if valid patch, else false. 127 | */ 128 | public abstract check(): boolean; 129 | 130 | /** 131 | * Apply patch. 132 | */ 133 | public abstract patch(): void; 134 | } 135 | -------------------------------------------------------------------------------- /src/util/internal/linux/path.ts: -------------------------------------------------------------------------------- 1 | import {findIndex} from '../data.ts'; 2 | 3 | import {Elf32, Elf64} from './elf.ts'; 4 | import {Patch} from './patch.ts'; 5 | 6 | /** 7 | * PatchPath object. 8 | * Patch broken Linux projector path code. 9 | * Replaces bad "file:" prefix with "file://" for projector self URL. 10 | */ 11 | export abstract class PatchPath extends Patch { 12 | /** 13 | * Get a remapped pointer, if pointer needs remapping. 14 | * 15 | * @param ptr Current pointer. 16 | * @returns Remapped pointer or null. 17 | */ 18 | protected _getRemap(ptr: number | bigint) { 19 | const str = this._readCstr(ptr); 20 | if (str !== 'file:') { 21 | return null; 22 | } 23 | const shdr = this._theShdrForAddress(ptr); 24 | const data = new Uint8Array(shdr.data); 25 | const strd = new TextEncoder().encode('\0file://\0'); 26 | const fileI = findIndex(data, strd) + 1; 27 | if (!fileI) { 28 | return null; 29 | } 30 | return ( 31 | this._elf.bits === 64 32 | ? (shdr.shAddr as bigint) + BigInt(fileI) 33 | : (shdr.shAddr as number) + fileI 34 | ) as T['elfHeader']['eEntry']; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/util/internal/linux/path32.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-classes-per-file */ 2 | 3 | import {PATH_I386} from './asm.ts'; 4 | import {Elf32} from './elf.ts'; 5 | import {PatchPath} from './path.ts'; 6 | 7 | /** 8 | * PatchPath32 object. 9 | */ 10 | export abstract class PatchPath32 extends PatchPath {} 11 | 12 | /** 13 | * PatchPath32Dir object. 14 | */ 15 | abstract class PatchPath32Dir extends PatchPath32 {} 16 | 17 | /** 18 | * PatchPath32File object. 19 | */ 20 | abstract class PatchPath32File extends PatchPath32 { 21 | /** 22 | * Relative offset. 23 | */ 24 | protected abstract readonly _relative: boolean; 25 | 26 | /** 27 | * Fuzzy find. 28 | */ 29 | protected abstract readonly _find: number[]; 30 | 31 | /** 32 | * Address offset. 33 | */ 34 | protected abstract readonly _offset: number; 35 | 36 | private _addr_ = 0; 37 | 38 | private _remap_ = 0; 39 | 40 | private _ebx_ = 0; 41 | 42 | /** 43 | * @inheritDoc 44 | */ 45 | public check() { 46 | const {_relative: rel, _find: find, _offset: o} = this; 47 | for (const [shdr, i, d] of this._findFuzzyCode(find)) { 48 | const addr = shdr.shAddr + i; 49 | const ebx = rel ? this._findEbx(addr) : null; 50 | if (rel && ebx === null) { 51 | continue; 52 | } 53 | const v = new DataView(d.buffer, d.byteOffset, d.byteLength); 54 | const ptr = rel 55 | ? (ebx as number) + v.getInt32(i + o, true) 56 | : v.getUint32(i + o, true); 57 | const remap = this._getRemap(ptr); 58 | if (!remap) { 59 | continue; 60 | } 61 | if (this._addr_) { 62 | return false; 63 | } 64 | this._addr_ = addr; 65 | this._remap_ = remap; 66 | if (rel) { 67 | this._ebx_ = ebx as number; 68 | } 69 | } 70 | return !!this._addr_; 71 | } 72 | 73 | /** 74 | * @inheritDoc 75 | */ 76 | public patch() { 77 | const {_relative: rel, _offset: o} = this; 78 | const addr = this._addr_; 79 | const shdr = this._theShdrForAddress(addr); 80 | const v = new DataView(shdr.data); 81 | const i = addr - shdr.shAddr; 82 | if (rel) { 83 | v.setInt32(i + o, this._remap_ - this._ebx_, true); 84 | } else { 85 | v.setUint32(i + o, this._remap_, true); 86 | } 87 | } 88 | } 89 | 90 | /** 91 | * PatchPath32FileAbs object. 92 | */ 93 | abstract class PatchPath32FileAbs extends PatchPath32File { 94 | /** 95 | * @inheritDoc 96 | */ 97 | protected readonly _relative = false; 98 | } 99 | 100 | /** 101 | * PatchPath32FileRel object. 102 | */ 103 | abstract class PatchPath32FileRel extends PatchPath32File { 104 | /** 105 | * @inheritDoc 106 | */ 107 | protected readonly _relative = true; 108 | } 109 | 110 | /** 111 | * Patch objects. 112 | */ 113 | export const path32 = [ 114 | /** 115 | * 6.0.79.0 i386. 116 | */ 117 | class extends PatchPath32Dir { 118 | private _addr_ = 0; 119 | 120 | /** 121 | * @inheritDoc 122 | */ 123 | public check() { 124 | for (const [shdr, i] of this._findFuzzyCode(PATH_I386['6'])) { 125 | if (this._addr_) { 126 | return false; 127 | } 128 | this._addr_ = shdr.shAddr + i; 129 | } 130 | return !!this._addr_; 131 | } 132 | 133 | /** 134 | * @inheritDoc 135 | */ 136 | public patch() { 137 | const {_addr_: addr} = this; 138 | const shdr = this._theShdrForAddress(addr); 139 | const v = new DataView(shdr.data); 140 | const i = addr - shdr.shAddr; 141 | 142 | // sIsProjector -> sExecutableName 143 | const ptr = v.getUint32(i + 24, true) + 4; 144 | 145 | // nop 2x 146 | v.setUint8(i + 68, 0x90); 147 | v.setUint8(i + 69, 0x90); 148 | 149 | // mov esi, DWORD PTR ds:... 150 | v.setUint8(i + 70, 0x8b); 151 | v.setUint8(i + 71, 0x35); 152 | v.setUint32(i + 72, ptr, true); 153 | 154 | // push esi 155 | v.setUint8(i + 96, 0x56); 156 | 157 | // push esi 158 | v.setUint8(i + 122, 0x56); 159 | } 160 | }, 161 | 162 | /** 163 | * 9.0.115.0 i386. 164 | */ 165 | class extends PatchPath32FileAbs { 166 | /** 167 | * @inheritDoc 168 | */ 169 | protected readonly _find = PATH_I386['9']; 170 | 171 | /** 172 | * @inheritDoc 173 | */ 174 | protected readonly _offset = 10; 175 | }, 176 | 177 | /** 178 | * 10.0.12.36 i386. 179 | */ 180 | class extends PatchPath32FileAbs { 181 | /** 182 | * @inheritDoc 183 | */ 184 | protected readonly _find = PATH_I386['10.0']; 185 | 186 | /** 187 | * @inheritDoc 188 | */ 189 | protected readonly _offset = 16; 190 | }, 191 | 192 | /** 193 | * 10.1.53.64 i386. 194 | */ 195 | class extends PatchPath32FileAbs { 196 | /** 197 | * @inheritDoc 198 | */ 199 | protected readonly _find = PATH_I386['10.1']; 200 | 201 | /** 202 | * @inheritDoc 203 | */ 204 | protected readonly _offset = 16; 205 | }, 206 | 207 | /** 208 | * 11.0.1.152 i386. 209 | */ 210 | class extends PatchPath32FileAbs { 211 | /** 212 | * @inheritDoc 213 | */ 214 | protected readonly _find = PATH_I386['11.0']; 215 | 216 | /** 217 | * @inheritDoc 218 | */ 219 | protected readonly _offset = 18; 220 | }, 221 | 222 | /** 223 | * 11.2.202.228 i386. 224 | */ 225 | class extends PatchPath32FileRel { 226 | /** 227 | * @inheritDoc 228 | */ 229 | protected readonly _find = PATH_I386['11.2']; 230 | 231 | /** 232 | * @inheritDoc 233 | */ 234 | protected readonly _offset = 2; 235 | } 236 | ] as (new (elf: Elf32) => PatchPath32File)[]; 237 | -------------------------------------------------------------------------------- /src/util/internal/linux/path64.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-classes-per-file */ 2 | 3 | import {PATH_X8664} from './asm.ts'; 4 | import {Elf64} from './elf.ts'; 5 | import {PatchPath} from './path.ts'; 6 | 7 | /** 8 | * PatchPath64 object. 9 | */ 10 | export abstract class PatchPath64 extends PatchPath {} 11 | 12 | /** 13 | * PatchPath64File object. 14 | */ 15 | abstract class PatchPath64File extends PatchPath64 { 16 | /** 17 | * Fuzzy find. 18 | */ 19 | protected abstract readonly _find: number[]; 20 | 21 | /** 22 | * Address offset. 23 | */ 24 | protected abstract readonly _offset: number; 25 | 26 | private _addr_ = 0n; 27 | 28 | private _remap_ = 0n; 29 | 30 | /** 31 | * @inheritDoc 32 | */ 33 | public check() { 34 | const {_find: find, _offset: o} = this; 35 | for (const [shdr, i, d] of this._findFuzzyCode(find)) { 36 | const v = new DataView(d.buffer, d.byteOffset, d.byteLength); 37 | const addr = shdr.shAddr + BigInt(i); 38 | const rip = addr + 10n; 39 | const ptr = rip + BigInt(v.getUint32(i + o, true)); 40 | const remap = this._getRemap(ptr); 41 | if (!remap) { 42 | continue; 43 | } 44 | if (this._addr_) { 45 | return false; 46 | } 47 | this._addr_ = addr; 48 | this._remap_ = remap; 49 | } 50 | return !!this._addr_; 51 | } 52 | 53 | /** 54 | * @inheritDoc 55 | */ 56 | public patch() { 57 | const {_offset: o} = this; 58 | const addr = this._addr_; 59 | const shdr = this._theShdrForAddress(addr); 60 | const v = new DataView(shdr.data); 61 | const i = Number(addr - shdr.shAddr); 62 | const rip = shdr.shAddr + BigInt(i + 10); 63 | v.setUint32(i + o, Number(this._remap_ - rip), true); 64 | } 65 | } 66 | 67 | /** 68 | * Patch objects. 69 | */ 70 | export const path64 = [ 71 | /** 72 | * 24.0.0.186 x86_64. 73 | */ 74 | class extends PatchPath64File { 75 | /** 76 | * @inheritDoc 77 | */ 78 | protected readonly _find = PATH_X8664[24]; 79 | 80 | /** 81 | * @inheritDoc 82 | */ 83 | protected readonly _offset = 6; 84 | } 85 | ] as (new (elf: Elf64) => PatchPath64)[]; 86 | -------------------------------------------------------------------------------- /src/util/internal/linux/title.ts: -------------------------------------------------------------------------------- 1 | import {Elf32, Elf64} from './elf.ts'; 2 | import {Patch} from './patch.ts'; 3 | 4 | // Match all known titles. 5 | export const titleMatchM = /^Macromedia Flash Player \d$/; 6 | export const titleMatchA = /^Adobe Flash Player \d+((?:,\d+){3})?$/; 7 | 8 | /** 9 | * PatchTitle object. 10 | */ 11 | export abstract class PatchTitle extends Patch { 12 | /** 13 | * New title address. 14 | */ 15 | protected readonly _titleA: T['elfHeader']['eEntry']; 16 | 17 | /** 18 | * New title length, without null termination. 19 | */ 20 | protected readonly _titleL: number; 21 | 22 | /** 23 | * PatchTitle constructor. 24 | * 25 | * @param elf ELF object. 26 | * @param titleA New title address. 27 | * @param titleL New title length, without null termination. 28 | */ 29 | constructor(elf: T, titleA: T['elfHeader']['eEntry'], titleL: number) { 30 | super(elf); 31 | 32 | this._titleA = titleA; 33 | this._titleL = titleL; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/util/internal/linux/title32.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-classes-per-file */ 2 | 3 | import {TITLE_I386} from './asm.ts'; 4 | import {Elf32} from './elf.ts'; 5 | import {PatchTitle, titleMatchM, titleMatchA} from './title.ts'; 6 | 7 | /** 8 | * PatchTitle32 object. 9 | */ 10 | export abstract class PatchTitle32 extends PatchTitle {} 11 | 12 | /** 13 | * Patch objects. 14 | */ 15 | export const title32 = [ 16 | /** 17 | * 6.0.79.0 i386. 18 | */ 19 | class extends PatchTitle32 { 20 | private _addr_ = 0; 21 | 22 | /** 23 | * @inheritDoc 24 | */ 25 | public check() { 26 | for (const [shdr, i, d] of this._findFuzzyCode(TITLE_I386['6'])) { 27 | const v = new DataView(d.buffer, d.byteOffset, d.byteLength); 28 | const ptr = v.getUint32(i + 9, true); 29 | const shdr2 = this._getShdrForAddress(ptr); 30 | if (!shdr2) { 31 | continue; 32 | } 33 | const v2 = new DataView(shdr2.data); 34 | const i2 = ptr - shdr2.shAddr; 35 | const ptr2 = v2.getUint32(i2, true); 36 | const str = this._readCstr(ptr2); 37 | if (!str || !titleMatchM.test(str)) { 38 | continue; 39 | } 40 | this._addr_ = shdr.shAddr + i; 41 | } 42 | return !!this._addr_; 43 | } 44 | 45 | /** 46 | * @inheritDoc 47 | */ 48 | public patch() { 49 | const addr = this._addr_; 50 | const shdr = this._theShdrForAddress(addr); 51 | const v = new DataView(shdr.data); 52 | let i = addr - shdr.shAddr + 7; 53 | 54 | // nop 55 | v.setUint8(i++, 0x90); 56 | 57 | // push ... 58 | v.setUint8(i++, 0x68); 59 | v.setUint32(i, this._titleA, true); 60 | } 61 | }, 62 | 63 | /** 64 | * 9.0.115.0 i386. 65 | */ 66 | class extends PatchTitle32 { 67 | private _addrs_ = [] as number[]; 68 | 69 | /** 70 | * @inheritDoc 71 | */ 72 | public check() { 73 | this._addrs_ = []; 74 | for (const [shdr, i, d] of this._findFuzzyCode(TITLE_I386['9'])) { 75 | const v = new DataView(d.buffer, d.byteOffset, d.byteLength); 76 | const ptr = v.getUint32(i + 6, true); 77 | const shdr2 = this._getShdrForAddress(ptr); 78 | if (!shdr2) { 79 | continue; 80 | } 81 | const v2 = new DataView(shdr2.data); 82 | const i2 = ptr - shdr2.shAddr; 83 | const ptr2 = v2.getUint32(i2, true); 84 | const str = this._readCstr(ptr2); 85 | if (!str || !titleMatchA.test(str)) { 86 | continue; 87 | } 88 | this._addrs_.push(shdr.shAddr + i); 89 | } 90 | return this._addrs_.length === 2; 91 | } 92 | 93 | /** 94 | * @inheritDoc 95 | */ 96 | public patch() { 97 | for (const addr of this._addrs_) { 98 | const shdr = this._theShdrForAddress(addr); 99 | const v = new DataView(shdr.data); 100 | let i = addr - shdr.shAddr + 5; 101 | 102 | // mov eax, ... 103 | v.setUint8(i++, 0xb8); 104 | v.setUint32(i, this._titleA, true); 105 | } 106 | } 107 | }, 108 | 109 | /** 110 | * 10.1.53.64 i386. 111 | */ 112 | class extends PatchTitle32 { 113 | private _addr_ = 0; 114 | 115 | /** 116 | * @inheritDoc 117 | */ 118 | public check() { 119 | for (const [shdr, i, d] of this._findFuzzyCode( 120 | TITLE_I386['10.1'] 121 | )) { 122 | const v = new DataView(d.buffer, d.byteOffset, d.byteLength); 123 | const addr = shdr.shAddr + i; 124 | const ptr = v.getUint32(i + 12, true); 125 | const len = v.getUint32(i + 4, true); 126 | const str = this._readCstr(ptr); 127 | if (!str || str.length !== len || !titleMatchA.test(str)) { 128 | continue; 129 | } 130 | if (this._addr_) { 131 | return false; 132 | } 133 | this._addr_ = addr; 134 | } 135 | return !!this._addr_; 136 | } 137 | 138 | /** 139 | * @inheritDoc 140 | */ 141 | public patch() { 142 | const addr = this._addr_; 143 | const shdr = this._theShdrForAddress(addr); 144 | const v = new DataView(shdr.data); 145 | const i = addr - shdr.shAddr; 146 | v.setUint32(i + 12, this._titleA, true); 147 | v.setUint32(i + 4, this._titleL, true); 148 | } 149 | }, 150 | 151 | /** 152 | * 11.2.202.228 i386. 153 | */ 154 | class extends PatchTitle32 { 155 | private _addr_ = 0; 156 | 157 | private _ebx_ = 0; 158 | 159 | /** 160 | * @inheritDoc 161 | */ 162 | public check() { 163 | for (const [shdr, i, d] of this._findFuzzyCode( 164 | TITLE_I386['11.2'] 165 | )) { 166 | const addr = shdr.shAddr + i; 167 | const ebx = this._findEbx(addr); 168 | if (ebx === null) { 169 | continue; 170 | } 171 | const v = new DataView(d.buffer, d.byteOffset, d.byteLength); 172 | const ptr = ebx + v.getInt32(i + 2, true); 173 | const len = v.getUint32(i + 14, true); 174 | const str = this._readCstr(ptr); 175 | if (!str || str.length !== len || !titleMatchA.test(str)) { 176 | continue; 177 | } 178 | if (this._addr_) { 179 | return false; 180 | } 181 | this._addr_ = addr; 182 | this._ebx_ = ebx; 183 | } 184 | return !!this._addr_; 185 | } 186 | 187 | /** 188 | * @inheritDoc 189 | */ 190 | public patch() { 191 | const addr = this._addr_; 192 | const ebx = this._ebx_; 193 | const shdr = this._theShdrForAddress(addr); 194 | const v = new DataView(shdr.data); 195 | const i = addr - shdr.shAddr; 196 | v.setInt32(i + 2, this._titleA - ebx, true); 197 | v.setUint32(i + 14, this._titleL, true); 198 | } 199 | } 200 | ] as (new (elf: Elf32, titleA: number, titleL: number) => PatchTitle32)[]; 201 | -------------------------------------------------------------------------------- /src/util/internal/linux/title64.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-classes-per-file */ 2 | 3 | import {TITLE_X8664} from './asm.ts'; 4 | import {Elf64} from './elf.ts'; 5 | import {PatchTitle, titleMatchA} from './title.ts'; 6 | 7 | /** 8 | * PatchTitle64 object. 9 | */ 10 | export abstract class PatchTitle64 extends PatchTitle {} 11 | 12 | /** 13 | * Patch objects. 14 | */ 15 | export const title64 = [ 16 | /** 17 | * 24.0.0.186 x86_64. 18 | */ 19 | class extends PatchTitle64 { 20 | private _addr_ = 0n; 21 | 22 | /** 23 | * @inheritDoc 24 | */ 25 | public check() { 26 | for (const [shdr, i, d] of this._findFuzzyCode(TITLE_X8664['24'])) { 27 | const v = new DataView(d.buffer, d.byteOffset, d.byteLength); 28 | const addr = shdr.shAddr + BigInt(i); 29 | const rip = addr + 7n; 30 | const ptr = rip + BigInt(v.getUint32(i + 3, true)); 31 | const len = v.getUint32(i + 8, true); 32 | const str = this._readCstr(ptr); 33 | if (!str || str.length !== len || !titleMatchA.test(str)) { 34 | continue; 35 | } 36 | if (this._addr_) { 37 | return false; 38 | } 39 | this._addr_ = addr; 40 | } 41 | return !!this._addr_; 42 | } 43 | 44 | /** 45 | * @inheritDoc 46 | */ 47 | public patch() { 48 | const addr = this._addr_; 49 | const shdr = this._theShdrForAddress(addr); 50 | const v = new DataView(shdr.data); 51 | const i = Number(addr - shdr.shAddr); 52 | const rip = shdr.shAddr + BigInt(i + 7); 53 | v.setUint32(i + 3, Number(this._titleA - rip), true); 54 | v.setUint32(i + 8, this._titleL, true); 55 | } 56 | } 57 | ] as (new (elf: Elf64, titleA: bigint, titleL: number) => PatchTitle64)[]; 58 | -------------------------------------------------------------------------------- /src/util/internal/mac/asm.ts: -------------------------------------------------------------------------------- 1 | // This file contains compile-time defined variables. 2 | 3 | export const TITLE_I386 = '#{spec/asm/mac/title-i386}' as unknown as { 4 | [p: string]: number[]; 5 | }; 6 | 7 | export const TITLE_X8664 = '#{spec/asm/mac/title-x86_64}' as unknown as { 8 | [p: string]: number[]; 9 | }; 10 | 11 | export const TITLE_ARM64 = '#{spec/asm/mac/title-arm64}' as unknown as { 12 | [p: string]: number[]; 13 | }; 14 | -------------------------------------------------------------------------------- /src/util/internal/mac/constants.ts: -------------------------------------------------------------------------------- 1 | export const VM_PROT_READ = 0x1; 2 | 3 | export const FAT_MAGIC = 0xcafebabe; 4 | export const MH_MAGIC = 0xfeedface; 5 | export const MH_CIGAM = 0xcefaedfe; 6 | export const MH_MAGIC_64 = 0xfeedfacf; 7 | export const MH_CIGAM_64 = 0xcffaedfe; 8 | 9 | export const LC_REQ_DYLD = 0x80000000; 10 | 11 | export const LC_SEGMENT = 0x1; 12 | export const LC_SYMTAB = 0x2; 13 | export const LC_DYSYMTAB = 0xb; 14 | export const LC_SEGMENT_64 = 0x19; 15 | export const LC_CODE_SIGNATURE = 0x1d; 16 | export const LC_SEGMENT_SPLIT_INFO = 0x1e; 17 | export const LC_DYLD_INFO = 0x22; 18 | // eslint-disable-next-line no-bitwise 19 | export const LC_DYLD_INFO_ONLY = (0x22 | LC_REQ_DYLD) >>> 0; 20 | export const LC_FUNCTION_STARTS = 0x26; 21 | export const LC_DATA_IN_CODE = 0x29; 22 | export const LC_DYLIB_CODE_SIGN_DRS = 0x2b; 23 | export const LC_LINKER_OPTIMIZATION_HINT = 0x2e; 24 | // eslint-disable-next-line no-bitwise 25 | export const LC_DYLD_EXPORTS_TRIE = (0x33 | LC_REQ_DYLD) >>> 0; 26 | // eslint-disable-next-line no-bitwise 27 | export const LC_DYLD_CHAINED_FIXUPS = (0x34 | LC_REQ_DYLD) >>> 0; 28 | 29 | export const SEG_TEXT = '__TEXT'; 30 | export const SECT_TEXT = '__text'; 31 | export const SEG_LINKEDIT = '__LINKEDIT'; 32 | 33 | export const CPU_TYPE_POWERPC = 0x00000012; 34 | export const CPU_TYPE_POWERPC64 = 0x01000012; 35 | export const CPU_TYPE_I386 = 0x00000007; 36 | export const CPU_TYPE_X86_64 = 0x01000007; 37 | export const CPU_TYPE_ARM64 = 0x0100000c; 38 | -------------------------------------------------------------------------------- /src/util/internal/patch.ts: -------------------------------------------------------------------------------- 1 | import {findIndex} from './data.ts'; 2 | 3 | /** 4 | * Find exact matches in data. 5 | * 6 | * @param data Data to search. 7 | * @param find Search for. 8 | * @param from Search from. 9 | * @yields Index. 10 | */ 11 | export function* findExact( 12 | data: Readonly, 13 | find: Readonly, 14 | from = 0 15 | ) { 16 | for (let index = from - 1; ; ) { 17 | index = findIndex(data, find, index + 1); 18 | if (index < 0) { 19 | break; 20 | } 21 | yield index; 22 | } 23 | } 24 | 25 | /** 26 | * Find similar matches in data. 27 | * 28 | * @param data Data to search. 29 | * @param find Search for. 30 | * @param from Search from. 31 | * @param until Search until. 32 | * @param backward Search backwards. 33 | * @yields Index. 34 | */ 35 | export function* findFuzzy( 36 | data: Readonly, 37 | find: readonly number[], 38 | from = 0, 39 | until = -1, 40 | backward = false 41 | ) { 42 | const end = (until < 0 ? data.length : until) - find.length; 43 | const add = backward ? -1 : 1; 44 | const stop = backward ? -1 : end + 1; 45 | for (let i = backward ? end : from; i !== stop; i += add) { 46 | let found = true; 47 | const {length} = find; 48 | for (let j = 0; j < length; j++) { 49 | const b = find[j]; 50 | if (!(b < 0) && data[i + j] !== b) { 51 | found = false; 52 | break; 53 | } 54 | } 55 | if (found) { 56 | yield i; 57 | } 58 | } 59 | } 60 | 61 | /** 62 | * Fuzzy find once, null if multiple. 63 | * 64 | * @param data Data. 65 | * @param fuzzy Fuzzy data. 66 | * @returns Index or null. 67 | */ 68 | export function findFuzzyOnce( 69 | data: Readonly, 70 | fuzzy: readonly number[] 71 | ) { 72 | let r = null; 73 | for (const found of findFuzzy(data, fuzzy)) { 74 | if (r !== null) { 75 | return null; 76 | } 77 | r = found; 78 | } 79 | return r; 80 | } 81 | 82 | /** 83 | * Write similar match in data. 84 | * 85 | * @param data Data to write into. 86 | * @param offset Offset to write at. 87 | * @param fuzzy The similar data. 88 | */ 89 | export function writeFuzzy( 90 | data: Uint8Array, 91 | offset: number, 92 | fuzzy: readonly number[] 93 | ) { 94 | const {length} = fuzzy; 95 | for (let i = 0; i < length; i++) { 96 | const b = fuzzy[i]; 97 | if (!(b < 0)) { 98 | data[offset + i] = b; 99 | } 100 | } 101 | } 102 | 103 | /** 104 | * Find the offsets for the patches in a group. 105 | * 106 | * @param data Data buffer. 107 | * @param patches Patches group. 108 | * @returns The offsets or null. 109 | */ 110 | export function patchGroupOffsets( 111 | data: Readonly, 112 | patches: { 113 | count: number; 114 | find: readonly number[]; 115 | replace: readonly number[]; 116 | }[] 117 | ) { 118 | const offsets = []; 119 | for (const {find, count} of patches) { 120 | const found = [...findFuzzy(data, find)]; 121 | if (found.length !== count) { 122 | return null; 123 | } 124 | offsets.push(found); 125 | } 126 | return offsets; 127 | } 128 | 129 | /** 130 | * Patch one group and only from list of patch groups. 131 | * 132 | * @param data Data to be patched. 133 | * @param patches Patches list. 134 | * @param type Patch type. 135 | */ 136 | export function patchOnce( 137 | data: Uint8Array, 138 | patches: { 139 | count: number; 140 | find: readonly number[]; 141 | replace: readonly number[]; 142 | }[][], 143 | type: string 144 | ) { 145 | // Search the buffer for patch candidates. 146 | let foundOffsets = null; 147 | let foundGroup = null; 148 | for (const group of patches) { 149 | const offsets = patchGroupOffsets(data, group); 150 | if (!offsets) { 151 | continue; 152 | } 153 | if (foundOffsets) { 154 | throw new Error(`Multiple patch candidates for: ${type}`); 155 | } 156 | foundOffsets = offsets; 157 | foundGroup = group; 158 | } 159 | if (!foundGroup || !foundOffsets) { 160 | throw new Error(`No patch candidates for: ${type}`); 161 | } 162 | 163 | // Apply the patches to the buffer. 164 | const {length} = foundGroup; 165 | for (let i = 0; i < length; i++) { 166 | for (const offset of foundOffsets[i]) { 167 | writeFuzzy(data, offset, foundGroup[i].replace); 168 | } 169 | } 170 | } 171 | 172 | /** 173 | * A utility to slide values within a window. 174 | * 175 | * @param amount The amount to slide. 176 | * @param offset Window offset. 177 | * @param size Window size. 178 | * @returns Sliding functions. 179 | */ 180 | export function slider(amount: number, offset: number, size: number) { 181 | const end = offset + size; 182 | return { 183 | /** 184 | * For UINT32. 185 | * 186 | * @param data Data view. 187 | * @param i Integer offset. 188 | * @param le Little endian if true. 189 | */ 190 | u32: (data: DataView, i: number, le: boolean) => { 191 | const v = data.getUint32(i, le); 192 | if (v >= offset && v <= end) { 193 | data.setUint32(i, v + amount, le); 194 | } 195 | } 196 | }; 197 | } 198 | -------------------------------------------------------------------------------- /src/util/internal/windows/asm.ts: -------------------------------------------------------------------------------- 1 | // This file contains compile-time defined variables. 2 | 3 | export const OOD_I386 = '#{spec/asm/windows/ood-i386}' as unknown as { 4 | [p: string]: number[]; 5 | }; 6 | 7 | export const OOD_X8664 = '#{spec/asm/windows/ood-x86_64}' as unknown as { 8 | [p: string]: number[]; 9 | }; 10 | -------------------------------------------------------------------------------- /src/util/internal/windows/constants.ts: -------------------------------------------------------------------------------- 1 | // IMAGE_DATA_DIRECTORY indexes. 2 | export const IDD_RESOURCE = 2; 3 | export const IDD_EXCEPTION = 3; 4 | export const IDD_BASE_RELOCATION = 5; 5 | export const IDD_RESERVED = 15; 6 | 7 | // IMAGE_SECTION_HEADER characteristics. 8 | export const IMAGE_SCN_CNT_CODE = 0x00000020; 9 | export const IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040; 10 | export const IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080; 11 | export const IMAGE_SCN_MEM_SHARED = 0x10000000; 12 | export const IMAGE_SCN_MEM_EXECUTE = 0x20000000; 13 | export const IMAGE_SCN_MEM_READ = 0x40000000; 14 | export const IMAGE_SCN_MEM_WRITE = 0x80000000; 15 | -------------------------------------------------------------------------------- /src/util/internal/windows/exe.ts: -------------------------------------------------------------------------------- 1 | import {NtExecutable} from '@shockpkg/resedit'; 2 | 3 | import {align} from '../data.ts'; 4 | 5 | import { 6 | IDD_BASE_RELOCATION, 7 | IMAGE_SCN_CNT_CODE, 8 | IMAGE_SCN_CNT_INITIALIZED_DATA, 9 | IMAGE_SCN_CNT_UNINITIALIZED_DATA 10 | } from './constants.ts'; 11 | 12 | /** 13 | * Get the EXE section that includes an address. 14 | * 15 | * @param exe NtExecutable instance. 16 | * @param address The address. 17 | * @returns The section or null if section not found. 18 | */ 19 | export function exeSectionByAddress(exe: NtExecutable, address: number) { 20 | for (const {info, data} of exe.getAllSections()) { 21 | const {virtualAddress, virtualSize} = info; 22 | if ( 23 | address >= virtualAddress && 24 | address < virtualAddress + virtualSize 25 | ) { 26 | return { 27 | info: info as {virtualAddress: number}, 28 | data 29 | }; 30 | } 31 | } 32 | return null; 33 | } 34 | 35 | /** 36 | * Get the EXE code section. 37 | * 38 | * @param exe NtExecutable instance. 39 | * @returns The section. 40 | */ 41 | export function exeCodeSection(exe: NtExecutable) { 42 | const s = exeSectionByAddress(exe, exe.newHeader.optionalHeader.baseOfCode); 43 | if (!s || !s.data) { 44 | throw new Error(`Invalid PE code section`); 45 | } 46 | return { 47 | info: s.info, 48 | data: s.data 49 | }; 50 | } 51 | 52 | /** 53 | * Assert the given section is last section. 54 | * 55 | * @param exe NtExecutable instance. 56 | * @param index ImageDirectory index. 57 | * @param name Friendly name for messages. 58 | */ 59 | export function exeAssertLastSection( 60 | exe: NtExecutable, 61 | index: number, 62 | name: string 63 | ) { 64 | const section = exe.getSectionByEntry(index); 65 | if (!section) { 66 | throw new Error(`Missing section: ${index}:${name}`); 67 | } 68 | const allSections = exe.getAllSections(); 69 | let last = allSections[0].info; 70 | for (const {info} of allSections) { 71 | if (info.pointerToRawData > last.pointerToRawData) { 72 | last = info; 73 | } 74 | } 75 | const {info} = section; 76 | if (info.pointerToRawData < last.pointerToRawData) { 77 | throw new Error(`Not the last section: ${index}:${name}`); 78 | } 79 | } 80 | 81 | /** 82 | * Removes the reloc section if exists, fails if not the last section. 83 | * 84 | * @param exe NtExecutable instance. 85 | * @returns Restore function. 86 | */ 87 | export function exeRemoveReloc(exe: NtExecutable) { 88 | const section = exe.getSectionByEntry(IDD_BASE_RELOCATION); 89 | if (!section) { 90 | return () => {}; 91 | } 92 | const {size} = 93 | exe.newHeader.optionalHeaderDataDirectory.get(IDD_BASE_RELOCATION); 94 | exeAssertLastSection(exe, IDD_BASE_RELOCATION, '.reloc'); 95 | exe.setSectionByEntry(IDD_BASE_RELOCATION, null); 96 | return () => { 97 | exe.setSectionByEntry(IDD_BASE_RELOCATION, section); 98 | const {virtualAddress} = 99 | exe.newHeader.optionalHeaderDataDirectory.get(IDD_BASE_RELOCATION); 100 | exe.newHeader.optionalHeaderDataDirectory.set(IDD_BASE_RELOCATION, { 101 | virtualAddress, 102 | size 103 | }); 104 | }; 105 | } 106 | 107 | /** 108 | * Update the sizes in EXE headers. 109 | * 110 | * @param exe NtExecutable instance. 111 | */ 112 | export function exeUpdateSizes(exe: NtExecutable) { 113 | const {optionalHeader} = exe.newHeader; 114 | const {fileAlignment} = optionalHeader; 115 | let sizeOfCode = 0; 116 | let sizeOfInitializedData = 0; 117 | let sizeOfUninitializedData = 0; 118 | for (const { 119 | info: {characteristics, sizeOfRawData, virtualSize} 120 | } of exe.getAllSections()) { 121 | // eslint-disable-next-line no-bitwise 122 | if (characteristics & IMAGE_SCN_CNT_CODE) { 123 | sizeOfCode += sizeOfRawData; 124 | } 125 | // eslint-disable-next-line no-bitwise 126 | if (characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) { 127 | sizeOfInitializedData += Math.max( 128 | sizeOfRawData, 129 | align(virtualSize, fileAlignment) 130 | ); 131 | } 132 | // eslint-disable-next-line no-bitwise 133 | if (characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA) { 134 | sizeOfUninitializedData += align(virtualSize, fileAlignment); 135 | } 136 | } 137 | optionalHeader.sizeOfCode = sizeOfCode; 138 | optionalHeader.sizeOfInitializedData = sizeOfInitializedData; 139 | optionalHeader.sizeOfUninitializedData = sizeOfUninitializedData; 140 | } 141 | -------------------------------------------------------------------------------- /src/util/internal/windows/ood32.ts: -------------------------------------------------------------------------------- 1 | import {OOD_I386} from './asm.ts'; 2 | 3 | export const ood32 = [ 4 | // 30.0.0.113 5 | [ 6 | { 7 | count: 1, 8 | find: OOD_I386['30'], 9 | replace: OOD_I386['ret4'] 10 | } 11 | ], 12 | // 31.0.0.108 13 | [ 14 | { 15 | count: 1, 16 | find: OOD_I386['31'], 17 | replace: OOD_I386['ret4'] 18 | } 19 | ] 20 | ]; 21 | -------------------------------------------------------------------------------- /src/util/internal/windows/ood64.ts: -------------------------------------------------------------------------------- 1 | import {OOD_X8664} from './asm.ts'; 2 | 3 | export const ood64 = [ 4 | // 26.0.0.137, 32.0.0.270 5 | [ 6 | { 7 | count: 1, 8 | find: OOD_X8664['26'], 9 | replace: OOD_X8664['ret'] 10 | } 11 | ], 12 | // 30.0.0.134 13 | [ 14 | { 15 | count: 1, 16 | find: OOD_X8664['30'], 17 | replace: OOD_X8664['ret'] 18 | } 19 | ] 20 | ]; 21 | -------------------------------------------------------------------------------- /src/util/internal/windows/rsrc.ts: -------------------------------------------------------------------------------- 1 | import {NtExecutableResource, Resource, Data} from '@shockpkg/resedit'; 2 | 3 | /** 4 | * Parse PE version string to integers (MS then LS bits) or null. 5 | * 6 | * @param version Version string. 7 | * @returns Version integers ([MS, LS]) or null. 8 | */ 9 | function peVersionInts(version: string): [number, number] | null { 10 | const parts = version.split(/[,.]/); 11 | const numbers = []; 12 | for (const part of parts) { 13 | const n = /^\d+$/.test(part) ? +part : -1; 14 | if (n < 0 || n > 0xffff) { 15 | return null; 16 | } 17 | numbers.push(n); 18 | } 19 | return numbers.length 20 | ? [ 21 | // eslint-disable-next-line no-bitwise 22 | (((numbers[0] || 0) << 16) | (numbers[1] || 0)) >>> 0, 23 | // eslint-disable-next-line no-bitwise 24 | (((numbers[2] || 0) << 16) | (numbers[3] || 0)) >>> 0 25 | ] 26 | : null; 27 | } 28 | 29 | /** 30 | * Replace all the icons in all icon groups. 31 | * 32 | * @param rsrc NtExecutableResource instance. 33 | * @param iconData Icon data. 34 | */ 35 | export function rsrcPatchIcon( 36 | rsrc: NtExecutableResource, 37 | iconData: Readonly 38 | ) { 39 | const ico = Data.IconFile.from( 40 | iconData.buffer.slice(iconData.byteOffset, iconData.byteLength) 41 | ); 42 | for (const iconGroup of Resource.IconGroupEntry.fromEntries(rsrc.entries)) { 43 | Resource.IconGroupEntry.replaceIconsForResource( 44 | rsrc.entries, 45 | iconGroup.id, 46 | iconGroup.lang, 47 | ico.icons.map(icon => icon.data) 48 | ); 49 | } 50 | } 51 | 52 | /** 53 | * Update strings if present for all the languages. 54 | * 55 | * @param rsrc NtExecutableResource instance. 56 | * @param versionStrings Version strings. 57 | */ 58 | export function rsrcPatchVersion( 59 | rsrc: NtExecutableResource, 60 | versionStrings: Readonly<{[key: string]: string}> 61 | ) { 62 | for (const versionInfo of Resource.VersionInfo.fromEntries(rsrc.entries)) { 63 | // Get all the languages, not just available languages. 64 | const languages = versionInfo.getAllLanguagesForStringValues(); 65 | for (const language of languages) { 66 | versionInfo.setStringValues(language, versionStrings); 67 | } 68 | 69 | // Update integer values from parsed strings if possible. 70 | const {FileVersion, ProductVersion} = versionStrings; 71 | if (FileVersion) { 72 | const uints = peVersionInts(FileVersion); 73 | if (uints) { 74 | const [ms, ls] = uints; 75 | versionInfo.fixedInfo.fileVersionMS = ms; 76 | versionInfo.fixedInfo.fileVersionLS = ls; 77 | } 78 | } 79 | if (ProductVersion) { 80 | const uints = peVersionInts(ProductVersion); 81 | if (uints) { 82 | const [ms, ls] = uints; 83 | versionInfo.fixedInfo.productVersionMS = ms; 84 | versionInfo.fixedInfo.productVersionLS = ls; 85 | } 86 | } 87 | 88 | versionInfo.outputToResourceEntries(rsrc.entries); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/util/mac.test.ts: -------------------------------------------------------------------------------- 1 | import {describe, it} from 'node:test'; 2 | import {deepStrictEqual, strictEqual} from 'node:assert'; 3 | import {createHash} from 'node:crypto'; 4 | 5 | import {machoAppLauncher, machoTypesData} from './mac.ts'; 6 | 7 | const unhex = (hex: string) => 8 | new Uint8Array( 9 | [...(hex.replace(/\s/g, '').match(/../g) || [])].map(h => 10 | // eslint-disable-next-line unicorn/prefer-number-properties 11 | parseInt(h, 16) 12 | ) 13 | ); 14 | 15 | function sha256(data: Uint8Array) { 16 | return createHash('sha256').update(data).digest('hex'); 17 | } 18 | 19 | const machoTypes = [ 20 | { 21 | name: 'slim: ppc', 22 | data: unhex('FE ED FA CE 00 00 00 12 00 00 00 0A'), 23 | format: { 24 | cpuType: 0x00000012, 25 | cpuSubtype: 10 26 | }, 27 | launcher: 28 | '17414c123fe82ac74a89fad9c80e36d8b612ded5a520e35f3c33eabe75a023a7' 29 | }, 30 | { 31 | name: 'slim: ppc64', 32 | data: unhex('FE ED FA CF 01 00 00 12 80 00 00 00'), 33 | format: { 34 | cpuType: 0x01000012, 35 | cpuSubtype: 0x80000000 36 | }, 37 | launcher: 38 | '9e159161fc21b72de6fddb5fb9c60c0e34e649e4660248778219e58198adfb3d' 39 | }, 40 | { 41 | name: 'slim: i386', 42 | data: unhex('CE FA ED FE 07 00 00 00 03 00 00 00'), 43 | format: { 44 | cpuType: 0x00000007, 45 | cpuSubtype: 3 46 | }, 47 | launcher: 48 | 'e52e19fce336130824dcfd4731bf85db7e8e96628ef8c6a49769dc5247ef6ed0' 49 | }, 50 | { 51 | name: 'slim: x86_64', 52 | data: unhex('CF FA ED FE 07 00 00 01 03 00 00 80'), 53 | format: { 54 | cpuType: 0x01000007, 55 | cpuSubtype: 0x80000003 56 | }, 57 | launcher: 58 | 'f5b7625da819324f442cea1f3af83ea4b2bf0af1d185a7747d81b698a6168562' 59 | }, 60 | { 61 | name: 'fat: ppc, ppc64, i386, x86_64', 62 | data: unhex( 63 | [ 64 | 'CA FE BA BE 00 00 00 04', 65 | '00 00 00 12 00 00 00 0A 00 00 00 00 00 00 00 00 00 00 00 00', 66 | '01 00 00 12 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00', 67 | '00 00 00 07 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 00', 68 | '01 00 00 07 80 00 00 03 00 00 00 00 00 00 00 00 00 00 00 00' 69 | ].join('') 70 | ), 71 | format: [ 72 | { 73 | cpuType: 0x00000012, 74 | cpuSubtype: 10 75 | }, 76 | { 77 | cpuType: 0x01000012, 78 | cpuSubtype: 0x80000000 79 | }, 80 | { 81 | cpuType: 0x00000007, 82 | cpuSubtype: 3 83 | }, 84 | { 85 | cpuType: 0x01000007, 86 | cpuSubtype: 0x80000003 87 | } 88 | ], 89 | launcher: 90 | '4646bb12e944d4cc2e1b2649b5b33112237a69e01d1aa30d64994135b7969b1d' 91 | } 92 | ]; 93 | 94 | void describe('util/mac', () => { 95 | void describe('machoTypesData', () => { 96 | for (const {name, data, format} of machoTypes) { 97 | void it(name, () => { 98 | deepStrictEqual(machoTypesData(data), format); 99 | }); 100 | } 101 | }); 102 | 103 | void describe('machoAppLauncher', () => { 104 | for (const {name, format, launcher} of machoTypes) { 105 | void it(name, async () => { 106 | const data = await machoAppLauncher(format); 107 | deepStrictEqual(machoTypesData(data), format); 108 | strictEqual(sha256(data), launcher); 109 | }); 110 | } 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /src/util/windows.test.ts: -------------------------------------------------------------------------------- 1 | import {describe, it} from 'node:test'; 2 | import {strictEqual} from 'node:assert'; 3 | import {createHash} from 'node:crypto'; 4 | 5 | import {windowsLauncher} from './windows.ts'; 6 | 7 | function sha256(data: Uint8Array) { 8 | return createHash('sha256').update(data).digest('hex'); 9 | } 10 | 11 | const launcherTypes: ['i686' | 'x86_64', string][] = [ 12 | [ 13 | 'i686', 14 | '166e5cb9228842e98e59d0cae1578fd0d97c9754944dae6533678716f7fd1c1c' 15 | ], 16 | [ 17 | 'x86_64', 18 | '6a8e15452b1049ed9727eee65e1f8c81a6ff496f7e452c75268e2c3193dd61b1' 19 | ] 20 | ]; 21 | 22 | void describe('util/windows', () => { 23 | void describe('windowsLauncher', () => { 24 | for (const [type, hash] of launcherTypes) { 25 | void it(type, async () => { 26 | const data = await windowsLauncher(type); 27 | strictEqual(sha256(data), hash); 28 | }); 29 | } 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "**/*.js", 5 | "**/*.jsx", 6 | "**/*.mjs", 7 | "**/*.mjsx", 8 | "**/*.ts", 9 | "**/*.tsx" 10 | ], 11 | "compilerOptions": { 12 | "allowJs": true, 13 | "checkJs": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "module": "commonjs", 6 | "moduleResolution": "Node", 7 | "allowSyntheticDefaultImports": true, 8 | "allowImportingTsExtensions": true, 9 | "lib": ["ESNext"], 10 | "newLine": "lf", 11 | 12 | "alwaysStrict": true, 13 | "emitDecoratorMetadata": true, 14 | "experimentalDecorators": true, 15 | "forceConsistentCasingInFileNames": true, 16 | 17 | "strictFunctionTypes": true, 18 | "strictNullChecks": true, 19 | "strictPropertyInitialization": true, 20 | "strictBindCallApply": true, 21 | 22 | "noImplicitAny": true, 23 | "noImplicitReturns": true, 24 | "noImplicitThis": true, 25 | 26 | "declaration": true, 27 | "emitDeclarationOnly": true, 28 | 29 | "types": ["node"], 30 | "outDir": "dts" 31 | }, 32 | "include": ["src/**/*.ts", "src/**/*.tsx"] 33 | } 34 | --------------------------------------------------------------------------------