/* ****************************************************************************** fpPS4 Temmie's Launcher paramSfoParser.js This file is responsible for holding all funcions / database for reading PARAM.SFO files! Many thanks to Control eXecute (@notzecoxao) for this sassy-challenge! Article used as reference: https://www.psdevwiki.com/ps4/Param.sfo ****************************************************************************** */ temp_PARAMSFO_PARSER = { // PARAM.SFO Key Database params: temp_PARAMSFO_DATABASE, /* Read PARAM.SFO files! This function returns all data as object. Info: Since all hex data is being read as String, here is a simple explanation: To read 0x04 bytes, Slice current string from starting point (0x00) to selection length (0x04) using it's hex value converted to int * 2 parseInt (Start) 0x00 ---------> 0 (Length) 0x04 ---------> (4 * 2) = 8; JS: sfoHex.slice(0, 8); ---> 00 50 53 46 (" PSF") */ parse: function(fLocation){ // Read file as hex (String) const sfoHex = APP.fs.readFileSync(fLocation, 'hex'); var sfoMetadata = {}, // SFO Header sfoHeader = { magic: APP.tools.parseEndian(sfoHex.slice(0, 8)), // (0x04) " PSF" Magic version: APP.tools.parseEndian(sfoHex.slice(8, 16)), // (0x04) File Version (01 01) keyTableStart: APP.tools.parseEndian(sfoHex.slice(16, 24)), // (0x04) Key table start offset dataTableStart: APP.tools.parseEndian(sfoHex.slice(24, 32)), // (0x04) Data table start offset totalIndexEntries: APP.tools.parseEndian(sfoHex.slice(32, 40)) // (0x04) Total entries in index table } /* Create key list [APP_TYPE, TITLE_ID...] */ var listAttrRaw = sfoHex.slice((parseInt(sfoHeader.keyTableStart, 16) * 2), (parseInt(sfoHeader.dataTableStart, 16) * 2)), listAttrArray = APP.tools.convertHexToUft8(listAttrRaw).split('\x00'); // Remove blank entries [I'm not feeling 100% secure about this method but... Here we go!] while (listAttrArray.indexOf('') !== -1){ listAttrArray.splice(listAttrArray.indexOf(''), 1); } /* Get reading mode for current entry [Key table offset, param_fmt...] */ // Set variables var readMode = {}, readerLocation = 0, hexStartLocation = sfoHex.slice(40); // Get key table data info listAttrArray.forEach(function(cAttr){ // Slice Current Data const cReadingMode = hexStartLocation.slice(readerLocation, parseInt(readerLocation + 32)); readMode[cAttr] = { keyTableOffset: cReadingMode.slice(0, 4), // Key table offset param_fmt: cReadingMode.slice(4, 8), // param_fmt (type of data) paramLength: cReadingMode.slice(8, 16), // Parameter length paramMaxLength: cReadingMode.slice(16, 24), // Parameter Max Length dataOffset: cReadingMode.slice(24, 32) // Data Offset } // Update position for next location readerLocation = parseInt(readerLocation + 32); }); /* Set Metadata Info */ // Set location to data table start create first slice var pointerLocation = 0, dataTableSlice = sfoHex.slice(parseInt(sfoHeader.dataTableStart, 16) * 2); // Process list listAttrArray.forEach(function(cAttr, cIndex){ // Get hex file starting from current location var keyData = '', convertUft8 = !1, cSlice = dataTableSlice.slice(pointerLocation), stopLocation = parseInt(pointerLocation + 8); // Default: int32 /* Check param length If not 32 bits unsigned (0x0404), use paramMaxLength converted to int minus 1 multiplied by 2 ...and (of course) convert it to utf-8! JS: length = ((parseInt(paramMaxLength, 16) - 1) * 2) */ if (readMode[cAttr].param_fmt !== '0404'){ convertUft8 = !0; stopLocation = (pointerLocation + ((parseInt(APP.tools.parseEndian(readMode[cAttr].paramLength), 16) - 1)) * 2); } // Get attr data keyData = dataTableSlice.slice(pointerLocation, stopLocation); // Set key value to attr if (convertUft8 === !0){ sfoMetadata[cAttr] = APP.tools.convertHexToUft8(keyData); } else { sfoMetadata[cAttr] = APP.tools.parseEndian(keyData); } // Update reader location if (listAttrArray[(cIndex + 1)] !== void 0){ pointerLocation = (parseInt(APP.tools.parseEndian(readMode[listAttrArray[(cIndex + 1)]].dataOffset), 16) * 2); } }); // End return sfoMetadata; } } delete temp_PARAMSFO_DATABASE;