mirror of
https://github.com/JaCzekanski/Avocado.git
synced 2024-05-10 00:23:47 -04:00
memory_card: added support for .vgs, .gme, .vmp memory card formats
Load memory cards by drag&drop Better parsing of memory card contents Card formatting
This commit is contained in:
parent
0c457b2665
commit
b51c2b8a64
|
@ -247,6 +247,7 @@ add_library(core STATIC
|
|||
src/disc/position.cpp
|
||||
src/disc/subchannel_q.cpp
|
||||
src/input/input_manager.cpp
|
||||
src/memory_card/card_formats.cpp
|
||||
src/sound/adpcm.cpp
|
||||
src/sound/tables.cpp
|
||||
src/sound/wave.cpp
|
||||
|
|
|
@ -16,6 +16,8 @@ See [Avocado compatibility list](https://avocado-db.czekanski.info)
|
|||
|
||||
## Changelog
|
||||
|
||||
*16.11.2021* - .vgs, .gme, .vmp memory card format support, load memory cards by drag&drop
|
||||
|
||||
*28.06.2020* - .ecm format support
|
||||
|
||||
*16.09.2019* - Save states
|
||||
|
|
|
@ -149,6 +149,9 @@ uint8_t MemoryCard::handleWrite(uint8_t byte) {
|
|||
flag.fresh = 0;
|
||||
state = 0;
|
||||
command = Command::None;
|
||||
|
||||
bus.notify(Event::Controller::MemoryCardContentsChanged{port});
|
||||
|
||||
return static_cast<uint8_t>(writeStatus);
|
||||
|
||||
default:
|
||||
|
|
|
@ -38,5 +38,7 @@ struct MemoryCard : public AbstractDevice {
|
|||
|
||||
MemoryCard(int port);
|
||||
uint8_t handle(uint8_t byte) override;
|
||||
|
||||
void setFresh() { flag.fresh = true; }
|
||||
};
|
||||
} // namespace peripherals
|
262
src/memory_card/card_formats.cpp
Normal file
262
src/memory_card/card_formats.cpp
Normal file
|
@ -0,0 +1,262 @@
|
|||
#include "card_formats.h"
|
||||
#include "utils/file.h"
|
||||
#include "fmt/core.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace memory_card {
|
||||
// Thanks ShendoXT - https://github.com/ShendoXT/memcardrex
|
||||
const std::array<std::string, 9> rawFormats = {
|
||||
// Raw 128kB images
|
||||
"psm", // SmartLink
|
||||
"ps", // WinPSM
|
||||
"ddf", // DataDeck
|
||||
"mcr", // FPSX
|
||||
"mcd", // ePSXe
|
||||
"mc", // PSXGame Edit
|
||||
"mci", // MCExplorer
|
||||
"srm", // PCSX ReARMed/RetroArch
|
||||
"vm1", // PS3 virtual card
|
||||
};
|
||||
|
||||
const std::array<std::string, 4> complexFormats = {
|
||||
"mem",
|
||||
"vgs", // Connectix Virtual Game Station
|
||||
"gme", // DexDrive
|
||||
"vmp", // PSP/Vita
|
||||
};
|
||||
|
||||
const std::array<std::string, 8> saveFileFormats = {
|
||||
"mcs", // PSXGameEdit single save
|
||||
"psv", // PS3 signed save
|
||||
"psx", // XP, AR, GS, Caetla single save
|
||||
"ps1", // Memory Juggler
|
||||
"mcb", // Smart Link
|
||||
"mcx",
|
||||
"pda", // Datel
|
||||
"B???????????*", // RAW single save
|
||||
};
|
||||
|
||||
bool isMemoryCardImage(const std::string& path) {
|
||||
std::string ext = getExtension(path);
|
||||
transform(ext.begin(), ext.end(), ext.begin(), tolower);
|
||||
|
||||
if (std::find(rawFormats.begin(), rawFormats.end(), ext) != rawFormats.end()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (std::find(complexFormats.begin(), complexFormats.end(), ext) != complexFormats.end()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr int VGS_HEADER_SIZE = 64;
|
||||
constexpr int GME_HEADER_SIZE = 3904;
|
||||
constexpr int VMP_HEADER_SIZE = 128;
|
||||
|
||||
std::optional<std::vector<uint8_t>> load(const std::string& path) {
|
||||
std::string ext = getExtension(path);
|
||||
transform(ext.begin(), ext.end(), ext.begin(), tolower);
|
||||
|
||||
auto raw = getFileContents(path);
|
||||
int offset = 0;
|
||||
|
||||
if (ext == "raw" || ext == "ps" || ext == "ddf" || ext == "mcr" || ext == "mcd") {
|
||||
if (memcmp("MC", raw.data(), 2) != 0) {
|
||||
fmt::print("[memory_card] Raw memory card image - Invalid header (expected MC).\n");
|
||||
return {};
|
||||
}
|
||||
} else if (ext == "mem" || ext == "vgs") {
|
||||
if (memcmp("VgsM", raw.data(), 4) != 0) {
|
||||
fmt::print("[memory_card] VGS image - Invalid header (expected VgsM).\n");
|
||||
return {};
|
||||
}
|
||||
offset = VGS_HEADER_SIZE;
|
||||
} else if (ext == "gme") {
|
||||
if (memcmp("123-456-STD", raw.data(), 11) != 0) {
|
||||
fmt::print("[memory_card] DexDrive image - Invalid header (expected 123-456-STD).\n");
|
||||
return {};
|
||||
}
|
||||
|
||||
offset = GME_HEADER_SIZE;
|
||||
} else if (ext == "vmp") {
|
||||
if (memcmp("\0PMV", raw.data(), 4) != 0) {
|
||||
fmt::print("[memory_card] PSP/Vita image - Invalid header (expected \\0PMV).\n");
|
||||
return {};
|
||||
}
|
||||
|
||||
offset = VMP_HEADER_SIZE;
|
||||
} else {
|
||||
fmt::print("Unsupported memory card image format {}, please report it here https://github.com/JaCzekanski/Avocado/issues/new\n",
|
||||
ext);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> memory(MEMCARD_SIZE, 0);
|
||||
int size = std::min(MEMCARD_SIZE, (int)raw.size() - offset);
|
||||
std::copy(raw.begin() + offset, raw.begin() + offset + size, memory.begin());
|
||||
return memory;
|
||||
}
|
||||
|
||||
bool saveRaw(const std::array<uint8_t, MEMCARD_SIZE>& data, const std::string& path) {
|
||||
auto output = std::vector<uint8_t>(data.begin(), data.end());
|
||||
|
||||
if (!putFileContents(path, output)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool saveVgs(const std::array<uint8_t, MEMCARD_SIZE>& data, const std::string& path) {
|
||||
auto output = std::vector<uint8_t>(VGS_HEADER_SIZE + MEMCARD_SIZE);
|
||||
|
||||
output[0] = 'V';
|
||||
output[1] = 'g';
|
||||
output[2] = 's';
|
||||
output[3] = 'M';
|
||||
|
||||
output[4] = 1;
|
||||
output[8] = 1;
|
||||
output[12] = 1;
|
||||
output[17] = 2;
|
||||
|
||||
std::copy(data.begin(), data.end(), output.begin() + VGS_HEADER_SIZE);
|
||||
|
||||
if (!putFileContents(path, output)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool saveGme(const std::array<uint8_t, MEMCARD_SIZE>& data, const std::string& path) {
|
||||
auto output = std::vector<uint8_t>(GME_HEADER_SIZE + MEMCARD_SIZE);
|
||||
|
||||
output[0] = '1';
|
||||
output[1] = '2';
|
||||
output[2] = '3';
|
||||
output[3] = '-';
|
||||
output[4] = '4';
|
||||
output[5] = '5';
|
||||
output[6] = '6';
|
||||
output[7] = '-';
|
||||
output[8] = 'S';
|
||||
output[9] = 'T';
|
||||
output[10] = 'D';
|
||||
|
||||
output[18] = 0x1;
|
||||
output[20] = 0x1;
|
||||
output[21] = 'M';
|
||||
|
||||
for (int slot = 0; slot < 15; slot++) {
|
||||
int ptr = (slot + 1) * 0x80;
|
||||
output[22 + slot] = data[ptr + 0];
|
||||
output[38 + slot] = data[ptr + 8];
|
||||
}
|
||||
|
||||
// Comments are skipped
|
||||
|
||||
std::copy(data.begin(), data.end(), output.begin() + GME_HEADER_SIZE);
|
||||
|
||||
if (!putFileContents(path, output)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool saveVmp(const std::array<uint8_t, MEMCARD_SIZE>& data, const std::string& path) {
|
||||
auto output = std::vector<uint8_t>(VMP_HEADER_SIZE + MEMCARD_SIZE);
|
||||
|
||||
output[0] = 0;
|
||||
output[1] = 'P';
|
||||
output[2] = 'M';
|
||||
output[3] = 'V';
|
||||
output[4] = 0x80;
|
||||
|
||||
// offset: 0x0c, length: 0x14 - key seed, aes 128 cbc
|
||||
// offset: 0x20, length: 0x14 - sha1 hmac
|
||||
|
||||
std::copy(data.begin(), data.end(), output.begin() + VMP_HEADER_SIZE);
|
||||
|
||||
if (!putFileContents(path, output)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool save(const std::array<uint8_t, MEMCARD_SIZE>& data, const std::string& path) {
|
||||
std::string ext = getExtension(path);
|
||||
transform(ext.begin(), ext.end(), ext.begin(), tolower);
|
||||
|
||||
if (ext == "raw" || ext == "ps" || ext == "ddf" || ext == "mcr" || ext == "mcd") {
|
||||
return saveRaw(data, path);
|
||||
} else if (ext == "mem" || ext == "vgs") {
|
||||
return saveVgs(data, path);
|
||||
} else if (ext == "gme") {
|
||||
return saveGme(data, path);
|
||||
} else if (ext == "vmp") {
|
||||
return saveVmp(data, path);
|
||||
} else {
|
||||
fmt::print("Unsupported memory card image format {}, please report it here https://github.com/JaCzekanski/Avocado/issues/new\n",
|
||||
ext);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void format(std::array<uint8_t, MEMCARD_SIZE>& data) {
|
||||
std::array<uint8_t, 0x80> frame;
|
||||
|
||||
auto writeFrame = [&](int frameNum, bool withChecksum = false) {
|
||||
if (withChecksum) {
|
||||
uint8_t checksum = 0;
|
||||
for (int i = 0; i < 0x7f; i++) {
|
||||
checksum ^= frame[i];
|
||||
}
|
||||
frame[0x7f] = checksum;
|
||||
}
|
||||
|
||||
int offset = frameNum * 128;
|
||||
for (int i = 0; i < 0x80; i++) {
|
||||
data[offset + i] = frame[i];
|
||||
}
|
||||
};
|
||||
|
||||
data.fill(0);
|
||||
|
||||
frame.fill(0);
|
||||
frame[0] = 'M';
|
||||
frame[1] = 'C';
|
||||
writeFrame(0, true);
|
||||
writeFrame(63, true);
|
||||
|
||||
// Directory frames
|
||||
for (int i = 0; i < 15; i++) {
|
||||
frame.fill(0);
|
||||
// 0x00-0x03 - Block allocation state
|
||||
frame[0] = 0xa0; // Free, freshly formatted
|
||||
frame[1] = 0x00;
|
||||
frame[2] = 0x00;
|
||||
frame[3] = 0x00;
|
||||
|
||||
// Pointer to next block
|
||||
frame[8] = 0xff;
|
||||
frame[9] = 0xff;
|
||||
writeFrame(1 + i, true);
|
||||
}
|
||||
|
||||
// Broken sector list
|
||||
for (int i = 0; i < 20; i++) {
|
||||
frame.fill(0);
|
||||
// Sector number
|
||||
frame[0] = 0xff;
|
||||
frame[1] = 0xff;
|
||||
frame[2] = 0xff;
|
||||
frame[3] = 0xff;
|
||||
|
||||
// Garbage
|
||||
frame[8] = 0xff;
|
||||
frame[9] = 0xff;
|
||||
writeFrame(16 + i);
|
||||
}
|
||||
}
|
||||
} // namespace memory_card
|
13
src/memory_card/card_formats.h
Normal file
13
src/memory_card/card_formats.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace memory_card {
|
||||
constexpr int MEMCARD_SIZE = 128 * 1024;
|
||||
bool isMemoryCardImage(const std::string& path);
|
||||
std::optional<std::vector<uint8_t>> load(const std::string& path);
|
||||
bool save(const std::array<uint8_t, MEMCARD_SIZE>& data, const std::string& path);
|
||||
void format(std::array<uint8_t, MEMCARD_SIZE>& data);
|
||||
}; // namespace memory_card
|
|
@ -12,6 +12,8 @@
|
|||
#include "utils/file.h"
|
||||
#include "utils/string.h"
|
||||
#include "images.h"
|
||||
#include "disc/load.h"
|
||||
#include "memory_card/card_formats.h"
|
||||
|
||||
float GUI::scale = 1.f;
|
||||
|
||||
|
@ -321,17 +323,21 @@ void GUI::render(std::unique_ptr<System>& sys) {
|
|||
notInitializedWindowShown = true;
|
||||
ImGui::OpenPopup("Avocado");
|
||||
} else if (droppedItem) {
|
||||
if (!droppedItemDialogShown) {
|
||||
droppedItemDialogShown = true;
|
||||
ImGui::OpenPopup("Disc");
|
||||
if (droppedItemDialog == DroppedItemDialog::None) {
|
||||
if (memory_card::isMemoryCardImage(*droppedItem)) {
|
||||
droppedItemDialog = DroppedItemDialog::MemoryCard;
|
||||
ImGui::OpenPopup("Memory card##select_file");
|
||||
} else if (disc::isDiscImage(*droppedItem)) {
|
||||
droppedItemDialog = DroppedItemDialog::Disc;
|
||||
ImGui::OpenPopup("Disc##select_file");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
drawControls(sys);
|
||||
}
|
||||
|
||||
if (droppedItem) {
|
||||
discDialog();
|
||||
}
|
||||
memoryCardDialog();
|
||||
discDialog();
|
||||
|
||||
// Work in progress
|
||||
// renderController();
|
||||
|
@ -340,8 +346,33 @@ void GUI::render(std::unique_ptr<System>& sys) {
|
|||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
}
|
||||
|
||||
void GUI::memoryCardDialog() {
|
||||
if (!ImGui::BeginPopupModal("Memory card##select_file", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::Text("Select card slot for %s", getFilenameExt(*droppedItem).c_str());
|
||||
for (int slot = 0; slot < 2; slot++) {
|
||||
if (ImGui::Button(fmt::format("Slot {}", slot + 1).c_str())) {
|
||||
config.memoryCard[slot].path = *droppedItem;
|
||||
bus.notify(Event::Controller::MemoryCardSwapped{slot});
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
if (ImGui::Button("Cancel")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
|
||||
if (!ImGui::IsPopupOpen("Memory card##select_file")) {
|
||||
droppedItem = {};
|
||||
droppedItemDialog = DroppedItemDialog::None;
|
||||
}
|
||||
}
|
||||
void GUI::discDialog() {
|
||||
if (!ImGui::BeginPopupModal("Disc", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
if (!ImGui::BeginPopupModal("Disc##select_file", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -372,9 +403,9 @@ void GUI::discDialog() {
|
|||
}
|
||||
ImGui::EndPopup();
|
||||
|
||||
if (!ImGui::IsPopupOpen("Disc")) {
|
||||
if (!ImGui::IsPopupOpen("Disc##select_file")) {
|
||||
droppedItem = {};
|
||||
droppedItemDialogShown = false;
|
||||
droppedItemDialog = DroppedItemDialog::None;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ class GUI {
|
|||
gui::Toasts toasts;
|
||||
|
||||
void mainMenu(std::unique_ptr<System>& sys);
|
||||
void memoryCardDialog();
|
||||
void discDialog();
|
||||
void drawControls(std::unique_ptr<System>& sys);
|
||||
void renderController();
|
||||
|
@ -62,7 +63,9 @@ class GUI {
|
|||
|
||||
// Drag&drop
|
||||
std::optional<std::string> droppedItem;
|
||||
bool droppedItemDialogShown = false;
|
||||
|
||||
enum class DroppedItemDialog { None, Disc, MemoryCard };
|
||||
DroppedItemDialog droppedItemDialog = DroppedItemDialog::None;
|
||||
|
||||
GUI(SDL_Window* window, void* glContext);
|
||||
~GUI();
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include <misc/cpp/imgui_stdlib.h>
|
||||
#include "config.h"
|
||||
#include "../../../../system.h"
|
||||
#include "utils/string.h"
|
||||
#include "memory_card/card_formats.h"
|
||||
|
||||
namespace gui::options {
|
||||
|
||||
|
@ -11,28 +13,34 @@ char sjisToAscii(uint16_t sjis) {
|
|||
uint8_t l = sjis & 0xff;
|
||||
uint8_t h = (sjis >> 8) & 0xff;
|
||||
if (sjis == 0) return 0;
|
||||
if (l == 0x00) {
|
||||
if (h == 0x40) return 0;
|
||||
}
|
||||
if (l == 0x81) {
|
||||
// if (h == 0x14) return '-';
|
||||
if (h == 0x5b) return '-';
|
||||
// clang-format off
|
||||
if (h == 0x40) return ' ';
|
||||
if (h == 0x46) return ':';
|
||||
if (h == 0x49) return '!';
|
||||
if (h == 0x5e) return '/';
|
||||
if (h == 0x6d) return '[';
|
||||
if (h == 0x6e) return ']';
|
||||
if (h == 0x69) return '(';
|
||||
if (h == 0x6a) return ')';
|
||||
if (h == 0x7b) return '+';
|
||||
if (h == 0x7c) return ',';
|
||||
if (h == 0x93) return '%';
|
||||
// if (h >= 0x43 && h <= 0x97) return ' ' + (h-0x43);
|
||||
else if (h == 0x44) return '.';
|
||||
else if (h == 0x46) return ':';
|
||||
else if (h == 0x49) return '!';
|
||||
else if (h == 0x51) return '_';
|
||||
else if (h == 0x5b) return '-';
|
||||
else if (h == 0x5e) return '/';
|
||||
else if (h == 0x68) return '"';
|
||||
else if (h == 0x69) return '(';
|
||||
else if (h == 0x6a) return ')';
|
||||
else if (h == 0x6d) return '[';
|
||||
else if (h == 0x6e) return ']';
|
||||
else if (h == 0x7b) return '+';
|
||||
else if (h == 0x7c) return ',';
|
||||
else if (h == 0x93) return '%';
|
||||
// clang-format on
|
||||
}
|
||||
if (l == 0x82) {
|
||||
if (h >= 0x4f && h <= 0x58) return '0' + (h - 0x4f);
|
||||
if (h >= 0x60 && h <= 0x79) return 'A' + (h - 0x60);
|
||||
if (h >= 0x81 && h <= 0x9a) return 'a' + (h - 0x81);
|
||||
}
|
||||
// fmt::print("Unknown S-JIS: 0x{:02x} 0x{:02x}\n", l, h);
|
||||
// fmt::print("Unknown S-JIS: 0x{:02x} 0x{:02x}\n", l, h);
|
||||
return '?';
|
||||
}
|
||||
|
||||
|
@ -77,7 +85,14 @@ void MemoryCard::parseAndDisplayCard(peripherals::MemoryCard* card) {
|
|||
uint32_t size = read32(offset + 4) / 1024;
|
||||
std::string filename;
|
||||
for (int i = 0; i < 20; i++) {
|
||||
filename += data[offset + 0x0a + i];
|
||||
char c = data[offset + 0x0a + i];
|
||||
if (c == 0) break;
|
||||
filename += c;
|
||||
}
|
||||
Region region = parseRegion(filename);
|
||||
std::string gameId;
|
||||
if (filename.length() >= 12) {
|
||||
gameId = filename.substr(2, 10); // Strip region
|
||||
}
|
||||
|
||||
// Parse block
|
||||
|
@ -91,14 +106,31 @@ void MemoryCard::parseAndDisplayCard(peripherals::MemoryCard* card) {
|
|||
if (c == 0) break;
|
||||
title += c;
|
||||
}
|
||||
title = trim(title);
|
||||
|
||||
ImGui::Selectable(fmt::format("{:2d}. {} ({})", i, title, filename).c_str());
|
||||
ImGui::Selectable(fmt::format("{:2d}. {} ({})", i, title, gameId).c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
ImGui::Selectable(fmt::format("{:2d}. USED", i).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
Region MemoryCard::parseRegion(const std::string& filename) const {
|
||||
if (filename.length() < 2) return Region::Unknown;
|
||||
|
||||
auto region = filename.substr(0, 2);
|
||||
if (region == "BI") {
|
||||
return Region::Japan;
|
||||
} else if (region == "BE") {
|
||||
return Region::Europe;
|
||||
} else if (region == "BA") {
|
||||
return Region::America;
|
||||
} else {
|
||||
return Region::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryCard::memoryCardWindow(System* sys) {
|
||||
if (loadPaths) {
|
||||
for (size_t i = 0; i < cardPaths.size(); i++) {
|
||||
|
@ -111,8 +143,9 @@ void MemoryCard::memoryCardWindow(System* sys) {
|
|||
|
||||
for (size_t i = 0; i < sys->controller->card.size(); i++) {
|
||||
if (ImGui::BeginTabItem(fmt::format("Slot {}", i + 1).c_str())) {
|
||||
if (ImGui::InputText("Path", &cardPaths[i])) {
|
||||
if (ImGui::InputText("Path (enter to load)", &cardPaths[i], ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
config.memoryCard[i].path = cardPaths[i];
|
||||
bus.notify(Event::Controller::MemoryCardSwapped{(int)i});
|
||||
}
|
||||
// TODO: Reload contents on change?
|
||||
// TODO: Check if card exists, override or reload?
|
||||
|
@ -126,12 +159,35 @@ void MemoryCard::memoryCardWindow(System* sys) {
|
|||
parseAndDisplayCard(sys->controller->card[i].get());
|
||||
// ImGui::EndChild();
|
||||
|
||||
if (showConfirmFormatButton) {
|
||||
if (ImGui::Button("Cancel")) {
|
||||
showConfirmFormatButton = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.8, 0., 0., 1.));
|
||||
if (ImGui::Button(showConfirmFormatButton ? "Confirm" : "Format")) {
|
||||
if (!showConfirmFormatButton) {
|
||||
showConfirmFormatButton = true;
|
||||
} else {
|
||||
memory_card::format(sys->controller->card[i]->data);
|
||||
bus.notify(Event::Controller::MemoryCardContentsChanged{(int)i});
|
||||
showConfirmFormatButton = false;
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
ImGui::End();
|
||||
|
||||
if (!memoryCardWindowOpen) {
|
||||
showConfirmFormatButton = false;
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryCard::displayWindows(System* sys) {
|
||||
|
|
|
@ -8,12 +8,17 @@ struct MemoryCard;
|
|||
};
|
||||
|
||||
namespace gui::options {
|
||||
|
||||
enum class Region { Unknown, Japan, Europe, America };
|
||||
|
||||
class MemoryCard {
|
||||
bool loadPaths = true;
|
||||
bool showConfirmFormatButton = false;
|
||||
std::array<std::string, 2> cardPaths;
|
||||
|
||||
void parseAndDisplayCard(peripherals::MemoryCard* card);
|
||||
void memoryCardWindow(System* sys);
|
||||
Region parseRegion(const std::string& filename) const;
|
||||
|
||||
public:
|
||||
bool memoryCardWindowOpen = false;
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "utils/string.h"
|
||||
#include "utils/platform_tools.h"
|
||||
#include "version.h"
|
||||
#include "memory_card/card_formats.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
|
@ -199,13 +200,13 @@ int main(int argc, char** argv) {
|
|||
std::unique_ptr<System> sys = system_tools::hardReset();
|
||||
|
||||
int busToken = bus.listen<Event::File::Load>([&](auto e) {
|
||||
if (disc::isDiscImage(e.file)) {
|
||||
if (e.action == Event::File::Load::Action::ask) {
|
||||
// Show dialog and decide what to do
|
||||
gui->droppedItem = e.file;
|
||||
return;
|
||||
}
|
||||
if (e.action == Event::File::Load::Action::ask && (disc::isDiscImage(e.file) || memory_card::isMemoryCardImage(e.file))) {
|
||||
// Show dialog and decide what to do
|
||||
gui->droppedItem = e.file;
|
||||
return;
|
||||
}
|
||||
|
||||
if (disc::isDiscImage(e.file)) {
|
||||
std::unique_ptr<disc::Disc> disc = disc::load(e.file);
|
||||
if (!disc) {
|
||||
toast(fmt::format("Cannot load {}", getFilenameExt(e.file)));
|
||||
|
@ -257,6 +258,18 @@ int main(int argc, char** argv) {
|
|||
bus.listen<Event::System::SaveState>(busToken, [&](auto e) { state::quickSave(sys.get(), e.slot); });
|
||||
bus.listen<Event::System::LoadState>(busToken, [&](auto e) { state::quickLoad(sys.get(), e.slot); });
|
||||
|
||||
bus.listen<Event::Controller::MemoryCardContentsChanged>(busToken, [&](auto e) {
|
||||
// TODO: throttleLast save event
|
||||
system_tools::saveMemoryCard(sys, e.slot);
|
||||
});
|
||||
|
||||
bus.listen<Event::Controller::MemoryCardSwapped>(busToken, [&](auto e) {
|
||||
sys->controller->card[e.slot]->inserted = false;
|
||||
for (int i = 0; i < 60; i++) sys->emulateFrame();
|
||||
|
||||
system_tools::loadMemoryCard(sys, e.slot);
|
||||
});
|
||||
|
||||
bus.listen<Event::Gui::ToggleFullscreen>(busToken, [&](auto) {
|
||||
if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) {
|
||||
SDL_SetWindowFullscreen(window, 0);
|
||||
|
@ -433,7 +446,8 @@ int main(int argc, char** argv) {
|
|||
if (config.options.emulator.preserveState && sys->state != System::State::halted) {
|
||||
state::saveLastState(sys.get());
|
||||
}
|
||||
system_tools::saveMemoryCards(sys, true);
|
||||
system_tools::saveMemoryCard(sys, 0, true);
|
||||
system_tools::saveMemoryCard(sys, 1, true);
|
||||
saveConfigFile();
|
||||
|
||||
bus.unlistenAll(busToken);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "sound/sound.h"
|
||||
#include "state/state.h"
|
||||
#include "system.h"
|
||||
#include "memory_card/card_formats.h"
|
||||
#include "utils/file.h"
|
||||
#include "utils/gpu_draw_list.h"
|
||||
#include "utils/psf.h"
|
||||
|
@ -83,32 +84,50 @@ void loadFile(std::unique_ptr<System>& sys, const std::string& path) {
|
|||
}
|
||||
}
|
||||
|
||||
void saveMemoryCards(std::unique_ptr<System>& sys, bool force) {
|
||||
auto saveMemoryCard = [&](int slot) {
|
||||
if (!force && !sys->controller->card[slot]->dirty) return;
|
||||
bool saveMemoryCard(std::unique_ptr<System>& sys, int slot, bool force) {
|
||||
if (!force && !sys->controller->card[slot]->dirty) return true;
|
||||
|
||||
std::string pathCard = config.memoryCard[slot].path;
|
||||
|
||||
if (pathCard.empty()) {
|
||||
fmt::print("[INFO] No memory card {} path in config, skipping save\n", slot + 1);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& data = sys->controller->card[slot]->data;
|
||||
auto output = std::vector<uint8_t>(data.begin(), data.end());
|
||||
|
||||
if (!putFileContents(pathCard, output)) {
|
||||
fmt::print("[INFO] Unable to save memory card {} to {}\n", slot + 1, getFilenameExt(pathCard));
|
||||
return;
|
||||
if (!memory_card::save(sys->controller->card[slot]->data, pathCard)) {
|
||||
fmt::print("[ERROR] Unable to save memory card {} to {}\n", slot + 1, getFilenameExt(pathCard));
|
||||
return false;
|
||||
}
|
||||
sys->controller->card[slot]->dirty = false;
|
||||
|
||||
fmt::print("[INFO] Saved memory card {} to {}\n", slot + 1, getFilenameExt(pathCard));
|
||||
return true;
|
||||
};
|
||||
|
||||
saveMemoryCard(0);
|
||||
saveMemoryCard(1);
|
||||
bool loadMemoryCard(std::unique_ptr<System>& sys, int slot) {
|
||||
assert(slot == 0 || slot == 1);
|
||||
auto card = sys->controller->card[slot].get();
|
||||
card->inserted = false;
|
||||
|
||||
auto path = config.memoryCard[slot].path;
|
||||
if (path.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto cardData = memory_card::load(path);
|
||||
if (!cardData) {
|
||||
fmt::print("[ERROR] Cannot load memory card {} ({})\n", slot, getFilenameExt(path));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::copy(cardData->begin(), cardData->end(), sys->controller->card[slot]->data.begin());
|
||||
card->inserted = true;
|
||||
card->setFresh();
|
||||
|
||||
fmt::print("[INFO] Loaded memory card {} from {}\n", slot + 1, getFilenameExt(path));
|
||||
return true;
|
||||
};
|
||||
|
||||
std::unique_ptr<System> hardReset() {
|
||||
auto sys = std::make_unique<System>();
|
||||
|
||||
|
@ -128,26 +147,8 @@ std::unique_ptr<System> hardReset() {
|
|||
fmt::print("[INFO] Using iso {}\n", iso);
|
||||
}
|
||||
|
||||
auto loadMemoryCard = [&](int slot) {
|
||||
assert(slot == 0 || slot == 1);
|
||||
auto card = sys->controller->card[slot].get();
|
||||
|
||||
std::string pathCard = config.memoryCard[slot].path;
|
||||
|
||||
card->inserted = false;
|
||||
|
||||
if (!pathCard.empty()) {
|
||||
auto cardData = getFileContents(pathCard);
|
||||
if (!cardData.empty()) {
|
||||
std::copy_n(std::make_move_iterator(cardData.begin()), cardData.size(), sys->controller->card[slot]->data.begin());
|
||||
card->inserted = true;
|
||||
fmt::print("[INFO] Loaded memory card {} from {}\n", slot + 1, getFilenameExt(pathCard));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadMemoryCard(0);
|
||||
loadMemoryCard(1);
|
||||
loadMemoryCard(sys, 0);
|
||||
loadMemoryCard(sys, 1);
|
||||
|
||||
return sys;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,8 @@ namespace system_tools {
|
|||
|
||||
void bootstrap(std::unique_ptr<System>& sys);
|
||||
void loadFile(std::unique_ptr<System>& sys, const std::string& path);
|
||||
void saveMemoryCards(std::unique_ptr<System>& sys, bool force = false);
|
||||
bool loadMemoryCard(std::unique_ptr<System>& sys, int slot);
|
||||
bool saveMemoryCard(std::unique_ptr<System>& sys, int slot, bool force = false);
|
||||
std::unique_ptr<System> hardReset();
|
||||
|
||||
}; // namespace system_tools
|
|
@ -53,6 +53,13 @@ struct Vibration {
|
|||
bool small;
|
||||
uint8_t big;
|
||||
};
|
||||
|
||||
struct MemoryCardContentsChanged {
|
||||
int slot = 0;
|
||||
};
|
||||
struct MemoryCardSwapped {
|
||||
int slot = 0;
|
||||
};
|
||||
} // namespace Controller
|
||||
}; // namespace Event
|
||||
|
||||
|
|
Loading…
Reference in a new issue