From 0368b44d2fb96247e8abbc75488d7ac6e0da695a Mon Sep 17 00:00:00 2001
From: bucanero <dparrino@gmail.com>
Date: Sun, 17 Mar 2024 20:08:55 -0300
Subject: [PATCH] Add ps2 VME support

show ps1 / ps2 save icons
---
 include/mcio.h       |   4 +-
 include/ps2icon.h    |  75 +++++++++++
 include/saves.h      |   7 +-
 source/draw.c        |   5 +-
 source/exec_cmd.c    |  69 ++++------
 source/main.c        |   2 +-
 source/mcio.c        | 156 +++++++++++++++++-----
 source/menu_cheats.c |   2 +-
 source/menu_main.c   |  36 +++--
 source/ps1card.c     |   2 +-
 source/ps2classic.c  |  21 ---
 source/ps2icon.c     | 139 ++++++++++++++++++++
 source/saves.c       | 307 +++++++++++++++++++------------------------
 13 files changed, 533 insertions(+), 292 deletions(-)
 create mode 100644 include/ps2icon.h
 create mode 100644 source/ps2icon.c

diff --git a/include/mcio.h b/include/mcio.h
index 00edfd6..52b041f 100644
--- a/include/mcio.h
+++ b/include/mcio.h
@@ -63,6 +63,8 @@ struct io_dirent {
 } __attribute__((packed));
 
 int mcio_vmcInit(const char* vmc);
+int mcio_vmcExportImage(const char *dst, int ecc);
+int mcio_vmcImportImage(const char *src);
 void mcio_vmcFinish(void);
 int mcio_mcDetect(void);
 int mcio_mcGetInfo(int *pagesize, int *blocksize, int *cardsize, int *cardflags);
@@ -77,7 +79,7 @@ int mcio_mcDopen(const char *dirname);
 int mcio_mcDclose(int fd);
 int mcio_mcDread(int fd, struct io_dirent *dirent);
 int mcio_mcMkDir(const char *dirname);
-int mcio_mcReadPage(int pagenum, void *buf);
+int mcio_mcReadPage(int pagenum, void *buf, void *ecc);
 int mcio_mcUnformat(void);
 int mcio_mcFormat(void);
 int mcio_mcRemove(const char *filename);
diff --git a/include/ps2icon.h b/include/ps2icon.h
new file mode 100644
index 0000000..167ba27
--- /dev/null
+++ b/include/ps2icon.h
@@ -0,0 +1,75 @@
+/*
+*
+* Copyright (c) 2008 Andreas Weis (http://www.ghulbus-inc.de/)
+*
+* Permission is hereby granted, free of charge, to any person obtaining a
+* copy  of  this   software  and  associated   documentation  files  (the
+* "Software"),  to deal  in the Software  without  restriction, including
+* without  limitation  the rights to  use, copy,  modify, merge, publish,
+* distribute,  sublicense, and/or  sell  copies of the  Software, and  to
+* permit persons to  whom the Software is furnished  to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be included
+* in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+* OR   IMPLIED,  INCLUDING   BUT  NOT  LIMITED  TO   THE  WARRANTIES   OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR  PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE  AUTHORS OR COPYRIGHT HOLDERS  BE LIABLE FOR  ANY
+* CLAIM,  DAMAGES  OR OTHER  LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT  OR OTHERWISE,  ARISING FROM,  OUT OF  OR IN  CONNECTION  WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+//================================================================================================
+//   Typedefs and Defines
+//================================================================================================
+
+/** File header
+ */
+typedef struct Icon_Header_t {
+	unsigned int file_id;						///< reserved; should be: 0x010000 (but does not have to ;) )
+	unsigned int animation_shapes;				///< number of animation shapes per vertex
+	unsigned int texture_type;					///< texture type - 0x07: uncompressed, 0x06: uncompresses, 0x0f: RLE compression
+	unsigned int reserved;						///< reserved; should be: 0x3F800000 (but does not have to ;) )
+	unsigned int n_vertices;					///< number of vertices; must be a multiple of 3
+} Icon_Header;
+/** Set of vertex coordinates
+ * @note The f16_* fields indicate float16 data; divide by 4096.0f to convert to float32;
+ */
+typedef struct Vertex_Coord_t {
+	short f16_x;								///< vertex x coordinate in float16
+	short f16_y;								///< vertex y coordinate in float16
+	short f16_z;								///< vertex z coordinate in float16
+	short f16_unknown;							///< unknown; seems to influence lightning?
+} Vertex_Coord;
+/** Set of texture coordinates
+ * @note The f16_* fields indicate float16 data; divide by 4096.0f to convert to float32;
+ */
+typedef struct Texture_Data_t {
+	short        f16_u;							///< vertex u texture coordinate in float16
+	short        f16_v;							///< vertex v texture coordinate in float16
+	unsigned int color;							///< vertex color (32 bit RGBA)
+} Texture_Data;
+/** Animation header
+ */
+typedef struct Animation_Header_t {
+	unsigned int id_tag;						///< ???
+	unsigned int frame_length;					///< ???
+	float        anim_speed;					///< ???
+	unsigned int play_offset;					///< ???
+	unsigned int n_frames;						///< number of frames in the animation
+} Animation_Header;
+/** Per-frame animation data
+ */
+typedef struct Frame_Data_t {
+	unsigned int shape_id;						///< shape used for this frame
+	unsigned int n_keys;						///< number of keys corresponding to this frame
+} Frame_Data;
+/** Per-key animation data
+ */
+typedef struct Frame_Key_t {
+	float time;									///< ???
+	float value;								///< ???
+} Frame_Key;
diff --git a/include/saves.h b/include/saves.h
index 448fae4..9fd1e95 100644
--- a/include/saves.h
+++ b/include/saves.h
@@ -91,8 +91,6 @@ enum cmd_code_enum
     CMD_VIEW_DETAILS,
     CMD_VIEW_RAW_PATCH,
     CMD_RESIGN_PSV,
-    CMD_DECRYPT_PS2_VME,
-    CMD_ENCRYPT_PS2_VMC,
     CMD_CONVERT_TO_PSV,
     CMD_COPY_DUMMY_PSV,
     CMD_IMPORT_DATA_FILE,
@@ -117,6 +115,7 @@ enum cmd_code_enum
     CMD_EXP_LICS_RAPS,
     CMD_EXP_FLASH2_USB,
     CMD_EXP_PS2_BINENC,
+    CMD_EXP_PS2_VM2,
     CMD_EXP_PSV_MCS,
     CMD_EXP_PSV_PSU,
     CMD_EXP_VM2_RAW,
@@ -126,6 +125,7 @@ enum cmd_code_enum
 
 // Import commands
     CMD_IMP_EXDATA_USB,
+    CMD_IMP_PS2_VM2,
     CMD_IMP_PS2_ISO,
     CMD_IMP_PS2_CONFIG,
     CMD_IMP_PS2VMC_USB,
@@ -262,6 +262,7 @@ typedef struct
 } save_list_t;
 
 list_t * ReadUserList(const char* userPath);
+list_t * ReadUsbList(const char* userPath);
 list_t * ReadOnlineList(const char* urlPath);
 list_t * ReadBackupList(const char* userPath);
 list_t * ReadTrophyList(const char* userPath);
@@ -318,7 +319,6 @@ void ps2_encrypt_image(uint8_t cfg_file, const char* image_name, const char* dat
 void ps2_decrypt_image(uint8_t dex_mode, const char* image_name, const char* data_file);
 void ps2_crypt_vmc(uint8_t dex_mode, const char* vmc_path, const char* vmc_out, int crypt_mode);
 int ps2_add_vmc_ecc(const char* src, const char* dst);
-int ps2_remove_vmc_ecc(const char* src, const char* dst);
 int psv_resign(const char *src_psv);
 int vmp_resign(const char *src_vmp);
 
@@ -337,3 +337,4 @@ int vmc_import_psv(const char *input);
 int vmc_import_psu(const char *input);
 
 char* sjis2utf8(char* input);
+uint8_t* getIconPS2(const char* folder, const char* iconfile);
diff --git a/source/draw.c b/source/draw.c
index f30a53c..fb101e8 100644
--- a/source/draw.c
+++ b/source/draw.c
@@ -121,8 +121,9 @@ static void _drawListBackground(int off, int icon)
 
 			if (menu_textures[icon_png_file_index].size)
 			{
-				DrawTexture(&menu_textures[help_png_index], 624, help_png_y + 4, 0, 168, 98, 0xFFFFFF00 | 0xFF);
-				DrawTexture(&menu_textures[icon_png_file_index], 628, help_png_y + 8, 0, 160, 88, 0xFFFFFF00 | 0xFF);
+				int adj = (menu_textures[icon_png_file_index].texture.width == 16) ? 6 : 1; // resize PS1 icons
+				DrawTexture(&menu_textures[help_png_index], 624, help_png_y + 4, 0, (menu_textures[icon_png_file_index].texture.width / 2 * adj) + 8, (menu_textures[icon_png_file_index].texture.height / 2 * adj) + 10, 0xFFFFFF00 | 0xFF);
+				DrawTexture(&menu_textures[icon_png_file_index], 628, help_png_y + 8, 0, menu_textures[icon_png_file_index].texture.width / 2 * adj, menu_textures[icon_png_file_index].texture.height / 2 * adj, 0xFFFFFF00 | 0xFF);
 			}
 			break;
 
diff --git a/source/exec_cmd.c b/source/exec_cmd.c
index 54a8256..c4fbce7 100644
--- a/source/exec_cmd.c
+++ b/source/exec_cmd.c
@@ -12,6 +12,7 @@
 #include "pfd.h"
 #include "sfo.h"
 #include "ps1card.h"
+#include "mcio.h"
 
 static char host_buf[256];
 
@@ -875,45 +876,21 @@ static void convertSavePSV(const save_entry_t* save, int dst)
 	show_message("File successfully saved to:\n%s", out_path);
 }
 
-static void decryptVMEfile(const char* vme_path, const char* vme_file, uint8_t dst)
+static void importVM2file(const char* vme_file, const char* src_name)
 {
-	char vmefile[256];
-	char outfile[256];
-	char path[256];
-
-	_set_dest_path(path, dst, VMC_PS2_PATH_USB);
-	if (dst == STORAGE_HDD)
-		snprintf(path, sizeof(path), VMC_PS2_PATH_HDD);
-
-	if (mkdirs(path) != SUCCESS)
-	{
-		show_message("Error! Export folder is not available:\n%s", path);
-		return;
-	}
-
-	snprintf(vmefile, sizeof(vmefile), "%s%s", vme_path, vme_file);
-	snprintf(outfile, sizeof(outfile), "%sAPOLLO%c.VM2", path, vme_file[6]);
-
-	init_loading_screen("Decrypting VME card...");
-	ps2_crypt_vmc(0, vmefile, outfile, 0);
-	stop_loading_screen();
-
-	show_message("File successfully saved to:\n%s", outfile);
-}
-
-static void encryptVM2file(const char* vme_path, const char* vme_file, const char* src_name)
-{
-	char vmefile[256];
+	int ret;
 	char srcfile[256];
 
-	snprintf(vmefile, sizeof(vmefile), "%s%s", vme_path, vme_file);
 	snprintf(srcfile, sizeof(srcfile), "%s%s", VMC_PS2_PATH_HDD, src_name);
 
-	init_loading_screen("Encrypting VM2 card...");
-	ps2_crypt_vmc(0, srcfile, vmefile, 1);
+	init_loading_screen("Importing VM2 card...");
+	ret = mcio_vmcImportImage(srcfile);
 	stop_loading_screen();
 
-	show_message("File successfully saved to:\n%s", vmefile);
+	if (ret == sceMcResSucceed)
+		show_message("File successfully imported to:\n%s", vme_file);
+	else
+		show_message("Error! Failed to import PS2 memory card");
 }
 
 static void importPS2VMC(const char* vmc_path, const char* vmc_file)
@@ -932,9 +909,9 @@ static void importPS2VMC(const char* vmc_path, const char* vmc_file)
 	show_message("File successfully saved to:\n%s", vm2file);
 }
 
-static void exportVM2raw(const char* vm2_path, const char* vm2_file, int dst)
+static void exportVM2raw(const char* vm2_file, int dst, int ecc)
 {
-	char vm2file[256];
+	int ret;
 	char dstfile[256];
 	char dst_path[256];
 
@@ -945,15 +922,17 @@ static void exportVM2raw(const char* vm2_path, const char* vm2_file, int dst)
 		return;
 	}
 
-	snprintf(vm2file, sizeof(vm2file), "%s%s", vm2_path, vm2_file);
-	snprintf(dstfile, sizeof(dstfile), "%s%s.vmc", dst_path, vm2_file);
+	snprintf(dstfile, sizeof(dstfile), "%s%s.%s", dst_path, vm2_file, ecc ? "VM2" : "vmc");
 
-	init_loading_screen("Exporting PS2 .VM2 memory card...");
-	ps2_remove_vmc_ecc(vm2file, dstfile);
+	init_loading_screen("Exporting PS2 memory card...");
+	ret = mcio_vmcExportImage(dstfile, ecc);
 	file_chmod(dstfile);
 	stop_loading_screen();
 
-	show_message("File successfully saved to:\n%s", dstfile);
+	if (ret == sceMcResSucceed)
+		show_message("File successfully saved to:\n%s", dstfile);
+	else
+		show_message("Error! Failed to export PS2 memory card");
 }
 
 static void importPS2classicsCfg(const char* cfg_path, const char* cfg_file)
@@ -1762,13 +1741,8 @@ void execCodeCommand(code_entry_t* code, const char* codecmd)
 			code->activated = 0;
 			break;
 
-		case CMD_DECRYPT_PS2_VME:
-			decryptVMEfile(selected_entry->path, code->file, codecmd[1]);
-			code->activated = 0;
-			break;
-
-		case CMD_ENCRYPT_PS2_VMC:
-			encryptVM2file(selected_entry->path, code->file, code->options[0].name[code->options[0].sel]);
+		case CMD_IMP_PS2_VM2:
+			importVM2file(selected_entry->path, code->options[0].name[code->options[0].sel]);
 			code->activated = 0;
 			break;
 
@@ -1807,8 +1781,9 @@ void execCodeCommand(code_entry_t* code, const char* codecmd)
 			code->activated = 0;
 			break;
 
+		case CMD_EXP_PS2_VM2:
 		case CMD_EXP_VM2_RAW:
-			exportVM2raw(selected_entry->path, code->file, codecmd[1]);
+			exportVM2raw(code->file, codecmd[1], codecmd[0] == CMD_EXP_PS2_VM2);
 			code->activated = 0;
 			break;
 
diff --git a/source/main.c b/source/main.c
index 05aeff2..25d06a7 100644
--- a/source/main.c
+++ b/source/main.c
@@ -121,7 +121,7 @@ save_list_t usb_saves = {
 	.title = "USB Saves",
     .list = NULL,
     .path = "",
-    .ReadList = &ReadUserList,
+    .ReadList = &ReadUsbList,
     .ReadCodes = &ReadCodes,
     .UpdatePath = &update_usb_path,
 };
diff --git a/source/mcio.c b/source/mcio.c
index 7da3677..10f5fbf 100644
--- a/source/mcio.c
+++ b/source/mcio.c
@@ -23,6 +23,7 @@
 #include "mcio.h"
 #include "util.h"
 #include "ps2mc.h"
+#include "common.h"
 
 #include <stdio.h>
 #include <time.h>
@@ -46,6 +47,7 @@ static const char SUPERBLOCK_MAGIC[]   = "Sony PS2 Memory Card Format ";
 static const char SUPERBLOCK_VERSION[] = "1.2.0.0\0\0\0\0";
 
 static FILE *vmc_fp = NULL;
+static char vmcpath[256];
 
 struct MCDevInfo {			/* size = 384 */
 	uint8_t  magic[28];		/* Superblock magic, on PS2 MC : "Sony PS2 Memory Card Format " */
@@ -126,40 +128,7 @@ static uint8_t mcio_eccdata[512]; /* size for 32 ecc */
 static int32_t mcio_badblock = 0;
 static int32_t mcio_replacementcluster[16];
 
-static const uint8_t mcio_xortable[256] = {
-	0x00, 0x87, 0x96, 0x11, 0xA5, 0x22, 0x33, 0xB4,
-	0xB4, 0x33, 0x22, 0xA5, 0x11, 0x96, 0x87, 0x00,
-	0xC3, 0x44, 0x55, 0xD2, 0x66, 0xE1, 0xF0, 0x77,
-	0x77, 0xF0, 0xE1, 0x66, 0xD2, 0x55, 0x44, 0xC3,
-	0xD2, 0x55, 0x44, 0xC3, 0x77, 0xF0, 0xE1, 0x66,
-	0x66, 0xE1, 0xF0, 0x77, 0xC3, 0x44, 0x55, 0xD2,
-	0x11, 0x96, 0x87, 0x00, 0xB4, 0x33, 0x22, 0xA5,
-	0xA5, 0x22, 0x33, 0xB4, 0x00, 0x87, 0x96, 0x11,
-	0xE1, 0x66, 0x77, 0xF0, 0x44, 0xC3, 0xD2, 0x55,
-	0x55, 0xD2, 0xC3, 0x44, 0xF0, 0x77, 0x66, 0xE1,
-	0x22, 0xA5, 0xB4, 0x33, 0x87, 0x00, 0x11, 0x96,
-	0x96, 0x11, 0x00, 0x87, 0x33, 0xB4, 0xA5, 0x22,
-	0x33, 0xB4, 0xA5, 0x22, 0x96, 0x11, 0x00, 0x87,
-	0x87, 0x00, 0x11, 0x96, 0x22, 0xA5, 0xB4, 0x33,
-	0xF0, 0x77, 0x66, 0xE1, 0x55, 0xD2, 0xC3, 0x44,
-	0x44, 0xC3, 0xD2, 0x55, 0xE1, 0x66, 0x77, 0xF0,
-	0xF0, 0x77, 0x66, 0xE1, 0x55, 0xD2, 0xC3, 0x44,
-	0x44, 0xC3, 0xD2, 0x55, 0xE1, 0x66, 0x77, 0xF0,
-	0x33, 0xB4, 0xA5, 0x22, 0x96, 0x11, 0x00, 0x87,
-	0x87, 0x00, 0x11, 0x96, 0x22, 0xA5, 0xB4, 0x33,
-	0x22, 0xA5, 0xB4, 0x33, 0x87, 0x00, 0x11, 0x96,
-	0x96, 0x11, 0x00, 0x87, 0x33, 0xB4, 0xA5, 0x22,
-	0xE1, 0x66, 0x77, 0xF0, 0x44, 0xC3, 0xD2, 0x55,
-	0x55, 0xD2, 0xC3, 0x44, 0xF0, 0x77, 0x66, 0xE1,
-	0x11, 0x96, 0x87, 0x00, 0xB4, 0x33, 0x22, 0xA5,
-	0xA5, 0x22, 0x33, 0xB4, 0x00, 0x87, 0x96, 0x11,
-	0xD2, 0x55, 0x44, 0xC3, 0x77, 0xF0, 0xE1, 0x66,
-	0x66, 0xE1, 0xF0, 0x77, 0xC3, 0x44, 0x55, 0xD2,
-	0xC3, 0x44, 0x55, 0xD2, 0x66, 0xE1, 0xF0, 0x77,
-	0x77, 0xF0, 0xE1, 0x66, 0xD2, 0x55, 0x44, 0xC3,
-	0x00, 0x87, 0x96, 0x11, 0xA5, 0x22, 0x33, 0xB4,
-	0xB4, 0x33, 0x22, 0xA5, 0x11, 0x96, 0x87, 0x00
-};
+extern const uint8_t ECC_Table[];
 
 
 struct MCFHandle { /* size = 48 */
@@ -185,6 +154,7 @@ struct MCFHandle { /* size = 48 */
 struct MCFHandle mcio_fdhandles[MAX_FDHANDLES];
 
 static int Card_FileClose(int fd);
+void ps2_crypt_vmc(uint8_t dex_mode, const char* vmc_path, const char* vmc_out, int crypt_mode);
 
 
 static void long_multiply(uint32_t v1, uint32_t v2, uint32_t *HI, uint32_t *LO)
@@ -211,6 +181,7 @@ static void Card_DataChecksum(uint8_t *pagebuf, uint8_t *ecc)
 {
 	uint8_t *p, *p_ecc;
 	int32_t i, a2, a3, v, t0;
+	const uint8_t *mcio_xortable = ECC_Table;
 
 	p = pagebuf;
 	a2 = 0;
@@ -3009,10 +2980,19 @@ static int Card_FileWrite(int fd, void *buffer, int nbyte)
 int mcio_vmcInit(const char* vmc)
 {
 	int r;
+	vmcpath[0] = 0;
 
 	if (vmc_fp)
 		fclose(vmc_fp);
 
+	// decrypt ps2classic format
+	if (strcmp(".VME", strrchr(vmc, '.')) == 0)
+	{
+		snprintf(vmcpath, sizeof(vmcpath), "%s%s", vmc, ".out");
+		ps2_crypt_vmc(0, vmc, vmcpath, 0);
+		vmc = vmcpath;
+	}
+
 	vmc_fp = fopen(vmc, "r+b");
 	if (!vmc_fp)
 		return sceMcResFailIO;
@@ -3031,6 +3011,17 @@ void mcio_vmcFinish(void)
 	if (vmc_fp)
 		fclose(vmc_fp);
 
+	// encrypt ps2classic format
+	if (vmcpath[0])
+	{
+		char vme[256];
+
+		strncpy(vme, vmcpath, sizeof(vme));
+		*strrchr(vme, '.') = 0;
+		ps2_crypt_vmc(0, vmcpath, vme, 1);
+		unlink_secure(vmcpath);
+	}
+
 	vmc_fp = NULL;
 }
 
@@ -3498,9 +3489,24 @@ int mcio_mcGetAvailableSpace(int *cardfree)
 	return 0;
 }
 
-int mcio_mcReadPage(int pagenum, void *buf)
+int mcio_mcReadPage(int pagenum, void *buf, void *ecc)
 {
-	return Card_ReadPage((int32_t)pagenum, (uint8_t *)buf);
+	int r;
+
+	r = Card_ReadPage((int32_t)pagenum, (uint8_t *)buf);
+
+	if (ecc)
+	{
+		struct MCDevInfo *mcdi = (struct MCDevInfo *)&mcio_devinfo;
+		uint16_t pagesize = read_le_uint16((uint8_t *)&mcdi->pagesize);
+		uint8_t* p_ecc = ecc;
+
+		memset(ecc, 0, pagesize >> 5);
+		for (int i = 0; i < pagesize; i += 128, p_ecc += 3)
+			Card_DataChecksum(buf + i, p_ecc);
+	}
+
+	return r;
 }
 
 int mcio_mcUnformat(void)
@@ -3566,3 +3572,81 @@ int mcio_mcRmDir(const char *dirname)
 
 	return r;
 }
+
+int mcio_vmcExportImage(const char *output, int add_ecc)
+{
+	int r;
+	int pagesize, blocksize, cardsize, cardflags;
+
+	r = mcio_mcGetInfo(&pagesize, &blocksize, &cardsize, &cardflags);
+	if (r < 0)
+		return -1;
+
+	FILE *fh = fopen(output, "wb");
+	if (fh == NULL)
+		return -2;
+
+	void *ecc = malloc(pagesize >> 5);
+	void *buf = malloc(pagesize);
+	if (buf == NULL || ecc == NULL) {
+		fclose(fh);
+		return -3;
+	}
+
+	for (int i = 0; i < (cardsize / pagesize); i++) {
+		mcio_mcReadPage(i, buf, ecc);
+		r = fwrite(buf, 1, pagesize, fh);
+		if (r != pagesize) {
+			free(buf);
+			fclose(fh);
+			return -4;
+		}
+
+		if (!add_ecc)
+			continue;
+
+		r = fwrite(ecc, 1, pagesize >> 5, fh);
+		if (r != pagesize >> 5) {
+			free(buf);
+			fclose(fh);
+			return -4;
+		}
+	}
+
+	fclose(fh);
+	free(buf);
+	free(ecc);
+
+	return sceMcResSucceed;
+}
+
+int mcio_vmcImportImage(const char *input)
+{
+	off_t size;
+	size_t r, w;
+	char tmpbuf[0x8000];
+
+	if (!vmc_fp)
+		return -1;
+
+	FILE *fh = fopen(input, "rb");
+	if (fh == NULL)
+		return -2;
+
+	fseek(fh, 0, SEEK_END);
+	size = ftell(fh);
+
+	fseek(fh, 0, SEEK_SET);
+	fseek(vmc_fp, 0, SEEK_SET);
+
+	do {
+		r = fread(tmpbuf, 1, sizeof(tmpbuf), fh);
+		w = fwrite(tmpbuf, 1, r, vmc_fp);
+	}
+	while ((r == w) && (r == sizeof(tmpbuf)));
+
+	ftruncate(fileno(vmc_fp), size);
+	fclose(fh);
+
+	return sceMcResSucceed;
+}
diff --git a/source/menu_cheats.c b/source/menu_cheats.c
index e8f849b..e521e61 100644
--- a/source/menu_cheats.c
+++ b/source/menu_cheats.c
@@ -378,7 +378,7 @@ void DrawGameList(int selIndex, list_t * games, u8 alpha)
 			tmp[1] = (item->flags & SAVE_FLAG_OWNER) ? CHAR_TAG_OWNER : ' ';
 			tmp[2] = (item->flags & SAVE_FLAG_LOCKED) ? CHAR_TAG_LOCKED : ' ';
 			if (item->flags & SAVE_FLAG_PSV) tmp[1] = CHAR_TAG_PSV;
-			if (item->type == FILE_TYPE_VMC) tmp[2] = CHAR_TAG_VMC;
+			if (item->type == FILE_TYPE_VMC) tmp[1] = CHAR_TAG_VMC;
 
 			DrawString(800 - (MENU_ICON_OFF * 1), game_y, tmp);
 			node = list_next(node);
diff --git a/source/menu_main.c b/source/menu_main.c
index 3e3f9a9..e67d76f 100644
--- a/source/menu_main.c
+++ b/source/menu_main.c
@@ -60,16 +60,25 @@ void initMenuOptions(void)
 	*menu_options[OWNER_SETTING].value = menu_options_maxsel[OWNER_SETTING] - 1;
 }
 
-static void LoadFileTexture(const char* fname, int idx)
+static void LoadFileTexture(const char* fname)
 {
-	if (!menu_textures[idx].buffer)
-		menu_textures[idx].buffer = free_mem;
+	pngLoadFromFile(fname, &menu_textures[icon_png_file_index].texture);
+	copyTexture(icon_png_file_index);
 
-	pngLoadFromFile(fname, &menu_textures[idx].texture);
-	copyTexture(idx);
+	menu_textures[icon_png_file_index].size = 1;
+	free_mem = (u32*) menu_textures[icon_png_file_index].buffer;
+}
 
-	menu_textures[idx].size = 1;
-	free_mem = (u32*) menu_textures[idx].buffer;
+static void LoadVmcTexture(int width, int height, uint8_t* icon)
+{
+	menu_textures[icon_png_file_index].texture.width = width;
+	menu_textures[icon_png_file_index].texture.height = height;
+	menu_textures[icon_png_file_index].texture.pitch = (width*4);
+	menu_textures[icon_png_file_index].texture.bmp_out = icon;
+	copyTexture(icon_png_file_index);
+
+	menu_textures[icon_png_file_index].size = 1;
+	free_mem = (u32*) menu_textures[icon_png_file_index].buffer;
 }
 
 static int ReloadUserSaves(save_list_t* save_list)
@@ -159,10 +168,12 @@ static code_entry_t* LoadSaveDetails(void)
 	asprintf(&centry->codes, "%s\n\n"
 		"Title: %s\n"
 		"Sub-Title: %s\n"
+		"Folder: %s\n"
 		"Lock: %s\n\n"
 		"User ID: %08d\n"
 		"Account ID: %.16s (%s)\n"
 		"PSID: %016lX %016lX\n", selected_entry->path, selected_entry->name, subtitle, 
+		selected_entry->dir_name,
 		(selected_entry->flags & SAVE_FLAG_LOCKED ? "Copying Prohibited" : "Unlocked"),
 		param_ids->user_id, param_ids->account_id, 
 		(selected_entry->flags & SAVE_FLAG_OWNER ? "Owner" : "Not Owner"),
@@ -307,6 +318,7 @@ static void SetMenu(int id)
 				menu_old_sel[MENU_PATCHES] = 0;
 
 			char iconfile[256];
+			menu_textures[icon_png_file_index].size = 0;
 			snprintf(iconfile, sizeof(iconfile), "%s" "ICON0.PNG", selected_entry->path);
 
 			if (selected_entry->flags & SAVE_FLAG_ONLINE)
@@ -317,10 +329,14 @@ static void SetMenu(int id)
 					http_download(selected_entry->path, "ICON0.PNG", iconfile, 1);
 			}
 
+			if (selected_entry->flags & SAVE_FLAG_VMC && selected_entry->type == FILE_TYPE_PS1)
+				LoadVmcTexture(16, 16, getIconRGBA(selected_entry->path[strlen(selected_entry->path)+1], 0));
+
+			if (selected_entry->flags & SAVE_FLAG_VMC && selected_entry->type == FILE_TYPE_PS2)
+				LoadVmcTexture(128, 128, getIconPS2(selected_entry->dir_name, strrchr(selected_entry->path, '\n')+1));
+
 			if (file_exists(iconfile) == SUCCESS)
-				LoadFileTexture(iconfile, icon_png_file_index);
-			else
-				menu_textures[icon_png_file_index].size = 0;
+				LoadFileTexture(iconfile);
 
 			if (apollo_config.doAni && menu_id != MENU_PATCH_VIEW && menu_id != MENU_CODE_OPTIONS)
 				Draw_CheatsMenu_Selection_Ani();
diff --git a/source/ps1card.c b/source/ps1card.c
index 403aa35..0b14bb0 100644
--- a/source/ps1card.c
+++ b/source/ps1card.c
@@ -301,7 +301,7 @@ static void loadPalette(void)
             if ((redChannel | greenChannel | blueChannel | blackFlag) == 0)
                 ps1saves[slotNumber].iconPalette[colorCounter] = 0x00000000;
             else
-                ps1saves[slotNumber].iconPalette[colorCounter] = redChannel | (greenChannel << 8) | (blueChannel << 16) | 0xFF000000;
+                ps1saves[slotNumber].iconPalette[colorCounter] = blueChannel | (greenChannel << 8) | (redChannel << 16) | 0xFF000000;
 
             colorCounter++;
         }
diff --git a/source/ps2classic.c b/source/ps2classic.c
index 30154a1..8cf3623 100644
--- a/source/ps2classic.c
+++ b/source/ps2classic.c
@@ -269,27 +269,6 @@ int ps2_add_vmc_ecc(const char* src, const char* dst)
 	return 1;
 }
 
-int ps2_remove_vmc_ecc(const char* src, const char* dst)
-{
-	ps2_block_t block;
-
-	FILE *in = fopen(src, "rb");
-	FILE *fo = fopen(dst, "wb");
-
-	if (!in || !fo)
-		return 0;
-
-	while(fread(&block, 1, sizeof(ps2_block_t), in) > 0)
-	{
-		fwrite(&block, PS2_VMC_DATASIZE, 1, fo);
-	}
-
-	fclose(in);
-	fclose(fo);
-
-	return 1;
-}
-
 /*
  *  ps2_iso9660_sig
  */
diff --git a/source/ps2icon.c b/source/ps2icon.c
new file mode 100644
index 0000000..2c02f5d
--- /dev/null
+++ b/source/ps2icon.c
@@ -0,0 +1,139 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <string.h>
+
+#include "ps2icon.h"
+#include "mcio.h"
+
+
+static uint32_t TIM2RGBA(const uint8_t *buf)
+{
+	uint8_t ARGB[4];
+	uint16_t lRGB = (int16_t) (buf[1] << 8) | buf[0];
+
+	ARGB[0] = 0xFF;
+	ARGB[1] = 8 * (lRGB & 0x1F);
+	ARGB[2] = 8 * ((lRGB >> 5) & 0x1F);
+	ARGB[3] = 8 * (lRGB >> 10);
+
+	return *((uint32_t *) &ARGB);
+}
+
+static void* ps2IconTexture(const uint8_t* iData)
+{
+	int i;
+	uint16_t j;
+	Icon_Header header;
+	Animation_Header anim_header;
+	Frame_Data animation;
+	uint32_t *lTexturePtr, *lRGBA;
+
+	lTexturePtr = (uint32_t *) calloc(128 * 128, sizeof(uint32_t));
+
+	//read header:
+	memcpy(&header, iData, sizeof(Icon_Header));
+	iData += sizeof(Icon_Header);
+
+	//convert to big endian
+	header.n_vertices = __builtin_bswap32(header.n_vertices);
+	header.texture_type = __builtin_bswap32(header.texture_type);
+	header.animation_shapes = __builtin_bswap32(header.animation_shapes);
+
+	//n_vertices has to be divisible by three, that's for sure:
+	if(header.file_id != 0x0100 || header.n_vertices % 3 != 0)
+		return lTexturePtr;
+
+	//read icon data from file: https://ghulbus-inc.de/projects/ps2iconsys/
+	///Vertex data
+	// each vertex consists of animation_shapes tuples for vertex coordinates,
+	// followed by one vertex coordinate tuple for normal coordinates
+	// followed by one texture data tuple for texture coordinates and color
+	for(i=0; i<header.n_vertices; i++) {
+		iData += sizeof(Vertex_Coord) * header.animation_shapes;
+		iData += sizeof(Vertex_Coord);
+		iData += sizeof(Texture_Data);
+	}
+
+	//animation data
+	// preceeded by an animation header, there is a frame data/key set for every frame:
+	memcpy(&anim_header, iData, sizeof(Animation_Header));
+	anim_header.n_frames = __builtin_bswap32(anim_header.n_frames);
+	iData += sizeof(Animation_Header);
+
+	//read animation data:
+	for(i=0; i<anim_header.n_frames; i++) {
+		memcpy(&animation, iData, sizeof(Frame_Data));
+		animation.n_keys = __builtin_bswap32(animation.n_keys);
+		iData += sizeof(Frame_Data);
+
+		if(animation.n_keys > 0)
+			iData += sizeof(Frame_Key) * animation.n_keys;
+	}
+
+	lRGBA = lTexturePtr;
+
+	if (header.texture_type <= 7)
+	{	// Uncompressed texture
+		for (i = 0; i < (128 * 128); i++)
+		{
+			*lRGBA = TIM2RGBA(iData);
+			lRGBA++;
+			iData += 2;
+		}
+	}
+	else
+	{	//Compressed texture
+		iData += 4;
+		do
+		{
+			j = (int16_t) (iData[1] << 8) | iData[0];
+			if (0xFF00 == (j & 0xFF00))
+			{
+				for (j = (0x0000 - j) & 0xFFFF; j > 0; j--)
+				{
+					iData += 2;
+					*lRGBA = TIM2RGBA(iData);
+					lRGBA++;
+				}
+			}
+			else
+			{
+				iData += 2;
+				for (; j > 0; j--)
+				{
+					*lRGBA = TIM2RGBA(iData);
+					lRGBA++;
+				}
+			}
+			iData += 2;
+		} while ((lRGBA - lTexturePtr) < 0x4000);
+	}
+
+	return (lTexturePtr);
+}
+
+//Get icon data as bytes
+uint8_t* getIconPS2(const char* folder, const char* iconfile)
+{
+	int fd;
+	uint8_t *buf, *out;
+	char filePath[256];
+	struct io_dirent st;
+
+	snprintf(filePath, sizeof(filePath), "%s/%s", folder, iconfile);
+	mcio_mcStat(filePath, &st);
+
+	fd = mcio_mcOpen(filePath, sceMcFileAttrReadable | sceMcFileAttrFile);
+	if (fd < 0)
+		return calloc(128 * 128, sizeof(uint32_t));
+
+	buf = malloc(st.stat.size);
+	mcio_mcRead(fd, buf, st.stat.size);
+	mcio_mcClose(fd);
+
+	out = ps2IconTexture(buf);
+	free(buf);
+
+	return out;
+}
diff --git a/source/saves.c b/source/saves.c
index d709aef..9750900 100644
--- a/source/saves.c
+++ b/source/saves.c
@@ -387,51 +387,6 @@ static int set_psv_codes(save_entry_t* item)
 	return list_count(item->codes);
 }
 
-static int set_ps2_codes(save_entry_t* item)
-{
-	code_entry_t* cmd;
-	item->codes = list_alloc();
-
-	cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_USER " View Save Details", CMD_VIEW_DETAILS);
-	list_append(item->codes, cmd);
-
-	cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Decrypt SCEVMC0.VME", CMD_CODE_NULL);
-	cmd->file = strdup("SCEVMC0.VME");
-	cmd->options_count = 1;
-	cmd->options = _createOptions(3, "Decrypt SCEVMC0.VME to USB", CMD_DECRYPT_PS2_VME);
-	asprintf(&cmd->options->name[2], "Decrypt SCEVMC0.VME to HDD");
-	asprintf(&cmd->options->value[2], "%c%c", CMD_DECRYPT_PS2_VME, STORAGE_HDD);
-	list_append(item->codes, cmd);
-
-	cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Decrypt SCEVMC1.VME", CMD_CODE_NULL);
-	cmd->file = strdup("SCEVMC1.VME");
-	cmd->options_count = 1;
-	cmd->options = _createOptions(3, "Decrypt SCEVMC1.VME to USB", CMD_DECRYPT_PS2_VME);
-	asprintf(&cmd->options->name[2], "Decrypt SCEVMC1.VME to HDD");
-	asprintf(&cmd->options->value[2], "%c%c", CMD_DECRYPT_PS2_VME, STORAGE_HDD);
-	list_append(item->codes, cmd);
-
-	cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Import a .VM2 to SCEVMC0.VME", CMD_CODE_NULL);
-	cmd->file = strdup("SCEVMC0.VME");
-	cmd->options_count = 1;
-	cmd->options = _getFileOptions(VMC_PS2_PATH_HDD, "*.VM2", CMD_ENCRYPT_PS2_VMC);
-	list_append(item->codes, cmd);
-
-	cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Import a .VM2 to SCEVMC1.VME", CMD_CODE_NULL);
-	cmd->file = strdup("SCEVMC1.VME");
-	cmd->options_count = 1;
-	cmd->options = _getFileOptions(VMC_PS2_PATH_HDD, "*.VM2", CMD_ENCRYPT_PS2_VMC);
-	list_append(item->codes, cmd);
-
-	cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Copy dummy .PSV Save", CMD_CODE_NULL);
-	cmd->file = strdup("APOLLO-99PS2.PSV");
-	cmd->options_count = 1;
-	cmd->options = _createOptions(2, "Copy APOLLO-99PS2.PSV to USB", CMD_COPY_DUMMY_PSV);
-	list_append(item->codes, cmd);
-
-	return list_count(item->codes);
-}
-
 static void add_vmp_commands(save_entry_t* save)
 {
 	char path[256];
@@ -483,9 +438,6 @@ int ReadCodes(save_entry_t * save)
 	if (save->flags & SAVE_FLAG_PSV)
 		return set_psv_codes(save);
 
-	if (save->flags & SAVE_FLAG_PS2)
-		return set_ps2_codes(save);
-
 	if (save->flags & SAVE_FLAG_PSP)
 		return set_psp_codes(save);
 
@@ -1096,12 +1048,6 @@ list_t * ReadBackupList(const char* userPath)
 	list_append(item->codes, cmd);
 	list_append(list, item);
 
-	item = _createSaveEntry(SAVE_FLAG_PS2, CHAR_ICON_COPY " Export PS2 .VM2 memory cards to USB");
-	item->path = strdup(VMC_PS2_PATH_HDD);
-	item->title_id = strdup("HDD");
-	item->type = FILE_TYPE_VM2;
-	list_append(list, item);
-
 	item = _createSaveEntry(0, CHAR_ICON_ZIP " Extract Archives (RAR, Zip, 7z)");
 	item->path = strdup(PS3_TMP_PATH);
 	item->title_id = strdup("HDD");
@@ -1196,46 +1142,6 @@ static int get_binenc_files(save_entry_t * item)
 	return list_count(item->codes);
 }
 
-static int get_vm2_files(save_entry_t * item)
-{
-	code_entry_t* cmd;
-	DIR *d;
-	struct dirent *dir;
-	char name[256];
-	item->codes = list_alloc();
-
-	d = opendir(item->path);
-
-	if (d)
-	{
-		while ((dir = readdir(d)) != NULL)
-		{
-			if (dir->d_type == DT_REG && endsWith(dir->d_name, ".VM2"))
-			{
-				snprintf(name, sizeof(name), "Export \"%s\" to .VMC", dir->d_name);
-
-				cmd = _createCmdCode(PATCH_COMMAND, name, CMD_CODE_NULL);
-				cmd->file = strdup(dir->d_name);
-				cmd->options_count = 1;
-				cmd->options = _createOptions(2, "Save .VMC to USB", CMD_EXP_VM2_RAW);
-				list_append(item->codes, cmd);
-
-				LOG("Adding File '%s'", cmd->file);
-			}
-		}
-		closedir(d);
-	}
-
-	if (!list_count(item->codes))
-	{
-		list_free(item->codes);
-		item->codes = NULL;
-		return 0;
-	}
-
-	return list_count(item->codes);
-}
-
 static int get_ps2_raw_files(save_entry_t * item)
 {
 	code_entry_t* cmd;
@@ -1326,9 +1232,6 @@ int ReadBackupCodes(save_entry_t * bup)
 	case FILE_TYPE_BINENC:
 		return get_binenc_files(bup);
 
-	case FILE_TYPE_VM2:
-		return get_vm2_files(bup);
-
 	case FILE_TYPE_PS2RAW:
 		return get_ps2_raw_files(bup);
 
@@ -1560,14 +1463,15 @@ int sortSaveList_Compare_TitleID(const void* a, const void* b)
 	return strcasecmp(ta, tb);
 }
 
-static void read_savegames(const char* userPath, list_t *list, uint32_t flag)
+static void read_savegames(const char* userPath, const char* folder, list_t *list, uint32_t flag)
 {
 	DIR *d;
 	struct dirent *dir;
 	save_entry_t *item;
 	char sfoPath[256];
 
-	d = opendir(userPath);
+	snprintf(sfoPath, sizeof(sfoPath), "%s%s", userPath, folder);
+	d = opendir(sfoPath);
 
 	if (!d)
 		return;
@@ -1577,7 +1481,7 @@ static void read_savegames(const char* userPath, list_t *list, uint32_t flag)
 		if (dir->d_type != DT_DIR || strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0)
 			continue;
 
-		snprintf(sfoPath, sizeof(sfoPath), "%s%s/PARAM.SFO", userPath, dir->d_name);
+		snprintf(sfoPath, sizeof(sfoPath), "%s%s%s/PARAM.SFO", userPath, folder, dir->d_name);
 		if (file_exists(sfoPath) == SUCCESS)
 		{
 			LOG("Reading %s...", sfoPath);
@@ -1590,9 +1494,38 @@ static void read_savegames(const char* userPath, list_t *list, uint32_t flag)
 			}
 
 			char *sfo_data = (char*) sfo_get_param_value(sfo, "TITLE");
-			item = _createSaveEntry(flag, sfo_data);
 
-			asprintf(&item->path, "%s%s/", userPath, dir->d_name);
+			if (flag & SAVE_FLAG_PS2)
+			{
+				snprintf(sfoPath, sizeof(sfoPath), "%s%s%s/SCEVMC0.VME", userPath, folder, dir->d_name);
+				if (file_exists(sfoPath) != SUCCESS)
+				{
+					sfo_free(sfo);
+					continue;
+				}
+
+				snprintf(sfoPath, sizeof(sfoPath), "%s (MemCard)", sfo_data);
+				item = _createSaveEntry(flag | SAVE_FLAG_VMC | SAVE_FLAG_LOCKED, sfoPath);
+				item->type = FILE_TYPE_VMC;
+				asprintf(&item->path, "%s%s%s/SCEVMC0.VME", userPath, folder, dir->d_name);
+				item->title_id = strdup("VME 0");
+				item->dir_name = strdup(userPath);
+				list_append(list, item);
+
+				item = _createSaveEntry(flag | SAVE_FLAG_VMC | SAVE_FLAG_LOCKED, sfoPath);
+				item->type = FILE_TYPE_VMC;
+				asprintf(&item->path, "%s%s%s/SCEVMC1.VME", userPath, folder, dir->d_name);
+				item->title_id = strdup("VME 1");
+				item->dir_name = strdup(userPath);
+				list_append(list, item);
+
+				sfo_free(sfo);
+				LOG("[%s] F(%X) name '%s'", item->title_id, item->flags, item->name);
+				continue;
+			}
+
+			item = _createSaveEntry(flag, sfo_data);
+			asprintf(&item->path, "%s%s%s/", userPath, folder, dir->d_name);
 			asprintf(&item->title_id, "%.9s", dir->d_name);
 			item->dir_name = strdup(dir->d_name);
 
@@ -1654,7 +1587,7 @@ static void read_vmc2_files(const char* userPath, list_t *list)
 	closedir(d);
 }
 
-static void read_psv_savegames(const char* userPath, list_t *list)
+static void read_psv_savegames(const char* userPath, const char* folder, list_t *list)
 {
 	DIR *d;
 	struct dirent *dir;
@@ -1662,7 +1595,8 @@ static void read_psv_savegames(const char* userPath, list_t *list)
 	char psvPath[256];
 	char data[0x100];
 
-	d = opendir(userPath);
+	snprintf(psvPath, sizeof(psvPath), "%s%s", userPath, folder);
+	d = opendir(psvPath);
 
 	if (!d)
 		return;
@@ -1672,7 +1606,7 @@ static void read_psv_savegames(const char* userPath, list_t *list)
 		if (dir->d_type != DT_REG || !endsWith(dir->d_name, ".PSV"))
 			continue;
 
-		snprintf(psvPath, sizeof(psvPath), "%s%s", userPath, dir->d_name);
+		snprintf(psvPath, sizeof(psvPath), "%s%s%s", userPath, folder, dir->d_name);
 		LOG("Reading %s...", psvPath);
 
 		FILE *psvf = fopen(psvPath, "rb");
@@ -1700,8 +1634,8 @@ static void read_psv_savegames(const char* userPath, list_t *list)
 		memset(item, 0, sizeof(save_entry_t));
 		item->flags = SAVE_FLAG_PSV | (type == 1 ? SAVE_FLAG_PS1 : SAVE_FLAG_PS2);
 
-		asprintf(&item->path, "%s%s", userPath, dir->d_name);
-		asprintf(&item->title_id, "%.12s", dir->d_name);
+		asprintf(&item->path, "%s%s%s", userPath, folder, dir->d_name);
+		asprintf(&item->title_id, "%.10s", dir->d_name + 2);
 
 		//PS2 Title offset 0xC0
 		//PS1 Title offset 0x04
@@ -1714,7 +1648,7 @@ static void read_psv_savegames(const char* userPath, list_t *list)
 	closedir(d);
 }
 
-static void read_psx_savegames(const char* userPath, list_t *list)
+static void read_psx_savegames(const char* userPath, const char* folder, list_t *list)
 {
 	DIR *d;
 	struct dirent *dir;
@@ -1723,7 +1657,8 @@ static void read_psx_savegames(const char* userPath, list_t *list)
 	char data[64];
 	int type, toff;
 
-	d = opendir(userPath);
+	snprintf(psvPath, sizeof(psvPath), "%s%s", userPath, folder);
+	d = opendir(psvPath);
 
 	if (!d)
 		return;
@@ -1760,13 +1695,13 @@ static void read_psx_savegames(const char* userPath, list_t *list)
 		}
 		else if (endsWith(dir->d_name, ".XPS") || endsWith(dir->d_name, ".SPS"))
 		{
-			toff = 0x04;
+			toff = 0x02;
 			type = FILE_TYPE_XPS;
 		}
 		else
 			continue;
 
-		snprintf(psvPath, sizeof(psvPath), "%s%s", userPath, dir->d_name);
+		snprintf(psvPath, sizeof(psvPath), "%s%s%s", userPath, folder, dir->d_name);
 		LOG("Reading %s...", psvPath);
 
 		FILE *fp = fopen(psvPath, "rb");
@@ -1786,8 +1721,8 @@ static void read_psx_savegames(const char* userPath, list_t *list)
 			item->flags = SAVE_FLAG_PS1;
 
 		item->type = type;
-		asprintf(&item->path, "%s%s", userPath, dir->d_name);
-		asprintf(&item->title_id, "%.12s", data);
+		asprintf(&item->path, "%s%s%s", userPath, folder, dir->d_name);
+		asprintf(&item->title_id, "%.10s", data + 2);
 			
 		LOG("[%s] F(%X) name '%s'", item->title_id, item->flags, item->name);
 		list_append(list, item);
@@ -1810,8 +1745,6 @@ list_t * ReadUserList(const char* userPath)
 	save_entry_t *item;
 	code_entry_t *cmd;
 	list_t *list;
-	char savePath[256];
-	char * save_paths[3];
 
 	if (dir_exists(userPath) != SUCCESS)
 		return NULL;
@@ -1821,6 +1754,7 @@ list_t * ReadUserList(const char* userPath)
 	item = _createSaveEntry(SAVE_FLAG_PS3, CHAR_ICON_COPY " Bulk Save Management");
 	item->type = FILE_TYPE_MENU;
 	item->codes = list_alloc();
+	asprintf(&item->path, "%s%s", userPath, PS3_SAVES_PATH_HDD);
 	//bulk management hack
 	item->dir_name = malloc(sizeof(void**));
 	((void**)item->dir_name)[0] = list;
@@ -1831,77 +1765,82 @@ list_t * ReadUserList(const char* userPath)
 	cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_SIGN " Resign & Unlock selected Saves", CMD_RESIGN_SAVES);
 	list_append(item->codes, cmd);
 
-	if (strncmp(userPath, USER_PATH_HDD, 15) == 0)
-	{
-		asprintf(&item->path, SAVES_PATH_HDD, apollo_config.user_id);
+	cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Copy all Saves to USB", CMD_CODE_NULL);
+	cmd->options_count = 1;
+	cmd->options = _createOptions(2, "Copy Saves to USB", CMD_COPY_ALL_SAVES_USB);
+	list_append(item->codes, cmd);
 
-		cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Copy all Saves to USB", CMD_CODE_NULL);
-		cmd->options_count = 1;
-		cmd->options = _createOptions(2, "Copy Saves to USB", CMD_COPY_ALL_SAVES_USB);
-		list_append(item->codes, cmd);
+	cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Copy selected Saves to USB", CMD_CODE_NULL);
+	cmd->options_count = 1;
+	cmd->options = _createOptions(2, "Copy Saves to USB", CMD_COPY_SAVES_USB);
+	list_append(item->codes, cmd);
 
-		cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Copy selected Saves to USB", CMD_CODE_NULL);
-		cmd->options_count = 1;
-		cmd->options = _createOptions(2, "Copy Saves to USB", CMD_COPY_SAVES_USB);
-		list_append(item->codes, cmd);
+	cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_NET " Start local Web Server", CMD_SAVE_WEB_SERVER);
+	list_append(item->codes, cmd);
+	list_append(list, item);
 
-		save_paths[0] = PS3_SAVES_PATH_HDD;
-		save_paths[1] = PS2_SAVES_PATH_HDD;
-		save_paths[2] = PSP_SAVES_PATH_HDD;
-	}
-	else
-	{
-		asprintf(&item->path, "%s" PS3_SAVES_PATH_USB, userPath);
+	read_savegames(userPath, PS3_SAVES_PATH_HDD, list, SAVE_FLAG_PS3);
+	read_savegames(userPath, PS2_SAVES_PATH_HDD, list, SAVE_FLAG_PS2);
+	read_savegames(userPath, PSP_SAVES_PATH_HDD, list, SAVE_FLAG_PSP);
 
-		cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Copy all Saves to HDD", CMD_COPY_ALL_SAVES_HDD);
-		list_append(item->codes, cmd);
+	read_vmc1_files(VMC_PS2_PATH_HDD, list);
+	read_vmc2_files(VMC_PS2_PATH_HDD, list);
 
-		cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Copy selected Saves to HDD", CMD_COPY_SAVES_HDD);
-		list_append(item->codes, cmd);
+	return list;
+}
 
-		save_paths[0] = PS3_SAVES_PATH_USB;
-		save_paths[1] = PS2_SAVES_PATH_USB;
-		save_paths[2] = PSP_SAVES_PATH_USB;
-	}
+list_t * ReadUsbList(const char* userPath)
+{
+	save_entry_t *item;
+	code_entry_t *cmd;
+	list_t *list;
+	char savePath[256];
 
-	cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_NET " Start local Web Server", CMD_SAVE_WEB_SERVER);
+	if (dir_exists(userPath) != SUCCESS)
+		return NULL;
+
+	list = list_alloc();
+
+	item = _createSaveEntry(SAVE_FLAG_PS3, CHAR_ICON_COPY " Bulk Save Management");
+	item->type = FILE_TYPE_MENU;
+	item->codes = list_alloc();
+	asprintf(&item->path, "%s%s", userPath, PS3_SAVES_PATH_USB);
+	//bulk management hack
+	item->dir_name = malloc(sizeof(void**));
+	((void**)item->dir_name)[0] = list;
+
+	cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_SIGN " Resign & Unlock all Saves", CMD_RESIGN_ALL_SAVES);
 	list_append(item->codes, cmd);
-	list_append(list, item);
 
-	snprintf(savePath, sizeof(savePath), "%s%s", userPath, save_paths[0]);
-	read_savegames(savePath, list, SAVE_FLAG_PS3);
+	cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_SIGN " Resign & Unlock selected Saves", CMD_RESIGN_SAVES);
+	list_append(item->codes, cmd);
 
-	snprintf(savePath, sizeof(savePath), "%s%s", userPath, save_paths[1]);
-	read_savegames(savePath, list, SAVE_FLAG_PS2);
+	cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Copy all Saves to HDD", CMD_COPY_ALL_SAVES_HDD);
+	list_append(item->codes, cmd);
 
-	snprintf(savePath, sizeof(savePath), "%s%s", userPath, save_paths[2]);
-	read_savegames(savePath, list, SAVE_FLAG_PSP);
+	cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Copy selected Saves to HDD", CMD_COPY_SAVES_HDD);
+	list_append(item->codes, cmd);
 
-	if (strncmp(userPath, USB_PATH, 8) == 0)
-	{
-		snprintf(savePath, sizeof(savePath), "%s%s", userPath, PSV_SAVES_PATH_USB);
-		read_psv_savegames(savePath, list);
+	cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_NET " Start local Web Server", CMD_SAVE_WEB_SERVER);
+	list_append(item->codes, cmd);
+	list_append(list, item);
 
-		snprintf(savePath, sizeof(savePath), "%s%s", userPath, PS2_IMP_PATH_USB);
-		read_psx_savegames(savePath, list);
+	read_savegames(userPath, PS3_SAVES_PATH_USB, list, SAVE_FLAG_PS3);
+	read_savegames(userPath, PS2_SAVES_PATH_USB, list, SAVE_FLAG_PS2);
+	read_savegames(userPath, PSP_SAVES_PATH_USB, list, SAVE_FLAG_PSP);
 
-		snprintf(savePath, sizeof(savePath), "%s%s", userPath, PS1_IMP_PATH_USB);
-		read_psx_savegames(savePath, list);
+	read_psv_savegames(userPath, PSV_SAVES_PATH_USB, list);
+	read_psx_savegames(userPath, PS2_IMP_PATH_USB, list);
+	read_psx_savegames(userPath, PS1_IMP_PATH_USB, list);
 
-		snprintf(savePath, sizeof(savePath), "%s%s", userPath, VMC_PS1_PATH_USB);
-		read_vmc1_files(savePath, list);
+	snprintf(savePath, sizeof(savePath), "%s%s", userPath, VMC_PS1_PATH_USB);
+	read_vmc1_files(savePath, list);
 
-		snprintf(savePath, sizeof(savePath), "%s%s", userPath, VMC_PS2_PATH_USB);
-		read_vmc2_files(savePath, list);
+	snprintf(savePath, sizeof(savePath), "%s%s", userPath, VMC_PS2_PATH_USB);
+	read_vmc2_files(savePath, list);
 
-		snprintf(savePath, sizeof(savePath), "%s%s", userPath, "VMC/");
-		read_vmc2_files(savePath, list);
-	}
-	else
-	{
-		read_vmc1_files(VMC_PS2_PATH_HDD, list);
-		read_vmc2_files(VMC_PS2_PATH_HDD, list);
-	}
+	snprintf(savePath, sizeof(savePath), "%s%s", userPath, "VMC/");
+	read_vmc2_files(savePath, list);
 
 	return list;
 }
@@ -2104,6 +2043,7 @@ list_t * ReadVmc2List(const char* userPath)
 	item = _createSaveEntry(SAVE_FLAG_PS2, CHAR_ICON_VMC " Memory Card Management");
 	item->type = FILE_TYPE_MENU;
 	item->path = strdup(userPath);
+	item->title_id = strdup("VMC");
 	item->codes = list_alloc();
 	//bulk management hack
 	item->dir_name = malloc(sizeof(void**));
@@ -2119,6 +2059,35 @@ list_t * ReadVmc2List(const char* userPath)
 	list_append(item->codes, cmd);
 	list_append(list, item);
 
+	cmd = _createCmdCode(PATCH_NULL, "----- " UTF8_CHAR_STAR " Virtual Memory Card " UTF8_CHAR_STAR " -----", CMD_CODE_NULL);
+	list_append(item->codes, cmd);
+
+	if (!endsWith(userPath, ".VM2"))
+	{
+		cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Export Memory Card to .VM2 format", CMD_CODE_NULL);
+		cmd->file = strdup(strrchr(userPath, '/')+1);
+		cmd->options_count = 1;
+		cmd->options = _createOptions(3, "Save .VM2 Memory Card to USB", CMD_EXP_PS2_VM2);
+		asprintf(&cmd->options->name[2], "Save .VM2 Memory Card to HDD");
+		asprintf(&cmd->options->value[2], "%c%c", CMD_EXP_PS2_VM2, STORAGE_HDD);
+		list_append(item->codes, cmd);
+	}
+
+	cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Export Memory Card to .VMC format (No ECC)", CMD_CODE_NULL);
+	cmd->file = strdup(strrchr(userPath, '/')+1);
+	cmd->options_count = 1;
+	cmd->options = _createOptions(2, "Save .VMC Memory Card to USB", CMD_EXP_VM2_RAW);
+	list_append(item->codes, cmd);
+
+	if (!endsWith(userPath, ".VMC"))
+	{
+		cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Import a .VM2 file to Memory Card", CMD_CODE_NULL);
+		cmd->file = strdup(strrchr(userPath, '/')+1);
+		cmd->options_count = 1;
+		cmd->options = _getFileOptions(VMC_PS2_PATH_HDD, "*.VM2", CMD_IMP_PS2_VM2);
+		list_append(item->codes, cmd);
+	}
+
 	item = _createSaveEntry(SAVE_FLAG_PS2, CHAR_ICON_COPY " Import Saves to Virtual Card");
 	if (strncmp(userPath, USB_PATH, 8) == 0)
 	{