Merge branch 'feat/app-tester'

This commit is contained in:
Sam Carlton 2021-02-16 14:57:05 -06:00
commit 8c09c41cf0
41 changed files with 2972 additions and 8 deletions

View file

@ -117,3 +117,21 @@
.hover\:bg-blur:hover {
backdrop-filter: blur(15px);
}
.shimmer {
animation: placeHolderShimmer 1s infinite;
animation-timing-function: linear;
background: #f6f7f8;
background: linear-gradient(to right, rgba(0, 0, 0, 0.07) 8%, rgba(0, 0, 0, 0.45) 18%, rgba(0, 0, 0, 0.07) 33%);
background-size: 200% 100px;
background-attachment: fixed;
}
@keyframes placeHolderShimmer {
0% {
background-position: 100% 0;
}
100% {
background-position: -100% 0;
}
}

View file

@ -0,0 +1,148 @@
<template>
<div
:class="[
'vue-full-screen-file-drop',
'fixed inset-0',
classes
]"
:style="{
'z-index': 10000
}"
>
<slot>
<div
class="vue-full-screen-file-drop__content"
>
{{ text }}
</div>
</slot>
<input
ref="file-selector"
:style="{
//'z-index': 10001
}"
type="file"
accept="application/**"
multiple
class="absolute inset-0 w-screen h-screen"
@change="fileInputChanged"
>
</div>
</template>
<script>
export default {
// name: 'VueFullScreenFileDrop',
props: {
formFieldName: {
type: String,
default: 'upload',
},
text: {
type: String,
default: 'Upload Files',
},
},
data() {
return {
visible: false,
lastTarget: null,
};
},
computed: {
classes() {
return {
'vue-full-screen-file-drop--visible': true //this.visible,
};
},
},
mounted () {
window.addEventListener('dragenter', this.onDragEnter);
window.addEventListener('dragleave', this.onDragLeave);
window.addEventListener('dragover', this.onDragOver);
// window.addEventListener('drop', this.onDrop);
},
beforeDestroy () {
window.removeEventListener('dragenter', this.onDragEnter);
window.removeEventListener('dragleave', this.onDragLeave);
window.removeEventListener('dragover', this.onDragOver);
// window.removeEventListener('drop', this.onDrop);
},
methods: {
onDragEnter(e) {
this.lastTarget = e.target;
this.visible = true;
},
onDragLeave(e) {
if (e.target === this.lastTarget) {
this.visible = false;
}
},
onDragOver(e) {
e.preventDefault();
},
onDrop(e) {
e.preventDefault();
// this.visible = false;
// const files = e.dataTransfer.files;
// const formData = this.getFormData(files);
// this.$emit('drop', formData, files);
},
async fileInputChanged () {
console.log('file-selector', this.$refs['file-selector'])
const files = this.$refs['file-selector'].files
console.log('fileInputChanged files', files)
this.visible = false;
// const files = e.dataTransfer.files;
const formData = this.getFormData(files);
this.$emit('drop', formData, files);
},
getFormData(files) {
const formData = new FormData();
Array.prototype.forEach.call(files, file => {
formData.append(this.formFieldName, file, file.name);
});
return formData;
},
},
}
</script>
<style lang='css'>
.vue-full-screen-file-drop {
/* position: fixed;
top: 0;
left: 0;
z-index: 10000;
width: 100%;
height: 100%; */
background-color: rgba(0,0,0,0.4);
visibility: hidden;
opacity: 0;
transition: visibility 200ms, opacity 200ms;
}
.vue-full-screen-file-drop--visible {
opacity: 1;
visibility: visible;
}
.vue-full-screen-file-drop__content {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
color: #fff;
font-size: 4em;
}
.vue-full-screen-file-drop__content:before {
border: 5px dashed #fff;
content: "";
bottom: 60px;
left: 60px;
position: absolute;
right: 60px;
top: 60px;
}
</style>

View file

@ -161,6 +161,10 @@ export default {
label: 'Games',
url: '/games',
},
// {
// label: 'Apple Silicon App Test',
// url: '/apple-silicon-app-test',
// },
])
}
},

View file

@ -0,0 +1,616 @@
import plist from 'plist'
import axios from 'axios'
import parseMacho from './macho/index.js'
const prettyBytes = require('pretty-bytes')
const knownArchiveExtensions = new Set([
'app',
'dmg',
// 'pkg',
'zip',
// 'gz',
// 'bz2'
])
const notAppFileTypes = new Set([
'image',
'text',
'audio',
'video'
])
const knownAppExtensions = new Set([
'.app',
'.app.zip'
])
function isString( maybeString ) {
return (typeof maybeString === 'string' || maybeString instanceof String)
}
function isValidHttpUrl( string ) {
if ( !isString( string ) ) return false
let url
try {
url = new URL(string)
} catch (_) {
return false
}
return url.protocol === "http:" || url.protocol === "https:"
}
function callWithTimeout(timeout, func) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error("timeout")), timeout)
func().then(
response => resolve(response),
err => reject(new Error(err))
).finally(() => clearTimeout(timer))
})
}
let zip
export default class AppFilesScanner {
constructor( {
observableFilesArray,
testResultStore
} ) {
// Files to process
this.files = observableFilesArray
this.testResultStore = testResultStore
// https://gildas-lormeau.github.io/zip.js/
zip = require('@zip.js/zip.js')
// https://gildas-lormeau.github.io/zip.js/core-api.html#configuration
zip.configure({
workerScripts: true,
// workerScripts: {
// inflate: ["lib/z-worker-pako.js", "pako_inflate.min.js"]
// }
})
}
isApp ( file ) {
if ( file.type.includes('/') && notAppFileTypes.has( file.type.split('/')[0] ) ) return false
return true
}
getStatusMessage () {
// 'Drag and drop one or multiple apps'
// return `Searching for apps at ${ file.url }`
}
getFileStatusMessage ( file ) {
// CORS error - 'This page has asked not to be scanned. '
// Status Code Error - 'This page is not loading properly. '
// No app urls found - 'No apps found on this page. Try a different page or entering the package URL directly. You can also manually download the package then drop it on here. '
// 'Found # apps'
// Fetching / File Loading from drag and drop - 'Loading # apps'
// Unzipping, archive search and Parsing - 'Processing # of #'
// Not able to unzip - 'Unable to open package. Try a different file. '
// No Mach-o binary found - 'Could not find Mac App data in package. Try a different package. '
// Mach-o Parsing Error - 'Unable to scan package. Try a different one. '
// No ARM64 Architecture found - 'This App's binary is not compatible with Apple Silicon and will only run via Rosetta 2 translation, however, software vendors will sometimes will ship separate install files for Intel and ARM instead of a single one. You can try submitting the download page link for an app and we'll scan that. '
// ARM64 Architecture found -
return 'This App is natively compatible with Apple Silicon!'
}
// async scanPageForAppUrls () {
// }
// async downloadArchiveFromUrl () {
// }
async unzipFile ( file ) {
const fileReader = new zip.BlobReader( file.instance )//new FileReader()
fileReader.onload = function() {
// do something on FileReader onload
console.log('File Read')
file.statusMessage = '📖 Reading file'
}
fileReader.onerror = error => {
// do something on FileReader onload
console.error('File Read Error', error)
throw new Error('File Read Error', error)
}
fileReader.onprogress = (data) => {
if (data.lengthComputable) {
const progress = parseInt( ((data.loaded / data.total) * 100), 10 );
console.log('Read progress', progress)
file.statusMessage = `📖 Reading file. ${ progress }% read`
}
}
// console.log('fileReader', fileReader)
// https://gildas-lormeau.github.io/zip.js/core-api.html#zip-reading
const zipReader = new zip.ZipReader( fileReader )
// zipReader.onprogress = console.log
// zipReader.onerror = console.log
const entries = await zipReader.getEntries()
.then( entries => entries.map( entry => {
return entry
// return {
// filename: entry.filename,
// directory: entry.directory
// }
}) )
.catch( error => {
// console.warn('Unzip Error', error)
return error
})
// console.log('entries', entries)
if ( !Array.isArray(entries) ) {
file.statusMessage = '❔ Could not decompress file'
file.status = 'finished'
throw new Error('Could not decompress file')
// return new Error('Could not decompress file')
}
return entries
}
matchesMacho ( entry ) {
// Skip files that are deeper than 3 folders
if ( entry.filename.split('/').length > 4 ) return false
// Skip folders
if ( entry.filename.endsWith('/') ) return false
// `${ appName }.app/Contents/MacOS/${ appName }`
// Does this entry path match any of our wanted paths
return [
// `${ appName }.app/Contents/MacOS/${ appName }`
`.app/Contents/MacOS/`
].some( pathToMatch => {
return entry.filename.includes(pathToMatch)
})
}
matchesRootInfo ( entry ) {
// Skip files that are deeper than 2 folders
if ( entry.filename.split('/').length > 3 ) return false
// Skip folders
if ( entry.filename.endsWith('/') ) return false
// Does this entry path match any of our wanted paths
return [
// `zoom.us.app/Contents/Info.plist`
`.app/Contents/Info.plist`,
`.zip/Contents/Info.plist`
].some( pathToMatch => {
return entry.filename.endsWith(pathToMatch)
})
}
findEntries ( entries, matchersObject ) {
const matches = {}
// const matcherKeys = Object.keys( matchers )
// Create a new set to store found App Names
const appNamesInArchive = new Set()
// Search App Names in entries
entries.forEach( entry => {
// Look through filename parts
entry.filename.split('/').forEach( filenamePart => {
if ( filenamePart.includes('.app') ) {
const appName = filenamePart.split('.')[0]
appNamesInArchive.add( appName )
}
} )
for ( const key in matchersObject ) {
// Deos it match the matcher method
const entryMatches = matchersObject[key]( entry )
if ( entryMatches ) {
// If we haven't set up an array for this key
// then create one
if ( !Array.isArray(matches[key]) ) matches[key] = []
// Push this entry to our matching list
matches[key].push( entry )
}
}
} )
return matches
}
async parseMachOBlob ( machOBlob, fileName ) {
const machOFile = new File([machOBlob], fileName)
return await parseMacho( machOFile )
}
getBundleExecutablePath ( info ) {
if ( info.CFBundleExecutable.includes('/') ) return `/Contents/${ info.CFBundleExecutable }`
return `/Contents/MacOS/${ info.CFBundleExecutable }`
}
classifyBinaryEntryArchitecture ( binaryEntry ) {
// Find an ARM Architecture
const armArchitecture = binaryEntry.architectures.find( architecture => {
// if ( architecture.processorType === 0 ) return false
// If processorType not a string
// then return false
if ( !isString(architecture.processorType) ) return false
return architecture.processorType.toLowerCase().includes('arm')
})
// Was an ARM Architecture found
return (armArchitecture !== undefined)
}
async submitScanInfo ({
filename,
appVersion,
result,
machoMeta,
infoPlist
}) {
// Each file scanned: Filename, Type(Drop or URL), File URL, Datetime, Architectures, Mach-o Meta
// console.log( 'this.testResultStore', this.testResultStore )
const { supportedVersionNumber } = await axios.post( this.testResultStore , {
filename,
appVersion,
result,
machoMeta: JSON.stringify( machoMeta ),
infoPlist: JSON.stringify( infoPlist )
})
.then( response => response.data )
.catch(function (error) {
console.error(error)
})
return {
supportedVersionNumber
}
}
async scanFile ( file, scanIndex ) {
// If we've already scanned this
// then skip
if ( file.status === 'finished' ) return
if ( !this.isApp( file ) ) {
file.statusMessage = '⏭ Skipped. Not app or archive'
file.status = 'finished'
return
}
// console.log('file', file)
await new Promise(r => setTimeout(r, 1500 * scanIndex))
file.statusMessage = '🗃 Decompressing file'
console.log(`Decompressing file at ${ file.size }`)
let entries
try {
entries = await this.unzipFile( file )
} catch ( Error ) {
// console.warn( Error )
this.submitScanInfo ({
filename: file.name,
appVersion: null,
result: 'error_decompression_error',
machoMeta: null,
infoPlist: null
})
// Set status message as error
file.statusMessage = `${ Error.message }`
file.status = 'finished'
return
}
file.statusMessage = '👀 Scanning App Files'
console.log(`Searching entries`)
const foundEntries = this.findEntries( entries, {
macho: this.matchesMacho,
rootInfo: this.matchesRootInfo
})
// Clean out entries now that we're done with them
entries = undefined
// console.log('foundEntries', foundEntries)
// file.machOEntries = this.findMachOEntries( entries )
file.machOEntries = foundEntries.macho
// If no Macho files were found
// then report and stop
if ( file.machOEntries.length === 0 ) {
console.log(`No Macho files found for ${file.name}`, file.machOEntries)
this.submitScanInfo ({
filename: file.name,
appVersion: null,
result: 'error_no_macho_files',
machoMeta: null,
infoPlist: null
})
file.statusMessage = `❔ Unkown app format`
file.status = 'finished'
return
}
// Warn if Info.plist doesn't look right
if ( foundEntries.rootInfo.length > 1) {
console.warn('More than one root Info.plist found', foundEntries.rootInfo)
} else if ( foundEntries.rootInfo.length === 0 ) {
console.warn('No root Info.plist found', foundEntries.rootInfo)
}
// Break out root entry into a variable
const [ rootInfoEntry ] = foundEntries.rootInfo
// Get blob data from zip
// https://gildas-lormeau.github.io/zip.js/core-api.html#zip-entry
const infoXml = await rootInfoEntry.getData(
// writer
// https://gildas-lormeau.github.io/zip.js/core-api.html#zip-writing
new zip.TextWriter(),
// options
{
useWebWorkers: true,
// onprogress: (index, max) => {
// const percentageNumber = (index / max * 100)
// // onprogress callback
// console.log(`Writer progress ${percentageNumber}`)
// }
}
)
// Parse the Info.plist data
const info = plist.parse( infoXml )
file.appVersion = info.CFBundleShortVersionString
file.displayName = info.CFBundleDisplayName
// Set details
const detailsData = [
[ 'Version', info.CFBundleShortVersionString ],
[ 'Bundle Identifier', info.CFBundleIdentifier ],
[ 'File Mime Type', file.type ],
[ 'Copyright', info.NSHumanReadableCopyright ],
// [ 'Version', info.CFBundleShortVersionString ],
]
detailsData.forEach( ([ label, value ]) => {
if ( !value || value.length === 0 ) return
file.details.push({
label,
value,
})
} )
// console.log('infoFiles', file.name, {
// path: rootInfoEntry.filename,
// info
// })
console.log(`Parsing Macho ${ file.machOEntries.length } files`)
// console.log('info.CFBundleExecutable', info.CFBundleExecutable)
// console.log('info', info)
// console.log('file.machOEntries', file.machOEntries)
const bundelExecutablePath = this.getBundleExecutablePath( info )
const bundleExecutables = file.machOEntries.filter( machoEntry => {
return machoEntry.filename.includes(bundelExecutablePath)
})
// Warn if Bundle Executable doesn't look right
if ( bundleExecutables.length > 1) {
console.warn('More than one root bundleExecutable found', bundleExecutables)
} else if ( bundleExecutables.length === 0 ) {
console.warn('No root bundleExecutable found', bundleExecutables)
}
const [ bundleExecutable ] = bundleExecutables
console.log('Parsing ', bundleExecutable.filename, bundleExecutable.uncompressedSize / 1000 )
file.displayBinarySize = prettyBytes( bundleExecutable.uncompressedSize )
file.binarySize = bundleExecutable.uncompressedSize
// Get blob data from zip
// https://gildas-lormeau.github.io/zip.js/core-api.html#zip-entry
const bundleExecutableBlob = await bundleExecutable.getData(
// writer
// https://gildas-lormeau.github.io/zip.js/core-api.html#zip-writing
new zip.BlobWriter(),
// options
{
useWebWorkers: true
}
)
const mainExecutableMeta = await this.parseMachOBlob( bundleExecutableBlob, file.name )
console.log( 'mainExecutableMeta', mainExecutableMeta )
const binarySupportsNative = this.classifyBinaryEntryArchitecture( mainExecutableMeta )
// Submit the scan to get any reports on preexisting native reports
const { supportedVersionNumber } = await this.submitScanInfo ({
filename: file.name,
appVersion: file.appVersion,
result: finishedStatusMessage,
machoMeta: {
...mainExecutableMeta,
file: undefined,
architectures: mainExecutableMeta.architectures.map( architecture => {
return {
bits: architecture.bits,
fileType: architecture.fileType,
header: architecture.header,
loadCommandsInfo: architecture.loadCommandsInfo,
magic: architecture.magic,
offset: architecture.offset,
processorSubType: architecture.processorSubType,
processorType: architecture.processorType,
}
})
},
infoPlist: info
})
console.log('supportedVersionNumber', supportedVersionNumber)
let finishedStatusMessage = ''
if ( binarySupportsNative ) {
finishedStatusMessage = '✅ This app is natively compatible with Apple Silicon!'
// Shift this scan to the top
this.files.unshift( this.files.splice( scanIndex, 1 )[0] )
} else if ( supportedVersionNumber !== null ) {
finishedStatusMessage = [
'✅ A native version of this has been reported',
(supportedVersionNumber.length > 0) ? `as of v${supportedVersionNumber}` : null
].join(' ')
} else {
finishedStatusMessage = `🔶 This app file is not natively compatible with Apple Silicon and may only run via Rosetta 2 translation, however, software vendors will sometimes will ship separate install files for Intel and ARM instead of a single one. `
}
file.statusMessage = finishedStatusMessage
file.status = 'finished'
return
}
async scan ( fileList ) {
// Push files to our files array
Array.from(fileList).forEach( (fileInstance, scanIndex) => {
this.files.unshift( {
status: 'loaded',
displayName: null,
statusMessage: '⏳ File Loaded and Queud',
details: [],
appVersion: null,
displayAppSize: prettyBytes( fileInstance.size ),
displayBinarySize: null,
binarySize: null,
name: fileInstance.name,
size: fileInstance.size,
type: fileList.item( scanIndex ).type,
lastModifiedDate: fileInstance.lastModifiedDate,
instance: fileInstance,
item: fileList.item( scanIndex )
} )
})
const scanTimeoutSeconds = 30
// Scan for archives
await Promise.all( this.files.map( ( file, scanIndex ) => {
return new Promise( (resolve, reject) => {
const timer = setTimeout(() => {
file.statusMessage = '❔ Scan timed out'
file.status = 'finished'
reject(new Error('Scan timed out'))
}, scanTimeoutSeconds * 1000)
this.scanFile( file, scanIndex ).then(
response => resolve(response),
err => reject(new Error(err))
).finally(() => clearTimeout(timer))
})
}))
// Go through and set all files to finished to clean up any straglers
this.files.forEach( file => {
file.status = 'finished'
})
console.log('All Scans Finished')
return
}
}

4
helpers/macho/index.js Normal file
View file

@ -0,0 +1,4 @@
import MachoParser from './macho.js'
export default MachoParser

View file

@ -0,0 +1,13 @@
export let SECTION_ATTRIBUTES_USR = 0xff000000;
export let S_ATTR_PURE_INSTRUCTIONS = 0x80000000;
export let SECTION_ATTRIBUTES_SYS = 0x00ffff00;
export let S_ATTR_SOME_INSTRUCTIONS = 0x0000400;
export let S_ATTR_EXT_RELOC = 0x00000200;
export let S_ATTR_LOC_RELOC = 0x00000100;
export let uint128_t = 16;
export let uint64_t = 8;
export let uint32_t = 4;
export let uint16_t = 2;
export let uint8_t = 1;

134
helpers/macho/macho.cpu.js Normal file
View file

@ -0,0 +1,134 @@
//Global Constants
var CPU_TYPES = [];
var CPUSubTypeARM = [];
let CPU_ARCH_CONST = {
MASK: 0x01000000,
ABI64: 0xff000000,
toString: function() {
JSON.stringify(this);
}
};
let CPU_TYPE = {
ANY: -1,
VAX: 1,
MC680: 6,
X86: 7,
MIPS: 8,
MC98000: 10,
HPPA: 11,
ARM: 12,
ARM64: 16777228,
MC88000: 13,
SPARC: 14,
I860: 15,
POWERPC: 18,
POWERPC64: 16777234,
DESCRIPTION: function(search) {
let result = CPU_TYPES[search];
return (result != undefined) ? result : search;
},
toString: function() {
return JSON.stringify(this);
}
};
CPU_TYPES[CPU_TYPE.ANY] = "Any";
CPU_TYPES[CPU_TYPE.VAX] = "VAX";
CPU_TYPES[CPU_TYPE.MC680] = "MC680";
CPU_TYPE[CPU_TYPE.HPPA] = "HPPA";
CPU_TYPES[CPU_TYPE.ARM] = "ARM";
CPU_TYPES[CPU_TYPE.ARM64] = "ARM64";
CPU_TYPES[CPU_TYPE.X86] = "X86";
CPU_TYPES[CPU_TYPE.I860] = "I860";
CPU_TYPES[CPU_TYPE.MIPS] = "Mips";
CPU_TYPES[CPU_TYPE.MC98000] = "MC98000";
CPU_TYPES[CPU_TYPE.SPARC] = "Sparc";
CPU_TYPES[CPU_TYPE.POWERPC] = "Power PC";
CPU_TYPES[CPU_TYPE.POWERPC64] = "Power PC 64-bit";
let CPU_SUB_TYPE = {
ARM: {
MULTIPLE: -1,
ALL: 0,
ARM_A500_ARCH: 1,
ARM_A500: 2,
ARM_A440: 3,
ARM_M4: 4,
V4T: 5,
V6: 6,
V5TEJ: 7,
XSCALE: 8,
V7: 9,
V7F: 10,
V7S: 11,
V7K: 12,
V8: 13,
V6M: 14,
V7M: 15,
V7EM: 16,
DESCRIPTION: function(search) {
let result = CPUSubTypeARM[search];
return (result != undefined) ? result : search;
},
toString: function() {
return JSON.stringify(this);
}
},
ARM64: {
MULTIPLE: -1,
ALL: 0,
V8: 1,
DESCRIPTION: function(search) {
var CPUSubTypeARM64 = [];
CPUSubTypeARM64[CPU_SUB_TYPE.ARM64.ALL] = 'all';
let result = CPUSubTypeARM64[search];
return (result != undefined) ? result : search;
},
toString: function() {
return JSON.stringify(this);
}
},
POWERPC64: {
MULTIPLE: -1,
POWERPC_ALL: 0,
POWERPC_601: 1,
POWERPC_602: 2,
POWERPC_603: 3,
POWERPC_603e: 4,
POWERPC_603ev: 5,
POWERPC_604: 6,
POWERPC_604e: 7,
POWERPC_620: 8,
POWERPC_750: 9,
POWERPC_7400: 10,
POWERPC_7450: 11,
POWERPC_970: 100,
POWERPC_ALL_LIB64: 2147483648,
toString: function() {
return JSON.stringify(this);
}
},
toString: function() {
return JSON.stringify(this);
}
};
CPUSubTypeARM[CPU_SUB_TYPE.ARM.ALL] = 'all';
CPUSubTypeARM[CPU_SUB_TYPE.ARM.V4T] = 'v4t';
CPUSubTypeARM[CPU_SUB_TYPE.ARM.V6] = 'v6';
CPUSubTypeARM[CPU_SUB_TYPE.ARM.V5] = 'v5';
CPUSubTypeARM[CPU_SUB_TYPE.ARM.XSCALE] = 'xscale';
CPUSubTypeARM[CPU_SUB_TYPE.ARM.V7] = 'v7';
CPUSubTypeARM[CPU_SUB_TYPE.ARM.V7F] = 'v7f';
CPUSubTypeARM[CPU_SUB_TYPE.ARM.V7S] = 'v7s';
CPUSubTypeARM[CPU_SUB_TYPE.ARM.V7K] = 'v7k';
CPUSubTypeARM[CPU_SUB_TYPE.ARM.V6M] = 'v6m';
CPUSubTypeARM[CPU_SUB_TYPE.ARM.V7M] = 'v7m';
CPUSubTypeARM[CPU_SUB_TYPE.ARM.V7EM] = 'v7em';
export { CPU_TYPES, CPUSubTypeARM, CPU_ARCH_CONST, CPU_TYPE, CPU_SUB_TYPE }

View file

@ -0,0 +1,12 @@
var Cstr = function Cstr(buf) {
this.toString = function() {
return JSON.stringify(this);
};
var cstr = '';
for(var i = 0; i < buf.length; i++) {
cstr += String.fromCharCode(buf[i]);
}
return cstr;
};
export default Cstr

View file

@ -0,0 +1,72 @@
var Dylib = function Dylib(name, timestamp, current_version, compatibility_version) {
this.name = name || 0x00000000;
this.timestamp = timestamp || 0x00000000;
this.current_version = current_version || 0x00000000;
this.compatibility_version = compatibility_version || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};
var DylibCommand = function DylibCommand(cmd, cmdsize, dylib) {
this.cmd = cmd || 0x00000000;
this.cmdsize = cmdsize || 0x00000000;
this.dylib = dylib || new Dylib(); //needs better input validation
this.toString = function() {
return JSON.stringify(this);
};
};
var DylibTableOfContents = function DylibTableOfContents(symbol_index, module_index) {
this.symbol_index = symbol_index || 0x00000000;
this.module_index = module_index || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};
var DylibModule = function DylibModule(module_name, iextdefsym, nextdefsym, irefsym, nrefsym, ilocalsym, nlocalsym, iextrel, nextrel, iinit_iterm, ninit_nterm, objc_module_info_addr, objc_module_info_size) {
this.module_name = module_name || 0x00000000;
this.iextdefsym = iextdefsym || 0x00000000;
this.nextdefsym = nextdefsym || 0x00000000;
this.irefsym = irefsym || 0x00000000;
this.nrefsym = nrefsym || 0x00000000;
this.ilocalsym = ilocalsym || 0x00000000;
this.nlocalsym = nlocalsym || 0x00000000;
this.iextrel = iextrel || 0x00000000;
this.nextrel = nextrel || 0x00000000;
this.iinit_iterm = iinit_iterm || 0x00000000;
this.ninit_nterm = ninit_nterm || 0x00000000;
this.objc_module_info_addr = objc_module_info_addr || 0x00000000;
this.objc_module_info_size = objc_module_info_size || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};
var DylibModule64 = function DylibModule64(module_name, iextdefsym, nextdefsym, irefsym, nrefsym, ilocalsym, nlocalsym, iextrel, nextrel, iinit_iterm, ninit_nterm, objc_module_info_addr, objc_module_info_size) {
this.module_name = module_name || 0x00000000;
this.iextdefsym = iextdefsym || 0x00000000;
this.nextdefsym = nextdefsym || 0x00000000;
this.irefsym = irefsym || 0x00000000;
this.nrefsym = nrefsym || 0x00000000;
this.ilocalsym = ilocalsym || 0x00000000;
this.nlocalsym = nlocalsym || 0x00000000;
this.iextrel = iextrel || 0x00000000;
this.nextrel = nextrel || 0x00000000;
this.iinit_iterm = iinit_iterm || 0x00000000;
this.ninit_nterm = ninit_nterm || 0x00000000;
this.objc_module_info_addr = objc_module_info_addr || 0x00000000;
this.objc_module_info_size = objc_module_info_size || 0x0000000000000000;
this.toString = function() {
return JSON.stringify(this);
};
};
var DylibReference = {
isym:24,
flags:8,
toString: function() {
return JSON.stringify(this);
}
};

View file

@ -0,0 +1,7 @@
var DylinkerCommand = function DylinkerCommand(cmd, cmdsize) {
this.cmd = 0x00000000;
this.cmdsize = 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};

View file

@ -0,0 +1,73 @@
var FILE_TYPES = [];
var FILE_FLAGS = [];
/*
@Class FILE_TYPE
@Description
File Type is the class for identifying what MACH-O file is being dealed with.
*/
let FILE_TYPE = {
MH_OBJECT: 0x1, /* relocatable object file */
MH_EXECUTE: 0x2, /* demand paged executable file */
MH_FVMLIB: 0x3, /* fixed VM shared library file */
MH_CORE: 0x4, /* core file */
MH_PRELOAD: 0x5, /* preloaded executable file */
MH_DYLIB: 0x6, /* dynamicly bound shared library file*/
MH_DYLINKER: 0x7, /* dynamic link editor */
MH_BUNDLE: 0x8, /* dynamicly bound bundle file */
MH_DYLIB_STUB: 0x9,
MH_DSYM: 0xa,
MH_KEXT_BUNDLE: 0xb,
DESCRIPTION: function(search) {
let result = FILE_TYPES[search];
return (result != undefined) ? result : search;
},
toString: function() {
return JSON.stringify(this);
},
debugdescription: ""
};
FILE_TYPES[FILE_TYPE.MH_OBJECT] = 'Relocatable Object File';
FILE_TYPES[FILE_TYPE.MH_EXECUTE] = 'Demand Paged Executable File';
FILE_TYPES[FILE_TYPE.MH_FVMLIB] = 'Fixed Virtual Memory Shared Library File';
FILE_TYPES[FILE_TYPE.MH_CORE] = 'Core File';
FILE_TYPES[FILE_TYPE.MH_PRELOAD] = 'Preloaded Executable File';
FILE_TYPES[FILE_TYPE.MH_DYLIB] = 'Dynamically Bound Shared Library File';
FILE_TYPES[FILE_TYPE.MH_DYLINKER] = 'Dynamic Link Editor';
FILE_TYPES[FILE_TYPE.MH_BUNDLE] = 'Dynamically Bound Bundle File';
FILE_TYPES[FILE_TYPE.MH_DYLIB_STUB] = 'Dynamic Library Predefined Symbol';
FILE_TYPES[FILE_TYPE.MH_DSYM] = 'Dynamic Symbol';
FILE_TYPES[FILE_TYPE.MH_KEXT_BUNDLE] = 'Kernel Extension Bundle';
let FILE_FLAG = {
MH_NOUNDEFS: 0x1, /* the object file has no undefined references, can be executed */
MH_INCRLINK: 0x2, /* the object file is the output of an incremental link against a base file and can't be link edited again */
MH_DYLDLINK: 0x4, /* the object file is input for the dynamic linker and can't be staticly link edited again */
MH_BINDATLOAD: 0x8, /* the object file's undefined references are bound by the dynamic linker when loaded. */
MH_PREBOUND: 0x10, /* the file has it's dynamic undefined references prebound. */
DESCRIPTION: function(search) {
let result = FILE_FLAGS[search];
return (result != undefined) ? result : search;
},
toString: function() {
return JSON.stringify(this);
}
};
FILE_FLAGS[FILE_FLAG.MH_NOUNDEFS] = 'The object file has no undefined references and is executable.';
FILE_FLAGS[FILE_FLAG.MH_INCRLINK] = 'The object file is the output of an incremental link against a base file and can not be link edited again.';
FILE_FLAGS[FILE_FLAG.MH_DYLDLINK] = 'The object file is the input for the dynamic linker and can not be staticly link edited again.';
FILE_FLAGS[FILE_FLAG.MH_BINDATLOAD] = 'The object file\'s undefined references are bound by the dynamic linker when loaded.';
FILE_FLAGS[FILE_FLAG.MH_PREBOUND] = 'The file has it\'s dynamic undefined references prebound.';
export { FILE_TYPES, FILE_FLAGS, FILE_TYPE, FILE_FLAG }

View file

@ -0,0 +1,31 @@
let FLAGS = {
NOUNDEFS: 1,
INCRLINK: 2,
DYLDLINK: 4,
BINDATLOAD: 8,
PREBOUND: 16,
SPLIT_SEGS: 32,
LAZY_INIT: 64,
TWOLEVEL: 128,
FORCE_FLAT: 256,
NOMULTIDEFS: 512,
NOFIXPREBINDING: 1024,
PREBINDABLE: 2048,
ALLMODSBOUND: 4096,
SUBSECTIONS_VIA_SYMBOLS: 8192,
CANONICAL: 32768,
WEAK_DEFINES: 32768,
BINDS_TO_WEAK: 65536,
ALLOW_STACK_EXECUTION: 131072,
ROOT_SAFE: 262144,
SETUID_SAFE: 524288,
NOREEXPORTED_DYLIBS: 1048576,
PIE: 2097152,
DEAD_STRIPPABLE_DYLIB: 4194304,
HAS_TLV_DESCRIPTORS: 8388608,
NO_HEAP_EXECUTION: 16777216,
APP_EXTENSION_SAFE: 33554432
toString: function(){
return JSON.stringify(this);
}
};

View file

@ -0,0 +1,17 @@
var Fvmlib = function Fvmlib(name, minor_version, header_addr) {
this.name = name || 0x00000000;
this.minor_version = minor_version || 0x00000000;
this.header_addr = header_addr || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};
var FvmlibCommand = function FvmlibCommand(cmd, cmdsize, fvmlib) {
this.cmd = cmd || 0x00000000;
this.cmdsize = cmdsize || 0x00000000;
this.fvmlib = fvmlib || new Fvmlib(); //needs better input validation
this.toString = function() {
return JSON.stringify(this);
};
};

View file

@ -0,0 +1,49 @@
//Mach-O Fat file header
export var FatHeader = function FatHeader() {
this.magic = 0x00000000;
this.nfat_arch = 0x00000000;
this.toString = function() {
return JSON.stringify(this);
}
};
//Mach-O Fat file header
export var FatArch = function FatArch() {
this.cputype = 0x00000000;
this.cpusubtype = 0x00000000;
this.offset = 0x00000000;
this.size = 0x00000000;
this.align = 0x00000000;
this.toString = function() {
return JSON.stringify(this);
}
}
//Mach-O Binary Executable header 32-bit
export var MachoHeader = function MachoHeader() {
this.magic = 0x00000000;
this.cputype = 0x00000000;
this.cpusubtype = 0x00000000;
this.filetype = 0x00000000;
this.ncmds = 0x00000000;
this.sizeofcmds = 0x00000000;
this.flags = 0x00000000;
this.toString = function() {
return JSON.stringify(this);
}
};
//Mach-O Binary Executable header 64-bit
export var MachoHeader64 = function MachoHeader64() {
this.magic = 0x00000000;
this.cputype = 0x00000000;
this.cpusubtype = 0x00000000;
this.filetype = 0x00000000;
this.ncmds = 0x00000000;
this.sizeofcmds = 0x00000000;
this.flags = 0x00000000;
this.reserved = 0x00000000;
this.toString = function() {
return JSON.stringify(this);
}
};

317
helpers/macho/macho.js Normal file
View file

@ -0,0 +1,317 @@
//macho.js
//Written by Sem Voigtländer
//Licensed under the MIT License
import { mime_binary } from './mimetypes.js'
import { ReadUint32, ReadUint16LE, ReadUint32LE, ReadUint16 } from './memory.js'
import { uint32_t, uint64_t } from './macho.constants.js'
import { MAGIC } from './macho.magic.js'
import { MachoHeader64, MachoHeader } from './macho.header.js'
import { LOAD_COMMAND_TYPE, LoadCommand } from './macho.loadcommand.js'
import { CPU_TYPE, CPU_SUB_TYPE } from './macho.cpu.js'
import { FILE_FLAGS, FILE_TYPE } from './macho.file.js'
import Cstr from './macho.cstr.js'
import { SegmentCommand } from './macho.segment.js'
function default_callback(buffer) {
console.log('Received ' + buffer.byteLength / (1024 * 1024) + ' MB');
}
// https://stackoverflow.com/a/57139182/1397641
async function arrayBufferToBlob( buffer ) {
return new Blob([buffer])
}
let readers = []
let tempFile
let tempFiles
var ChunkReader = function ChunkReader(file, chunksize = (1024 * 1024), callback = default_callback) {
if(file == undefined) {
throw new Error('Invalid argument for file parameter.');
}
if(readers == undefined) { readers = []; } //Free list
this.chunksize = chunksize; //Read a kilobyte at a time
this.filesize = file.size;
this.offset = 0;
//Start reading the chunks
while(this.offset + this.chunksize <= this.filesize) {
this.blob = file.slice(this.offset, this.offset+this.chunksize);
readers[readers.length] = new FileReader();
readers[readers.length-1].onloadend = function(e) {
callback(e.target.result);
};
readers[readers.length-1].readAsArrayBuffer(this.blob);
// console.log('Sending chunk from 0x'+this.offset.toString(16));
this.offset+=this.chunksize;
}
for(obj = 0; obj < readers.length; obj++) {
readers[obj] = undefined;
}
// Clean up readers
readers = undefined
// free("readers");
};
export function MachoParser(file, callback) {
const machoOutputData = {}
//properties
this.reader = new FileReader();
function writeToCallback(val) {
return
// if(callbackElement) {
// callbackElement.innerHTML +='<tr>'+val+'</tr>';
// } else {
// throw new Error('Invalid callback.');
// }
}
/*
@Function FindMagic
@Params (Uint8Array) buffer, (bool) breakwhenfound
@Return (Array) offsets of found magics
*/
function FindMagic(data, breakwhenfound = false) {
var results = [];
for(var byte = 0; byte < (data.length - uint32_t); byte++) { //Read 32-bits at a time until the end of the buffer
var magic = ReadUint32(data, byte); //Read the next 32-bit magic value
if(MAGIC.VALIDATE(magic)) {
results.push(byte);
if(breakwhenfound) {
break;
}
}
}
return results;
}
/*
@Function MapFlags
@Params (Uint8Array)flags, (Object)map
@Return (Object) key-value mapped dictionary
*/
function MapFlags(value, map) {
var res = {};
for (var bit = 1; (value < 0 || bit <= value) && bit !== 0; bit <<= 1)
if (value & bit) res[map[bit]] = true; //If value and the bit are equal then map the value to the result
return res;
}
function ParseCommand(type, data, size, off) {
var cmd = null;
if(type == LOAD_COMMAND_TYPE.LC_SEGMENT) {
if(data.length < 48) {
if(process.env.VERBOSE){ console.log('Segment command OOB'); }
return new LoadCommand(type, data, size, off);
}
let name = new Cstr(data.slice(0, (4*uint32_t)));
cmd = new SegmentCommand(
type,
size,
name,
ReadUint32(data, (4*uint32_t)),
ReadUint32(data, (5*uint32_t)),
ReadUint32(data, (6*uint32_t)),
ReadUint32(data, (7*uint32_t)),
ReadUint32(data, (8*uint32_t)),
ReadUint32(data, (9*uint32_t)),
ReadUint32(data, (10*uint32_t)),
ReadUint32(data, (11*uint32_t))
);
function prot(p) {
var res = {read: false, write: false, exec: false};
return res;
}
let sectSize = 17 * uint32_t;
var sections = [];
//for(var i = 0, off = 48; i < nsects)
return cmd;
} else {
return new LoadCommand(type, data, size, off);
}
}
this.reader.onloadend = function(e) {
let fileType = mime_binary;
//Handle the file buffer and eventually create a Blob of the result
// blobUtil.
arrayBufferToBlob(e.target.result, fileType).then(function(blob) {
let filesize = ((blob.size / 1024) / 1024); //Calculate FileSize in Megabyte
tempFile = blob;
//Set up the file and print out for verbosity
machoOutputData.file = file
machoOutputData.fileSize = filesize
machoOutputData.architectures = []
// writeToCallback('<td><strong>File</strong></td><td>'+file.name.toString()+'</td>');
// writeToCallback('<td><strong>Size</strong></td><td>' + filesize.toFixed(2).toString() + 'MB</td>');
//Construct a new 8-bit array from the file buffer
let data = new Uint8Array(e.target.result);
let magics = FindMagic(data, false); //Try to find all Mach-O magics in the byte array
if ( process.env.DEBUG ) { console.log('Parsing all magics...'); }
//If magics where found, parse the binary.
if(magics.length > 0) {
for(var cMagic = 0; cMagic < magics.length; cMagic++) { //Start parsing the binary from each found magic's offset
const architecture = {}
let magicOff = magics[cMagic]; //The offset of the magic currently being parsed
let magic = ReadUint32(data, magicOff); //Read the magic from the byte array
let littleendian = MAGIC.ISLITTLEENDIAN(magic); //Get the endianness of the magic
let x64 = MAGIC.IS64BIT(magic); //Check which bit architecture is being used
// machoOutputData = {}; //Create the Mach-O information object
architecture.bits = (x64 ? "64-bit" : "32-bit"); //Get the architecture
architecture.endianness = (littleendian ? "little endian" : "big endian"); //Get the endianness
architecture.header = (x64 ? new MachoHeader64() : new MachoHeader()); //Depending on architecture, construct a new header
architecture.header.magic = magic; //Add the magic to the header
architecture.header.cputype = (littleendian ? ReadUint16LE(data, magicOff+uint32_t) : ReadUint16(data, magicOff+uint32_t)); //Read the cputype which comes after the magic
architecture.header.cpusubtype = (littleendian ? ReadUint16LE(data, magicOff+(2*uint32_t)) : ReadUint16(data, magicOff+(2*uint32_t))); //Read the cpu subtype which comes after the cputype
architecture.header.filetype = (littleendian ? ReadUint32LE(data, magicOff+(3*uint32_t)) : ReadUint32(data, magicOff+(3*uint32_t))); //Read the file type which comes after the cpu subtype
architecture.header.ncmds = (littleendian ? ReadUint32LE(data, magicOff+(4*uint32_t)) : ReadUint32(data, magicOff+(4*uint32_t))); //Read the number of commands which comes after the filetype
architecture.header.sizeofcmds =(littleendian ? ReadUint32LE(data, magicOff+(5*uint32_t)) : ReadUint32(data, magicOff+(5*uint32_t))); //Read the size of the commands which comes after the number of commands
architecture.header.flags = (littleendian ? ReadUint32LE(data, magicOff+(6*uint32_t)) : ReadUint32(data, magicOff+(6*uint32_t))); //Read the flags which come after the size of the commands
architecture.loadcommands = [];
var align = (x64 ? uint64_t : uint32_t); //Depending on our architecture set an align for parsing the load commands
for(var i = 0, off = magicOff; off < data.length, i < architecture.header.ncmds; i++) {
var curr_cmd = {};
curr_cmd.cmd = (littleendian ? ReadUint32LE(data, off) : ReadUint32(data, off)); //Read the command type from the offset
curr_cmd.cmdsize = (littleendian ? ReadUint32LE(data, off+align) : ReadUint32(data, off+align)); //Read the size of the command from the offset
curr_cmd.fileoff = off; //Add the offset of the loadcommand for later use
curr_cmd.data = data.slice(off, curr_cmd.cmdsize);
curr_cmd = ParseCommand(curr_cmd.cmd, curr_cmd.data, curr_cmd.cmdsize, curr_cmd.fileoff);
if(curr_cmd.cmdsize > 0) { //Commands with a size of zero are not valid or not interesting
architecture.loadcommands.push(curr_cmd);
}
off+=8;
i++; //Increate the loadcommand counter
}
architecture.offset = magicOff.toString(16)
architecture.magic = architecture.header.magic.toString(16)
architecture.processorType = CPU_TYPE.DESCRIPTION(architecture.header.cputype)
architecture.processorSubType = CPU_SUB_TYPE.ARM.DESCRIPTION(architecture.header.cpusubtype)
architecture.fileType = FILE_TYPE.DESCRIPTION(architecture.header.filetype)
/* Parse all collected information to Human Readable strings */
// writeToCallback('');
// writeToCallback('<td><strong>Header</strong></td>');
// writeToCallback('<td><strong>Offset</strong></td><td>0x'+magicOff.toString(16)+'</td>');
// writeToCallback('<td><strong>Magic</strong></td><td>0x'+architecture.header.magic.toString(16)+'</td>');
// writeToCallback('<td><strong>Bits</strong></td><td>'+architecture.bits+'</td>');
// writeToCallback('<td><strong>Endianness</strong></td><td>'+architecture.endianness+'</td>');
// writeToCallback('<td><strong>Processor type</strong></td><td>' + CPU_TYPE.DESCRIPTION(architecture.header.cputype)+'</td>');
// writeToCallback('<td><strong>Processor subtype</strong></td><td>'+ CPU_SUB_TYPE.ARM.DESCRIPTION(architecture.header.cpusubtype)+'</td>');
// writeToCallback('<td><strong>File type</strong></td><td>'+FILE_TYPE.DESCRIPTION(architecture.header.filetype)+'</td>');
// writeToCallback('<td><strong>Number of commands</strong></td><td>'+architecture.header.ncmds+'</td>');
// writeToCallback('<td><strong>Size of commands</strong></td><td>'+architecture.header.sizeofcmds+'</td>');
architecture.header.flags = MapFlags(architecture.header.flags, FILE_FLAGS);
architecture.header.flags = Object.keys(architecture.header.flags).map(function(k) { if(k){return k}});
// writeToCallback('<td><strong>Flags</strong></td><td>'+architecture.header.flags.toString().replace(',','<br>').replace(',','<br>')+"</td>");
// writeToCallback('');
// writeToCallback('<td><strong>Load Command</strong></td><td><strong>Size</strong></td><td><strong>Offset</strong></td>');
architecture.loadCommandsInfo = []
for(var curr_lc = 0; curr_lc < architecture.loadcommands.length; curr_lc++) {
architecture.loadCommandsInfo.push({
description: LOAD_COMMAND_TYPE.DESCRIPTION(architecture.loadcommands[curr_lc].cmd),
size: architecture.loadcommands[curr_lc].cmdsize,
offset: ` 0x${architecture.loadcommands[curr_lc].fileoff.toString(16)}`
})
// writeToCallback(
// '<td>'+
// LOAD_COMMAND_TYPE.DESCRIPTION(architecture.loadcommands[curr_lc].cmd) +
// '</td>'+
// '<td>'+
// architecture.loadcommands[curr_lc].cmdsize+
// '</td>'+
// '<td>'+
// ' 0x' + architecture.loadcommands[curr_lc].fileoff.toString(16) +
// '</td>');
}
magicOff = null;
littleendian = null;
x64 = null;
// blobUtil.
arrayBufferToBlob(data.buffer, fileType).then(function(blob){
if(!tempFiles) { tempFiles = []; }
tempFiles[tempFiles.length] = blob;
}).catch(console.log.bind(console));
machoOutputData.architectures.push( architecture )
}
callback( machoOutputData )
// console.log('The parser has finished')
} else { //(magics.length <= 0) (If we end up here it means no magics were found in the file).
// writeToCallback('This is not a valid Mach-O file.');
throw new Error('This is not a valid Mach-O file.')
}
}).catch(console.log.bind(console));
};
this.reader.readAsArrayBuffer(file);
// console.log('Parsing, please wait...');
};
export default async function ( file ) {
return new Promise( ( resolve, reject ) => {
try {
(new MachoParser( file, resolve ))
} catch ( error ) {
reject( error )
}
} )
}

View file

@ -0,0 +1,9 @@
var LinkeditDataCommand = function LinkeditDataCommand(cmd, cmdsize, dataoff, datasize) {
this.cmd = cmd || 0x00000000;
this.cmdsize = cmdsize || 0x00000000;
this.dataoff = dataoff || 0x00000000;
this.datasize = datasize || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};

View file

@ -0,0 +1,133 @@
//Global Constants
var LOAD_COMMAND_TYPES = [];
/*
@Class LoadCommand
@Description
*/
let LOAD_COMMAND_TYPE = {
LC_UNKNOWN: 0,
LC_SEGMENT: 1,
LC_SYMTAB: 2, //Symbol table
LC_SYMSEG: 3, //Segment
LC_THREAD: 4,
LC_UNIXTHREAD: 5,
LC_LOADFVMLIB: 6, /* load a specified fixed VM shared library */
LC_IDFVMLIB: 7, /* fixed VM shared library identification */
LC_IDENT: 8, /* object identification info (obsolete) */
LC_FVMFILE: 9, /* fixed VM file inclusion (internal use) */
LC_PREPAGE: 10, /* prepage command (internal use) */
LC_DYSYMTAB: 11, /* dynamic link-edit symbol table info */
LC_LOAD_DYLIB: 12, /* load a dynamicly linked shared library */
LC_ID_DYLIB: 13, /* dynamicly linked shared lib identification */
LC_LOAD_DYLINKER: 14, /* load a dynamic linker */
LC_ID_DYLINKER: 15,
LC_PREBOUND_DYLIB: 16, /* modules prebound for a dynamicly */
LC_ROUTINES: 17,
LC_SUB_FRAMEWORK: 18,
LC_SUB_UMBRELLA: 19,
LC_SUB_CLIENT: 20,
LC_SUB_LIBRARY: 21,
LC_TWOLEVEL_HINTS: 22,
LC_PREBIND_CKSUM: 23,
LC_SEGMENT_64: 25,
LC_ROUTINES_64: 26,
LC_UUID: 27,
LC_CODE_SIGNATURE: 29,
LC_SEGMENT_SPLIT_INFO: 30,
LC_LAZY_LOAD_DYLIB: 32,
LC_ENCRYPTION_INFO: 33,
LC_DYLD_INFO: 34,
LC_VERSION_MIN_MACOSX: 36,
LC_VERSION_MIN_IPHONEOS: 37,
LC_FUNCTION_STARTS: 38,
LC_DYLD_ENVIRONMENT: 39,
LC_DATA_IN_CODE: 41,
LC_SOURCE_VERSION: 42,
LC_DYLIB_CODE_SIGN_DRS: 43,
LC_ENCRYPTION_INFO_64: 44,
LC_LINKER_OPTION: 45,
LC_LINKER_OPTIMIZATION_HINT: 46,
LC_VERSION_MIN_TVOS: 47,
LC_VERSION_MIN_WATCHOS: 48,
LC_NOTE: 49,
LC_BUILD_VERSION: 50,
LC_LOAD_WEAK_DYLIB: 2147483672,
LC_RPATH: 2147483676,
LC_REEXPORT_DYLIB: 2147483679,
LC_DYLD_INFO_ONLY: 2147483682,
LC_LOAD_UPWARD_DYLIB: 2147483683,
LC_MAIN: 2147483688,
DESCRIPTION: function(search) {
let result = LOAD_COMMAND_TYPES[search];
return (result != undefined) ? result : search;
},
toString: function() {
return JSON.stringify(this);
}
};
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_SEGMENT] = '32-bits Segment command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_SYMTAB] = 'Symbol table command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_SYMSEG] = 'Symbol segment command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_THREAD] = 'Thread command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_UNIXTHREAD] = 'UNIX Thread command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_LOADFVMLIB] = 'Fixed Virtual Memory Library Load command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_IDFVMLIB] = 'Fixed Virtual Memory Library identification information command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_IDENT] = 'Object identification information command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_FVMFILE] = 'Fixed VM File inclusion commmand';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_PREPAGE] = 'Prepage command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_DYSYMTAB] = 'Dynamic Link-Edit Symbol Table information command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_LOAD_DYLIB] = 'Dynamically linked shared library identification command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_ID_DYLIB] = 'Dynamic Library Identifier';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_LOAD_DYLINKER] = 'Dynamic Linker Load Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_ID_DYLINKER] = 'Dynamic Linker Identifier';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_PREBOUND_DYLIB] = 'Prebound Dynamic Library Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_ROUTINES] = 'Routines Load Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_SUB_FRAMEWORK] = 'Sub Framework Load Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_SUB_UMBRELLA] = 'Sub Umbrella Load Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_SUB_CLIENT] = 'Sub Client Load Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_SUB_LIBRARY] = 'Sub Library Load Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_TWOLEVEL_HINTS] = 'TwoLevel Hints Load Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_PREBIND_CKSUM] = 'Prebind Checksum Load Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_SEGMENT_64] = '64-bits Segment Load Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_ROUTINES_64] = '64-bits Routines Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_UUID] = 'UUID Load Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_CODE_SIGNATURE] = 'Code Signature';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_SEGMENT_SPLIT_INFO] = 'Segment Split Information Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_LAZY_LOAD_DYLIB] = 'Lazy Dynamic Library Load Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_ENCRYPTION_INFO] = '32-bits encryption Information';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_DYLD_INFO] = 'Dynamic Loader Information Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_VERSION_MIN_MACOSX] = 'Minimum Version Requirement Command for OSX';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_VERSION_MIN_IPHONEOS] = 'Minimum Version Requirement Command for iPhone';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_FUNCTION_STARTS] = 'Function Starts Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_DYLD_ENVIRONMENT] = 'Dynamic Loader Environment Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_DATA_IN_CODE] = 'Data In Code Load Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_SOURCE_VERSION] = 'Source Version Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_DYLIB_CODE_SIGN_DRS] = 'Dynamic Library Code Sign Directories Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_ENCRYPTION_INFO_64] = '64-bits Encryption Information';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_LINKER_OPTION] = 'Linker Option Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_LINKER_OPTIMIZATION_HINT] = 'Linker Optimization Hint';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_VERSION_MIN_TVOS] = 'Minimum Version Requirement Command for tvOS';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_VERSION_MIN_WATCHOS] = 'Minimum Version Requirement Command for watchOS';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_NOTE] = 'Additional Note';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_BUILD_VERSION] = 'Build Version';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_LOAD_WEAK_DYLIB] = 'Weak Dynamic Library Load Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_RPATH] = 'Executable Path';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_REEXPORT_DYLIB] = 'Re-Exported Dynamic Library Load Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_DYLD_INFO_ONLY] = 'Dynamic Library Loader Information Only';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_LOAD_UPWARD_DYLIB] = 'Upward Dynamic Library Load Command';
LOAD_COMMAND_TYPES[LOAD_COMMAND_TYPE.LC_MAIN] = 'Main Entrypoint';
var LoadCommand = function LoadCommand(type, data, size, fileoff) {
this.cmd = type || 0;
this.cmdsize = size || 0;
this.data = data || new Uint8Array();
this.fileoff = fileoff || 0;
};
export { LOAD_COMMAND_TYPE, LOAD_COMMAND_TYPES, LoadCommand }

View file

@ -0,0 +1,72 @@
//Global Constants
var MAGICS = [];
/*
@Class MAGIC
@Description
Magic is a specific byte that indicates what for file it is.
In Mach-O there a multiple magics differing from endianess, bits or file type.
*/
let MAGIC =
{
//Macho Magics
FAT_MAGIC: 0xcafebabe,
FAT_CIGAM: 0xbebafeca,
FAT_MAGIC64: 0xcafebabf,
FAT_CIGAM64: 0xbfbafeca,
MH_MAGIC: 0xfeedface,
MH_CIGAM: 0xcefaedfe,
MH_MAGIC64: 0xfeedfacf,
MH_CIGAM64: 0xcffaedfe,
//Retrieve the magic name of a specified magic
//If an invalid magic was specifed undefined is returned
KEYFORVALUE: function(value) {
return MAGICS[value];
},
//Validate that a specified magic is correct
VALIDATE: function(magic) {
return MAGIC.KEYFORVALUE(magic) != undefined;
},
//returns true if the specified magic is a little-endian magic
ISLITTLEENDIAN: function(magic) {
if(!MAGIC.VALIDATE(magic)) //If it is not a valid magic we are basically done here
return false;
return MAGIC.KEYFORVALUE(magic).split('CIGAM').length > 1; //hacky trick to check if the magic value's keyname contains CIGAM which basically indicates little endian
},
//returns true if the specified magic is 64-bit
IS64BIT: function(magic) {
if(!MAGIC.VALIDATE(magic))
return false;
return MAGIC.KEYFORVALUE(magic).split('64').length > 1; //hacky trick to check if the magic value's keyname contains 64 which basically indicates 64-bit
},
toString : function() {
return JSON.stringify(this);
},
debugdescription: ""
};
MAGICS[MAGIC.FAT_MAGIC] = "FAT_MAGIC";
MAGICS[MAGIC.FAT_CIGAM] = "FAT_CIGAM";
MAGICS[MAGIC.FAT_MAGIC64] = "FAT_MAGIC64";
MAGICS[MAGIC.FAT_CIGAM64] = "FAT_CIGAM64";
MAGICS[MAGIC.MH_MAGIC] = "MH_MAGIC";
MAGICS[MAGIC.MH_CIGAM] = "MH_CIGAM";
MAGICS[MAGIC.MH_MAGIC64] = "MH_MAGIC64";
MAGICS[MAGIC.MH_CIGAM64] = "MH_CIGAM64";
export { MAGIC, MAGICS }

View file

@ -0,0 +1,25 @@
let OPERATORS = {
False: 0,
True: 1,
Ident: 2,
AppleAnchor: 3,
AnchorHash: 4,
InfoKeyValue: 5,
And: 6,
Or: 7,
CDHash: 8,
Not: 9,
InfoKeyField: 10,
CertField: 11,
TrustedCert: 12,
TrustedCerts: 13,
CertGeneric: 14,
AppleGenericAnchor: 15,
EntitlementField: 16,
CertPolicy: 17,
NamedAnchor: 18,
Platform: 20,
toString: function() {
return JSON.stringify(this);
}
};

View file

@ -0,0 +1,8 @@
var PrebindCksumCommand = function PrebindCksumCommand(cmd, cmdsize, cksum) {
this.cmd = cmd || 0x00000000;
this.cmdsize = cmdsize || 0x00000000;
this.cksum = cksum || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};

View file

@ -0,0 +1,10 @@
var PreboundDylibCommand = function PreboundDylibCommand(cmd, cmdsize, name, nmodules, linked_modules) {
this.cmd = cmd || 0x00000000;
this.cmdsize = cmdsize || 0x00000000;
this.name = name || 0x00000000;
this.nmodules = nmodules || 0x00000000;
this.linked_modules = linked_modules || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};

View file

@ -0,0 +1,30 @@
var RoutinesCommand = function RoutinesCommand(cmd, cmdsize, init_address, init_module, reserved1, reserved2, reserved3, reserved4, reserved5, reserved6) {
this.cmd = cmd || 0x00000000;
this.cmdsize = cmdsize || 0x00000000;
this.init_address = init_address || 0x00000000;
this.init_module = init_module || 0x00000000;
this.reserved1 = reserved1 || 0x00000000;
this.reserved2 = reserved2 || 0x00000000;
this.reserved3 = reserved3 || 0x00000000;
this.reserved4 = reserved4 || 0x00000000;
this.reserved5 = reserved5 || 0x00000000;
this.reserved6 = reserved6 || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};
var RoutinesCommand64 = function RoutinesCommand64(cmd, cmdsize, init_address, init_module, reserved1, reserved2, reserved3, reserved4, reserved5, reserved6) {
this.cmd = cmd || 0x00000000;
this.cmdsize = cmdsize || 0x00000000;
this.init_address = init_address || 0x0000000000000000;
this.init_module = init_module || 0x0000000000000000;
this.reserved1 = reserved1 || 0x0000000000000000;
this.reserved2 = reserved2 || 0x0000000000000000;
this.reserved3 = reserved3 || 0x0000000000000000;
this.reserved4 = reserved4 || 0x0000000000000000;
this.reserved5 = reserved5 || 0x0000000000000000;
this.reserved6 = reserved6 || 0x0000000000000000;
this.toString = function() {
return JSON.stringify(this);
};
};

View file

@ -0,0 +1,8 @@
var RpathCommand = function RpathCommand(cmd, cmdsize, path) {
this.cmd = cmd || 0x00000000;
this.cmdsize = cmdsize || 0x00000000;
this.path = path || 0x00000000;
this.toString = function() {
JSON.stringify(this);
};
};

View file

@ -0,0 +1,90 @@
//Global Constants
let SECTION_MASK = {
SECTION_TYPE: 0x000000ff,
SECTION_ATTRIBUTES: 0xffffff00
toString = function() {
return JSON.stringify(this);
}
};
let SECTION_TYPE = {
REGULAR: 0,
ZEROFILL: 1,
CSTRING_LITERALS: 2,
BYTE4_LITERALS: 3,
BYTE8_LITERALS: 4,
LITERAL_POINTERS: 5,
NON_LAZY_SYMBOL_POINTERS: 6,
LAZY_SYMBOL_POINTERS: 7,
SYMBOL_STUBS: 8,
MOD_INIT_FUNC_POINTERS: 9,
MOD_TERM_FUNC_POINTERS: 0xa,
COALESCED: 0xb,
GB_ZEROFILL: 0xc,
INTERPOSING: 0xd,
BYTE16_LITERALS: 0xe,
DTRACE_DOF: 0xf,
LAZY_DYLIB_SYMBOL_POINTERS: 0x10,
THREAD_LOCAL_REGULAR: 0x11,
THREAD_LOCAL_ZEROFILL: 0x12,
THREAD_LOCAL_VARIABLES: 0x13,
THREAD_LOCAL_VARIABLE_POINTERS: 0x14,
THREAD_LOCAL_INIT_FUNCTION_POINTERS: 0x15,
toString: function() {
return JSON.stringify(this);
}
};
let SECTION = {
SECT_TEXT: "__text",
SECT_FVMLIB_INIT0: "__fvmlib_init0",
SECT_FVMLIB_INIT1: "__fvmlib_init1",
SECT_DATA: "__data",
SECT_BSS: "__bss",
SECT_COMMON: "__common",
SECT_OBJC_SYMBOLS: "__symbol_table",
SECT_OBJC_MODULES: "__module_info",
SECT_OBJC_STRINGS: "__selector_strs",
SECT_OBJC_REFS: "__selector_refs",
SECT_ICON_HEADER: "__header",
SECT_ICON_TIFF: "__tiff",
toString: function() {
return JSON.stringify(this);
}
};
var Section = function Section(sectname, segname, addr, size, offset, align, reloff, nreloc, flags, reserved1, reserved2) {
this.sectname = new Uint8Array(16);
this.segname = new Uint8Array(16);
this.addr = addr || 0x00000000;
this.size = size || 0x00000000;
this.offset = offset || 0x00000000;
this.align = align || 0x00000000;
this.reloff = reloff || 0x00000000;
this.nreloc = nreloc || 0x00000000;
this.flags = flags || 0x00000000;
this.reserved1 = reserved1 || 0x00000000;
this.reserved2 = reserved2 || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};
var Section64 = function Section64(sectname, segname, addr, size, offset, align, reloff, nreloc, flags, reserved1, reserved2, reserved3) {
this.sectname = new Uint8Array(16);
this.segname = new Uint8Array(16);
this.addr = addr || 0x0000000000000000;
this.size = size || 0x0000000000000000;
this.offset = offset || 0x00000000;
this.align = align || 0x00000000;
this.reloff = reloff || 0x00000000;
this.nreloc = nreloc || 0x00000000;
this.flags = flags || 0x00000000;
this.reserved1 = reserved1 || 0x00000000;
this.reserved2 = reserved2 || 0x00000000;
this.reserved3 = reserved3 || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};

View file

@ -0,0 +1,73 @@
//Global Constants
var SG_FLAGS = [];
let SG_FLAG = {
SG_HIGHVM: 0x1,
SG_FVMLIB: 0x2,
SG_NORELOC: 0x4,
DESCRIPTION: function(search) {
let result = SG_FLAGS[search];
return (result != undefined) ? result : search;
},
toString: function() {
return JSON.stringify(this);
}
};
SG_FLAGS[SG_FLAG.SG_HIGHVM] = 'SG_HIGHVM';
SG_FLAGS[SG_FLAG.SG_FVMLIB] = 'SG_FVMLIB';
SG_FLAGS[SG_FLAG.SG_NORELOC] = 'SG_NORELOC';
let SEGMENT = {
SEG_PAGEZERO: "__PAGEZERO",
SEG_TEXT: "__TEXT",
SEG_ICON: "__ICON",
SEG_OBJC: "__OBJC",
SEG_LINKEDIT: "__LINKEDIT",
SEG_UNIXSTACK: "__UNIXSTACK",
SEG_DATA: "__DATA",
toString: function() {
return JSON.stringify(this);
}
};
//32-bits segment command
var SegmentCommand = function SegmentCommand(type, size, segname, vmaddr, vmsize, fileoff, filesize, maxprot, initprot, nsects, flags) {
this.cmd = type || 0x00000000;
this.cmdsize = size || 0x00000000;
this.segname = new TextEncoder("utf-8").encode(segname) || new Uint8Array(16);
this.vmaddr = vmaddr || 0x00000000;
this.vmsize = vmsize || 0x00000000;
this.fileoff = fileoff || 0x00000000;
this.filesize = filesize || 0x00000000;
this.maxprot = maxprot || 0x00000000;
this.initprot = initprot || 0x00000000;
this.nsects = nsects || 0x00000000;
this.flags = flags || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};
//64-bits segment command
var SegmentCommand64 = function SegmentCommand64(cmd, cmdsize, segname, vmaddr, vmsize, fileoff, filesize, maxprot, initprot, nsects, flags) {
this.cmd = cmd || 0x00000000;
this.cmdsize = cmdsize || 0x00000000;
this.segname = new TextEncoder("utf-8").encode(segname) || new Uint8Array(16);
this.vmaddr = vmaddr || 0x0000000000000000;
this.vmsize = vmsize || 0x0000000000000000;
this.fileoff = fileoff || 0x0000000000000000;
this.filesize = filesize || 0x0000000000000000;
this.maxprot = maxprot || 0x00000000;
this.initprot = initprot || 0x00000000;
this.nsects = nsects || 0x00000000;
this.flags = flags || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};
export { SG_FLAGS, SG_FLAG, SEGMENT, SegmentCommand, SegmentCommand64 }

View file

@ -0,0 +1,12 @@
//Global Constants
let SIGNATURES = {
REQUIREMENT: 0xfade0c00,
REQUIREMENTS: 0xfade0c01,
CODEDIRECTORY: 0xfade0c02,
ENTITLEMENT: 0xfade7171,
BLOBWRAPPER: 0xfade0b01,
EMBEDDED_SIGNATURE: 0xfade0cc0,
DETACHED_SIGNATURE: 0xfade0cc1,
CODE_SIGN_DRS: 0xfade0c05
};

View file

@ -0,0 +1,40 @@
let STABS = {
GSYM: 0x20,
FNAME: 0x22,
FUN: 0x24,
STSYM: 0x26,
LCSYM: 0x28,
MAIN: 0x2a,
BNSYM: 0x2e,
PC: 0x30,
AST: 0x32,
MAC_UNDEF: 0x3a,
OPT: 0x3c,
RSYM: 0x40,
SLINE: 0x44,
DSLINE: 0x46,
BSLINE: 0x48,
ENSYM: 0x4e,
SSYM: 0x60,
SO: 0x64,
OSO: 0x66,
LSYM: 0x80,
BINCL: 0x82,
SOL: 0x84,
PARAMS: 0x86,
VERSION: 0x88,
OLEVEL: 0x8a,
PSYM: 0xa0,
EINCL: 0xa2,
ENTRY: 0xa4,
LBRAC: 0xc0,
EXCL: 0xc2,
RBRAC: 0xe0,
BCOMM: 0xe2,
ECOMM: 0xe4,
ECOML: 0xe8,
LENG: 0xfe,
toString: function() {
return JSON.stringify(this);
}
};

View file

@ -0,0 +1,8 @@
var SubClientCommand = function SubClientCommand(cmd, cmdsize, client) {
this.cmd = cmd || 0x00000000;
this.cmdsize = cmdsize || 0x00000000;
this.client = client || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};

View file

@ -0,0 +1,5 @@
var SubFrameworkCommand = function SubFrameworkCommand(cmd, cmdsize, umbrella) {
this.cmd = cmd || 0x00000000;
this.cmdsize = cmdsize || 0x00000000;
this.umbrella = umbrella || 0x00000000;
};

View file

@ -0,0 +1,8 @@
var SubLibraryCommand = function SubLibraryCommand(cmd, cmdsize, sub_library) {
this.cmd = cmd || 0x00000000;
this.cmdsize = cmdsize || 0x00000000;
this.sub_library = sub_library || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};

View file

@ -0,0 +1,8 @@
var SubUmbrellaCommand = function SubUmbrellaCommand(cmd, cmdsize, sub_umbrella) {
this.cmd = cmd || 0x00000000;
this.cmdsize = cmdsize || 0x00000000;
this.sub_umbrella = sub_umbrella || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};

View file

@ -0,0 +1,56 @@
var SymtabCommand = function SymtabCommand(cmd, cmdsize, symoff, nsyms, stroff, strsize) {
this.cmd = cmd || 0x00000000;
this.cmdsize = cmdsize || 0x00000000;
this.symoff = symoff || 0x00000000;
this.nsyms = nsyms || 0x00000000;
this.stroff = stroff || 0x00000000;
this.strsize = strsize || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};
var DySymtabCommand = function DySymtabCommand(cmd,
cmdsize,
ilocalsym,
nlocalsym,
iextdefsym,
nextdefsym,
iundefsym,
nundefsym,
ntoc,
modtaboff,
nmodtab,
extrefsymoff,
nextrefsymoff,
nextrefsyms,
indirectsymoff,
ninderectsyms,
extreloff,
nextrel,
locreloff,
nlocrel) {
this.cmd = cmd || 0x00000000;
this.cmdsize = cmdsize || 0x00000000;
this.ilocalsym = ilocalsym || 0x00000000;
this.nlocalsym = nlocalsym || 0x00000000;
this.iextdefsym = iextdefsym || 0x00000000;
this.nextdefsym = nextdefsym || 0x00000000;
this.iundefsym = iundefsym || 0x00000000;
this.nundefsym = nundefsym || 0x00000000;
this.ntoc = ntoc || 0x00000000;
this.modtaboff = modtaboff || 0x00000000;
this.nmodtab = nmodtab || 0x00000000;
this.extrefsymoff = extrefsymoff || 0x00000000;
this.nextrefsymoff = nextrefsymoff || 0x00000000;
this.nextrefsyms = nextrefsyms || 0x00000000;
this.indirectsymoff= indirectsymoff || 0x00000000;
this.ninderectsyms = ninderectsyms || 0x00000000;
this.extreloff = extreloff || 0x00000000;
this.nextrel = nextrel || 0x00000000;
this.locreloff = locreloff || 0x00000000;
this.nlocrel = nlocrel || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};

View file

@ -0,0 +1,8 @@
var ThreadCommand = function ThreadCommand(cmd, cmdsize, name) {
this.cmd = cmd || 0x00000000;
this.cmdsize = cmdsize || 0x00000000;
this.name = name || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};

View file

@ -0,0 +1,17 @@
var TwoLevelHintsCommand = function TwoLevelHintsCommand(cmd, cmdsize, offset, nhints) {
this.cmd = cmd || 0x00000000;
this.cmdsize = cmdsize || 0x00000000;
this.offset = offset || 0x00000000;
this.nhints = nhints || 0x00000000;
this.toString = function() {
return JSON.stringify(this);
};
};
var TwoLevelHint = {
isub_image:8,
itoc:24,
toString: function() {
return JSON.stringify(this);
}
};

View file

@ -0,0 +1,8 @@
var UUIDCommand = function UUIDCommand(cmd, cmdsize, uuid) {
this.cmd = cmd || 0x00000000;
this.cmdsize = cmdsize || 0x00000000;
this.uuid = uuid || new Uint8Array(16); //needs better input validation
this.toString = function() {
return JSON.stringify(this);
};
};

429
helpers/macho/memory.js Normal file
View file

@ -0,0 +1,429 @@
//memory.js
//Written by Sem Voigtländer
//Licensed under the MIT License
/*
Functionality for geting the memory size of a javascript object
*/
export function free(objectName)
{
if(eval(objectName) != 'undefined')
{
console.log('Helping the garbage collector a little bit with freeing.');
eval(objectName + '=undefined;');
}
}
export function sizeof(object) {
var objectList = [];
var stack = [object];
var bytes = 0;
while (stack.length) {
var value = stack.pop();
if (typeof value === 'boolean') {
bytes += 4;
} else if (typeof value === 'string') {
bytes += value.length * 2;
} else if (typeof value === 'number') {
bytes += 8;
} else if (
typeof value === 'object' &&
objectList.indexOf(value) === -1
) {
objectList.push(value);
for (var i in value) {
stack.push(value[i]);
}
}
}
return bytes;
}
/*
Functions for swapping the endianness of an integer
Supports 16, 32 and 64 bit integers. (Experimental 128-bits as well)
Smaller integers are automatically casted to the corresponding size.
Bigger integers are not casted to smaller integers as they may lose precision.
*/
export function SwapEndian16(integer) {
var _hex = '0';
var _output = '';
//Input validation
if(typeof integer !== 'number')
throw new Error('Invalid argument. argument provided is not an integer.');
if(integer < 0)
throw new Error('Invalid argument. argument provided may not be negative.');
_hex = integer.toString(16); //Conver the input to a hexadecimal string
//Conver the hexadecimal string to a 16-bit integer hexadecimal string
while(_hex.length < 4)
_hex = '0'+_hex;
let _arr = _hex.split(''); //Convert the 16-bit integer hexadecimal string to a char array
//Sanity check
if (_arr.length != 4)
throw new Error('Invalid argument. argument provided is not a 16-bit integer.');
//Swap the endianness
_output += (_arr[2] + _arr[3]).toString();
_output += (_arr[0] + _arr[1]).toString();
//Output sanity check
if(_output.length != 4)
throw new Error('Sanity check failed. Output is not a 16-bit integer');
_output = parseInt('0x'+_output); //convert the hexadecimal output string to an integer
//Conversion sanity check
if(_output === NaN)
throw new Error('Conversion from a hexadecimal string to an integer failed.');
return _output;
}
export function SwapEndian32(integer) {
var _hex = '0';
var _output = '';
//Input validation
if(typeof integer !== 'number')
throw new Error('Invalid argument. argument provided is not an integer.');
if(integer < 0)
throw new Error('Invalid argument. argument provided may not be negative.');
_hex = integer.toString(16); //Conver the input to a hexadecimal string
//Conver the hexadecimal string to a 32-bit integer hexadecimal string
while(_hex.length < 8)
_hex = '0'+_hex;
let _arr = _hex.split(''); //Convert the 32-bit integer hexadecimal string to a char array
//Sanity check
if (_arr.length != 8)
throw new Error('Invalid argument. argument provided is not a 32-bit integer.');
//Swap the endianness
_output += (_arr[6] + _arr[7]).toString();
_output += (_arr[4] + _arr[5]).toString();
_output += (_arr[2] + _arr[3]).toString();
_output += (_arr[0] + _arr[1]).toString();
//Output sanity check
if(_output.length != 8)
throw new Error('Sanity check failed. Output is not a 32-bit integer');
_output = parseInt('0x'+_output); //convert the hexadecimal output string to an integer
//Conversion sanity check
if(_output === NaN)
throw new Error('Conversion from a hexadecimal string to an integer failed.');
//Lets help javascripts garbage collector a bit
_arr = undefined;
_hex = undefined;
return _output;
}
export function SwapEndian64(integer) {
var _hex = '0';
var _output = '';
//Input validation
if(typeof integer !== 'number')
throw new Error('Invalid argument. argument provided is not an integer.');
if(integer < 0)
throw new Error('Invalid argument. argument provided may not be negative.');
_hex = integer.toString(16); //Conver the input to a hexadecimal string
//Conver the hexadecimal string to a 64-bit integer hexadecimal string
while(_hex.length < 16)
_hex = '0'+_hex;
let _arr = _hex.split(''); //Convert the 64-bit integer hexadecimal string to a char array
//Sanity check
if (_arr.length != 16)
throw new Error('Invalid argument. argument provided is not a 64-bit integer.');
//Swap the endianness
_output += (_arr[14] + _arr[15]).toString();
_output += (_arr[12] + _arr[13]).toString();
_output += (_arr[10] + _arr[11]).toString();
_output += (_arr[8] + _arr[9]).toString();
_output += (_arr[6] + _arr[7]).toString();
_output += (_arr[4] + _arr[5]).toString();
_output += (_arr[2] + _arr[3]).toString();
_output += (_arr[0] + _arr[1]).toString();
//Output sanity check
if(_output.length != 16)
throw new Error('Sanity check failed. Output is not a 64-bit integer');
_output = parseInt('0x'+_output); //convert the hexadecimal output string to an integer
//Conversion sanity check
if(_output === NaN)
throw new Error('Conversion from a hexadecimal string to an integer failed.');
//Lets help javascripts garbage collector a bit
_arr = undefined;
_hex = undefined;
return _output;
}
export function SwapEndian128(integer) {
var _hex = '0';
var _output = '';
//Input validation
if(typeof integer !== 'number')
throw new Error('Invalid argument. argument provided is not an integer.');
if(integer < 0)
throw new Error('Invalid argument. argument provided may not be negative.');
_hex = integer.toString(16); //Conver the input to a hexadecimal string
//Conver the hexadecimal string to a 128-bit integer hexadecimal string
while(_hex.length < 32)
_hex = '0'+_hex;
let _arr = _hex.split(''); //Convert the 128-bit integer hexadecimal string to a char array
//Sanity check
if (_arr.length != 32)
throw new Error('Invalid argument. argument provided is not a 128-bit integer.');
//Swap the endianness
_output += (_arr[30] + _arr[31]).toString();
_output += (_arr[28] + _arr[29]).toString();
_output += (_arr[26] + _arr[27]).toString();
_output += (_arr[24] + _arr[25]).toString();
_output += (_arr[22] + _arr[23]).toString();
_output += (_arr[20] + _arr[21]).toString();
_output += (_arr[18] + _arr[19]).toString();
_output += (_arr[16] + _arr[17]).toString();
_output += (_arr[14] + _arr[15]).toString();
_output += (_arr[12] + _arr[13]).toString();
_output += (_arr[10] + _arr[11]).toString();
_output += (_arr[8] + _arr[9]).toString();
_output += (_arr[6] + _arr[7]).toString();
_output += (_arr[4] + _arr[5]).toString();
_output += (_arr[2] + _arr[3]).toString();
_output += (_arr[0] + _arr[1]).toString();
//Output sanity check
if(_output.length != 32)
throw new Error('Sanity check failed. Output is not a 128-bit integer');
_output = parseInt('0x'+_output); //convert the hexadecimal output string to an integer
//Conversion sanity check
if(_output === NaN)
throw new Error('Conversion from a hexadecimal string to an integer failed.');
//Lets help javascripts garbage collector a bit
_arr = undefined;
_hex = undefined;
return _output;
}
/*
Functions for reading integers from a given offset in a given array.
Supports 16, 32 and 64 bit integers. (Experimental 128-bits as well)
It also supports reading with a different Endianness.
*/
//Big Endian
export function ReadUint16(arr, off) {
if(arr.length < 2 || off > arr.length) //sanity check
throw new Error('Cannot read OOB.');
return parseInt("0x" +
arr[off].toString(16) +
arr[off + 1].toString(16)
);
}
export function ReadUint32(arr, off) {
if(arr.length < 4 || off > arr.length) //sanity check
throw new Error('Cannot read OOB.');
return parseInt("0x" +
arr[off].toString(16) +
arr[off + 1].toString(16) +
arr[off + 2].toString(16) +
arr[off + 3].toString(16)
);
}
export function ReadUint64(arr, off) {
if(arr.length < 8 || off > arr.length) //sanity check
throw new Error('Cannot read OOB.');
return parseInt("0x" +
arr[off].toString(16) +
arr[off + 1].toString(16) +
arr[off + 2].toString(16) +
arr[off + 3].toString(16) +
arr[off + 4].toString(16) +
arr[off + 5].toString(16) +
arr[off + 6].toString(16) +
arr[off + 7].toString(16)
);
}
export function ReadUint128(arr, off) {
if(arr.length < 16 || off > arr.length) //sanity check
throw new Error('Cannot read OOB.');
return parseInt("0x" +
arr[off].toString(16) +
arr[off + 1].toString(16) +
arr[off + 2].toString(16) +
arr[off + 3].toString(16) +
arr[off + 4].toString(16) +
arr[off + 5].toString(16) +
arr[off + 6].toString(16) +
arr[off + 7].toString(16) +
arr[off + 8].toString(16) +
arr[off + 9].toString(16) +
arr[off + 10].toString(16) +
arr[off + 11].toString(16) +
arr[off + 12].toString(16) +
arr[off + 13].toString(16) +
arr[off + 14].toString(16) +
arr[off + 15].toString(16)
);
}
//Little Endian
export function ReadUint16LE(arr, off) {
if(arr.length < 2 || off > arr.length) //sanity check
throw new Error('Cannot read OOB.');
return parseInt("0x" +
arr[off + 1].toString(16) +
arr[off].toString(16)
);
}
export function ReadUint32LE(arr, off) {
if(arr.length < 4 || off > arr.length) //sanity check
throw new Error('Cannot read OOB.');
return parseInt("0x" +
arr[off + 3].toString(16) +
arr[off + 2].toString(16) +
arr[off + 1].toString(16) +
arr[off].toString(16)
);
}
export function ReadUint64LE(arr, off) {
if(arr.length < 8 || off > arr.length) //sanity check
throw new Error('Cannot read OOB.');
return parseInt("0x" +
arr[off + 7].toString(16) +
arr[off + 6].toString(16) +
arr[off + 5].toString(16) +
arr[off + 4].toString(16) +
arr[off + 3].toString(16) +
arr[off + 2].toString(16) +
arr[off + 1].toString(16) +
arr[off].toString(16)
);
}
export function ReadUint128LE(arr, off) {
if(arr.length < 16 || off > arr.length) //sanity check
throw new Error('Cannot read OOB.');
return parseInt("0x" +
arr[off + 15].toString(16) +
arr[off + 14].toString(16) +
arr[off + 13].toString(16) +
arr[off + 12].toString(16) +
arr[off + 11].toString(16) +
arr[off + 10].toString(16) +
arr[off + 9].toString(16) +
arr[off + 8].toString(16) +
arr[off + 7].toString(16) +
arr[off + 6].toString(16) +
arr[off + 5].toString(16) +
arr[off + 4].toString(16) +
arr[off + 3].toString(16) +
arr[off + 2].toString(16) +
arr[off + 1].toString(16) +
arr[off].toString(16)
);
}
export function writeUint8(arr, off, val) {
if(arr.length < 2 || off > arr.length || off < 0) {
throw new Error('Cannot write OOB.');
}
if(val.length > 0xff)
throw new Error('Invalid argument. This should be a valid 8-bit integer.');
let old = arr[off];
arr[off] = val;
}
export function writeUint16(arr, off, val) {
if(arr.length < 4 || off > arr.length || off < 0) { //We should never underflow or overflow our buffer
throw new Error('Cannot write OOB.');
}
if(val.length > 0xffff) //Values that after serialization never will be a 16 bit integer should never be treated
throw new Error('Invalid argument. This should be a valid 16-bit integer.');
let old = new Array(arr[off], arr[off+1]);
if(val <= 0xff) { //Integers below 256 are always valid
arr[off] = val;
} else if(val > 0xff && val <= 0xfff) { //Integers bigger than 256 but below 4096 should be treated as two bytes instead of one and fixed with a leading zero
arr[off] = parseInt('0x'+(val.toString(16).split('')[0] + val.toString(16).split('')[1]));
arr[off+1] = parseInt('0x0'+val.toString(16).split('')[2]);
} else if(val > 0xff && val <= 0xffff) { //Integers bigger than 256 and below 65535 should be treated as two bytes and can be splitted without having to fix them with a leading zero
arr[off] = parseInt('0x'+(val.toString(16).split('')[0] + val.toString(16).split('')[1]));
arr[off+1] = parseInt('0x'+val.toString(16).split('')[2] + val.toString(16).split('')[3]);
}
}

View file

@ -0,0 +1,6 @@
//MimeTypes Library
export let mime_binary = 'application/octet-stream';
export let mime_zip = 'application/octet-stream';
export let mime_html = 'text/html';
export let mime_text = 'text/plain';

View file

@ -95,7 +95,8 @@ export default {
target: 'static',
publicRuntimeConfig: {
allUpdateSubscribe: process.env.ALL_UPDATE_SUBSCRIBE
allUpdateSubscribe: process.env.ALL_UPDATE_SUBSCRIBE,
testResultStore: process.env.TEST_RESULT_STORE
},
/*
@ -328,8 +329,19 @@ export default {
** You can extend webpack config here
*/
extend(config, ctx) {
// Client
if (ctx.isClient) {
// Push meta import rule for zip.js
config.module.rules.push({
test: /\.js$/,
loader: require.resolve('@open-wc/webpack-import-meta-loader')
})
}
// Run ESLint on save
if (ctx.isDev && ctx.isClient) {
config.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,

47
package-lock.json generated
View file

@ -1853,6 +1853,11 @@
}
}
},
"@open-wc/webpack-import-meta-loader": {
"version": "0.4.7",
"resolved": "https://registry.npmjs.org/@open-wc/webpack-import-meta-loader/-/webpack-import-meta-loader-0.4.7.tgz",
"integrity": "sha512-F3d1EHRckk2+ZpgEEAgVITp8BU9DYLBhKOhNMREeQ1BwILRIhrt+V1bebpnd0Mz595jzd7Yh1wSibLsXibkCpg=="
},
"@types/anymatch": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",
@ -2304,6 +2309,11 @@
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
"dev": true
},
"@zip.js/zip.js": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.2.6.tgz",
"integrity": "sha512-vD9jB0B3Ed6dun2x4EK6IkOelNmMhRdJOgQ8P0F3OZblvb9bJN9q+w2o0uTCxOTnl7vwmS9/tog6hw7mTbeCcg=="
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@ -2961,8 +2971,7 @@
"base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"dev": true
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"bfj": {
"version": "6.1.2",
@ -9049,6 +9058,23 @@
"find-up": "^4.0.0"
}
},
"plist": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.1.tgz",
"integrity": "sha512-GpgvHHocGRyQm74b6FWEZZVRroHKE1I0/BTjAmySaohK+cUn+hZpbqXkc3KWgW3gQYkqcQej35FohcT0FRlkRQ==",
"requires": {
"base64-js": "^1.2.3",
"xmlbuilder": "^9.0.7",
"xmldom": "0.1.x"
},
"dependencies": {
"xmlbuilder": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
}
}
},
"posix-character-classes": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@ -10202,10 +10228,9 @@
"dev": true
},
"pretty-bytes": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.4.1.tgz",
"integrity": "sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA==",
"dev": true
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.5.0.tgz",
"integrity": "sha512-p+T744ZyjjiaFlMUZZv6YPC5JrkNj8maRmPaQCWFJFplUAzpIUTRaTcS+7wmZtUoFXHtESJb23ISliaWyz3SHA=="
},
"pretty-error": {
"version": "2.1.2",
@ -12640,6 +12665,11 @@
}
}
},
"vue-full-screen-file-drop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/vue-full-screen-file-drop/-/vue-full-screen-file-drop-2.0.0.tgz",
"integrity": "sha512-LPonSWmJGeeMB9iXDtkLr3s2Kyd1VeayvYdoHmdHaKe1BmQlRRN4PrnkFPuLPAQBI/caUU26PUl+xwHHDgMDvA=="
},
"vue-hot-reload-api": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",
@ -13530,6 +13560,11 @@
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz",
"integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ=="
},
"xmldom": {
"version": "0.1.31",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz",
"integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ=="
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

View file

@ -17,6 +17,8 @@
"dependencies": {
"@fontsource/inter": "^4.0.1",
"@nuxtjs/sitemap": "^2.4.0",
"@open-wc/webpack-import-meta-loader": "^0.4.7",
"@zip.js/zip.js": "^2.2.6",
"axios": "^0.21.0",
"cross-env": "^5.2.0",
"lazysizes": "^5.3.0-beta1",
@ -24,8 +26,11 @@
"marked": "^1.2.7",
"node-html-parser": "^2.0.0",
"observe-element-in-viewport": "0.0.15",
"plist": "^3.0.1",
"pretty-bytes": "^5.5.0",
"scroll-into-view-if-needed": "^2.2.26",
"slugify": "^1.4.6"
"slugify": "^1.4.6",
"vue-full-screen-file-drop": "^2.0.0"
},
"devDependencies": {
"@nuxtjs/tailwindcss": "^3.3.4",

View file

@ -0,0 +1,334 @@
<template>
<section class="container py-24">
<div class="flex flex-col items-center space-y-24">
<header class="flex flex-col items-center space-y-6 md:px-12">
<div class="title-container relative">
<h1 class="title text-3xl md:text-5xl font-hairline leading-tight text-center">
Apple Silicon App Test
</h1>
<div
class="beta-pill absolute h-5 text-xs bg-white-2 flex justify-center items-center outline-0 rounded-full ease px-2"
style="top: -1em; right: 0;"
>
Beta
</div>
</div>
<h2 class="subtitle md:text-xl text-center">
Check for Apple Silicon compatibility for your apps before you buy an M1 Mac.
</h2>
</header>
<div class="app-tester w-full space-y-4 pb-64">
<div
class="relative w-full flex flex-col justify-center items-center space-y-4 pb-8"
>
<button
:class="[
'rounded-xl text-3xl font-semibold scale-150 bg-darkest neumorphic-shadow focus:outline-none py-4 px-6',
isLoadingFiles ? 'shimmer' : ''
]"
@click="triggerFilepicker"
>{{ isLoadingFiles ? 'Loading Files' : 'Select Apps' }}</button>
<template v-if="isLoadingFiles">
<div>Loading usually takes about a minute per 500mb</div>
<button
class="underline"
@click="isLoadingFiles = false"
>Cancel</button>
</template>
<small>Supports: Mac Apps, Zip files, and <em>some</em> DMG files. Bigger files take longer.</small>
<input
ref="file-selector"
type="file"
accept="application/**"
multiple
hidden
@change="fileInputChanged"
>
<!-- Directories only: webkitdirectory directory -->
</div>
<div
v-if="foundFiles.length !== 0"
class="w-full text-center"
>
Total Files: {{ foundFiles.length }}
</div>
<div
v-if="foundFiles.length !== 0"
class="app-scans-container relative divide-y divide-gray-700 w-full rounded-lg border border-gray-700 bg-gradient-to-br from-darker to-dark spac-y-3 my-4 px-5"
>
<ul
class="results-container divide-y divide-gray-700"
>
<li
v-for="( appScan, index ) in foundFiles"
:key="`${appScan.name}-${index}`"
:ref="`${appScan.name}-row`"
class="relative"
>
<!-- app.endpoint: {{ app.endpoint }} -->
<div
:class="[
'flex flex-col justify-center inset-x-0 hover:bg-darkest border-2 border-white border-opacity-0 hover:border-opacity-50 focus:outline-none focus:bg-gray-50 duration-300 ease-in-out rounded-lg space-y-3 -mx-5 pl-5 md:pl-20 pr-6 md:pr-64 py-5',
(appScan.status !== 'finished') ? 'shimmer' : ''
]"
style="transition-property: border;"
>
<div class="absolute hidden left-0 h-12 w-12 rounded-full md:flex items-center justify-center bg-darker">
{{ appScan.name.charAt(0) }}
</div>
{{ appScan.displayName || appScan.name }}
{{ appScan.appVersion ? `- v${appScan.appVersion}` : '' }}
{{ appScan.displayAppSize ? `- App ${appScan.displayAppSize}` : '' }}
{{ appScan.displayBinarySize ? `- Binary ${appScan.displayBinarySize}` : '' }}
<div class="text-sm leading-5 font-bold">
{{ appScan.statusMessage }}
</div>
<!-- appScan.binarySize: {{ appScan.binarySize }} -->
<div
v-if="appScan.binarySize && appScan.binarySize < (10 ^ 6)"
class="text-sm leading-5 font-bold"
>
Large Binary - This scan may take a while an/or have issues
</div>
<details class="w-full pt-6">
<summary class="cursor-pointer mb-3">Details</summary>
<div>
<div v-if="appScan.details.length === 0">No details available</div>
<ul v-else>
<li
v-for="( detail ) in appScan.details"
:key="`${appScan.name}-detail-${detail.label}`"
><strong>{{ detail.label }}</strong> <span v-html="detail.value" /></li>
</ul>
</div>
</details>
</div>
</li>
</ul>
</div>
</div>
<div class="w-full max-w-2xl">
<details
v-for="([ question, answer ], index) in faqs"
:key="`question-${index}`"
class="w-full"
>
<summary class="cursor-pointer">{{ question }}</summary>
<div class="p-4">
<p>{{ answer }}</p>
</div>
</details>
</div>
<!-- <pre class="w-full">{{ appsBeingScanned }}</pre> -->
<AllUpdatesSubscribe
:input-class-groups="{
shadow: 'hover:neumorphic-shadow',
bg: '',
focus: 'bg-transparent neumorphic-shadow pl-8',
blur: 'placeholder-white text-center border border-transparent bg-transparent opacity-50 hover:opacity-100 px-3',
}"
class="my-12"
/>
</div>
<!-- <client-only>
<FullScreenFileDrop @drop="onDrop" />
</client-only> -->
</section>
</template>
<script>
// import axios from 'axios'
// import AppFilesScanner from '~/helpers/app-files-scanner.js'
import LinkButton from '~/components/link-button.vue'
import AllUpdatesSubscribe from '~/components/all-updates-subscribe.vue'
export default {
// async asyncData () {
// return {
// allAppSearchLinks,
// customSummaryNumbers: getListSummaryNumbers(allList)
// }
// },
components: {
// FullScreenFileDrop: () => process.client ? import('~/components/fullscreen-file-drop.vue') : null,
LinkButton,
AllUpdatesSubscribe
},
data: function () {
return {
query: '',
isLoadingFiles: false,
appsBeingScanned: []
}
},
computed: {
foundFiles () {
return this.appsBeingScanned.filter( appScan => {
return !appScan.statusMessage.includes('⏭')
})
},
faqs () {
return [
[
'Non-native Apps (🔶)',
`
You can try getting the latest version from the developer\'s the download page scan that.
You can also request a manual review to determine the current status of the app on Rosetta 2.
`
],
[
'Why can\'t it tell me if an app will work on Rosetta 2? ',
`
Currently, Rosetta 2 is a proprietary Apple software that is only available on macOS on Apple Silicon devices.
This means there isn't any way to test Rosetta 2 compatibility with an app without a physical Apple Silicon device and so you definitely can't test that with just a website alone... for now...
Feel free to signup for email updates.
`
],
[
'Don\'t all previous Mac Apps work via Rosetta 2 translation? ',
`
Most apps will work with Rosetta 2 translation well but it's not a perfect technology.
Some apps will have various small issues and graphical bugs but will work well enough, a few apps will fail to launch entirely and some apps will runs with virtually no issues and perform even faster than an equivalent Intel-based Mac.
For now the best way to determine how well an app will run under Rosetta 2 is by human review.
`
]
]
},
title () {
return `Apple Silicon Compatibility`
},
description () {
return `Check for Apple Silicon compatibility for any of your apps instantly before you buy an M1 Mac. `
}
},
mounted () {
this.scanner = null
},
methods: {
log ( thing ) {
console.log( thing )
},
triggerFilepicker () {
this.isLoadingFiles = true
// this.watchFileInput()
this.$refs['file-selector'].dispatchEvent(new MouseEvent('click'))
},
async fileInputChanged () {
this.isLoadingFiles = false
// console.log('file-selector', this.$refs['file-selector'])
// Get FileList from input
const fileList = this.$refs['file-selector'].files
// If the scanner instance is not set up yet
// then create and initialize it
if ( this.scanner === null ) {
console.log('Initializing scanner instance')
// Bring in code
const { default: AppFilesScanner} = await import('~/helpers/app-files-scanner.js')
// Initialize instance
this.scanner = new AppFilesScanner({
observableFilesArray: this.appsBeingScanned,
testResultStore: this.$config.testResultStore
})
}
// console.log('fileInputChanged files', fileList)
this.scanner.scan( fileList )
},
// async onDrop ( formData, fileList ) {
// console.log('Off to the races')
// // await new Promise(r => setTimeout(r, 2000))
// const formValues = formData.values()
// for ( const value of formValues ) {
// console.log( 'value', value )
// }
// // console.log( 'formData', formData.values() ) // Can be posted to server
// // console.log( 'fileList', fileList ) // Can get access to things like file name and size
// this.scanner.scan( fileList )
// }
// async onQueryUpdate ( $event ) {
// // console.log('$event', $event)
// this.query = $event
// return
// }
},
head() {
return {
title: this.title,
meta: [
// hid is used as unique identifier. Do not use `vmid` for it as it will not work
{
'hid': 'description',
'name': 'description',
'content': this.description
},
// Twitter Card
{
'hid': 'twitter:title',
'property': 'twitter:title',
'content': this.title
},
{
'hid': 'twitter:description',
'property': 'twitter:description',
'content': this.description
},
{
'property': 'twitter:url',
'content': `${process.env.URL}${this.$nuxt.$route.path}`
},
]
}
}
}
</script>