diff --git a/helpers/app-files-scanner.js b/helpers/app-files-scanner.js index 950441a..dc0e217 100644 --- a/helpers/app-files-scanner.js +++ b/helpers/app-files-scanner.js @@ -9,6 +9,11 @@ // import statuses from './statuses' // import parseGithubDate from './parse-github-date' +// import EndianReader from 'endian-reader' +import parseMacho from './macho/index.js' + +// console.log('MachOParser', MachOParser) + const knownArchiveExtensions = new Set([ 'app', 'dmg', @@ -221,8 +226,10 @@ export default class AppFilesScanner { } - parseMachOFile () { + async parseMachOBlob ( machOBlob, fileName ) { + const machOFile = new File([machOBlob], fileName) + return await parseMacho( machOFile ) } classifyArchitecture () { @@ -235,8 +242,6 @@ export default class AppFilesScanner { async scan ( fileList ) { - // console.log('this.files', this.files) - // Push files to our files array Array.from(fileList).forEach( (fileInstance, index) => { this.files.unshift( { @@ -287,7 +292,7 @@ export default class AppFilesScanner { file.machOEntries = this.findMachOEntries( entries ) if ( file.machOEntries.length === 0 ) { - console.log('entries', entries) + console.log('file.machOEntries', file.machOEntries) file.statusMessage = `🚫 Could not find any application data` file.status = 'finished' @@ -295,23 +300,74 @@ export default class AppFilesScanner { return } - // const machOContents = await file.machOFile.getData( - // // writer - // // https://gildas-lormeau.github.io/zip.js/core-api.html#zip-writing - // new zip.TextWriter(), - // // options - // { - // onprogress: (index, max) => { - // // onprogress callback - // console.log('Writer progress', index, max) - // } - // } - // ) + // const machOBlob = await file.machOEntries + + + const parsedMachoEntries = await Promise.all( file.machOEntries.map( async machOEntry => { + // Get blob data from zip + const machOBlob = await machOEntry.getData( + // writer + // https://gildas-lormeau.github.io/zip.js/core-api.html#zip-writing + // new zip.TextWriter(), + new zip.BlobWriter(), + // options + { + onprogress: (index, max) => { + // onprogress callback + console.log('Writer progress', index, max) + } + } + ) + + return await this.parseMachOBlob( machOBlob, file.name ) + } ) ) + + + + // const machoData = await parseMacho( machOFile )//new MachoParser( machOFile, document.getElementById('callback')); + + console.log('parsedMachoEntries', parsedMachoEntries) + + + + // const machOBuffer = await machOBlob.arrayBuffer() + + // const machOData = this.parseMachOFile( Buffer.from( machOBuffer ) ) // text contains the entry data as a String - // console.log('Mach-O contents', machOContents) + // console.log('Mach-O contents', machOBuffer) - file.statusMessage = `🏁 Scan Finished. ${file.machOEntries.length} Mach-o files` + // file.statusMessage = `🏁 Scan Finished. ${file.machOEntries.length} Mach-o files` + file.statusMessage = `🏁 Scan Finished. ` + + let supportedBinaries = 0 + let unsupportedBinaries = 0 + + parsedMachoEntries.forEach( binaryEntry => { + const armBinary = binaryEntry.architectures.find( architecture => { + if ( architecture.processorType === 0 ) return false + + return architecture.processorType.toLowerCase().includes('arm') + }) + + if ( armBinary !== undefined ) { + supportedBinaries++ + } else { + unsupportedBinaries++ + } + } ) + + + // console.log('supportedBinaries', supportedBinaries) + // console.log('unsupportedBinaries', unsupportedBinaries) + + if (supportedBinaries !== 0 && unsupportedBinaries !== 0) { + file.statusMessage = `🔶 App has some support. ` + } else if ( unsupportedBinaries !== 0 ) { + file.statusMessage = `🚫 This App's binary is not 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. You can try submitting the download page link for an app and we'll scan that. You can request a manual review to determine the current status of the app on Rosetta 2. ` + } else if ( supportedBinaries !== 0 ) { + file.statusMessage = '✅ This App is natively compatible with Apple Silicon!' + } file.status = 'finished' diff --git a/helpers/macho/macho.js b/helpers/macho/macho.js index 87098fc..b4593ad 100644 --- a/helpers/macho/macho.js +++ b/helpers/macho/macho.js @@ -3,11 +3,11 @@ //Licensed under the MIT License import { mime_binary } from './mimetypes.js' -import { ReadUint32, ReadUint16LE, ReadUint32LE } from './memory.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 } from './macho.header.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' @@ -53,17 +53,21 @@ var ChunkReader = function ChunkReader(file, chunksize = (1024 * 1024), callback free("window.readers"); }; -export default function MachoParser(file, callbackElement = document.body) { +export function MachoParser(file, callback) { + + const machoOutputData = {} //properties this.reader = new FileReader(); function writeToCallback(val) { - if(callbackElement) { - callbackElement.innerHTML +=''+val+''; - } else { - throw new Error('Invalid callback.'); - } + return + + // if(callbackElement) { + // callbackElement.innerHTML +=''+val+''; + // } else { + // throw new Error('Invalid callback.'); + // } } /* @@ -147,8 +151,11 @@ export default function MachoParser(file, callbackElement = document.body) { window.tempFile = blob; //Set up the file and print out for verbosity - writeToCallback('File'+file.name.toString()+''); - writeToCallback('Size' + filesize.toFixed(2).toString() + 'MB'); + 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); @@ -161,27 +168,29 @@ export default function MachoParser(file, callbackElement = document.body) { 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 - window.machoObj = {}; //Create the Mach-O information object - window.machoObj.bits = (x64 ? "64-bit" : "32-bit"); //Get the architecture - window.machoObj.endianness = (littleendian ? "little endian" : "big endian"); //Get the endianness - window.machoObj.header = (x64 ? new MachoHeader64() : new MachoHeader()); //Depending on architecture, construct a new header - window.machoObj.header.magic = magic; //Add the magic to the header - window.machoObj.header.cputype = (littleendian ? ReadUint16LE(data, magicOff+uint32_t) : ReadUint16(data, magicOff+uint32_t)); //Read the cputype which comes after the magic - window.machoObj.header.cpusubtype = (littleendian ? ReadUint16LE(data, magicOff+(2*uint32_t)) : ReadUint16(data, magicOff+(2*uint32_t))); //Read the cpu subtype which comes after the cputype - window.machoObj.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 - window.machoObj.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 - window.machoObj.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 - window.machoObj.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 - window.machoObj.loadcommands = []; + // 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 < window.machoObj.header.ncmds; i++) { + 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 @@ -191,45 +200,65 @@ export default function MachoParser(file, callbackElement = document.body) { 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 - window.machoObj.loadcommands.push(curr_cmd); + 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'+window.machoObj.header.magic.toString(16)+''); - writeToCallback('Bits'+window.machoObj.bits+''); - writeToCallback('Endianness'+window.machoObj.endianness+''); - writeToCallback('Processor type' + CPU_TYPE.DESCRIPTION(window.machoObj.header.cputype)+''); - writeToCallback('Processor subtype'+ CPU_SUB_TYPE.ARM.DESCRIPTION(window.machoObj.header.cpusubtype)+''); - writeToCallback('File type'+FILE_TYPE.DESCRIPTION(window.machoObj.header.filetype)+''); - writeToCallback('Number of commands'+window.machoObj.header.ncmds+''); - writeToCallback('Size of commands'+window.machoObj.header.sizeofcmds+''); + // 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+''); - window.machoObj.header.flags = MapFlags(window.machoObj.header.flags, FILE_FLAGS); - window.machoObj.header.flags = Object.keys(window.machoObj.header.flags).map(function(k) { if(k){return k}}); + 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) + + // ''); + } - writeToCallback('Flags'+window.machoObj.header.flags.toString().replace(',','
').replace(',','
')+""); - writeToCallback(''); - writeToCallback('Load CommandSizeOffset'); - for(var curr_lc = 0; curr_lc < window.machoObj.loadcommands.length; curr_lc++) { - writeToCallback( - ''+ - LOAD_COMMAND_TYPE.DESCRIPTION(window.machoObj.loadcommands[curr_lc].cmd) + - ''+ - ''+ - window.machoObj.loadcommands[curr_lc].cmdsize+ - ''+ - ''+ - ' 0x' + window.machoObj.loadcommands[curr_lc].fileoff.toString(16) + - ''); - } magicOff = null; littleendian = null; x64 = null; @@ -239,11 +268,19 @@ export default function MachoParser(file, callbackElement = document.body) { arrayBufferToBlob(data.buffer, fileType).then(function(blob){ if(!window.tempFiles) { window.tempFiles = []; } window.tempFiles[window.tempFiles.length] = blob; - }).catch(console.log.bind(console)); - } - console.log('The parser has finished'); + }).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.'); + // 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)); @@ -254,3 +291,15 @@ export default function MachoParser(file, callbackElement = document.body) { console.log('Parsing, please wait...'); }; + + + +export default async function ( file ) { + return new Promise( ( resolve, reject ) => { + try { + (new MachoParser( file, resolve )) + } catch ( error ) { + reject( error ) + } + } ) +}