Skip to content

Commit

Permalink
Merge branch '185-add-ps1-virtual-memcard-management'
Browse files Browse the repository at this point in the history
  • Loading branch information
bucanero committed Nov 10, 2024
2 parents b472a71 + 1537f0c commit eaf27d1
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 99 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Package metadata.
TITLE := Apollo Save Tool
VERSION := 01.60
VERSION := 01.70
TITLE_ID := APOL00004
CONTENT_ID := IV0000-APOL00004_00-APOLLO0000000PS4

Expand Down
24 changes: 22 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,20 @@ This homebrew app allows you to download, unlock, patch, and resign save-game fi
* **Account activation:** create fake Account IDs and generate offline PS4 activations
* **Recover passcode:** simple recovery method for the Parental Security Passcode.

## PS1 Virtual Memory Card Management

* **VMC saves management:** quick access to all save files on Virtual Memory Cards images.
- Supported PS1 VMC formats: `.VMP`, `.MCR`, `.VM1`, `.BIN`, `.VMC`, `.GME`, `.VGS`, `.SRM`, `.MCD`
* **Import PS1 saves:** import saves to VMCs from other systems and consoles (`.MCS`, `.PSV`, `.PSX`, `.PS1`, `.MCB`, `.PDA` supported).
* **Export PS1 saves:** allows the user export saves on VMC images to `.MCS`/`.PSV`/`.PSX` formats.

## PS2 Virtual Memory Card Management

* **VMC saves management:** quick access to all save files on Virtual Memory Cards images.
- Supported PS2 VMC formats: `.VM2`, `.CARD`, `.PS2`, `.VMC`, `.BIN`
- Supports ECC and non-ECC images
* **VMC saves import:** import saves to VMCs from other systems and consoles (`.PSU`, `.PSV`, `.XPS`, `.CBS`, `.MAX`, `.SPS` supported).
* **VMC saves export:** allows the user export saves on VMC images to `.PSU` and `.PSV` formats
* **Import PS2 saves:** import saves to VMCs from other systems and consoles (`.PSU`, `.PSV`, `.XPS`, `.CBS`, `.MAX`, `.SPS` supported).
* **Export PS2 saves:** allows the user export saves on VMC images to `.PSU` and `.PSV` formats

# Download

Expand Down Expand Up @@ -102,6 +109,17 @@ On first run, the application will detect and setup the required user settings.
| **External VMCs (HDD)** | `/data/fakeusb/PS2/VMC/` |
| **HDD VMC cards** | VMCs will be scanned from PS4 saves on the hard disk |

### PS1

| PS1 | Folder |
|-----|--------|
| **USB saves** | `/mnt/usbX/PS1/SAVEDATA/` (`*.mcs`, `*.psx`, `*.ps1`, `*.mcb`, `*.psv`, `*.pda`) |
| **VMC cards** | `/mnt/usbX/PS1/VMC/` (`*.vmc`, `*.mcd`, `*.mcr`, `*.gme`, `*.vm1`, `*.vmp`, `*.vgs`, `*.srm`, `*.bin`) |
| **PSV saves** | `/mnt/usbX/PS3/EXPORT/PSV/` |
| **External saves (HDD)** | `/data/fakeusb/PS1/SAVEDATA/` |
| **External VMCs (HDD)** | `/data/fakeusb/PS1/VMC/` |
| **HDD VMC cards** | VMCs will be scanned from PS4 saves on the hard disk |

## Offline Account activation

To activate an account offline, go to the `User Tools` menu, and select `Activate PS4 Accounts`.
Expand Down Expand Up @@ -148,6 +166,8 @@ Currently, the list of available games and files is limited, but the project aim
* [Berion](https://www.psx-place.com/members/berion.1431/): GUI design
* [flatz](https://github.com/flatz): [SFO tools](https://github.com/bucanero/pfd_sfo_tools/)
* [aldostools](https://aldostools.org/): [Bruteforce Save Data](https://bruteforcesavedata.forumms.net/)
* [jimmikaelkael](https://github.com/jimmikaelkael): ps3mca tool
* [ShendoXT](https://github.com/ShendoXT): [MemcardRex](https://github.com/ShendoXT/memcardrex)
* [Nobody/Wild Light](https://github.com/nobodo): [Background music track](https://github.com/bucanero/apollo-vita/blob/main/data/haiku.s3m)

# Building
Expand Down
2 changes: 1 addition & 1 deletion include/settings.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#define APOLLO_VERSION "1.6.0" //Apollo PS4 version (about menu)
#define APOLLO_VERSION "1.7.0" //Apollo PS4 version (about menu)

#define MENU_TITLE_OFF 45 //Offset of menu title text from menu mini icon
#define MENU_ICON_OFF 105 //X Offset to start printing menu mini icon
Expand Down
26 changes: 26 additions & 0 deletions source/exec_cmd.c
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,26 @@ static void exportVmcSave(const save_entry_t* save, int type, int dst_id)
show_message("Error exporting save:\n%s", save->path);
}

static void export_ps1vmc(const char* vm1_file, int dst, int vmp)
{
char dstfile[256];
char dst_path[256];

_set_dest_path(dst_path, dst, VMC_PS1_PATH_USB);
if (mkdirs(dst_path) != SUCCESS)
{
show_message("Error! Export folder is not available:\n%s", dst_path);
return;
}

snprintf(dstfile, sizeof(dstfile), "%s%s.%s", dst_path, vm1_file, vmp ? "VMP" : "VM1");

if (saveMemoryCard(dstfile, vmp ? PS1CARD_VMP : PS1CARD_RAW, 0))
show_message("Memory card successfully exported to:\n%s", dstfile);
else
show_message("Error! Failed to export PS1 memory card");
}

static void export_vmc2save(const save_entry_t* save, int type, int dst_id)
{
int ret = 0;
Expand Down Expand Up @@ -1487,6 +1507,12 @@ void execCodeCommand(code_entry_t* code, const char* codecmd)
code->activated = 0;
break;

case CMD_EXP_PS1_VM1:
case CMD_EXP_PS1_VMP:
export_ps1vmc(code->file, codecmd[1], codecmd[0] == CMD_EXP_PS1_VMP);
code->activated = 0;
break;

default:
break;
}
Expand Down
2 changes: 1 addition & 1 deletion source/psv_resign.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
#define PSV_TYPE_OFFSET 0x3C
#define VMP_SEED_OFFSET 0x0C
#define VMP_HASH_OFFSET 0x20
#define VMP_MAGIC 0x00504D56
#define VMP_MAGIC 0x564D5000
#define VMP_SIZE 0x20080

static const char SJIS_REPLACEMENT_TABLE[] =
Expand Down
152 changes: 58 additions & 94 deletions source/saves.c
Original file line number Diff line number Diff line change
Expand Up @@ -1016,70 +1016,6 @@ static void add_vmc_import_saves(list_t* list, const char* path, const char* fol
closedir(d);
}

static void read_vmc1_files(const char* vmcPath, const save_entry_t* parent, list_t* list)
{
uint64_t size;
char filePath[256];
save_entry_t *item;
DIR *d;
struct dirent *dir;

d = opendir(vmcPath);
if (!d)
return;

while ((dir = readdir(d)) != NULL)
{
if (!endsWith(dir->d_name, ".VMP") && !endsWith(dir->d_name, ".MCR") && !endsWith(dir->d_name, ".GME") &&
!endsWith(dir->d_name, ".VM1") && !endsWith(dir->d_name, ".MCD") && !endsWith(dir->d_name, ".VGS") &&
!endsWith(dir->d_name, ".VMC") && !endsWith(dir->d_name, ".BIN") && !endsWith(dir->d_name, ".SRM"))
continue;

snprintf(filePath, sizeof(filePath), "%s%s", vmcPath, dir->d_name);
get_file_size(filePath, &size);

LOG("Checking %s...", filePath);
switch (size)
{
case PS1CARD_SIZE:
case 0x20040:
case 0x20080:
case 0x200A0:
case 0x20F40:
break;

default:
continue;
}

item = _createSaveEntry(SAVE_FLAG_PS1 | SAVE_FLAG_VMC, dir->d_name);
item->type = FILE_TYPE_VMC;

if (parent)
{
item->flags |= (parent->flags & SAVE_FLAG_HDD);
item->path = strdup((parent->flags & SAVE_FLAG_HDD) ? dir->d_name : vmcPath);
item->dir_name = strdup((parent->flags & SAVE_FLAG_HDD) ? parent->dir_name : VMC_PS1_PATH_USB);
item->title_id = strdup(parent->title_id);
item->blocks = parent->blocks;

free(item->name);
asprintf(&item->name, "%s - %s", parent->name, dir->d_name);
}
else
{
item->title_id = strdup("VMC");
item->dir_name = strdup(VMC_PS1_PATH_USB);
asprintf(&item->path, "%s%s", vmcPath, dir->d_name);
}

list_append(list, item);
LOG("[%s] F(%X) name '%s'", item->path, item->flags, item->name);
}

closedir(d);
}

int ReadVmc1Codes(save_entry_t * save)
{
code_entry_t * cmd;
Expand Down Expand Up @@ -1837,32 +1773,59 @@ static void read_hdd_savegames(const char* userPath, list_t *list, sqlite3 *appd
sqlite3_close(db);
}

static void read_vmc2_files(const char* userPath, const save_entry_t* parent, list_t *list)
static void scan_vmc_files(const char* userPath, const save_entry_t* parent, list_t *list)
{
DIR *d;
struct dirent *dir;
save_entry_t *item;
char psvPath[256];
uint64_t size;
uint16_t flag;

d = opendir(userPath);
if (!d)
return;

while ((dir = readdir(d)) != NULL)
{
if (dir->d_type != DT_REG || !(endsWith(dir->d_name, ".VMC") || endsWith(dir->d_name, ".VM2") ||
endsWith(dir->d_name, ".BIN") || endsWith(dir->d_name, ".PS2")|| endsWith(dir->d_name, ".CARD")))
if (dir->d_type != DT_REG || !(endsWith(dir->d_name, ".CARD") || endsWith(dir->d_name, ".VM2") ||
endsWith(dir->d_name, ".BIN") || endsWith(dir->d_name, ".PS2") || endsWith(dir->d_name, ".VMC") ||
// PS1 VMCs
endsWith(dir->d_name, ".MCD") || endsWith(dir->d_name, ".MCR") || endsWith(dir->d_name, ".GME") ||
endsWith(dir->d_name, ".VM1") || endsWith(dir->d_name, ".VMP") || endsWith(dir->d_name, ".VGS") ||
endsWith(dir->d_name, ".SRM")))
continue;

snprintf(psvPath, sizeof(psvPath), "%s%s", userPath, dir->d_name);
get_file_size(psvPath, &size);

LOG("Checking %s...", psvPath);
if (size % 0x840000 != 0 && size % 0x800000 != 0)
switch (size)
{
case PS1CARD_SIZE:
case 0x20040:
case 0x20080:
case 0x200A0:
case 0x20F40:
flag = SAVE_FLAG_PS1;
break;

case 0x800000:
case 0x840000:
case 0x1000000:
case 0x1080000:
case 0x2000000:
case 0x2100000:
case 0x4000000:
case 0x4200000:
flag = SAVE_FLAG_PS2;
break;

default:
continue;
}

item = _createSaveEntry(SAVE_FLAG_PS2 | SAVE_FLAG_VMC, dir->d_name);
item = _createSaveEntry(flag | SAVE_FLAG_VMC, dir->d_name);
item->type = FILE_TYPE_VMC;

if (parent)
Expand Down Expand Up @@ -1912,8 +1875,7 @@ static void read_inner_vmc2_files(list_t *list)
else
snprintf(save_path, sizeof(save_path), "%s", item->path);

read_vmc2_files(save_path, item, list);
read_vmc1_files(save_path, item, list);
scan_vmc_files(save_path, item, list);

if (item->flags & SAVE_FLAG_HDD)
orbis_SaveUmount(mount);
Expand Down Expand Up @@ -1973,10 +1935,10 @@ list_t * ReadUsbList(const char* userPath)
read_usb_encrypted_savegames(path, list);

snprintf(path, sizeof(path), "%s%s", userPath, VMC_PS2_PATH_USB);
read_vmc2_files(path, NULL, list);
scan_vmc_files(path, NULL, list);

snprintf(path, sizeof(path), "%s%s", userPath, VMC_PS1_PATH_USB);
read_vmc1_files(path, NULL, list);
scan_vmc_files(path, NULL, list);

return list;
}
Expand Down Expand Up @@ -2125,22 +2087,6 @@ list_t * ReadOnlineList(const char* urlPath)
return list;
}

static void add_vmp_commands(save_entry_t* save)
{
code_entry_t* cmd;

cmd = _createCmdCode(PATCH_NULL, "----- " UTF8_CHAR_STAR " Virtual Memory Card " UTF8_CHAR_STAR " -----", CMD_CODE_NULL);
list_append(save->codes, cmd);

cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Export Memory Card to .VM1", CMD_EXP_PS1_VM1);
list_append(save->codes, cmd);

cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Export Memory Card to .VMP", CMD_EXP_PS1_VMP);
list_append(save->codes, cmd);

return;
}

list_t * ReadVmc1List(const char* userPath)
{
char filePath[256];
Expand Down Expand Up @@ -2178,10 +2124,28 @@ list_t * ReadVmc1List(const char* userPath)
cmd->options_count = 1;
cmd->options = _createOptions(2, "Copy all Saves to USB", CMD_EXP_ALL_SAVES_VMC);
list_append(item->codes, cmd);
add_vmp_commands(item);

cmd = _createCmdCode(PATCH_NULL, "----- " UTF8_CHAR_STAR " Virtual Memory Card " UTF8_CHAR_STAR " -----", CMD_CODE_NULL);
list_append(item->codes, cmd);

cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Export Memory Card to .VM1 format", CMD_CODE_NULL);
cmd->file = strdup(strrchr(userPath, '/')+1);
cmd->options_count = 1;
cmd->options = _createOptions(3, "Save .VM1 Memory Card to USB", CMD_EXP_PS1_VM1);
asprintf(&cmd->options->name[2], "Save .VM1 Memory Card to HDD");
asprintf(&cmd->options->value[2], "%c%c", CMD_EXP_PS1_VM1, STORAGE_HDD);
list_append(item->codes, cmd);

cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Export Memory Card to .VMP format", CMD_CODE_NULL);
cmd->file = strdup(strrchr(userPath, '/')+1);
cmd->options_count = 1;
cmd->options = _createOptions(3, "Save .VMP Memory Card to USB", CMD_EXP_PS1_VMP);
asprintf(&cmd->options->name[2], "Save .VMP Memory Card to HDD");
asprintf(&cmd->options->value[2], "%c%c", CMD_EXP_PS1_VMP, STORAGE_HDD);
list_append(item->codes, cmd);
list_append(list, item);

item = _createSaveEntry(SAVE_FLAG_PS1, CHAR_ICON_COPY " Import Saves to Virtual Card");
item = _createSaveEntry(SAVE_FLAG_PS1, CHAR_ICON_COPY " Import Saves to Virtual MemCard");
item->path = strdup(FAKE_USB_PATH);
item->title_id = strdup("HDD");
item->dir_name = strdup(userPath);
Expand All @@ -2194,7 +2158,7 @@ list_t * ReadVmc1List(const char* userPath)
if (i && dir_exists(filePath) != SUCCESS)
continue;

item = _createSaveEntry(SAVE_FLAG_PS1, CHAR_ICON_COPY " Import Saves to Virtual Card");
item = _createSaveEntry(SAVE_FLAG_PS1, CHAR_ICON_COPY " Import Saves to Virtual MemCard");
asprintf(&item->path, USB_PATH, i);
asprintf(&item->title_id, "USB %d", i);
item->dir_name = strdup(userPath);
Expand Down Expand Up @@ -2281,7 +2245,7 @@ list_t * ReadVmc2List(const char* userPath)
asprintf(&cmd->options->value[2], "%c%c", CMD_EXP_PS2_RAW, STORAGE_HDD);
list_append(item->codes, cmd);

item = _createSaveEntry(SAVE_FLAG_PS2, CHAR_ICON_COPY " Import Saves to Virtual Card");
item = _createSaveEntry(SAVE_FLAG_PS2, CHAR_ICON_COPY " Import Saves to Virtual MemCard");
item->path = strdup(FAKE_USB_PATH);
item->title_id = strdup("HDD");
item->type = FILE_TYPE_MENU;
Expand All @@ -2293,7 +2257,7 @@ list_t * ReadVmc2List(const char* userPath)
if (i && dir_exists(filePath) != SUCCESS)
continue;

item = _createSaveEntry(SAVE_FLAG_PS2, CHAR_ICON_COPY " Import Saves to Virtual Card");
item = _createSaveEntry(SAVE_FLAG_PS2, CHAR_ICON_COPY " Import Saves to Virtual MemCard");
asprintf(&item->path, USB_PATH, i);
asprintf(&item->title_id, "USB %d", i);
item->type = FILE_TYPE_MENU;
Expand Down

0 comments on commit eaf27d1

Please sign in to comment.