From ea8030bd2f33c13a1cf8058365f063ea94db8d61 Mon Sep 17 00:00:00 2001 From: bucanero Date: Sat, 26 Oct 2024 10:15:57 -0300 Subject: [PATCH 1/7] add ps1 code --- include/ps1card.h | 92 ++++ source/ps1card.c | 1140 +++++++++++++++++++++++++++++++++++++++++++ source/psv_resign.c | 9 +- 3 files changed, 1236 insertions(+), 5 deletions(-) create mode 100644 include/ps1card.h create mode 100644 source/ps1card.c diff --git a/include/ps1card.h b/include/ps1card.h new file mode 100644 index 0000000..f753795 --- /dev/null +++ b/include/ps1card.h @@ -0,0 +1,92 @@ +#include + +#define PS1CARD_MAX_SLOTS 15 +#define PS1CARD_HEADER_SIZE 128 +#define PS1CARD_BLOCK_SIZE 8192 +#define PS1CARD_SIZE 131072 + +enum ps1card_type +{ + PS1CARD_NULL, + PS1CARD_RAW, + PS1CARD_GME, + PS1CARD_VGS, + PS1CARD_VMP, + PS1CARD_MCX, +}; + +enum ps1save_type +{ + PS1SAVE_NULL, + PS1SAVE_AR, + PS1SAVE_MCS, + PS1SAVE_RAW, + PS1SAVE_PSV, +}; + +enum ps1block_type +{ + PS1BLOCK_FORMATTED, + PS1BLOCK_INITIAL, + PS1BLOCK_MIDDLELINK, + PS1BLOCK_ENDLINK, + PS1BLOCK_DELETED_INITIAL, + PS1BLOCK_DELETED_MIDDLELINK, + PS1BLOCK_DELETED_ENDLINK, + PS1BLOCK_CORRUPTED, +}; + +typedef struct ps1McData +{ + uint8_t* headerData; //Memory Card header data (128 bytes each) + uint8_t* saveData; //Memory Card save data (8192 bytes each) + uint32_t iconPalette[16]; //Memory Card icon palette data + uint8_t iconData[3][256]; //Memory Card icon data, 3 icons per slot, (16*16px icons) + uint8_t iconFrames; //Number of icon frames + uint8_t saveType; //Type of the save (0 - formatted, 1 - initial, 2 - middle link, 3 - end link, 4 - deleted initial, 5 - deleted middle link, 6 - deleted end link, 7 - corrupted) + uint16_t saveRegion; //Region of the save (16 bit value, ASCII representation: BA - America, BE - Europe, BI - Japan) + char saveProdCode[11]; //Product code of the save + char saveIdentifier[9]; //Identifier string of the save + int saveSize; //Size of the save in Bytes + char saveName[21]; //Name of the save + char saveTitle[65]; //Title of the save data (in Shift-JIS format) +} ps1mcData_t; + +//Open Memory Card from the given filename (return error message if operation is not sucessful) +int openMemoryCard(const char* fileName, int fixData); + +//Open memory card from the given byte stream +int openMemoryCardStream(const uint8_t* memCardData, int dataSize, int fixData); + +//Save (export) Memory Card to a given byte stream +uint8_t* saveMemoryCardStream(int fixData); + +//Save Memory Card to the given filename +int saveMemoryCard(const char* fileName, int memoryCardType, int fixData); + +//Format a complete Memory Card +void formatMemoryCard(void); + +//Import single save to the Memory Card +int openSingleSave(const char* fileName, int* requiredSlots); + +//Save single save to the given filename +int saveSingleSave(const char* fileName, int slotNumber, int singleSaveType); + +//Get icon data as bytes +uint8_t* getIconRGBA(int slotNumber, int frame); + +//Set icon data to saveData +void setIconBytes(int slotNumber, uint8_t* iconBytes); + +//Return all bytes of the specified save +uint8_t* getSaveBytes(int slotNumber, uint32_t* saveLen); + +//Toggle deleted/undeleted status +void toggleDeleteSave(int slotNumber); + +//Format save +int formatSave(int slotNumber); + +//Get Memory Card data +ps1mcData_t* getMemoryCardData(void); diff --git a/source/ps1card.c b/source/ps1card.c new file mode 100644 index 0000000..27f56d5 --- /dev/null +++ b/source/ps1card.c @@ -0,0 +1,1140 @@ +// PS1 Memory Card management by Bucanero +// +//based on PS1 Memory Card class +//by Shendo 2009-2021 (MemcardRex) +// https://github.com/ShendoXT/memcardrex/blob/master/MemcardRex/ps1card.cs + +#include +#include +#include +#include +#include +#include + +#include "ps1card.h" +#include "util.h" +#include "saves.h" + + +//Memory Card's type (0 - unset, 1 - raw, 2 - gme, 3 - vgs, 4 - vmp); +static uint8_t cardType = PS1CARD_NULL; + +//Flag used to determine if the card has been edited since the last saving +static bool changedFlag = false; + +//Complete Memory Card in the raw format (131072 bytes) +static uint8_t rawMemoryCard[PS1CARD_SIZE]; +static ps1mcData_t ps1saves[PS1CARD_MAX_SLOTS]; + +//Save comments (supported by .gme files only), 255 characters allowed +static char saveComments[PS1CARD_MAX_SLOTS][256]; + +//AES-CBC key and IV for MCX memory cards +static const uint8_t mcxKey[] = { 0x81, 0xD9, 0xCC, 0xE9, 0x71, 0xA9, 0x49, 0x9B, 0x04, 0xAD, 0xDC, 0x48, 0x30, 0x7F, 0x07, 0x92 }; +static const uint8_t mcxIv[] = { 0x13, 0xC2, 0xE7, 0x69, 0x4B, 0xEC, 0x69, 0x6D, 0x52, 0xCF, 0x00, 0x09, 0x2A, 0xC1, 0xF2, 0x72 }; + + +//Encrypts a buffer using AES CBC 128 +static void AesCbcEncrypt(uint8_t* toEncrypt, size_t len, const uint8_t* key, const uint8_t* aes_iv) +{ + uint8_t iv[16]; + aes_context ctx; + + memcpy(iv, aes_iv, 16); + + aes_init(&ctx); + aes_setkey_enc(&ctx, key, 128); + aes_crypt_cbc(&ctx, AES_ENCRYPT, len, iv, toEncrypt, toEncrypt); + aes_free(&ctx); +} + +//Decrypts a buffer using AES CBC 128 +static void AesCbcDecrypt(uint8_t* toDecrypt, size_t len, const uint8_t* key, const uint8_t* aes_iv) +{ + uint8_t iv[16]; + aes_context ctx; + + memcpy(iv, aes_iv, 16); + + aes_init(&ctx); + aes_setkey_dec(&ctx, key, 128); + aes_crypt_cbc(&ctx, AES_DECRYPT, len, iv, toDecrypt, toDecrypt); + aes_free(&ctx); +} + +//Load Data from raw Memory Card +static void loadDataFromRawCard(void) +{ + for (int slotNumber = 0; slotNumber < PS1CARD_MAX_SLOTS; slotNumber++) + { + //Load header data + ps1saves[slotNumber].headerData = &rawMemoryCard[PS1CARD_HEADER_SIZE * (slotNumber + 1)]; + + //Load save + ps1saves[slotNumber].saveData = &rawMemoryCard[PS1CARD_BLOCK_SIZE * (slotNumber + 1)]; + } +} + +//Recreate raw Memory Card +static void loadDataToRawCard(bool fixData) +{ + uint8_t* tmpMemoryCard; + + //Skip fixing data if it's not needed + if (!fixData) return; + + //Clear existing data + tmpMemoryCard = calloc(1, PS1CARD_SIZE); + + //Recreate the signature + tmpMemoryCard[0] = 0x4D; //M + tmpMemoryCard[1] = 0x43; //C + tmpMemoryCard[127] = 0x0E; //XOR (precalculated) + + tmpMemoryCard[8064] = 0x4D; //M + tmpMemoryCard[8065] = 0x43; //C + tmpMemoryCard[8191] = 0x0E; //XOR (precalculated) + + //This can be copied freely without fixing + for (int slotNumber = 0; slotNumber < PS1CARD_MAX_SLOTS; slotNumber++) + { + //Load header data + memcpy(&tmpMemoryCard[PS1CARD_HEADER_SIZE * (slotNumber + 1)], ps1saves[slotNumber].headerData, PS1CARD_HEADER_SIZE); + + //Load save data + memcpy(&tmpMemoryCard[PS1CARD_BLOCK_SIZE * (slotNumber + 1)], ps1saves[slotNumber].saveData, PS1CARD_BLOCK_SIZE); + } + + //Create authentic data (just for completeness) + for (int i = 0; i < 20; i++) + { + //Reserved slot typed + tmpMemoryCard[2048 + (i * PS1CARD_HEADER_SIZE)] = 0xFF; + tmpMemoryCard[2048 + (i * PS1CARD_HEADER_SIZE) + 1] = 0xFF; + tmpMemoryCard[2048 + (i * PS1CARD_HEADER_SIZE) + 2] = 0xFF; + tmpMemoryCard[2048 + (i * PS1CARD_HEADER_SIZE) + 3] = 0xFF; + + //Next slot pointer doesn't point to anything + tmpMemoryCard[2048 + (i * PS1CARD_HEADER_SIZE) + 8] = 0xFF; + tmpMemoryCard[2048 + (i * PS1CARD_HEADER_SIZE) + 9] = 0xFF; + } + + memcpy(rawMemoryCard, tmpMemoryCard, PS1CARD_SIZE); + free(tmpMemoryCard); + return; +} + +//Recreate GME header(add signature, slot description and comments) +static void fillGmeHeader(FILE* fp) +{ + uint8_t gmeHeader[3904]; + + //Clear existing data + memset(gmeHeader, 0, sizeof(gmeHeader)); + + //Fill in the signature + gmeHeader[0] = 0x31; //1 + gmeHeader[1] = 0x32; //2 + gmeHeader[2] = 0x33; //3 + gmeHeader[3] = 0x2D; //- + gmeHeader[4] = 0x34; //4 + gmeHeader[5] = 0x35; //5 + gmeHeader[6] = 0x36; //6 + gmeHeader[7] = 0x2D; //- + gmeHeader[8] = 0x53; //S + gmeHeader[9] = 0x54; //T + gmeHeader[10] = 0x44; //D + + gmeHeader[18] = 0x1; + gmeHeader[20] = 0x1; + gmeHeader[21] = 0x4D; //M + + for (int slotNumber = 0; slotNumber < PS1CARD_MAX_SLOTS; slotNumber++) + { + gmeHeader[22 + slotNumber] = ps1saves[slotNumber].headerData[0]; + gmeHeader[38 + slotNumber] = ps1saves[slotNumber].headerData[8]; + + //Inject comments to GME header + memcpy(&gmeHeader[64 + (256 * slotNumber)], saveComments[slotNumber], 256); + } + + fwrite(gmeHeader, 1, sizeof(gmeHeader), fp); + return; +} + +// Check if a given card is a MCX image +static bool IsMcxCard(uint8_t* mcxCard) +{ + AesCbcDecrypt(mcxCard, 0x200A0, mcxKey, mcxIv); + + // Check for "MC" header 0x80 bytes in + if (mcxCard[0x80] == 'M' && mcxCard[0x81] == 'C') + return true; + else + return false; +} + +//Generate encrypted MCX Memory Card +static void MakeMcxCard(const uint8_t* rawCard, FILE* fp) +{ + uint8_t* mcxCard = malloc(0x200A0); + + memset(mcxCard, 0, 0x80); + memcpy(mcxCard + 0x80, rawCard, PS1CARD_SIZE); + sha256(mcxCard, 0x20080, mcxCard + 0x20080, 0); + + AesCbcEncrypt(mcxCard, 0x200A0, mcxKey, mcxIv); + fwrite(mcxCard, 1, 0x200A0, fp); + free(mcxCard); + + return; +} + +//Generate unsigned VMP Memory Card +static void setVmpCardHeader(FILE* fp) +{ + uint8_t vmpCard[0x80]; + + memset(vmpCard, 0, sizeof(vmpCard)); + vmpCard[1] = 0x50; + vmpCard[2] = 0x4D; + vmpCard[3] = 0x56; + vmpCard[4] = 0x80; //header length + + fwrite(vmpCard, 1, sizeof(vmpCard), fp); + return; +} + +//Generate unsigned PSV save +static void setPsvHeader(const char* saveFilename, uint32_t saveLength, FILE* fp) +{ + uint8_t psvSave[0x84]; + + memset(psvSave, 0, sizeof(psvSave)); + psvSave[1] = 0x56; + psvSave[2] = 0x53; + psvSave[3] = 0x50; + psvSave[0x38] = 0x14; + psvSave[0x3C] = 1; + psvSave[0x44] = 0x84; + psvSave[0x49] = 2; + psvSave[0x5D] = 0x20; + psvSave[0x60] = 3; + psvSave[0x61] = 0x90; + + memcpy(&psvSave[0x64], saveFilename, 20); + memcpy(&psvSave[0x40], &saveLength, sizeof(uint32_t)); + + fwrite(psvSave, 1, sizeof(psvSave), fp); + return; +} + +static void setArHeader(const char* saveName, FILE* fp) +{ + uint8_t arHeader[54]; + + //Copy header data to arHeader + memset(arHeader, 0, sizeof(arHeader)); + strncpy((char*) arHeader, saveName, 20); + + fwrite(arHeader, 1, sizeof(arHeader), fp); + return; +} +//Calculate XOR checksum +static void calculateXOR(void) +{ + uint8_t XORchecksum = 0; + + //Cycle through each slot + for (int slotNumber = 0; slotNumber < PS1CARD_MAX_SLOTS; slotNumber++) + { + //Set default value + XORchecksum = 0; + + //Count 127 bytes + for (int byteCount = 0; byteCount < 126; byteCount++) + XORchecksum ^= ps1saves[slotNumber].headerData[byteCount]; + + //Store checksum in 128th byte + ps1saves[slotNumber].headerData[127] = XORchecksum; + } +} + +//Load region of the saves +static void loadRegion(void) +{ + //Cycle trough each slot + for (int slotNumber = 0; slotNumber < PS1CARD_MAX_SLOTS; slotNumber++) + { + //Store save region + ps1saves[slotNumber].saveRegion = (uint16_t)((ps1saves[slotNumber].headerData[11] << 8) | ps1saves[slotNumber].headerData[10]); + } +} + +//Load palette +static void loadPalette(void) +{ + int redChannel = 0; + int greenChannel = 0; + int blueChannel = 0; + int colorCounter = 0; + int blackFlag = 0; + + //Cycle through each slot on the Memory Card + for (int slotNumber = 0; slotNumber < PS1CARD_MAX_SLOTS; slotNumber++) + { + //Clear existing data + memset(ps1saves[slotNumber].iconPalette, 0, sizeof(uint32_t)*16); + + //Reset color counter + colorCounter = 0; + + //Fetch two bytes at a time + for (int byteCount = 0; byteCount < 32; byteCount += 2) + { + redChannel = (ps1saves[slotNumber].saveData[byteCount + 96] & 0x1F) << 3; + greenChannel = ((ps1saves[slotNumber].saveData[byteCount + 97] & 0x3) << 6) | ((ps1saves[slotNumber].saveData[byteCount + 96] & 0xE0) >> 2); + blueChannel = ((ps1saves[slotNumber].saveData[byteCount + 97] & 0x7C) << 1); + blackFlag = (ps1saves[slotNumber].saveData[byteCount + 97] & 0x80); + + //Get the color value + if ((redChannel | greenChannel | blueChannel | blackFlag) == 0) + ps1saves[slotNumber].iconPalette[colorCounter] = 0x00000000; + else + ps1saves[slotNumber].iconPalette[colorCounter] = blueChannel | (greenChannel << 8) | (redChannel << 16) | 0xFF000000; + + colorCounter++; + } + } +} + +//Load the icons +static void loadIcons(void) +{ + int byteCount = 0; + + //Cycle through each slot + for (int slotNumber = 0; slotNumber < PS1CARD_MAX_SLOTS; slotNumber++) + { + //Clear existing data + memset(ps1saves[slotNumber].iconData, 0, 256*3); + + //Each save has 3 icons (some are data but those will not be shown) + for (int iconNumber = 0; iconNumber < ps1saves[slotNumber].iconFrames; iconNumber++) + { + byteCount = 128 * (1 + iconNumber); + + for (int y = 0; y < 16; y++) + { + for (int x = 0; x < 16; x += 2) + { + ps1saves[slotNumber].iconData[iconNumber][y * 16 + x] = ps1saves[slotNumber].saveData[byteCount] & 0xF; + ps1saves[slotNumber].iconData[iconNumber][y * 16 + x + 1] = ps1saves[slotNumber].saveData[byteCount] >> 4; + byteCount++; + } + } + } + } +} + +//Load icon frames +static void loadIconFrames(void) +{ + //Cycle through each slot + for (int slotNumber = 0; slotNumber < PS1CARD_MAX_SLOTS; slotNumber++) + { + switch(ps1saves[slotNumber].saveData[2]) + { + case 0x11: //1 frame + ps1saves[slotNumber].iconFrames = 1; + break; + + case 0x12: //2 frames + ps1saves[slotNumber].iconFrames = 2; + break; + + case 0x13: //3 frames + ps1saves[slotNumber].iconFrames = 3; + break; + + default: //No frames (save data is probably clean) + ps1saves[slotNumber].iconFrames = 0; + break; + } + } +} + +//Recreate VGS header +static void setVGSheader(FILE* fp) +{ + uint8_t vgsHeader[64]; + + memset(vgsHeader, 0, sizeof(vgsHeader)); + //Fill in the signature + vgsHeader[0] = 0x56; //V + vgsHeader[1] = 0x67; //g + vgsHeader[2] = 0x73; //s + vgsHeader[3] = 0x4D; //M + + vgsHeader[4] = 0x1; + vgsHeader[8] = 0x1; + vgsHeader[12] = 0x1; + vgsHeader[17] = 0x2; + + fwrite(vgsHeader, 1, sizeof(vgsHeader), fp); + return; +} + +//Get the type of the save slots +static void loadSlotTypes(void) +{ + for (int slotNumber = 0; slotNumber < PS1CARD_MAX_SLOTS; slotNumber++) + { + //Clear existing data + ps1saves[slotNumber].saveType = 0; + + switch(ps1saves[slotNumber].headerData[0]) + { + case 0xA0: //Formatted + ps1saves[slotNumber].saveType = PS1BLOCK_FORMATTED; + break; + + case 0x51: //Initial + ps1saves[slotNumber].saveType = PS1BLOCK_INITIAL; + break; + + case 0x52: //Middle link + ps1saves[slotNumber].saveType = PS1BLOCK_MIDDLELINK; + break; + + case 0x53: //End link + ps1saves[slotNumber].saveType = PS1BLOCK_ENDLINK; + break; + + case 0xA1: //Initial deleted + ps1saves[slotNumber].saveType = PS1BLOCK_DELETED_INITIAL; + break; + + case 0xA2: //Middle link deleted + ps1saves[slotNumber].saveType = PS1BLOCK_DELETED_MIDDLELINK; + break; + + case 0xA3: //End link deleted + ps1saves[slotNumber].saveType = PS1BLOCK_DELETED_ENDLINK; + break; + + default: //Regular values have not been found, save is corrupted + ps1saves[slotNumber].saveType = PS1BLOCK_CORRUPTED; + break; + } + } +} + +//Load Save name, Product code and Identifier from the header data +static void loadStringData(void) +{ + for (int slotNumber = 0; slotNumber < PS1CARD_MAX_SLOTS; slotNumber++) + { + //Clear existing data + memset(ps1saves[slotNumber].saveProdCode, 0, 11); + memset(ps1saves[slotNumber].saveIdentifier, 0, 9); + memset(ps1saves[slotNumber].saveName, 0, 21); + memset(ps1saves[slotNumber].saveTitle, 0, 65); + + //Copy Product code + strncpy(ps1saves[slotNumber].saveProdCode, (char*) &ps1saves[slotNumber].headerData[12], 10); + + //Copy Identifier + strncpy(ps1saves[slotNumber].saveIdentifier, (char*) &ps1saves[slotNumber].headerData[22], 8); + + //Copy Save filename + strncpy(ps1saves[slotNumber].saveName, (char*) &ps1saves[slotNumber].headerData[10], 20); + + //Copy save title from Shift-JIS + memcpy(ps1saves[slotNumber].saveTitle, &ps1saves[slotNumber].saveData[4], 64); + } +} + +//Load size of each slot in Bytes +static void loadSaveSize(void) +{ + //Fill data for each slot + for (int slotNumber = 0; slotNumber < PS1CARD_MAX_SLOTS; slotNumber++) + ps1saves[slotNumber].saveSize = (ps1saves[slotNumber].headerData[4] | (ps1saves[slotNumber].headerData[5]<<8) | (ps1saves[slotNumber].headerData[6]<<16)); +} + +//Find and return all save links +static int findSaveLinks(int initialSlotNumber, int* tempSlotList) +{ + int j = 0; + int currentSlot = initialSlotNumber; + + //Maximum number of cycles is 15 + for (int i = 0; i < PS1CARD_MAX_SLOTS; i++) + { + //Add current slot to the list + tempSlotList[j++] = currentSlot; + + //Check if next slot pointer overflows + if (currentSlot > PS1CARD_MAX_SLOTS) break; + + //Check if current slot is corrupted + if (ps1saves[currentSlot].saveType == PS1BLOCK_CORRUPTED) break; + + //Check if pointer points to the next save + if (ps1saves[currentSlot].headerData[8] == 0xFF) break; + else currentSlot = ps1saves[currentSlot].headerData[8]; + } + + //Return int array + return j; +} + +//Toggle deleted/undeleted status +void toggleDeleteSave(int slotNumber) +{ + //Get all linked saves + int saveSlots_Length; + int saveSlots[PS1CARD_MAX_SLOTS]; + + saveSlots_Length = findSaveLinks(slotNumber, saveSlots); + + //Cycle through each slot + for (int i = 0; i < saveSlots_Length; i++) + { + //Check the save type + switch (ps1saves[saveSlots[i]].saveType) + { + //Regular save + case PS1BLOCK_INITIAL: + ps1saves[saveSlots[i]].headerData[0] = 0xA1; + break; + + //Middle link + case PS1BLOCK_MIDDLELINK: + ps1saves[saveSlots[i]].headerData[0] = 0xA2; + break; + + //End link + case PS1BLOCK_ENDLINK: + ps1saves[saveSlots[i]].headerData[0] = 0xA3; + break; + + //Regular deleted save + case PS1BLOCK_DELETED_INITIAL: + ps1saves[saveSlots[i]].headerData[0] = 0x51; + break; + + //Middle link deleted + case PS1BLOCK_DELETED_MIDDLELINK: + ps1saves[saveSlots[i]].headerData[0] = 0x52; + break; + + //End link deleted + case PS1BLOCK_DELETED_ENDLINK: + ps1saves[saveSlots[i]].headerData[0] = 0x53; + break; + + //Slot should not be deleted + default: + break; + } + } + + //Reload data + calculateXOR(); + loadSlotTypes(); + + //Memory Card is changed + changedFlag = true; +} + +//Format a specified slot (Data MUST be reloaded after the use of this function) +static void formatSlot(int slotNumber) +{ + //Clear headerData + memset(ps1saves[slotNumber].headerData, 0, PS1CARD_HEADER_SIZE); + + //Clear saveData + memset(ps1saves[slotNumber].saveData, 0, PS1CARD_BLOCK_SIZE); + + //Clear GME comment for selected slot + memset(saveComments[slotNumber], 0, 256); + + //Place default values in headerData + ps1saves[slotNumber].headerData[0] = 0xA0; + ps1saves[slotNumber].headerData[8] = 0xFF; + ps1saves[slotNumber].headerData[9] = 0xFF; +} + +//Format save +int formatSave(int slotNumber) +{ + //Get all linked saves + int saveSlots_Length; + int saveSlots[PS1CARD_MAX_SLOTS]; + + saveSlots_Length = findSaveLinks(slotNumber, saveSlots); + + if (!saveSlots_Length) + return false; + + //Cycle through each slot + for (int i = 0; i < saveSlots_Length; i++) + { + formatSlot(saveSlots[i]); + } + + //Reload data + calculateXOR(); + loadStringData(); + loadSlotTypes(); + loadRegion(); + loadSaveSize(); + loadPalette(); + loadIconFrames(); + loadIcons(); + + //Set changedFlag to edited + changedFlag = true; + return true; +} + +//Find and return continuous free slots +static int findFreeSlots(int slotCount, int* tempSlotList) +{ + //Cycle through available slots + for (int j, slotNumber = 0; slotNumber < PS1CARD_MAX_SLOTS; slotNumber++) + { + j = 0; + for (int i = slotNumber; i < (slotNumber + slotCount); i++) + { + if (ps1saves[i].saveType == PS1BLOCK_FORMATTED) tempSlotList[j++]=i; + else break; + + //Exit if next save would be over the limit of 15 + if (slotNumber + slotCount > PS1CARD_MAX_SLOTS) break; + } + + if (j >= slotCount) + return j; + } + + return 0; +} + +//Return all bytes of the specified save +uint8_t* getSaveBytes(int slotNumber, uint32_t* saveLen) +{ + //Get all linked saves + int saveSlots_Length; + int saveSlots[PS1CARD_MAX_SLOTS]; + uint8_t* saveBytes; + + saveSlots_Length = findSaveLinks(slotNumber, saveSlots); + + //Calculate the number of bytes needed to store the save + *saveLen = PS1CARD_HEADER_SIZE + (saveSlots_Length * PS1CARD_BLOCK_SIZE); + saveBytes = malloc(*saveLen); + + //Copy save header + memcpy(saveBytes, ps1saves[saveSlots[0]].headerData, PS1CARD_HEADER_SIZE); + + //Copy save data + for (int sNumber = 0; sNumber < saveSlots_Length; sNumber++) + memcpy(&saveBytes[PS1CARD_HEADER_SIZE + (sNumber * PS1CARD_BLOCK_SIZE)], ps1saves[saveSlots[sNumber]].saveData, PS1CARD_BLOCK_SIZE); + + //Return save bytes + return saveBytes; +} + +//Input given bytes back to the Memory Card +int setSaveBytes(const uint8_t* saveBytes, int saveBytes_Length, int* reqSlots) +{ + //Number of slots to set + int freeSlots_Length; + int freeSlots[PS1CARD_MAX_SLOTS]; + int slotCount = (saveBytes_Length - PS1CARD_HEADER_SIZE) / PS1CARD_BLOCK_SIZE; + int numberOfBytes = slotCount * PS1CARD_BLOCK_SIZE; + + *reqSlots = slotCount; + freeSlots_Length = findFreeSlots(slotCount, freeSlots); + + //Check if there are enough free slots for the operation + if (freeSlots_Length < slotCount) return false; + + //Place header data + memcpy(ps1saves[freeSlots[0]].headerData, saveBytes, PS1CARD_HEADER_SIZE); + + //Place save size in the header + ps1saves[freeSlots[0]].headerData[4] = (uint8_t)(numberOfBytes & 0xFF); + ps1saves[freeSlots[0]].headerData[5] = (uint8_t)((numberOfBytes & 0xFF00) >> 8); + ps1saves[freeSlots[0]].headerData[6] = (uint8_t)((numberOfBytes & 0xFF0000) >> 16); + + //Place save data(cycle through each save) + for (int i = 0; i < slotCount; i++) + //Set all bytes + memcpy(ps1saves[freeSlots[i]].saveData, &saveBytes[PS1CARD_HEADER_SIZE + (i * PS1CARD_BLOCK_SIZE)], PS1CARD_BLOCK_SIZE); + + //Recreate header data + //Set pointer to all slots except the last + for (int i = 0; i < (freeSlots_Length - 1); i++) + { + ps1saves[freeSlots[i]].headerData[0] = 0x52; + ps1saves[freeSlots[i]].headerData[8] = (uint8_t)freeSlots[i + 1]; + ps1saves[freeSlots[i]].headerData[9] = 0x00; + } + + //Add final slot pointer to the last slot in the link + ps1saves[freeSlots[freeSlots_Length - 1]].headerData[0] = 0x53; + ps1saves[freeSlots[freeSlots_Length - 1]].headerData[8] = 0xFF; + ps1saves[freeSlots[freeSlots_Length - 1]].headerData[9] = 0xFF; + + //Add initial saveType to the first slot + ps1saves[freeSlots[0]].headerData[0] = 0x51; + + //Reload data + calculateXOR(); + loadStringData(); + loadSlotTypes(); + loadRegion(); + loadSaveSize(); + loadPalette(); + loadIconFrames(); + loadIcons(); + + //Set changedFlag to edited + changedFlag = true; + + return true; +} + +//Get icon data as bytes +uint8_t* getIconRGBA(int slotNumber, int frame) +{ + uint32_t* iconBytes; + + if (frame >= ps1saves[slotNumber].iconFrames) + return NULL; + + iconBytes = malloc(16 * 16 * sizeof(uint32_t)); + + //Copy bytes from the given slot + for (int i = 0; i < 256; i++) + iconBytes[i] = ps1saves[slotNumber].iconPalette[ps1saves[slotNumber].iconData[frame][i]]; + + return (uint8_t*) iconBytes; +} + +//Set icon data to saveData +void setIconBytes(int slotNumber, uint8_t* iconBytes) +{ + //Set bytes from the given slot + memcpy(&ps1saves[slotNumber].saveData[96], iconBytes, 416); + + //Reload data + loadPalette(); + loadIcons(); + + //Set changedFlag to edited + changedFlag = true; +} + +//Format a complete Memory Card +void formatMemoryCard(void) +{ + //Format each slot in Memory Card + for (int slotNumber = 0; slotNumber < PS1CARD_MAX_SLOTS; slotNumber++) + formatSlot(slotNumber); + + //Reload data + calculateXOR(); + loadStringData(); + loadSlotTypes(); + loadRegion(); + loadSaveSize(); + loadPalette(); + loadIconFrames(); + loadIcons(); + + //Set changedFlag to edited + changedFlag = true; +} + +//Save single save to the given filename +int saveSingleSave(const char* fileName, int slotNumber, int singleSaveType) +{ + char psvName[0x100 + 4]; + FILE* binWriter; + uint32_t outputData_Length; + uint8_t* outputData; + + if (singleSaveType == PS1SAVE_PSV) + { + snprintf(psvName, sizeof(psvName), "%s%c%c%s", fileName, ps1saves[slotNumber].saveRegion & 0xFF, ps1saves[slotNumber].saveRegion >> 8, ps1saves[slotNumber].saveProdCode); + for(char *c = ps1saves[slotNumber].saveIdentifier; *c; c++) + { + snprintf(&psvName[0x100], 4, "%02X", *c); + strcat(psvName, &psvName[0x100]); + } + strcat(psvName, ".PSV"); + fileName = psvName; + } + + //Check if the file is allowed to be opened for writing + binWriter = fopen(fileName, "wb"); + if (!binWriter) + return false; + + outputData = getSaveBytes(slotNumber, &outputData_Length); + + //Check what kind of file to output according to singleSaveType + switch (singleSaveType) + { + //Action Replay single save + case PS1SAVE_AR: + setArHeader(ps1saves[slotNumber].saveName, binWriter); + break; + + //MCS single save + case PS1SAVE_MCS: + fwrite(outputData, 1, PS1CARD_HEADER_SIZE, binWriter); + break; + + //RAW single save + case PS1SAVE_RAW: + break; + + //PS3 unsigned save + case PS1SAVE_PSV: + setPsvHeader(ps1saves[slotNumber].saveName, outputData_Length - PS1CARD_HEADER_SIZE, binWriter); + break; + } + + fwrite(outputData + PS1CARD_HEADER_SIZE, 1, outputData_Length - PS1CARD_HEADER_SIZE, binWriter); + + //File is sucesfully saved, close the stream + fclose(binWriter); + free(outputData); + + if (singleSaveType == PS1SAVE_PSV) + return psv_resign(fileName); + + return true; +} + +//Import single save to the Memory Card +int openSingleSave(const char* fileName, int* requiredSlots) +{ + uint8_t* inputData; + uint8_t* finalData; + int finalData_Length; + size_t inputData_Length; + + *requiredSlots = -1; + + //Check if the file is allowed to be opened + //Put data into temp array + if (read_buffer(fileName, &inputData, &inputData_Length) < 0) + { + return false; + } + + //Check the format of the save and if it's supported load it + //MCS single save + if (inputData[0] == 'Q') + { + finalData_Length = inputData_Length; + finalData = malloc(finalData_Length); + + memcpy(finalData, inputData, finalData_Length); + } + //RAW single save + //Also valid as seen with MMX4 save + else if (toupper(inputData[0]) == 'S' && toupper(inputData[1]) == 'C') + { + finalData_Length = inputData_Length + PS1CARD_HEADER_SIZE; + finalData = calloc(1, finalData_Length); + + const char* singleSaveName = strrchr(fileName, '/'); + singleSaveName = singleSaveName ? singleSaveName + 1 : fileName; + + //Recreate save header + finalData[0] = 0x51; //Q + strncpy((char*) &finalData[10], singleSaveName, 20); + + //Copy save data + memcpy(&finalData[PS1CARD_HEADER_SIZE], inputData, inputData_Length); + } + //PSV single save (PS3 virtual save) + else if (memcmp(inputData, "\0VSP", 4) == 0 && inputData[60] == 1) + { + // Check if this is a PS1 type save + finalData_Length = inputData_Length - 4; + finalData = calloc(1, finalData_Length); + + //Recreate save header + finalData[0] = 0x51; //Q + memcpy(&finalData[10], &inputData[100], 20); + + //Copy save data + memcpy(&finalData[PS1CARD_HEADER_SIZE], &inputData[132], inputData_Length - 132); + } + //Action Replay single save + //Check if this is really an AR save (check for SC magic) + else if (inputData[0x36] == 'S' && inputData[0x37] == 'C') + { + finalData_Length = inputData_Length + 74; + finalData = calloc(1, finalData_Length); + + //Recreate save header + finalData[0] = 0x51; //Q + memcpy(&finalData[10], inputData, 20); + + //Copy save data + memcpy(&finalData[PS1CARD_HEADER_SIZE], &inputData[54], inputData_Length - 54); + } + else + { + free(inputData); + return false; + } + + //If save already exists, remove it (overwrite) + for (int i = 0; i < PS1CARD_MAX_SLOTS; i++) + if (strncmp(ps1saves[i].saveName, (char*) &finalData[10], 20) == 0) + { + formatSave(i); + break; + } + + //Import the save to Memory Card + if (setSaveBytes(finalData, finalData_Length, requiredSlots)) + { + free(finalData); + free(inputData); + return true; + } + + free(finalData); + free(inputData); + return (false); +} + +//Save Memory Card to the given filename +int saveMemoryCard(const char* fileName, int memoryCardType, int fixData) +{ + FILE* binWriter = NULL; + + if (!changedFlag && !memoryCardType) + return false; + + binWriter = fopen(fileName, "wb"); + //Check if the file is allowed to be opened for writing + if (!binWriter) + return false; + + //Save as original format if memoryCardType is not set + if (!memoryCardType) + memoryCardType = cardType; + + //Prepare data for saving + loadDataToRawCard(fixData); + + //Check what kind of file to output according to memoryCardType + switch (memoryCardType) + { + //GME Memory Card + case PS1CARD_GME: + fillGmeHeader(binWriter); + fwrite(rawMemoryCard, 1, PS1CARD_SIZE, binWriter); + break; + + //VGS Memory Card + case PS1CARD_VGS: + setVGSheader(binWriter); + fwrite(rawMemoryCard, 1, PS1CARD_SIZE, binWriter); + break; + + //VMP Memory Card + case PS1CARD_VMP: + setVmpCardHeader(binWriter); + fwrite(rawMemoryCard, 1, PS1CARD_SIZE, binWriter); + break; + + //MCX Memory Card + case PS1CARD_MCX: + MakeMcxCard(rawMemoryCard, binWriter); + break; + + //Raw Memory Card + default: + fwrite(rawMemoryCard, 1, PS1CARD_SIZE, binWriter); + break; + } + + //Set changedFlag to saved + changedFlag = false; + + //File is sucesfully saved, close the stream + fclose(binWriter); + + if (memoryCardType == PS1CARD_VMP) + return vmp_resign(fileName); + + return true; +} + +//Save (export) Memory Card to a given byte stream +uint8_t* saveMemoryCardStream(int fixData) +{ + //Prepare data for saving + loadDataToRawCard(fixData); + + //Return complete Memory Card data + return rawMemoryCard; +} + +//Get Memory Card data and free slots +ps1mcData_t* getMemoryCardData(void) +{ + if (cardType == PS1CARD_NULL) + return NULL; + + //Return Memory Card data + return ps1saves; +} + +//Open memory card from the given byte stream +int openMemoryCardStream(const uint8_t* memCardData, int dataSize, int fixData) +{ + if (!memCardData || (dataSize != PS1CARD_SIZE)) + return false; + + //Set the reference for the recieved data + memcpy(rawMemoryCard, memCardData, sizeof(rawMemoryCard)); + + //Load Memory Card data from raw card + loadDataFromRawCard(); + + if(fixData) calculateXOR(); + loadStringData(); + loadSlotTypes(); + loadRegion(); + loadSaveSize(); + loadPalette(); + loadIconFrames(); + loadIcons(); + + //Since the stream is of the unknown origin Memory Card is treated as edited + changedFlag = true; + + return true; +} + +//Open Memory Card from the given filename +int openMemoryCard(const char* fileName, int fixData) +{ + cardType = PS1CARD_NULL; + changedFlag = fixData; + + //Check if the Memory Card should be opened or created + if (fileName != NULL) + { + uint8_t *tempData; + int startOffset; + size_t fileSize; + + //File cannot be opened, return error message + if (read_buffer(fileName, &tempData, &fileSize) < 0) + return false; + + if (fileSize < PS1CARD_SIZE) + { + free(tempData); + return false; + } + + //Check the format of the card and if it's supported load it + //Standard raw Memory Card + if (memcmp(tempData, "MC", 2) == 0) + { + startOffset = 0; + cardType = PS1CARD_RAW; + } + //DexDrive GME Memory Card + else if (fileSize == 0x20F40 && memcmp(tempData, "123-456-STD", 11) == 0) + { + startOffset = 3904; + cardType = PS1CARD_GME; + + //Copy comments from GME header + for (int i = 0; i < PS1CARD_MAX_SLOTS; i++) + memcpy(saveComments[i], &tempData[64 + (256 * i)], 256); + } + //VGS Memory Card + else if (fileSize == 0x20040 && memcmp(tempData, "VgsM", 4) == 0) + { + startOffset = 64; + cardType = PS1CARD_VGS; + } + //PSP virtual Memory Card + else if (fileSize == 0x20080 && memcmp(tempData, "\0PMV", 4) == 0) + { + startOffset = 128; + cardType = PS1CARD_VMP; + } + //PS Vita MCX PocketStation Memory Card + else if (fileSize == 0x200A0 && IsMcxCard(tempData)) + { + startOffset = 128; + cardType = PS1CARD_MCX; + } + //File type is not supported + else + { + free(tempData); + return false; + } + + //Copy data to rawMemoryCard array with offset from input data + memcpy(rawMemoryCard, tempData + startOffset, PS1CARD_SIZE); + free(tempData); + + //Load Memory Card data from raw card + loadDataFromRawCard(); + } + // Memory Card should be created + else + { + loadDataToRawCard(true); + formatMemoryCard(); + } + + //Calculate XOR checksum (in case if any of the saveHeaders have corrputed XOR) + if(fixData) calculateXOR(); + + //Convert various Memory Card data to strings + loadStringData(); + + //Load slot descriptions (types) + loadSlotTypes(); + + //Load region data + loadRegion(); + + //Load size data + loadSaveSize(); + + //Load icon palette data as Color values + loadPalette(); + + //Load number of frames + loadIconFrames(); + + //Load icon data to bitmaps + loadIcons(); + + //Everything went well, no error messages + return cardType; +} diff --git a/source/psv_resign.c b/source/psv_resign.c index 41669a3..ced7658 100644 --- a/source/psv_resign.c +++ b/source/psv_resign.c @@ -65,14 +65,13 @@ static void XorWithIv(uint8_t* buf, const uint8_t* Iv) } } -static void generateHash(const uint8_t *input, uint8_t *dest, size_t sz, uint8_t type) +static void generateHash(const uint8_t *input, const uint8_t *salt_seed, uint8_t *dest, size_t sz, uint8_t type) { aes_context aes_ctx; sha1_context sha1_ctx; uint8_t iv[0x10]; uint8_t salt[0x40]; uint8_t work_buf[0x14]; - const uint8_t *salt_seed = input + PSV_SEED_OFFSET; memset(salt , 0, sizeof(salt)); memset(&aes_ctx, 0, sizeof(aes_context)); @@ -157,7 +156,7 @@ int psv_resign(const char *src_psv) return 0; } - generateHash(input, input + PSV_HASH_OFFSET, sz, input[PSV_TYPE_OFFSET]); + generateHash(input, input + PSV_SEED_OFFSET, input + PSV_HASH_OFFSET, sz, input[PSV_TYPE_OFFSET]); LOG("New signature: "); dump_data(input + PSV_HASH_OFFSET, 0x14); @@ -173,7 +172,7 @@ int psv_resign(const char *src_psv) return 1; } -/* + int vmp_resign(const char *src_vmp) { size_t sz; @@ -208,7 +207,7 @@ int vmp_resign(const char *src_vmp) return 1; } -*/ + void get_psv_filename(char* psvName, const char* path, const char* dirName) { char tmpName[13]; From e1cfd81b801cdfdb5c4dbf885bcb0b633d60c13c Mon Sep 17 00:00:00 2001 From: bucanero Date: Sat, 26 Oct 2024 11:07:49 -0300 Subject: [PATCH 2/7] add ps1 commands --- include/saves.h | 21 +++- source/exec_cmd.c | 46 ++++++++- source/main.c | 13 +++ source/menu_main.c | 35 ++++++- source/saves.c | 234 ++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 337 insertions(+), 12 deletions(-) diff --git a/include/saves.h b/include/saves.h index f451fcb..61ff3cd 100644 --- a/include/saves.h +++ b/include/saves.h @@ -54,7 +54,7 @@ #define EXP_PSV_PATH_USB1 USB1_PATH PSV_SAVES_PATH_USB #define VMC_PS2_PATH_USB "PS2/VMC/" -#define VMC_PS2_PATH_HDD "/dev_hdd0/savedata/vmc/" +#define VMC_PS1_PATH_USB "PS1/VMC/" #define IMP_PS2VMC_PATH_USB USB_PATH "PS2/VMC/" #define IMPORT_RAP_PATH_USB USB_PATH PS3_LICENSE_PATH @@ -115,8 +115,11 @@ enum cmd_code_enum // Export commands CMD_EXP_KEYSTONE, CMD_EXP_PS2_VM2, + CMD_EXP_PS2_RAW, CMD_EXP_VMC2SAVE, - CMD_EXP_VM2_RAW, + CMD_EXP_VMC1SAVE, + CMD_EXP_PS1_VMP, + CMD_EXP_PS1_VM1, CMD_EXP_DATABASE, CMD_DB_REBUILD, CMD_DB_DEL_FIX, @@ -126,6 +129,7 @@ enum cmd_code_enum CMD_IMP_KEYSTONE, CMD_IMP_DATABASE, CMD_IMP_VMC2SAVE, + CMD_IMP_VMC1SAVE, CMD_CREATE_ACT_DAT, CMD_EXTRACT_ARCHIVE, CMD_URL_DOWNLOAD, @@ -159,11 +163,15 @@ enum save_type_enum FILE_TYPE_NULL, FILE_TYPE_MENU, FILE_TYPE_PS4, - FILE_TYPE_PSV, FILE_TYPE_TRP, FILE_TYPE_VMC, // PS1 File Types + FILE_TYPE_PS1, + FILE_TYPE_PSX, + FILE_TYPE_MCS, + + // Misc FILE_TYPE_ZIP, FILE_TYPE_SQL, FILE_TYPE_NET, @@ -179,7 +187,7 @@ enum save_type_enum FILE_TYPE_MAX, FILE_TYPE_CBS, FILE_TYPE_XPS, - FILE_TYPE_VM2, + FILE_TYPE_PSV, }; enum char_flag_enum @@ -259,6 +267,7 @@ list_t * ReadUserList(const char* userPath); list_t * ReadOnlineList(const char* urlPath); list_t * ReadBackupList(const char* userPath); list_t * ReadTrophyList(const char* userPath); +list_t * ReadVmc1List(const char* userPath); list_t * ReadVmc2List(const char* userPath); void UnloadGameList(list_t * list); char * readTextFile(const char * path, long* size); @@ -270,6 +279,7 @@ int ReadCodes(save_entry_t * save); int ReadTrophies(save_entry_t * game); int ReadOnlineSaves(save_entry_t * game); int ReadBackupCodes(save_entry_t * bup); +int ReadVmc1Codes(save_entry_t * save); int ReadVmc2Codes(save_entry_t * save); int http_init(void); @@ -319,5 +329,8 @@ int ps2_xps2psv(const char *save, const char *psv_path); int ps2_cbs2psv(const char *save, const char *psv_path); int ps2_max2psv(const char *save, const char *psv_path); +int psv_resign(const char *src_psv); +int vmp_resign(const char *src_vmp); + char* sjis2utf8(char* input); uint8_t* getIconPS2(const char* folder, const char* iconfile); diff --git a/source/exec_cmd.c b/source/exec_cmd.c index ce274c8..6d36217 100644 --- a/source/exec_cmd.c +++ b/source/exec_cmd.c @@ -14,6 +14,7 @@ #include "util.h" #include "sfo.h" #include "mcio.h" +#include "ps1card.h" static char host_buf[256]; @@ -760,8 +761,8 @@ static void exportAllSavesVMC(const save_entry_t* save, int dev, int all) if (!all && !(item->flags & SAVE_FLAG_SELECTED)) continue; -// if (item->type == FILE_TYPE_PS1) -// (saveSingleSave(outPath, save->dir_name[0], PS1SAVE_PSV) ? done++ : err_count++); + if (item->type == FILE_TYPE_PS1) + (saveSingleSave(outPath, save->dir_name[0], PS1SAVE_PSV) ? done++ : err_count++); if (item->type == FILE_TYPE_PS2) (vmc_export_psv(item->dir_name, outPath) ? done++ : err_count++); @@ -772,6 +773,28 @@ static void exportAllSavesVMC(const save_entry_t* save, int dev, int all) show_message("%d/%d Saves exported to\n%s", done, done+err_count, outPath); } +static void exportVmcSave(const save_entry_t* save, int type, int dst_id) +{ + char outPath[256]; + struct tm t; + + _set_dest_path(outPath, dst_id, (type == PS1SAVE_PSV) ? PSV_SAVES_PATH_USB : PS1_IMP_PATH_USB); + mkdirs(outPath); + if (type != PS1SAVE_PSV) + { + // build file path + gmtime_r(&(time_t){time(NULL)}, &t); + sprintf(strrchr(outPath, '/'), "/%s_%d-%02d-%02d_%02d%02d%02d.%s", save->title_id, + t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, + (type == PS1SAVE_MCS) ? "mcs" : "psx"); + } + + if (saveSingleSave(outPath, save->dir_name[0], type)) + show_message("Save successfully exported to:\n%s", outPath); + else + show_message("Error exporting save:\n%s", save->path); +} + static void export_vmc2save(const save_entry_t* save, int type, int dst_id) { int ret = 0; @@ -851,7 +874,7 @@ static void deleteVmcSave(const save_entry_t* save) if (!show_dialog(DIALOG_TYPE_YESNO, "Do you want to delete %s?", save->dir_name)) return; - if ((save->flags & SAVE_FLAG_PS1) ? /*formatSave(save->dir_name[0])*/0 : vmc_delete_save(save->dir_name)) + if ((save->flags & SAVE_FLAG_PS1) ? formatSave(save->dir_name[0]) : vmc_delete_save(save->dir_name)) show_message("Save successfully deleted:\n%s", save->dir_name); else show_message("Error! Couldn't delete save:\n%s", save->dir_name); @@ -1444,11 +1467,26 @@ void execCodeCommand(code_entry_t* code, const char* codecmd) break; case CMD_EXP_PS2_VM2: - case CMD_EXP_VM2_RAW: + case CMD_EXP_PS2_RAW: exportVM2raw(code->file, codecmd[1], codecmd[0] == CMD_EXP_PS2_VM2); code->activated = 0; break; + case CMD_EXP_VMC1SAVE: + exportVmcSave(selected_entry, code->options[0].id, codecmd[1]); + code->activated = 0; + break; + + case CMD_IMP_VMC1SAVE: + if (openSingleSave(code->file, (int*) host_buf)) + show_message("Save successfully imported:\n%s", code->file); + else + show_message("Error! Couldn't import save:\n%s", code->file); + + selected_entry->flags |= SAVE_FLAG_UPDATED; + code->activated = 0; + break; + default: break; } diff --git a/source/main.c b/source/main.c index 4b9300b..699030a 100644 --- a/source/main.c +++ b/source/main.c @@ -137,6 +137,19 @@ save_list_t user_backup = { .UpdatePath = NULL, }; +/* +* PS1 VMC list +*/ +save_list_t vmc1_saves = { + .icon_id = cat_usb_png_index, + .title = "PS1 Virtual Memory Card", + .list = NULL, + .path = "", + .ReadList = &ReadVmc1List, + .ReadCodes = &ReadVmc1Codes, + .UpdatePath = &update_vmc_path, +}; + /* * PS2 VMC list */ diff --git a/source/menu_main.c b/source/menu_main.c index bf5effa..82288da 100644 --- a/source/menu_main.c +++ b/source/menu_main.c @@ -11,12 +11,14 @@ #include "ttf_render.h" #include "common.h" #include "mcio.h" +#include "ps1card.h" extern save_list_t hdd_saves; extern save_list_t usb_saves; extern save_list_t trophies; extern save_list_t online_saves; extern save_list_t user_backup; +extern save_list_t vmc1_saves; extern save_list_t vmc2_saves; extern int close_app; @@ -113,6 +115,14 @@ static void SetMenu(int id) { switch (menu_id) //Leaving menu { + case MENU_PS1VMC_SAVES: + if (id == MENU_MAIN_SCREEN) + { + UnloadGameList(vmc1_saves.list); + vmc1_saves.list = NULL; + } + break; + case MENU_PS2VMC_SAVES: if (id == MENU_MAIN_SCREEN) { @@ -150,6 +160,12 @@ static void SetMenu(int id) selected_entry->flags ^= SAVE_FLAG_UPDATED; ReloadUserSaves(&vmc2_saves); } + else if (selected_entry->flags & SAVE_FLAG_UPDATED && id == MENU_PS1VMC_SAVES) + { + selected_entry->flags ^= SAVE_FLAG_UPDATED; + saveMemoryCard(vmc1_saves.path, 0, 0); + ReloadUserSaves(&vmc1_saves); + } break; case MENU_SAVE_DETAILS: @@ -209,6 +225,14 @@ static void SetMenu(int id) Draw_UserCheatsMenu_Ani(&online_saves); break; + case MENU_PS1VMC_SAVES: //VMC Menu + if (!vmc1_saves.list && !ReloadUserSaves(&vmc1_saves)) + return; + + if (apollo_config.doAni) + Draw_UserCheatsMenu_Ani(&vmc1_saves); + break; + case MENU_PS2VMC_SAVES: //Trophies Menu if (!vmc2_saves.list && !ReloadUserSaves(&vmc2_saves)) return; @@ -238,7 +262,7 @@ static void SetMenu(int id) case MENU_PATCHES: //Cheat Selection Menu //if entering from game list, don't keep index, otherwise keep if (menu_id == MENU_USB_SAVES || menu_id == MENU_HDD_SAVES || menu_id == MENU_ONLINE_DB || - menu_id == MENU_TROPHIES || menu_id == MENU_PS2VMC_SAVES) + menu_id == MENU_TROPHIES || menu_id == MENU_PS1VMC_SAVES || menu_id == MENU_PS2VMC_SAVES) menu_old_sel[MENU_PATCHES] = 0; char iconfile[256]; @@ -255,6 +279,9 @@ static void SetMenu(int id) else 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)); + else if (selected_entry->flags & SAVE_FLAG_VMC && selected_entry->type == FILE_TYPE_PS1) + LoadVmcTexture(16, 16, getIconRGBA(selected_entry->dir_name[0], 0)); + else if (selected_entry->flags & SAVE_FLAG_HDD) snprintf(iconfile, sizeof(iconfile), PS4_SAVES_PATH_HDD "%s/%s_icon0.png", apollo_config.user_id, selected_entry->title_id, selected_entry->dir_name); @@ -381,7 +408,7 @@ static void doSaveMenu(save_list_t * save_list) if (selected_entry->flags & SAVE_FLAG_PS1) { -// strncpy(vmc1_saves.path, selected_entry->path, sizeof(vmc1_saves.path)); + strncpy(vmc1_saves.path, selected_entry->path, sizeof(vmc1_saves.path)); SetMenu(MENU_PS1VMC_SAVES); } else @@ -865,6 +892,10 @@ void drawScene(void) doHexEditor(); break; + case MENU_PS1VMC_SAVES: //PS1 VMC Menu + doSaveMenu(&vmc1_saves); + break; + case MENU_PS2VMC_SAVES: //PS2 VMC Menu doSaveMenu(&vmc2_saves); break; diff --git a/source/saves.c b/source/saves.c index 26308da..615e812 100644 --- a/source/saves.c +++ b/source/saves.c @@ -15,6 +15,7 @@ #include "util.h" #include "ps2mc.h" #include "mcio.h" +#include "ps1card.h" #define UTF8_CHAR_STAR "\xE2\x98\x85" @@ -976,6 +977,135 @@ int ReadTrophies(save_entry_t * game) return list_count(game->codes); } +static void add_vmc_import_saves(list_t* list, const char* path, const char* folder) +{ + code_entry_t * cmd; + DIR *d; + struct dirent *dir; + char psvPath[256]; + + snprintf(psvPath, sizeof(psvPath), "%s%s", path, folder); + d = opendir(psvPath); + + if (!d) + return; + + while ((dir = readdir(d)) != NULL) + { + if (!endsWith(dir->d_name, ".PSV") && !endsWith(dir->d_name, ".MCS") && !endsWith(dir->d_name, ".PSX") && + !endsWith(dir->d_name, ".PS1") && !endsWith(dir->d_name, ".MCB") && !endsWith(dir->d_name, ".PDA")) + continue; + + // check for PS1 PSV saves + if (endsWith(dir->d_name, ".PSV")) + { + snprintf(psvPath, sizeof(psvPath), "%s%s%s", path, folder, dir->d_name); + if (read_file(psvPath, (uint8_t*) psvPath, 0x40) < 0 || psvPath[0x3C] != 0x01) + continue; + } + + snprintf(psvPath, sizeof(psvPath), "%s %s", CHAR_ICON_COPY, dir->d_name); + cmd = _createCmdCode(PATCH_COMMAND, psvPath, CMD_IMP_VMC1SAVE); + asprintf(&cmd->file, "%s%s%s", path, folder, dir->d_name); + cmd->codes[1] = FILE_TYPE_PS1; + list_append(list, cmd); + + LOG("[%s] F(%X) name '%s'", cmd->file, cmd->flags, cmd->name+2); + } + + closedir(d); +} + +static void read_vmc1_files(const char* vmcPath, list_t* list) +{ + 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; + + item = _createSaveEntry(SAVE_FLAG_PS1 | SAVE_FLAG_VMC, dir->d_name); + item->type = FILE_TYPE_VMC; + 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; + + save->codes = list_alloc(); + + if (save->type == FILE_TYPE_MENU) + { + add_vmc_import_saves(save->codes, save->path, PS1_IMP_PATH_USB); + add_vmc_import_saves(save->codes, save->path, PSV_SAVES_PATH_USB); + if (!list_count(save->codes)) + { + list_free(save->codes); + save->codes = NULL; + return 0; + } + + list_bubbleSort(save->codes, &sortCodeList_Compare); + + return list_count(save->codes); + } + + cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_USER " View Save Details", CMD_VIEW_DETAILS); + list_append(save->codes, cmd); + + cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_WARN " Delete Save Game", CMD_DELETE_VMCSAVE); + list_append(save->codes, cmd); + + cmd = _createCmdCode(PATCH_NULL, "----- " UTF8_CHAR_STAR " Save Game Backup " UTF8_CHAR_STAR " -----", CMD_CODE_NULL); + list_append(save->codes, cmd); + + cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Export save game to .MCS format", CMD_CODE_NULL); + cmd->options_count = 1; + cmd->options = _createOptions(3, "Copy .MCS Save to USB", CMD_EXP_VMC1SAVE); + asprintf(&cmd->options->name[2], "Copy .MCS Save to HDD"); + asprintf(&cmd->options->value[2], "%c%c", CMD_EXP_VMC1SAVE, STORAGE_HDD); + cmd->options[0].id = PS1SAVE_MCS; + list_append(save->codes, cmd); + + cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Export save game to .PSV format", CMD_CODE_NULL); + cmd->options_count = 1; + cmd->options = _createOptions(3, "Copy .PSV Save to USB", CMD_EXP_VMC1SAVE); + asprintf(&cmd->options->name[2], "Copy .PSV Save to HDD"); + asprintf(&cmd->options->value[2], "%c%c", CMD_EXP_VMC1SAVE, STORAGE_HDD); + cmd->options[0].id = PS1SAVE_PSV; + list_append(save->codes, cmd); + + cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Export save game to .PSX format", CMD_CODE_NULL); + cmd->options_count = 1; + cmd->options = _createOptions(3, "Copy .PSX Save to USB", CMD_EXP_VMC1SAVE); + asprintf(&cmd->options->name[2], "Copy .PSX Save to HDD"); + asprintf(&cmd->options->value[2], "%c%c", CMD_EXP_VMC1SAVE, STORAGE_HDD); + cmd->options[0].id = PS1SAVE_AR; + list_append(save->codes, cmd); + + LOG("Loaded %ld codes", list_count(save->codes)); + + return list_count(save->codes); +} + static void add_vmc2_import_saves(list_t* list, const char* path, const char* folder) { code_entry_t * cmd; @@ -1959,6 +2089,106 @@ 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]; + save_entry_t *item; + code_entry_t *cmd; + list_t *list; + ps1mcData_t* mcdata; + + if (!openMemoryCard(userPath, 0)) + { + LOG("Error: no PS1 Memory Card detected! (%s)", userPath); + return NULL; + } + + mcdata = getMemoryCardData(); + if (!mcdata) + return NULL; + + list = list_alloc(); + + item = _createSaveEntry(SAVE_FLAG_PS1, 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**)); + ((void**)item->dir_name)[0] = list; + + cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Export selected Saves to USB", CMD_CODE_NULL); + cmd->options_count = 1; + cmd->options = _createOptions(2, "Copy selected Saves to USB", CMD_EXP_SAVES_VMC); + list_append(item->codes, cmd); + cmd = _createCmdCode(PATCH_COMMAND, CHAR_ICON_COPY " Export all Saves to USB", CMD_CODE_NULL); + 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); + list_append(list, item); + + item = _createSaveEntry(SAVE_FLAG_PS1, CHAR_ICON_COPY " Import Saves to Virtual Card"); + item->path = strdup(FAKE_USB_PATH); + item->title_id = strdup("HDD"); + item->dir_name = strdup(userPath); + item->type = FILE_TYPE_MENU; + list_append(list, item); + + for (int i = 0; i <= MAX_USB_DEVICES; i++) + { + snprintf(filePath, sizeof(filePath), USB_PATH, i); + if (i && dir_exists(filePath) != SUCCESS) + continue; + + item = _createSaveEntry(SAVE_FLAG_PS1, CHAR_ICON_COPY " Import Saves to Virtual Card"); + asprintf(&item->path, USB_PATH, i); + asprintf(&item->title_id, "USB %d", i); + item->dir_name = strdup(userPath); + item->type = FILE_TYPE_MENU; + list_append(list, item); + } + + for (int i = 0; i < PS1CARD_MAX_SLOTS; i++) + { + if (mcdata[i].saveType != PS1BLOCK_INITIAL) + continue; + + LOG("Reading '%s'...", mcdata[i].saveName); + + char* tmp = sjis2utf8(mcdata[i].saveTitle); + item = _createSaveEntry(SAVE_FLAG_PS1 | SAVE_FLAG_VMC, tmp); + item->type = FILE_TYPE_PS1; + item->title_id = strdup(mcdata[i].saveProdCode); + //hack to keep the save block + asprintf(&item->dir_name, "%c%s", i, mcdata[i].saveName); + asprintf(&item->path, "%s\n%s", userPath, mcdata[i].saveName); + free(tmp); + + LOG("[%s] F(%X) name '%s'", item->title_id, item->flags, item->name); + list_append(list, item); + } + + return list; +} + list_t * ReadVmc2List(const char* userPath) { char filePath[256]; @@ -2010,9 +2240,9 @@ list_t * ReadVmc2List(const char* userPath) 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(3, "Save .VMC Memory Card to USB", CMD_EXP_VM2_RAW); + cmd->options = _createOptions(3, "Save .VMC Memory Card to USB", CMD_EXP_PS2_RAW); asprintf(&cmd->options->name[2], "Save .VMC Memory Card to HDD"); - asprintf(&cmd->options->value[2], "%c%c", CMD_EXP_VM2_RAW, STORAGE_HDD); + 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"); From 36b383ef3b854708dbf4a39d206243de943bda7f Mon Sep 17 00:00:00 2001 From: bucanero Date: Sat, 26 Oct 2024 11:13:12 -0300 Subject: [PATCH 3/7] clean up --- include/saves.h | 3 --- source/exec_cmd.c | 6 +++--- source/saves.c | 6 +++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/include/saves.h b/include/saves.h index 61ff3cd..72af2c0 100644 --- a/include/saves.h +++ b/include/saves.h @@ -35,9 +35,6 @@ #define PS2_SAVES_PATH_HDD "ps2emu2_savedata/" #define PSP_SAVES_PATH_HDD "minis_savedata/" -#define PS1_IMP_PATH_USB "PS1/SAVEDATA/" -#define PS2_IMP_PATH_USB "PS2/SAVEDATA/" - #define SAVES_PATH_USB0 USB0_PATH PS4_SAVES_PATH_USB #define SAVES_PATH_USB1 USB1_PATH PS4_SAVES_PATH_USB diff --git a/source/exec_cmd.c b/source/exec_cmd.c index 6d36217..8a89868 100644 --- a/source/exec_cmd.c +++ b/source/exec_cmd.c @@ -751,7 +751,7 @@ static void exportAllSavesVMC(const save_entry_t* save, int dev, int all) list_t *list = ((void**)save->dir_name)[0]; init_progress_bar("Exporting all VMC saves..."); - _set_dest_path(outPath, dev, PS1_IMP_PATH_USB); + _set_dest_path(outPath, dev, PS1_SAVES_PATH_USB); mkdirs(outPath); LOG("Exporting all saves from '%s' to %s...", save->path, outPath); @@ -778,7 +778,7 @@ static void exportVmcSave(const save_entry_t* save, int type, int dst_id) char outPath[256]; struct tm t; - _set_dest_path(outPath, dst_id, (type == PS1SAVE_PSV) ? PSV_SAVES_PATH_USB : PS1_IMP_PATH_USB); + _set_dest_path(outPath, dst_id, (type == PS1SAVE_PSV) ? PSV_SAVES_PATH_USB : PS1_SAVES_PATH_USB); mkdirs(outPath); if (type != PS1SAVE_PSV) { @@ -801,7 +801,7 @@ static void export_vmc2save(const save_entry_t* save, int type, int dst_id) char outPath[256]; struct tm t; - _set_dest_path(outPath, dst_id, (type == FILE_TYPE_PSV) ? PSV_SAVES_PATH_USB : PS2_IMP_PATH_USB); + _set_dest_path(outPath, dst_id, (type == FILE_TYPE_PSV) ? PSV_SAVES_PATH_USB : PS2_SAVES_PATH_USB); mkdirs(outPath); if (type != FILE_TYPE_PSV) { diff --git a/source/saves.c b/source/saves.c index 615e812..f5ca624 100644 --- a/source/saves.c +++ b/source/saves.c @@ -1054,7 +1054,7 @@ int ReadVmc1Codes(save_entry_t * save) if (save->type == FILE_TYPE_MENU) { - add_vmc_import_saves(save->codes, save->path, PS1_IMP_PATH_USB); + add_vmc_import_saves(save->codes, save->path, PS1_SAVES_PATH_USB); add_vmc_import_saves(save->codes, save->path, PSV_SAVES_PATH_USB); if (!list_count(save->codes)) { @@ -1184,7 +1184,7 @@ int ReadVmc2Codes(save_entry_t * save) if (save->type == FILE_TYPE_MENU) { - add_vmc2_import_saves(save->codes, save->path, PS2_IMP_PATH_USB); + add_vmc2_import_saves(save->codes, save->path, PS2_SAVES_PATH_USB); add_vmc2_import_saves(save->codes, save->path, PSV_SAVES_PATH_USB); if (!list_count(save->codes)) { @@ -2253,7 +2253,7 @@ list_t * ReadVmc2List(const char* userPath) for (int i = 0; i <= MAX_USB_DEVICES; i++) { - snprintf(filePath, sizeof(filePath), USB_PATH PS2_IMP_PATH_USB, i); + snprintf(filePath, sizeof(filePath), USB_PATH PS2_SAVES_PATH_USB, i); if (i && dir_exists(filePath) != SUCCESS) continue; From f8300ee56d1db2d52ecdc89e879c996c8296c3a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Parrino?= Date: Mon, 28 Oct 2024 13:28:52 -0300 Subject: [PATCH 4/7] Update source/ps1card.c --- source/ps1card.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/ps1card.c b/source/ps1card.c index 27f56d5..3d916d1 100644 --- a/source/ps1card.c +++ b/source/ps1card.c @@ -218,14 +218,15 @@ static void setPsvHeader(const char* saveFilename, uint32_t saveLength, FILE* fp psvSave[0x3C] = 1; psvSave[0x44] = 0x84; psvSave[0x49] = 2; - psvSave[0x5D] = 0x20; psvSave[0x60] = 3; psvSave[0x61] = 0x90; memcpy(&psvSave[0x64], saveFilename, 20); memcpy(&psvSave[0x40], &saveLength, sizeof(uint32_t)); + memcpy(&psvSave[0x5C], &saveLength, sizeof(uint32_t)); fwrite(psvSave, 1, sizeof(psvSave), fp); + return; } From f58eb3745de5db0d9f14859d852d15bcef440173 Mon Sep 17 00:00:00 2001 From: bucanero Date: Sat, 2 Nov 2024 15:11:56 -0300 Subject: [PATCH 5/7] fixes --- README.md | 13 +++++++-- source/exec_cmd.c | 6 ++-- source/main.c | 2 +- source/menu_main.c | 14 ++++++++-- source/ps1card.c | 3 +- source/saves.c | 69 ++++++++++++++++++++++++++++++++++++++++------ 6 files changed, 88 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 7bf1d2d..8e9819d 100644 --- a/README.md +++ b/README.md @@ -48,13 +48,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 @@ -153,6 +160,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 diff --git a/source/exec_cmd.c b/source/exec_cmd.c index 8a89868..0f0f550 100644 --- a/source/exec_cmd.c +++ b/source/exec_cmd.c @@ -762,7 +762,7 @@ static void exportAllSavesVMC(const save_entry_t* save, int dev, int all) continue; if (item->type == FILE_TYPE_PS1) - (saveSingleSave(outPath, save->dir_name[0], PS1SAVE_PSV) ? done++ : err_count++); + (saveSingleSave(outPath, save->blocks, PS1SAVE_PSV) ? done++ : err_count++); if (item->type == FILE_TYPE_PS2) (vmc_export_psv(item->dir_name, outPath) ? done++ : err_count++); @@ -789,7 +789,7 @@ static void exportVmcSave(const save_entry_t* save, int type, int dst_id) (type == PS1SAVE_MCS) ? "mcs" : "psx"); } - if (saveSingleSave(outPath, save->dir_name[0], type)) + if (saveSingleSave(outPath, save->blocks, type)) show_message("Save successfully exported to:\n%s", outPath); else show_message("Error exporting save:\n%s", save->path); @@ -874,7 +874,7 @@ static void deleteVmcSave(const save_entry_t* save) if (!show_dialog(DIALOG_TYPE_YESNO, "Do you want to delete %s?", save->dir_name)) return; - if ((save->flags & SAVE_FLAG_PS1) ? formatSave(save->dir_name[0]) : vmc_delete_save(save->dir_name)) + if ((save->flags & SAVE_FLAG_PS1) ? formatSave(save->blocks) : vmc_delete_save(save->dir_name)) show_message("Save successfully deleted:\n%s", save->dir_name); else show_message("Error! Couldn't delete save:\n%s", save->dir_name); diff --git a/source/main.c b/source/main.c index f53bbec..eb0deaa 100644 --- a/source/main.c +++ b/source/main.c @@ -463,7 +463,7 @@ void update_vmc_path(char* path) static void registerSpecialChars(void) { // Register save tags - RegisterSpecialCharacter(CHAR_TAG_PS1, 2, 1.5, &menu_textures[tag_ps1_png_index]); + RegisterSpecialCharacter(CHAR_TAG_PS1, 0, 1.5, &menu_textures[tag_ps1_png_index]); RegisterSpecialCharacter(CHAR_TAG_PS2, 0, 1.5, &menu_textures[tag_ps2_png_index]); RegisterSpecialCharacter(CHAR_TAG_PS3, 2, 1.5, &menu_textures[tag_ps3_png_index]); RegisterSpecialCharacter(CHAR_TAG_PS4, 2, 1.5, &menu_textures[tag_ps4_png_index]); diff --git a/source/menu_main.c b/source/menu_main.c index 82288da..145333f 100644 --- a/source/menu_main.c +++ b/source/menu_main.c @@ -120,6 +120,12 @@ static void SetMenu(int id) { UnloadGameList(vmc1_saves.list); vmc1_saves.list = NULL; + + if(strncmp(APOLLO_SANDBOX_PATH, vmc1_saves.path, 16) == 0) + { + *strrchr(vmc1_saves.path, '/') = 0; + orbis_SaveUmount(strrchr(vmc1_saves.path, '/')); + } } break; @@ -280,7 +286,11 @@ static void SetMenu(int id) LoadVmcTexture(128, 128, getIconPS2(selected_entry->dir_name, strrchr(selected_entry->path, '\n')+1)); else if (selected_entry->flags & SAVE_FLAG_VMC && selected_entry->type == FILE_TYPE_PS1) - LoadVmcTexture(16, 16, getIconRGBA(selected_entry->dir_name[0], 0)); + { + LoadVmcTexture(16, 16, getIconRGBA(selected_entry->blocks, 0)); + menu_textures[icon_png_file_index].width = 128; + menu_textures[icon_png_file_index].height = 128; + } else if (selected_entry->flags & SAVE_FLAG_HDD) snprintf(iconfile, sizeof(iconfile), PS4_SAVES_PATH_HDD "%s/%s_icon0.png", apollo_config.user_id, selected_entry->title_id, selected_entry->dir_name); @@ -408,7 +418,7 @@ static void doSaveMenu(save_list_t * save_list) if (selected_entry->flags & SAVE_FLAG_PS1) { - strncpy(vmc1_saves.path, selected_entry->path, sizeof(vmc1_saves.path)); + strncpy(vmc1_saves.path, tmp_path, sizeof(vmc1_saves.path)); SetMenu(MENU_PS1VMC_SAVES); } else diff --git a/source/ps1card.c b/source/ps1card.c index 3d916d1..0db75d0 100644 --- a/source/ps1card.c +++ b/source/ps1card.c @@ -226,7 +226,6 @@ static void setPsvHeader(const char* saveFilename, uint32_t saveLength, FILE* fp memcpy(&psvSave[0x5C], &saveLength, sizeof(uint32_t)); fwrite(psvSave, 1, sizeof(psvSave), fp); - return; } @@ -302,7 +301,7 @@ static void loadPalette(void) if ((redChannel | greenChannel | blueChannel | blackFlag) == 0) ps1saves[slotNumber].iconPalette[colorCounter] = 0x00000000; else - ps1saves[slotNumber].iconPalette[colorCounter] = blueChannel | (greenChannel << 8) | (redChannel << 16) | 0xFF000000; + ps1saves[slotNumber].iconPalette[colorCounter] = (redChannel << 24) | (greenChannel << 16) | (blueChannel << 8) | 0xFF; colorCounter++; } diff --git a/source/saves.c b/source/saves.c index a792206..03be874 100644 --- a/source/saves.c +++ b/source/saves.c @@ -1016,8 +1016,10 @@ 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, list_t* list) +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; @@ -1033,13 +1035,45 @@ static void read_vmc1_files(const char* vmcPath, list_t* list) !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; - 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); + 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); } @@ -1824,7 +1858,7 @@ static void read_vmc2_files(const char* userPath, const save_entry_t* parent, li snprintf(psvPath, sizeof(psvPath), "%s%s", userPath, dir->d_name); get_file_size(psvPath, &size); - LOG("Adding %s...", psvPath); + LOG("Checking %s...", psvPath); if (size % 0x840000 != 0 && size % 0x800000 != 0) continue; @@ -1879,6 +1913,7 @@ static void read_inner_vmc2_files(list_t *list) snprintf(save_path, sizeof(save_path), "%s", item->path); read_vmc2_files(save_path, item, list); + read_vmc1_files(save_path, item, list); if (item->flags & SAVE_FLAG_HDD) orbis_SaveUmount(mount); @@ -1940,6 +1975,9 @@ list_t * ReadUsbList(const char* userPath) snprintf(path, sizeof(path), "%s%s", userPath, VMC_PS2_PATH_USB); read_vmc2_files(path, NULL, list); + snprintf(path, sizeof(path), "%s%s", userPath, VMC_PS1_PATH_USB); + read_vmc1_files(path, NULL, list); + return list; } @@ -2154,7 +2192,7 @@ list_t * ReadVmc1List(const char* userPath) for (int i = 0; i <= MAX_USB_DEVICES; i++) { - snprintf(filePath, sizeof(filePath), USB_PATH, i); + snprintf(filePath, sizeof(filePath), USB_PATH PS1_SAVES_PATH_USB, i); if (i && dir_exists(filePath) != SUCCESS) continue; @@ -2176,9 +2214,9 @@ list_t * ReadVmc1List(const char* userPath) char* tmp = sjis2utf8(mcdata[i].saveTitle); item = _createSaveEntry(SAVE_FLAG_PS1 | SAVE_FLAG_VMC, tmp); item->type = FILE_TYPE_PS1; + item->blocks = i; item->title_id = strdup(mcdata[i].saveProdCode); - //hack to keep the save block - asprintf(&item->dir_name, "%c%s", i, mcdata[i].saveName); + item->dir_name = strdup(mcdata[i].saveName); asprintf(&item->path, "%s\n%s", userPath, mcdata[i].saveName); free(tmp); @@ -2376,6 +2414,19 @@ int get_save_details(const save_entry_t* save, char **details) sqlite3 *db; sqlite3_stmt *res; + if(save->type == FILE_TYPE_PS1) + { + asprintf(details, "%s\n\n----- PS1 Save -----\n" + "Game: %s\n" + "Title ID: %s\n" + "File: %s\n", + save->path, + save->name, + save->title_id, + save->dir_name); + return 1; + } + if(save->type == FILE_TYPE_PS2) { asprintf(details, "%s\n\n----- PS2 Save -----\n" From 8987e5ed3f01dd6a80539cc4b1a2cefd6d6593b0 Mon Sep 17 00:00:00 2001 From: bucanero Date: Sat, 2 Nov 2024 16:45:49 -0300 Subject: [PATCH 6/7] enable ps1 online db --- source/exec_cmd.c | 2 +- source/saves.c | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/source/exec_cmd.c b/source/exec_cmd.c index 0f0f550..00c7864 100644 --- a/source/exec_cmd.c +++ b/source/exec_cmd.c @@ -43,7 +43,7 @@ static void downloadSave(const save_entry_t* entry, const char* file, int dst) { char path[256]; - _set_dest_path(path, dst, (entry->flags & SAVE_FLAG_PS4) ? PS4_SAVES_PATH_USB : PS2_SAVES_PATH_USB); + _set_dest_path(path, dst, (entry->flags & SAVE_FLAG_PS4) ? PS4_SAVES_PATH_USB : PSV_SAVES_PATH_USB); if (mkdirs(path) != SUCCESS) { show_message("Error! Export folder is not available:\n%s", path); diff --git a/source/saves.c b/source/saves.c index 03be874..279b16b 100644 --- a/source/saves.c +++ b/source/saves.c @@ -2112,11 +2112,9 @@ list_t * ReadOnlineList(const char* urlPath) snprintf(url, sizeof(url), "%s" "PS2/", urlPath); _ReadOnlineListEx(url, SAVE_FLAG_PS2, list); -/* // PS1 save-games (Zip PSV) - //snprintf(url, sizeof(url), "%s" "PS1/", urlPath); - //_ReadOnlineListEx(url, SAVE_FLAG_PS1, list); -*/ + snprintf(url, sizeof(url), "%s" "PS1/", urlPath); + _ReadOnlineListEx(url, SAVE_FLAG_PS1, list); if (!list_count(list)) { From 1537f0cae7cfb88c4689f26f6b5f1b39a91430af Mon Sep 17 00:00:00 2001 From: bucanero Date: Sun, 10 Nov 2024 09:39:39 -0300 Subject: [PATCH 7/7] ps1 vmc fixes --- Makefile | 2 +- docs/README.md | 24 ++++++- include/settings.h | 2 +- source/exec_cmd.c | 26 ++++++++ source/psv_resign.c | 2 +- source/saves.c | 152 +++++++++++++++++--------------------------- 6 files changed, 109 insertions(+), 99 deletions(-) diff --git a/Makefile b/Makefile index 72e11dd..cee7d50 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/docs/README.md b/docs/README.md index 88db9c7..9592e35 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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 @@ -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`. @@ -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 diff --git a/include/settings.h b/include/settings.h index ca05a57..a663384 100644 --- a/include/settings.h +++ b/include/settings.h @@ -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 diff --git a/source/exec_cmd.c b/source/exec_cmd.c index 00c7864..2279fe8 100644 --- a/source/exec_cmd.c +++ b/source/exec_cmd.c @@ -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; @@ -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; } diff --git a/source/psv_resign.c b/source/psv_resign.c index ced7658..e9261a6 100644 --- a/source/psv_resign.c +++ b/source/psv_resign.c @@ -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[] = diff --git a/source/saves.c b/source/saves.c index 279b16b..aef3e0a 100644 --- a/source/saves.c +++ b/source/saves.c @@ -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; @@ -1837,13 +1773,14 @@ 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) @@ -1851,18 +1788,44 @@ static void read_vmc2_files(const char* userPath, const save_entry_t* parent, li 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) @@ -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); @@ -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; } @@ -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]; @@ -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); @@ -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); @@ -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; @@ -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;