2020-08-22 22:30:12 -04:00
|
|
|
/*!
|
|
|
|
* @file kmemcard.cpp
|
2022-03-06 18:58:22 -05:00
|
|
|
* Memory card interface. Very messy code. Most of it is commented out now, as we've switched away
|
|
|
|
* from memory cards to just raw saves.
|
2022-06-26 18:17:11 -04:00
|
|
|
*
|
|
|
|
* Not checked carefully for differences in jak 2.
|
2020-08-22 22:30:12 -04:00
|
|
|
*/
|
|
|
|
|
2022-06-22 23:37:46 -04:00
|
|
|
#include "kmemcard.h"
|
|
|
|
|
|
|
|
#include <array>
|
2021-10-01 23:12:34 -04:00
|
|
|
#include <cstdio>
|
|
|
|
#include <cstring>
|
|
|
|
|
2022-06-22 23:37:46 -04:00
|
|
|
#include "common/util/Assert.h"
|
|
|
|
#include "common/util/FileUtil.h"
|
|
|
|
#include "common/util/Timer.h"
|
2021-10-01 23:12:34 -04:00
|
|
|
|
2022-06-22 23:37:46 -04:00
|
|
|
#include "game/sce/sif_ee.h"
|
|
|
|
#include "game/sce/sif_ee_memcard.h"
|
|
|
|
|
|
|
|
#include "third-party/fmt/core.h"
|
2021-10-01 23:12:34 -04:00
|
|
|
|
2022-03-06 18:58:22 -05:00
|
|
|
static constexpr bool memcard_debug = false;
|
2020-08-22 22:30:12 -04:00
|
|
|
|
2021-08-22 20:12:47 -04:00
|
|
|
using McCallbackFunc = void (*)(s32);
|
|
|
|
|
|
|
|
McCallbackFunc callback;
|
|
|
|
|
2021-07-11 16:35:25 -04:00
|
|
|
static s32 language;
|
2021-08-22 20:12:47 -04:00
|
|
|
static MemoryCardOperation op;
|
2022-03-06 18:58:22 -05:00
|
|
|
// instead of two memory cards we just simulate the 4 save files (8 banks).
|
|
|
|
static MemoryCardFile mc_files[4];
|
|
|
|
// keep track of latest file selected. this is only used in an auto-save mode thats not used
|
|
|
|
static int mc_last_file = -1;
|
|
|
|
|
|
|
|
// a random value we will use as the memory card "handle" for the pc port, which has no memcards.
|
|
|
|
constexpr u32 PC_MEM_CARD_HANDLE = 0x6C616F67;
|
2021-10-01 23:12:34 -04:00
|
|
|
|
|
|
|
constexpr u32 MEM_CARD_MAGIC = 0x12345678;
|
|
|
|
|
|
|
|
struct McHeader {
|
|
|
|
u32 save_count;
|
|
|
|
u32 checksum;
|
|
|
|
u32 magic;
|
|
|
|
u8 preview_data[64];
|
|
|
|
u8 data[944];
|
|
|
|
u32 unk1_repeated;
|
|
|
|
};
|
|
|
|
static_assert(sizeof(McHeader) == 0x400, "McHeader size");
|
2022-03-06 18:58:22 -05:00
|
|
|
constexpr s32 BANK_TOTAL_SIZE = BANK_SIZE + sizeof(McHeader) * 2;
|
2021-10-01 23:12:34 -04:00
|
|
|
|
|
|
|
static McHeader header;
|
2021-08-22 20:12:47 -04:00
|
|
|
|
|
|
|
// these are the return value for sceMcGetInfo.
|
|
|
|
static s32 p1, p2, p3, p4;
|
|
|
|
using namespace ee;
|
|
|
|
|
2021-10-01 23:12:34 -04:00
|
|
|
template <typename... Args>
|
|
|
|
void mc_print(const std::string& str, Args&&... args) {
|
|
|
|
if (memcard_debug) {
|
|
|
|
fmt::print("[MC] ");
|
|
|
|
if (!str.empty() && str.back() == '\n') {
|
|
|
|
fmt::print(str, std::forward<Args>(args)...);
|
|
|
|
} else {
|
|
|
|
fmt::print(str + '\n', std::forward<Args>(args)...);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-08-22 20:12:47 -04:00
|
|
|
|
|
|
|
const char* filename[12] = {
|
2022-03-20 20:29:44 -04:00
|
|
|
"BASCUS-97124AYBABTU!", "BASCUS-97124AYBABTU!/icon.sys",
|
|
|
|
"BASCUS-97124AYBABTU!/icon.ico", "BASCUS-97124AYBABTU!/BASCUS-97124AYBABTU!",
|
|
|
|
"BASCUS-97124AYBABTU!/bank0.bin", "BASCUS-97124AYBABTU!/bank1.bin",
|
|
|
|
"BASCUS-97124AYBABTU!/bank2.bin", "BASCUS-97124AYBABTU!/bank3.bin",
|
|
|
|
"BASCUS-97124AYBABTU!/bank4.bin", "BASCUS-97124AYBABTU!/bank5.bin",
|
|
|
|
"BASCUS-97124AYBABTU!/bank6.bin", "BASCUS-97124AYBABTU!/bank7.bin"};
|
2020-08-22 22:30:12 -04:00
|
|
|
|
|
|
|
void kmemcard_init_globals() {
|
2022-03-06 18:58:22 -05:00
|
|
|
// next = 0;
|
2021-07-11 16:35:25 -04:00
|
|
|
language = 0;
|
2021-08-22 20:12:47 -04:00
|
|
|
op = {};
|
2022-03-06 18:58:22 -05:00
|
|
|
// mc[0] = {};
|
|
|
|
// mc[1] = {};
|
|
|
|
mc_files[0] = {};
|
|
|
|
mc_files[1] = {};
|
|
|
|
mc_files[2] = {};
|
|
|
|
mc_files[3] = {};
|
2021-08-22 20:12:47 -04:00
|
|
|
callback = nullptr;
|
|
|
|
p1 = 0;
|
|
|
|
p2 = 0;
|
|
|
|
p3 = 0;
|
|
|
|
p4 = 0;
|
2022-03-06 18:58:22 -05:00
|
|
|
// memset(&dirent, 0, sizeof(sceMcTblGetDir));
|
2021-10-01 23:12:34 -04:00
|
|
|
memset(&header, 0, sizeof(McHeader));
|
2021-08-22 20:12:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2021-10-01 23:12:34 -04:00
|
|
|
* A questionable checksum used on memory card data.
|
2021-08-22 20:12:47 -04:00
|
|
|
*/
|
|
|
|
u32 mc_checksum(Ptr<u8> data, s32 size) {
|
|
|
|
if (size < 0) {
|
|
|
|
size += 3;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 result = 0;
|
|
|
|
u32* data_u32 = (u32*)data.c();
|
|
|
|
for (s32 i = 0; i < size / 4; i++) {
|
2021-10-01 23:12:34 -04:00
|
|
|
result = result << 1 ^ (s32)result >> 0x1f ^ data_u32[i] ^ MEM_CARD_MAGIC;
|
2021-08-22 20:12:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return result ^ 0xedd1e666;
|
|
|
|
}
|
|
|
|
|
2022-03-06 18:58:22 -05:00
|
|
|
/*!
|
|
|
|
* PC port function that returns whether a given bank ID's file exists or not.
|
|
|
|
*/
|
|
|
|
bool file_is_present(int id, int bank = 0) {
|
2022-08-21 18:13:27 -04:00
|
|
|
auto bankname = file_util::get_user_memcard_dir(g_game_version) / filename[4 + id * 2 + bank];
|
2022-07-05 20:38:13 -04:00
|
|
|
if (!fs::exists(bankname) || fs::file_size(bankname) < BANK_TOTAL_SIZE) {
|
2022-06-10 13:20:00 -04:00
|
|
|
// file doesn't exist, or size is bad. we do not want to open files that will crash on read!
|
2022-03-06 18:58:22 -05:00
|
|
|
return false;
|
|
|
|
}
|
2022-06-10 13:20:00 -04:00
|
|
|
// avoid file check here tbh. there shouldn't be any saves with a save count of zero anyway.
|
2022-03-06 18:58:22 -05:00
|
|
|
// the file check is quite slow and ultimately not very useful.
|
|
|
|
return true;
|
|
|
|
|
|
|
|
/*
|
|
|
|
// file exists. but let's see if it's an empty one.
|
|
|
|
// this prevents the game from reading a bank but classifying it as corrupt data.
|
|
|
|
// which a file full of zeros logically is.
|
2022-07-05 20:38:13 -04:00
|
|
|
auto fp = file_util::open_file(bankname.c_str(), "rb");
|
2022-03-06 18:58:22 -05:00
|
|
|
|
|
|
|
// we can actually just check if the save count is over zero...
|
|
|
|
u32 savecount = 0;
|
|
|
|
fread(&savecount, sizeof(u32), 1, fp);
|
|
|
|
fclose(fp);
|
|
|
|
return savecount > 0;
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* PC port function to set memcard info. We don't use a memory card, instead just the raw savefiles.
|
|
|
|
*/
|
|
|
|
void pc_update_card() {
|
|
|
|
// int highest_save_count = 0;
|
|
|
|
mc_last_file = -1;
|
|
|
|
for (s32 file = 0; file < 4; file++) {
|
2022-08-21 18:13:27 -04:00
|
|
|
auto bankname = file_util::get_user_memcard_dir(g_game_version) / filename[4 + file * 2];
|
2022-03-06 18:58:22 -05:00
|
|
|
mc_files[file].present = file_is_present(file);
|
|
|
|
if (mc_files[file].present) {
|
2022-03-20 20:29:44 -04:00
|
|
|
auto bankdata = file_util::read_binary_file(bankname.string());
|
2022-03-06 18:58:22 -05:00
|
|
|
auto header1 = reinterpret_cast<McHeader*>(bankdata.data());
|
|
|
|
if (file_is_present(file, 1)) {
|
2022-08-21 18:13:27 -04:00
|
|
|
auto bankname2 =
|
|
|
|
file_util::get_user_memcard_dir(g_game_version) / filename[1 + 4 + file * 2];
|
2022-03-20 20:29:44 -04:00
|
|
|
auto bankdata2 = file_util::read_binary_file(bankname2.string());
|
2022-06-10 13:20:00 -04:00
|
|
|
auto header2 = reinterpret_cast<McHeader*>(bankdata2.data());
|
2022-03-06 18:58:22 -05:00
|
|
|
|
|
|
|
if (header2->save_count > header1->save_count) {
|
|
|
|
// use most recent bank here.
|
|
|
|
header1 = header2;
|
|
|
|
}
|
|
|
|
|
|
|
|
// banks chosen and checked. copy data and set info.
|
|
|
|
mc_files[file].last_saved_bank = header1 == header2;
|
|
|
|
mc_files[file].most_recent_save_count = header1->save_count;
|
|
|
|
|
|
|
|
memcpy(mc_files[file].data, header1->preview_data, 64);
|
|
|
|
} else {
|
|
|
|
// banks chosen and checked. copy data and set info.
|
|
|
|
mc_files[file].last_saved_bank = 0;
|
|
|
|
mc_files[file].most_recent_save_count = header1->save_count;
|
|
|
|
|
|
|
|
memcpy(mc_files[file].data, header1->preview_data, 64);
|
|
|
|
}
|
|
|
|
|
|
|
|
// if (mc_files[file].most_recent_save_count > highest_save_count) {
|
|
|
|
// mc_last_file = file;
|
|
|
|
// highest_save_count = mc_files[file].most_recent_save_count;
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* PC port function to save a file. This does the whole saving at once, synchronously.
|
2021-08-22 20:12:47 -04:00
|
|
|
*/
|
2022-03-06 18:58:22 -05:00
|
|
|
void pc_game_save_synch() {
|
|
|
|
Timer mc_timer;
|
|
|
|
mc_timer.start();
|
|
|
|
pc_update_card();
|
2022-08-21 18:13:27 -04:00
|
|
|
auto path = file_util::get_user_memcard_dir(g_game_version) / filename[0];
|
2022-03-20 20:29:44 -04:00
|
|
|
file_util::create_dir_if_needed_for_file(path.string());
|
2022-03-06 18:58:22 -05:00
|
|
|
|
|
|
|
// cd_reprobe_save //
|
|
|
|
if (!file_is_present(op.param2)) {
|
|
|
|
mc_print("reprobe save: first time!");
|
|
|
|
// first time saving!
|
|
|
|
p2 = 0; // save count 0
|
|
|
|
p4 = 0; // first bank for file
|
|
|
|
} else {
|
|
|
|
p2 = mc_files[op.param2].most_recent_save_count + 1; // increment save count
|
|
|
|
p4 = mc_files[op.param2].last_saved_bank ^ 1; // use the other bank
|
|
|
|
}
|
|
|
|
|
|
|
|
// reserve 0 as "I never saved" and use 1 instead.
|
|
|
|
if (p2 == 0) {
|
|
|
|
p2 = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// file*2 + p4 is the bank (2 banks per file, p4 is 0 or 1 to select the bank)
|
|
|
|
// 4 is the first bank file
|
|
|
|
mc_print("open {} for saving", filename[op.param2 * 2 + 4 + p4]);
|
2022-08-21 18:13:27 -04:00
|
|
|
auto save_path =
|
|
|
|
file_util::get_user_memcard_dir(g_game_version) / filename[op.param2 * 2 + 4 + p4];
|
2022-03-20 20:29:44 -04:00
|
|
|
file_util::create_dir_if_needed_for_file(save_path.string());
|
2022-07-05 20:38:13 -04:00
|
|
|
auto fd = file_util::open_file(save_path.string().c_str(), "wb");
|
2022-06-10 13:20:00 -04:00
|
|
|
mc_print("synchronous save file open took {:.2f}ms\n", mc_timer.getMs());
|
2022-03-06 18:58:22 -05:00
|
|
|
if (fd) {
|
|
|
|
// cb_openedsave //
|
|
|
|
mc_print("save file opened, writing header...");
|
|
|
|
memset(&header, 0, sizeof(McHeader));
|
|
|
|
header.save_count = p2;
|
|
|
|
header.checksum = mc_checksum(op.data_ptr, BANK_SIZE);
|
|
|
|
header.magic = MEM_CARD_MAGIC;
|
|
|
|
header.unk1_repeated = p2;
|
|
|
|
memcpy(header.preview_data, op.data_ptr2.c(), 64);
|
|
|
|
if (fwrite(&header, sizeof(McHeader), 1, fd) == 1) {
|
|
|
|
// cb_savedheader //
|
|
|
|
mc_print("save file writing main data");
|
|
|
|
if (fwrite(op.data_ptr.c(), BANK_SIZE, 1, fd) == 1) {
|
|
|
|
// cb_saveddata //
|
|
|
|
mc_print("save file writing footer");
|
|
|
|
if (fwrite(&header, sizeof(McHeader), 1, fd) == 1) {
|
|
|
|
// cb_savedfooter //
|
|
|
|
if (fclose(fd) == 0) {
|
|
|
|
// cb_closedsave //
|
|
|
|
mc_print("All done with saving!!");
|
|
|
|
op.operation = MemoryCardOperationKind::NO_OP;
|
|
|
|
op.result = McStatusCode::OK;
|
|
|
|
mc_files[op.param2].present = 1;
|
|
|
|
mc_files[op.param2].most_recent_save_count = p2;
|
|
|
|
mc_files[op.param2].last_saved_bank = p4;
|
|
|
|
memcpy(mc_files[op.param2].data, op.data_ptr2.c(), 64);
|
|
|
|
mc_last_file = op.param2;
|
|
|
|
} else {
|
|
|
|
op.operation = MemoryCardOperationKind::NO_OP;
|
|
|
|
op.result = McStatusCode::INTERNAL_ERROR;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fclose(fd);
|
|
|
|
op.operation = MemoryCardOperationKind::NO_OP;
|
|
|
|
op.result = McStatusCode::INTERNAL_ERROR;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fclose(fd);
|
|
|
|
op.operation = MemoryCardOperationKind::NO_OP;
|
|
|
|
op.result = McStatusCode::INTERNAL_ERROR;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fclose(fd);
|
|
|
|
op.operation = MemoryCardOperationKind::NO_OP;
|
|
|
|
op.result = McStatusCode::INTERNAL_ERROR;
|
|
|
|
}
|
|
|
|
} else {
|
2022-03-20 20:29:44 -04:00
|
|
|
fmt::print("[MC] Error opening file, errno - {}", errno);
|
2022-03-06 18:58:22 -05:00
|
|
|
op.operation = MemoryCardOperationKind::NO_OP;
|
|
|
|
op.result = McStatusCode::INTERNAL_ERROR;
|
2021-08-22 20:12:47 -04:00
|
|
|
}
|
2022-03-06 18:58:22 -05:00
|
|
|
|
2022-06-10 13:20:00 -04:00
|
|
|
mc_print("[MC] synchronous save took {:.2f}ms\n", mc_timer.getMs());
|
2022-03-06 18:58:22 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void pc_game_load_open_file(FILE* fd) {
|
|
|
|
if (fd) {
|
|
|
|
// cb_openedload //
|
|
|
|
size_t read_size = BANK_TOTAL_SIZE;
|
|
|
|
mc_print("reading save file...");
|
|
|
|
if (fread(op.data_ptr.c() + p2 * read_size, read_size, 1, fd) == 1) {
|
|
|
|
// cb_readload //
|
|
|
|
mc_print("closing save file..");
|
|
|
|
if (fclose(fd) == 0) {
|
|
|
|
// cb_closedload //
|
|
|
|
// added : check if aux bank exists
|
2022-08-21 18:13:27 -04:00
|
|
|
if (p2 < 1 && fs::exists(file_util::get_user_memcard_dir(g_game_version) /
|
|
|
|
filename[op.param2 * 2 + 4 + p2 + 1])) {
|
2022-05-02 21:26:17 -04:00
|
|
|
p2++;
|
2022-03-06 18:58:22 -05:00
|
|
|
mc_print("reading next save bank {}", filename[op.param2 * 2 + 4 + p2]);
|
2022-08-21 18:13:27 -04:00
|
|
|
auto new_bankname =
|
|
|
|
file_util::get_user_memcard_dir(g_game_version) / filename[op.param2 * 2 + 4 + p2];
|
2022-07-05 20:38:13 -04:00
|
|
|
auto new_fd = file_util::open_file(new_bankname.string().c_str(), "rb");
|
2022-03-06 18:58:22 -05:00
|
|
|
pc_game_load_open_file(new_fd);
|
|
|
|
} else {
|
|
|
|
// let's verify the data.
|
|
|
|
McHeader* headers[2];
|
|
|
|
McHeader* footers[2];
|
|
|
|
bool ok[2];
|
|
|
|
|
|
|
|
headers[0] = (McHeader*)(op.data_ptr.c());
|
|
|
|
footers[0] = (McHeader*)(op.data_ptr.c() + sizeof(McHeader) + BANK_SIZE);
|
|
|
|
headers[1] = (McHeader*)(op.data_ptr.c() + BANK_TOTAL_SIZE);
|
|
|
|
footers[1] =
|
|
|
|
(McHeader*)(op.data_ptr.c() + BANK_TOTAL_SIZE + sizeof(McHeader) + BANK_SIZE);
|
|
|
|
static_assert(BANK_TOTAL_SIZE * 2 == 0x21000, "save layout");
|
|
|
|
ok[0] = true;
|
2022-05-02 21:26:17 -04:00
|
|
|
ok[1] = p2 == 1;
|
2022-03-06 18:58:22 -05:00
|
|
|
|
|
|
|
for (int idx = 0; idx < 2; idx++) {
|
|
|
|
u32 expected_save_count = headers[idx]->save_count;
|
|
|
|
if (headers[idx]->unk1_repeated == expected_save_count &&
|
|
|
|
footers[idx]->save_count == expected_save_count &&
|
|
|
|
footers[idx]->unk1_repeated == expected_save_count) {
|
|
|
|
// save count is okay!
|
|
|
|
if (headers[idx]->magic == MEM_CARD_MAGIC && footers[idx]->magic == MEM_CARD_MAGIC) {
|
|
|
|
// magic numbers okay!
|
|
|
|
if (headers[idx]->checksum == footers[idx]->checksum) {
|
|
|
|
// checksum
|
|
|
|
auto expected_checksum = headers[idx]->checksum;
|
|
|
|
if (mc_checksum(make_u8_ptr(headers[idx] + 1), BANK_SIZE) != expected_checksum) {
|
|
|
|
mc_print("failed checksum");
|
|
|
|
ok[idx] = false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
mc_print("corrupted checksum");
|
|
|
|
ok[idx] = false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
mc_print("bad magic");
|
|
|
|
ok[idx] = false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
mc_print("bad save count");
|
|
|
|
ok[idx] = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mc_print("checking loaded banks");
|
|
|
|
|
|
|
|
//
|
|
|
|
if (!ok[0] && !ok[1]) {
|
|
|
|
// no good data.
|
|
|
|
if (headers[0]->save_count == 0 && headers[0]->checksum == 0 &&
|
|
|
|
headers[0]->magic == 0 && headers[0]->unk1_repeated == 0 &&
|
|
|
|
headers[1]->save_count == 0 && headers[1]->checksum == 0 &&
|
|
|
|
headers[1]->magic == 0 && headers[1]->unk1_repeated == 0) {
|
|
|
|
// this is a fresh file that you tried to load from...
|
|
|
|
mc_print("new game result");
|
|
|
|
op.operation = MemoryCardOperationKind::NO_OP;
|
|
|
|
op.result = McStatusCode::NEW_GAME;
|
|
|
|
mc_last_file = op.param2;
|
|
|
|
} else {
|
|
|
|
mc_print("corrupted data");
|
|
|
|
op.operation = MemoryCardOperationKind::NO_OP;
|
|
|
|
op.result = McStatusCode::READ_ERROR;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// pick the bank
|
|
|
|
int bank = 0;
|
|
|
|
|
|
|
|
if (!ok[0] || !ok[1]) {
|
|
|
|
if (ok[1]) {
|
|
|
|
bank = 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
bank = headers[0]->save_count <= headers[1]->save_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
mc_print(fmt::format("loading bank {}", bank));
|
|
|
|
u32 current_save_count = headers[bank]->save_count;
|
2022-04-29 20:07:58 -04:00
|
|
|
memmove(op.data_ptr.c(), op.data_ptr.c() + bank * BANK_TOTAL_SIZE + sizeof(McHeader),
|
|
|
|
BANK_SIZE);
|
2022-03-06 18:58:22 -05:00
|
|
|
mc_last_file = op.param2;
|
|
|
|
mc_files[op.param2].most_recent_save_count = current_save_count;
|
|
|
|
mc_files[op.param2].last_saved_bank = bank;
|
|
|
|
op.operation = MemoryCardOperationKind::NO_OP;
|
|
|
|
op.result = McStatusCode::OK;
|
|
|
|
mc_print("load succeeded");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
op.operation = MemoryCardOperationKind::NO_OP;
|
|
|
|
op.result = McStatusCode::INTERNAL_ERROR;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fclose(fd);
|
|
|
|
op.operation = MemoryCardOperationKind::NO_OP;
|
|
|
|
op.result = McStatusCode::INTERNAL_ERROR;
|
|
|
|
}
|
2021-08-22 20:12:47 -04:00
|
|
|
} else {
|
2022-03-06 18:58:22 -05:00
|
|
|
op.operation = MemoryCardOperationKind::NO_OP;
|
|
|
|
op.result = McStatusCode::INTERNAL_ERROR;
|
2021-08-22 20:12:47 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-06 18:58:22 -05:00
|
|
|
/*!
|
|
|
|
* PC port function to load a file. This does the whole loading at once, synchronously.
|
|
|
|
*/
|
|
|
|
void pc_game_load_synch() {
|
|
|
|
Timer mc_timer;
|
|
|
|
mc_timer.start();
|
|
|
|
pc_update_card();
|
|
|
|
|
|
|
|
// cb_reprobe_load //
|
|
|
|
p2 = 0;
|
|
|
|
mc_print("opening save file {}", filename[op.param2 * 2 + 4]);
|
2022-03-20 20:29:44 -04:00
|
|
|
|
2022-08-21 18:13:27 -04:00
|
|
|
auto path = file_util::get_user_memcard_dir(g_game_version) / filename[op.param2 * 2 + 4];
|
2022-07-05 20:38:13 -04:00
|
|
|
auto fd = file_util::open_file(path.string().c_str(), "rb");
|
2022-03-06 18:58:22 -05:00
|
|
|
pc_game_load_open_file(fd);
|
|
|
|
|
2022-06-10 13:20:00 -04:00
|
|
|
mc_print("synchronous load took {:.2f}ms\n", mc_timer.getMs());
|
2022-03-06 18:58:22 -05:00
|
|
|
}
|
|
|
|
|
2021-08-22 20:12:47 -04:00
|
|
|
/*!
|
2021-10-01 23:12:34 -04:00
|
|
|
* Run the Memory Card state machine. This is called once per frame in GOAL.
|
|
|
|
* It:
|
|
|
|
* - does nothing if there is an in-progress memory card operation
|
|
|
|
* - if async memory card functions are done, runs their callbacks
|
|
|
|
* - if there is a requested operation, starts running sony functions.
|
|
|
|
* - if there is none of the above, and unknown cards, finds out about them.
|
|
|
|
* - every now and then, recheck cards.
|
2021-08-22 20:12:47 -04:00
|
|
|
*/
|
|
|
|
void MC_run() {
|
2021-10-01 23:12:34 -04:00
|
|
|
// if we have an in-progress operation, it will have set a callback.
|
2021-08-22 20:12:47 -04:00
|
|
|
if (callback) {
|
|
|
|
s32 sony_cmd, sony_status;
|
2021-10-01 23:12:34 -04:00
|
|
|
// check the status
|
2021-08-22 20:12:47 -04:00
|
|
|
s32 status = sceMcSync(1, &sony_cmd, &sony_status);
|
|
|
|
McCallbackFunc callback_for_sync = callback;
|
|
|
|
if (status == sceMcExecRun) {
|
|
|
|
// busy, return.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status == sceMcExecFinish) {
|
2021-10-01 23:12:34 -04:00
|
|
|
// sony function is done. do the callback.
|
2021-08-22 20:12:47 -04:00
|
|
|
callback = nullptr;
|
|
|
|
(*callback_for_sync)(sony_status);
|
|
|
|
} else {
|
|
|
|
// sony function is done, but failed.
|
|
|
|
callback = nullptr;
|
|
|
|
(*callback_for_sync)(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (callback) {
|
|
|
|
// if we got another callback, it means there's another op started by the prev callback.
|
2021-10-01 23:12:34 -04:00
|
|
|
// and this case, we want to wait for that operation to finish.
|
2021-08-22 20:12:47 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-01 23:12:34 -04:00
|
|
|
// if we got here, there is no in-progress sony function. So start the next one, if we should
|
2021-08-22 20:12:47 -04:00
|
|
|
if (op.operation == MemoryCardOperationKind::FORMAT) {
|
2022-03-06 18:58:22 -05:00
|
|
|
// format memory card. Not used in PC port, so lets move on.
|
|
|
|
return;
|
2021-08-22 20:12:47 -04:00
|
|
|
} else if (op.operation == MemoryCardOperationKind::UNFORMAT) {
|
2022-03-06 18:58:22 -05:00
|
|
|
// unformat memory card.
|
|
|
|
return;
|
2021-08-22 20:12:47 -04:00
|
|
|
} else if (op.operation == MemoryCardOperationKind::CREATE_FILE) {
|
2022-03-06 18:58:22 -05:00
|
|
|
// create the game file.
|
|
|
|
// there's no cards, keep in mind.
|
|
|
|
return;
|
2021-08-22 20:12:47 -04:00
|
|
|
} else if (op.operation == MemoryCardOperationKind::SAVE) {
|
2022-03-06 18:58:22 -05:00
|
|
|
// write game save.
|
|
|
|
// there's no cards, keep in mind.
|
|
|
|
pc_game_save_synch();
|
|
|
|
// allow some number of errors.
|
|
|
|
op.retry_count--;
|
|
|
|
if (op.retry_count == 0) {
|
2021-08-22 20:12:47 -04:00
|
|
|
op.operation = MemoryCardOperationKind::NO_OP;
|
2022-03-06 18:58:22 -05:00
|
|
|
op.result = McStatusCode::INTERNAL_ERROR;
|
2021-08-22 20:12:47 -04:00
|
|
|
}
|
2021-10-01 23:12:34 -04:00
|
|
|
} else if (op.operation == MemoryCardOperationKind::LOAD) {
|
2022-03-06 18:58:22 -05:00
|
|
|
// load game save.
|
|
|
|
// potato.
|
|
|
|
if (!file_is_present(op.param2)) {
|
|
|
|
// tried to load, but there's no save data in the file.
|
2021-10-01 23:12:34 -04:00
|
|
|
op.operation = MemoryCardOperationKind::NO_OP;
|
2022-03-06 18:58:22 -05:00
|
|
|
op.result = McStatusCode::NO_MEMORY;
|
2021-10-01 23:12:34 -04:00
|
|
|
} else {
|
2022-03-06 18:58:22 -05:00
|
|
|
pc_game_load_synch();
|
|
|
|
op.retry_count--;
|
|
|
|
if (op.retry_count == 0) {
|
2021-10-01 23:12:34 -04:00
|
|
|
op.operation = MemoryCardOperationKind::NO_OP;
|
2022-03-06 18:58:22 -05:00
|
|
|
op.result = McStatusCode::INTERNAL_ERROR;
|
2021-10-01 23:12:34 -04:00
|
|
|
}
|
|
|
|
}
|
2022-06-10 13:20:00 -04:00
|
|
|
}
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
|
2021-10-01 23:12:34 -04:00
|
|
|
/////////////////////////
|
|
|
|
// Memory Card Functions
|
|
|
|
/////////////////////////
|
|
|
|
|
|
|
|
// These functions are called from GOAL to start memory card operations.
|
|
|
|
|
2021-07-11 16:35:25 -04:00
|
|
|
/*!
|
|
|
|
* Set the language or something.
|
2022-03-06 18:58:22 -05:00
|
|
|
* Why is this a memory card func?
|
2021-07-11 16:35:25 -04:00
|
|
|
*/
|
|
|
|
void MC_set_language(s32 l) {
|
|
|
|
printf("Language set to %d\n", l);
|
|
|
|
language = l;
|
|
|
|
}
|
|
|
|
|
2021-08-22 20:12:47 -04:00
|
|
|
/*!
|
|
|
|
* Set the current memory card operation to FORMAT the given card.
|
2022-03-06 18:58:22 -05:00
|
|
|
* Doesn't do anything in the port because we don't use memory cards.
|
2021-08-22 20:12:47 -04:00
|
|
|
*/
|
2022-03-11 22:27:11 -05:00
|
|
|
u64 MC_format(s32 /*card_idx*/) {
|
2022-03-06 18:58:22 -05:00
|
|
|
return u64(McStatusCode::OK);
|
|
|
|
// u64 can_add = op.operation == MemoryCardOperationKind::NO_OP;
|
|
|
|
// mc_print("requested format");
|
|
|
|
// if (can_add) {
|
|
|
|
// mc_print("setting op to format");
|
|
|
|
// op.operation = MemoryCardOperationKind::FORMAT;
|
|
|
|
// op.result = McStatusCode::BUSY;
|
|
|
|
// op.retry_count = 100;
|
|
|
|
// op.param = card_idx;
|
|
|
|
//}
|
|
|
|
// return can_add;
|
2021-08-22 20:12:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Set the current memory card operation to UNFORMAT the given card.
|
2022-03-06 18:58:22 -05:00
|
|
|
* You get the idea.
|
2021-08-22 20:12:47 -04:00
|
|
|
*/
|
2022-03-11 22:27:11 -05:00
|
|
|
u64 MC_unformat(s32 /*card_idx*/) {
|
2022-03-06 18:58:22 -05:00
|
|
|
return u64(McStatusCode::OK);
|
|
|
|
// u64 can_add = op.operation == MemoryCardOperationKind::NO_OP;
|
|
|
|
// mc_print("requested unformat");
|
|
|
|
// if (can_add) {
|
|
|
|
// mc_print("setting op to unformat");
|
|
|
|
// op.operation = MemoryCardOperationKind::UNFORMAT;
|
|
|
|
// op.result = McStatusCode::BUSY;
|
|
|
|
// op.retry_count = 100;
|
|
|
|
// op.param = card_idx;
|
|
|
|
//}
|
|
|
|
// return can_add;
|
2021-08-22 20:12:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Set the current memory card operation to create the save file.
|
2021-10-01 23:12:34 -04:00
|
|
|
* The data I believe is just an empty buffer used as temporary storage.
|
2021-08-22 20:12:47 -04:00
|
|
|
*/
|
2022-03-11 22:27:11 -05:00
|
|
|
u64 MC_createfile(s32 /*param*/, Ptr<u8> /*data*/) {
|
2022-03-06 18:58:22 -05:00
|
|
|
return u64(McStatusCode::OK);
|
|
|
|
// u64 can_add = op.operation == MemoryCardOperationKind::NO_OP;
|
|
|
|
// mc_print("requested createfile");
|
|
|
|
// if (can_add) {
|
|
|
|
// mc_print("setting op to create file");
|
|
|
|
// op.operation = MemoryCardOperationKind::CREATE_FILE;
|
|
|
|
// op.result = McStatusCode::BUSY;
|
|
|
|
// op.retry_count = 100;
|
|
|
|
// op.param = param;
|
|
|
|
// op.data_ptr = data;
|
|
|
|
//}
|
|
|
|
// return can_add;
|
2021-08-22 20:12:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Set the current operation to SAVE.
|
2021-10-01 23:12:34 -04:00
|
|
|
* The "summary data" is data that will be used when previewing save files (number of orbs etc)
|
2022-03-06 18:58:22 -05:00
|
|
|
* TODO put synchronous call here
|
2021-08-22 20:12:47 -04:00
|
|
|
*/
|
|
|
|
u64 MC_save(s32 card_idx, s32 file_idx, Ptr<u8> save_data, Ptr<u8> save_summary_data) {
|
2021-10-01 23:12:34 -04:00
|
|
|
mc_print("requested save");
|
2021-08-22 20:12:47 -04:00
|
|
|
u64 can_add = op.operation == MemoryCardOperationKind::NO_OP;
|
|
|
|
if (can_add) {
|
2021-10-01 23:12:34 -04:00
|
|
|
mc_print("setting op to save");
|
2021-08-22 20:12:47 -04:00
|
|
|
op.operation = MemoryCardOperationKind::SAVE;
|
|
|
|
op.result = McStatusCode::BUSY;
|
2021-10-01 23:12:34 -04:00
|
|
|
op.retry_count = 100;
|
2021-08-22 20:12:47 -04:00
|
|
|
op.param = card_idx;
|
|
|
|
op.param2 = file_idx;
|
|
|
|
op.data_ptr = save_data;
|
|
|
|
op.data_ptr2 = save_summary_data;
|
|
|
|
}
|
|
|
|
return can_add;
|
|
|
|
}
|
|
|
|
|
2021-10-01 23:12:34 -04:00
|
|
|
/*!
|
|
|
|
* Set the current operation to LOAD.
|
2022-03-06 18:58:22 -05:00
|
|
|
* TODO put synchronous call here
|
2021-10-01 23:12:34 -04:00
|
|
|
*/
|
2021-08-22 20:12:47 -04:00
|
|
|
u64 MC_load(s32 card_idx, s32 file_idx, Ptr<u8> data) {
|
2021-10-01 23:12:34 -04:00
|
|
|
mc_print("requested load");
|
2021-08-22 20:12:47 -04:00
|
|
|
u64 can_add = op.operation == MemoryCardOperationKind::NO_OP;
|
|
|
|
if (can_add) {
|
2021-10-01 23:12:34 -04:00
|
|
|
mc_print("setting op to load");
|
2021-08-22 20:12:47 -04:00
|
|
|
op.operation = MemoryCardOperationKind::LOAD;
|
|
|
|
op.result = McStatusCode::BUSY;
|
2021-10-01 23:12:34 -04:00
|
|
|
op.retry_count = 100;
|
2021-08-22 20:12:47 -04:00
|
|
|
op.param = card_idx;
|
|
|
|
op.param2 = file_idx;
|
|
|
|
op.data_ptr = data;
|
|
|
|
}
|
|
|
|
return can_add;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Some sort of test function for memory card stuff.
|
|
|
|
* This is exported as a GOAL function, but nothing calls it.
|
|
|
|
*/
|
|
|
|
void MC_makefile(s32 port, s32 size) {
|
|
|
|
sceMcMkdir(port, 0, "/BASCUS-00000XXXXXXXX");
|
|
|
|
// wait for operation to complete
|
|
|
|
s32 cmd, result, fd;
|
|
|
|
sceMcSync(0, &cmd, &result);
|
|
|
|
|
|
|
|
if (result == sceMcResSucceed || result == sceMcResNoEntry) {
|
|
|
|
// it worked, or the folder already exists...
|
|
|
|
|
|
|
|
// open file
|
|
|
|
sceMcOpen(port, 0, "/BASCUS-00000XXXXXXXX/BASCUS-00000XXXXXXXX", SCE_CREAT | SCE_WRONLY);
|
|
|
|
sceMcSync(0, &cmd, &fd);
|
|
|
|
|
|
|
|
if (result < 0) {
|
2022-03-06 18:58:22 -05:00
|
|
|
printf("Can't open file on memcard [%d]\n", result);
|
2021-08-22 20:12:47 -04:00
|
|
|
} else {
|
|
|
|
// write some random crap into the memory card.
|
|
|
|
sceMcWrite(fd, Ptr<u8>(0x1000000).c(), size);
|
|
|
|
sceMcSync(0, &cmd, &result);
|
|
|
|
if (result != size) {
|
|
|
|
printf("Only written %d bytes\n", result);
|
|
|
|
}
|
|
|
|
sceMcClose(fd);
|
|
|
|
sceMcSync(0, &cmd, &result);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
printf("Can\'t create garbage folder [%d]\n", result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-01 23:12:34 -04:00
|
|
|
/*!
|
|
|
|
* Get the result of the currently executing (or most recently executed) command
|
|
|
|
*/
|
2021-08-22 20:12:47 -04:00
|
|
|
u32 MC_check_result() {
|
|
|
|
return (u32)op.result;
|
|
|
|
}
|
|
|
|
|
2021-10-01 23:12:34 -04:00
|
|
|
/*!
|
|
|
|
* Update the info for the given slot.
|
|
|
|
* You can call this at any time.
|
2022-03-06 18:58:22 -05:00
|
|
|
* The slot includes the four save slots (8 banks), and a few other files.
|
2021-10-01 23:12:34 -04:00
|
|
|
*/
|
2022-05-11 22:53:53 -04:00
|
|
|
void MC_get_status(s32 /*slot*/, Ptr<mc_slot_info> info) {
|
2022-04-30 14:48:24 -04:00
|
|
|
// slot is ignored, so you'll get the same thing regardless of what slot you pick
|
2022-03-06 18:58:22 -05:00
|
|
|
|
2021-08-22 20:12:47 -04:00
|
|
|
info->handle = 0;
|
|
|
|
info->known = 0;
|
|
|
|
info->formatted = 0;
|
|
|
|
info->initted = 0;
|
|
|
|
for (s32 i = 0; i < 4; i++) {
|
|
|
|
info->files[i].present = 0;
|
|
|
|
}
|
|
|
|
info->last_file = 0xffffffff;
|
|
|
|
info->mem_required = SAVE_SIZE;
|
|
|
|
info->mem_actual = 0;
|
|
|
|
|
2022-03-06 18:58:22 -05:00
|
|
|
pc_update_card();
|
|
|
|
info->known = 1;
|
|
|
|
info->handle = PC_MEM_CARD_HANDLE;
|
|
|
|
info->formatted = 1;
|
|
|
|
info->mem_actual = SAVE_SIZE; // idk TODO does this matter?
|
|
|
|
info->initted = 1;
|
|
|
|
// copy over the preview data.
|
|
|
|
for (s32 file = 0; file < 4; file++) {
|
|
|
|
info->files[file].present = mc_files[file].present;
|
|
|
|
for (s32 i = 0; i < 64; i++) { // actually a loop over u32's
|
|
|
|
info->files[file].data[i] = mc_files[file].data[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
info->last_file = mc_last_file;
|
2021-08-22 20:12:47 -04:00
|
|
|
}
|