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('Offset | 0x'+magicOff.toString(16)+' | ');
- writeToCallback('Magic | 0x'+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('Offset | 0x'+magicOff.toString(16)+' | ');
+ // writeToCallback('Magic | 0x'+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 Command | Size | Offset | ');
+
+ 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 Command | Size | Offset | ');
- 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 )
+ }
+ } )
+}