diff --git a/.gitignore b/.gitignore
index 95de712..6330cd1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,15 @@
+# Extracted files
+
*.ico
*.icn
icon.sys
-*.psu
\ No newline at end of file
+# Save export files
+
+*.psu
+*.psv
+*.xps
+*.sps
+
+# Unused data
+
+tmp/*
\ No newline at end of file
diff --git a/README.md b/README.md
index dd9abc8..edf0e2c 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,11 @@ A JavaScript library (sorta) to read PS2 icons, and their related formats.
## What it supports
* EMS Memory Adapter export files (.psu)
+* PS3 virtual memory card export files (.psv)
+* SharkPort export files (.sps)
+* X-Port export files (.xps)
* PS2 icons (.ico, .icn)
-* PS2D format (icon.sys)
+* PS2D format (icon.sys)
## What can it do
* Allow any file in a PSU's virtual filesystem to be dumped.
@@ -22,4 +25,4 @@ A JavaScript library (sorta) to read PS2 icons, and their related formats.
(todo: write this)
## Why "icondumper2"?
-Because it replaced what *was* left of icondumper.
+Because it replaced what *was* left of icondumper (1).
diff --git a/icon.js b/icon.js
index 7f71e49..dc77a5f 100644
--- a/icon.js
+++ b/icon.js
@@ -1,7 +1,7 @@
//todo: Make this a module/mjs file. C6 compatibility can stay, if needed.
ICONJS_DEBUG = false;
ICONJS_STRICT = true;
-ICONJS_VERSION = "0.3.4";
+ICONJS_VERSION = "0.3.5";
function setDebug(value) {
ICONJS_DEBUG = !!value;
@@ -65,7 +65,8 @@ function readPS2D(input) {
{r: f32le(160), g: f32le(164), b: f32le(168), a: f32le(172)},
{r: f32le(176), g: f32le(180), b: f32le(184), a: f32le(188)}
]
- // save builder says color 1 is ambient, 2-4 are for 3-point cameras
+ // P2SB says color 1 is ambient, 2-4 are for 3-point cameras
+ // official HDD icon.sys files (completely different PS2ICON text-based format) also say the same.
const int_title = input.slice(0xc0, 0x100);
const tmp_title16 = new Uint16Array(int_title);
for (let index = 0; index < 32; index++) {
@@ -214,7 +215,6 @@ function readIconFile(input) {
function readEntryBlock(input) {
const view = new DataView(input);
const u32le = function(i){return view.getUint32(i, 1)};
- const u16le = function(i){return view.getUint16(i, 1)};
const t64le = function(i){return {
seconds: view.getUint8(i+1),
minutes: view.getUint8(i+2),
@@ -254,6 +254,9 @@ function readEntryBlock(input) {
function readEmsPsuFile(input){
const header = readEntryBlock(input.slice(0,0x1ff));
+ if(header.size > 0x7f) {
+ throw `Directory is too large! (maximum size: ${0x7f}, was ${header.size})`
+ }
let fsOut = {length: header.size, rootDirectory: header.filename, timestamps: {created: header.createdTime, modified: header.modifiedTime}};
let output = new Object();
let offset = 512;
@@ -346,7 +349,106 @@ function readPsvFile(input){
return {icons, "icon.sys": input.slice(ps2dOffset, ps2dOffset+ps2dSize), timestamps};
}
+function readSxpsDescriptor(input) {
+ const view = new DataView(input);
+ const u32le = function(i){return view.getUint32(i, 1)};
+ const t64le = function(i){return {
+ seconds: view.getUint8(i+1),
+ minutes: view.getUint8(i+2),
+ hours: view.getUint8(i+3),
+ day: view.getUint8(i+4),
+ month: view.getUint8(i+5),
+ year: view.getUint16(i+6, 1)
+ }}; //NOTE: times are in JST timezone (GMT+09:00), so clients should implement correctly!
+ //!pattern sps-xps_file.hexpat
+ //:skip 2 // ... it's the file descriptor block size (including the bytes themselves, so 250)
+ const int_filename = input.slice(2, 66);
+ const filename = stringScrubber((new TextDecoder("utf-8")).decode(int_filename));
+ const size = u32le(66);
+ const startSector = u32le(70);
+ const endSector = u32le(74);
+ const permissions = u32le(78); // the first two bytes are *swapped*. The comments that ensued were not kept.
+ let type;
+ if (permissions>0xffff) {
+ throw `Not a SharkPort (SPS) or X-Port (XPS) export file (was ${permissions}, expected less than ${0xffff})`;
+ }
+ if((permissions & 0b0010000000000000)>=1){
+ type = "directory";
+ }
+ if((permissions & 0b0001000000000000)>=1){
+ type = "file";
+ }
+ if((permissions & 0b00011000)>=1){
+ throw `I don't parse portable applications or legacy save data. (${permissions} has bits 4 or 5 set)`;
+ }
+ const timestamps = {created: t64le(82), modified: t64le(90)};
+ //:skip 4
+ //:skip 4 - u32 optional (98)
+ //:skip 8 - t64 optionalTime (102) // I don't know why this is here.
+ const int_asciiName = input.slice(114, 178);
+ const int_shiftjisName = input.slice(178, 242); // Because why parse a PS2D when you can hard-code it?
+ //:skip 8
+ if(ICONJS_DEBUG) {
+ console.log({int_filename, size, startSector, endSector, permissions, type, timestamps, int_asciiName, int_shiftjisName});
+ }
+ return {type, size, filename, timestamps};
+}
+
+function readSharkXPortSxpsFile(input) {
+ const view = new DataView(input);
+ const u32le = function(i){return view.getUint32(i, 1)};
+ //!pattern sps-xps_file.hexpat
+ const identLength = u32le(0);
+ let offset = 4;
+ const ident = input.slice(offset, offset+identLength);
+ if((new TextDecoder("utf-8")).decode(ident) !== "SharkPortSave") {
+ throw `Unrecognized file identification string. Expected "SharkPortSave".`
+ }
+ offset += (identLength + 4);
+ const titleLength = u32le(offset);
+ const title = input.slice(offset + 4, (offset + 4) + titleLength);
+ offset += (titleLength + 4);
+ const descriptionLength = u32le(offset);
+ const description = input.slice(offset + 4, (offset + 4) + descriptionLength);
+ offset += (descriptionLength + 8);
+ const comments = {
+ "game": stringScrubber((new TextDecoder("utf-8")).decode(title)),
+ "name": stringScrubber((new TextDecoder("utf-8")).decode(description))
+ }
+ const totalSize = u32le(offset);
+ offset += 4;
+ const header = readSxpsDescriptor(input.slice(offset, offset + 250));
+ offset += 250;
+ // alright now lets parse some actual data
+ let fsOut = {length: header.size, rootDirectory: header.filename, timestamps: header.timestamps, comments};
+ let output = new Object();
+ for (let index = 0; index < (header.size - 2); index++) {
+ fdesc = readSxpsDescriptor(input.slice(offset, offset + 250));
+ switch(fdesc.type) {
+ case "directory": {
+ offset += 250;
+ output[fdesc.filename] = null;
+ break;
+ }
+ case "file": {
+ if(ICONJS_DEBUG){
+ console.log(`PARSING | F: "${fdesc.filename}" O: ${offset} S: ${fdesc.size}`);
+ }
+ offset += 250;
+ output[fdesc.filename] = {
+ size: fdesc.size,
+ data: input.slice(offset, offset+fdesc.size)
+ };
+ offset += fdesc.size;
+ break;
+ }
+ }
+ }
+ fsOut[header.filename] = output;
+ return fsOut;
+}
+
if(typeof module !== "undefined") {
// for C6JS
- module.exports = {readIconFile, readPS2D, readEmsPsuFile, setDebug, setStrictness};
+ module.exports = {readIconFile, readPS2D, readEmsPsuFile, readPsvFile, readSharkXPortSxpsFile, setDebug, setStrictness};
}
\ No newline at end of file
diff --git a/index.js b/index.js
index 9275bda..c05023a 100644
--- a/index.js
+++ b/index.js
@@ -12,7 +12,6 @@ iconjs.setDebug(false);
// node.js client
if(processObj.argv[2] === "psu") {
- let output = new Object();
let inputFile = filesystem.readFileSync(processObj.argv[3] ? processObj.argv[3] : "file.psu");
const parsed = iconjs.readEmsPsuFile(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength));
const PS2D = iconjs.readPS2D(parsed[parsed.rootDirectory]["icon.sys"].data);
@@ -28,6 +27,16 @@ if(processObj.argv[2] === "psu") {
const PS2D = iconjs.readPS2D(parsed["icon.sys"]);
let output = {parsed, PS2D};
console.log(output);
+} else if(processObj.argv[2] === "sps") {
+ let inputFile = filesystem.readFileSync(processObj.argv[3] ? processObj.argv[3] : "file.sps");
+ const parsed = iconjs.readSharkXPortSxpsFile(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength));
+ console.log(parsed);
+ const PS2D = iconjs.readPS2D(parsed[parsed.rootDirectory]["icon.sys"].data);
+ let output = {parsed, PS2D}
+ Object.keys(PS2D.filenames).forEach(function(file) {
+ output[file] = iconjs.readIconFile(parsed[parsed.rootDirectory][PS2D.filenames[file]].data);
+ });
+ console.log(output);
} else {
let inputFile = filesystem.readFileSync(processObj.argv[2] ? processObj.argv[2] : "icon.sys");
const metadata = iconjs.readPS2D(inputFile.buffer.slice(inputFile.byteOffset, inputFile.byteOffset + inputFile.byteLength));
diff --git a/input.htm b/input.htm
index 1e8f537..ab22280 100644
--- a/input.htm
+++ b/input.htm
@@ -17,6 +17,7 @@
margin: 0;
}
#version {position: fixed;bottom:4px;right:4px}
+ #advanced {display: none}
icondumper2 (unknown icon.js version) - © 2023 yellows111