diff --git a/assets/css/tailwind.css b/assets/css/tailwind.css index b01a3fc..9121065 100644 --- a/assets/css/tailwind.css +++ b/assets/css/tailwind.css @@ -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; + } +} diff --git a/components/fullscreen-file-drop.vue b/components/fullscreen-file-drop.vue new file mode 100644 index 0000000..89143a1 --- /dev/null +++ b/components/fullscreen-file-drop.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/components/navbar.vue b/components/navbar.vue index 4ccec3b..8615eb8 100644 --- a/components/navbar.vue +++ b/components/navbar.vue @@ -161,6 +161,10 @@ export default { label: 'Games', url: '/games', }, + // { + // label: 'Apple Silicon App Test', + // url: '/apple-silicon-app-test', + // }, ]) } }, diff --git a/helpers/app-files-scanner.js b/helpers/app-files-scanner.js new file mode 100644 index 0000000..5154f3c --- /dev/null +++ b/helpers/app-files-scanner.js @@ -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 + } + +} diff --git a/helpers/macho/index.js b/helpers/macho/index.js new file mode 100644 index 0000000..6770937 --- /dev/null +++ b/helpers/macho/index.js @@ -0,0 +1,4 @@ +import MachoParser from './macho.js' + + +export default MachoParser diff --git a/helpers/macho/macho.constants.js b/helpers/macho/macho.constants.js new file mode 100644 index 0000000..c78a021 --- /dev/null +++ b/helpers/macho/macho.constants.js @@ -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; + diff --git a/helpers/macho/macho.cpu.js b/helpers/macho/macho.cpu.js new file mode 100644 index 0000000..b41ad0b --- /dev/null +++ b/helpers/macho/macho.cpu.js @@ -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 } diff --git a/helpers/macho/macho.cstr.js b/helpers/macho/macho.cstr.js new file mode 100644 index 0000000..4c94b5c --- /dev/null +++ b/helpers/macho/macho.cstr.js @@ -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 diff --git a/helpers/macho/macho.dylib.js b/helpers/macho/macho.dylib.js new file mode 100644 index 0000000..12369a1 --- /dev/null +++ b/helpers/macho/macho.dylib.js @@ -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); + } +}; \ No newline at end of file diff --git a/helpers/macho/macho.dylinker.js b/helpers/macho/macho.dylinker.js new file mode 100644 index 0000000..49850e5 --- /dev/null +++ b/helpers/macho/macho.dylinker.js @@ -0,0 +1,7 @@ +var DylinkerCommand = function DylinkerCommand(cmd, cmdsize) { + this.cmd = 0x00000000; + this.cmdsize = 0x00000000; + this.toString = function() { + return JSON.stringify(this); + }; +}; \ No newline at end of file diff --git a/helpers/macho/macho.file.js b/helpers/macho/macho.file.js new file mode 100644 index 0000000..416e9aa --- /dev/null +++ b/helpers/macho/macho.file.js @@ -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 } diff --git a/helpers/macho/macho.flags.js b/helpers/macho/macho.flags.js new file mode 100644 index 0000000..bb1f0fe --- /dev/null +++ b/helpers/macho/macho.flags.js @@ -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); + } +}; \ No newline at end of file diff --git a/helpers/macho/macho.fvmlib.js b/helpers/macho/macho.fvmlib.js new file mode 100644 index 0000000..2ddf954 --- /dev/null +++ b/helpers/macho/macho.fvmlib.js @@ -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); + }; +}; \ No newline at end of file diff --git a/helpers/macho/macho.header.js b/helpers/macho/macho.header.js new file mode 100644 index 0000000..b5ae248 --- /dev/null +++ b/helpers/macho/macho.header.js @@ -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); + } +}; diff --git a/helpers/macho/macho.js b/helpers/macho/macho.js new file mode 100644 index 0000000..43f5b48 --- /dev/null +++ b/helpers/macho/macho.js @@ -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 +=''+val+''; + // } 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('File'+file.name.toString()+''); + // writeToCallback('Size' + filesize.toFixed(2).toString() + 'MB'); + + //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('Header'); + // writeToCallback('Offset0x'+magicOff.toString(16)+''); + // writeToCallback('Magic0x'+architecture.header.magic.toString(16)+''); + // writeToCallback('Bits'+architecture.bits+''); + // writeToCallback('Endianness'+architecture.endianness+''); + // writeToCallback('Processor type' + CPU_TYPE.DESCRIPTION(architecture.header.cputype)+''); + // writeToCallback('Processor subtype'+ CPU_SUB_TYPE.ARM.DESCRIPTION(architecture.header.cpusubtype)+''); + // writeToCallback('File type'+FILE_TYPE.DESCRIPTION(architecture.header.filetype)+''); + // writeToCallback('Number of commands'+architecture.header.ncmds+''); + // writeToCallback('Size of commands'+architecture.header.sizeofcmds+''); + + 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('Flags'+architecture.header.flags.toString().replace(',','
').replace(',','
')+""); + // writeToCallback(''); + // writeToCallback('Load CommandSizeOffset'); + + 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( + // ''+ + // LOAD_COMMAND_TYPE.DESCRIPTION(architecture.loadcommands[curr_lc].cmd) + + // ''+ + // ''+ + // architecture.loadcommands[curr_lc].cmdsize+ + // ''+ + // ''+ + // ' 0x' + architecture.loadcommands[curr_lc].fileoff.toString(16) + + // ''); + } + + 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 ) + } + } ) +} diff --git a/helpers/macho/macho.linkeditdata.js b/helpers/macho/macho.linkeditdata.js new file mode 100644 index 0000000..d508cf6 --- /dev/null +++ b/helpers/macho/macho.linkeditdata.js @@ -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); + }; +}; \ No newline at end of file diff --git a/helpers/macho/macho.loadcommand.js b/helpers/macho/macho.loadcommand.js new file mode 100644 index 0000000..8ae73fc --- /dev/null +++ b/helpers/macho/macho.loadcommand.js @@ -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 } diff --git a/helpers/macho/macho.magic.js b/helpers/macho/macho.magic.js new file mode 100644 index 0000000..34cc0d1 --- /dev/null +++ b/helpers/macho/macho.magic.js @@ -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 } diff --git a/helpers/macho/macho.operators.js b/helpers/macho/macho.operators.js new file mode 100644 index 0000000..d283333 --- /dev/null +++ b/helpers/macho/macho.operators.js @@ -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); + } +}; \ No newline at end of file diff --git a/helpers/macho/macho.prebindcksum.js b/helpers/macho/macho.prebindcksum.js new file mode 100644 index 0000000..060ca4a --- /dev/null +++ b/helpers/macho/macho.prebindcksum.js @@ -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); + }; +}; \ No newline at end of file diff --git a/helpers/macho/macho.prebounddylib.js b/helpers/macho/macho.prebounddylib.js new file mode 100644 index 0000000..3d7c1f8 --- /dev/null +++ b/helpers/macho/macho.prebounddylib.js @@ -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); + }; +}; \ No newline at end of file diff --git a/helpers/macho/macho.routines.js b/helpers/macho/macho.routines.js new file mode 100644 index 0000000..921fc58 --- /dev/null +++ b/helpers/macho/macho.routines.js @@ -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); + }; +}; \ No newline at end of file diff --git a/helpers/macho/macho.rpath.js b/helpers/macho/macho.rpath.js new file mode 100644 index 0000000..1c8db8d --- /dev/null +++ b/helpers/macho/macho.rpath.js @@ -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); + }; +}; \ No newline at end of file diff --git a/helpers/macho/macho.section.js b/helpers/macho/macho.section.js new file mode 100644 index 0000000..35eb265 --- /dev/null +++ b/helpers/macho/macho.section.js @@ -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); + }; +}; \ No newline at end of file diff --git a/helpers/macho/macho.segment.js b/helpers/macho/macho.segment.js new file mode 100644 index 0000000..7ae5530 --- /dev/null +++ b/helpers/macho/macho.segment.js @@ -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 } diff --git a/helpers/macho/macho.signatures.js b/helpers/macho/macho.signatures.js new file mode 100644 index 0000000..495eb09 --- /dev/null +++ b/helpers/macho/macho.signatures.js @@ -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 +}; \ No newline at end of file diff --git a/helpers/macho/macho.stabs.js b/helpers/macho/macho.stabs.js new file mode 100644 index 0000000..22ce582 --- /dev/null +++ b/helpers/macho/macho.stabs.js @@ -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); + } +}; \ No newline at end of file diff --git a/helpers/macho/macho.subclient.js b/helpers/macho/macho.subclient.js new file mode 100644 index 0000000..be572e5 --- /dev/null +++ b/helpers/macho/macho.subclient.js @@ -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); + }; +}; \ No newline at end of file diff --git a/helpers/macho/macho.subframework.js b/helpers/macho/macho.subframework.js new file mode 100644 index 0000000..7c37147 --- /dev/null +++ b/helpers/macho/macho.subframework.js @@ -0,0 +1,5 @@ +var SubFrameworkCommand = function SubFrameworkCommand(cmd, cmdsize, umbrella) { + this.cmd = cmd || 0x00000000; + this.cmdsize = cmdsize || 0x00000000; + this.umbrella = umbrella || 0x00000000; +}; \ No newline at end of file diff --git a/helpers/macho/macho.sublibrary.js b/helpers/macho/macho.sublibrary.js new file mode 100644 index 0000000..06212ce --- /dev/null +++ b/helpers/macho/macho.sublibrary.js @@ -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); + }; +}; \ No newline at end of file diff --git a/helpers/macho/macho.subumbrella.js b/helpers/macho/macho.subumbrella.js new file mode 100644 index 0000000..9717005 --- /dev/null +++ b/helpers/macho/macho.subumbrella.js @@ -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); + }; +}; \ No newline at end of file diff --git a/helpers/macho/macho.symtab.js b/helpers/macho/macho.symtab.js new file mode 100644 index 0000000..2ac22a6 --- /dev/null +++ b/helpers/macho/macho.symtab.js @@ -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); + }; +}; \ No newline at end of file diff --git a/helpers/macho/macho.thread.js b/helpers/macho/macho.thread.js new file mode 100644 index 0000000..3c15ef3 --- /dev/null +++ b/helpers/macho/macho.thread.js @@ -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); + }; +}; \ No newline at end of file diff --git a/helpers/macho/macho.twolevelhints.js b/helpers/macho/macho.twolevelhints.js new file mode 100644 index 0000000..bd7831c --- /dev/null +++ b/helpers/macho/macho.twolevelhints.js @@ -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); + } +}; \ No newline at end of file diff --git a/helpers/macho/macho.uuid.js b/helpers/macho/macho.uuid.js new file mode 100644 index 0000000..dbec4fe --- /dev/null +++ b/helpers/macho/macho.uuid.js @@ -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); + }; +}; \ No newline at end of file diff --git a/helpers/macho/memory.js b/helpers/macho/memory.js new file mode 100644 index 0000000..4530ebc --- /dev/null +++ b/helpers/macho/memory.js @@ -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]); + + } + +} diff --git a/helpers/macho/mimetypes.js b/helpers/macho/mimetypes.js new file mode 100644 index 0000000..5c32141 --- /dev/null +++ b/helpers/macho/mimetypes.js @@ -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'; diff --git a/nuxt.config.js b/nuxt.config.js index d66fdb0..ed2346f 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -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)$/, diff --git a/package-lock.json b/package-lock.json index 1393938..d7602ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index fbc2063..b7f1cc3 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pages/apple-silicon-app-test.vue b/pages/apple-silicon-app-test.vue new file mode 100644 index 0000000..29b241e --- /dev/null +++ b/pages/apple-silicon-app-test.vue @@ -0,0 +1,334 @@ + + +