mirror of
https://github.com/libretro/parallel-gx1.git
synced 2026-11-07 04:26:01 -10:00
GBA: ROM, BIOS and saves through the frontend's VFS
The GBA core's files open as an mGBA VFile over a libretro-common filestream (GBAVFile.c). A read-only map of a mapped file lends the mapping itself, so the ROM and the BIOS are read where they lie; a save is mapped into a buffer of its own and written back when mGBA syncs it. A ROM in an archive or a zip is read or inflated straight into the memory file mGBA maps, with no buffer in between. The state and save import/export, e-Reader card queueing, ROM info and forced disconnect entry points nothing calls are gone.
This commit is contained in:
1 parent
4d9402dd6e
commit
734ceaa90c
5 files changed
+254
-139
No files matched your search
+2
-1
@@ -1025,7 +1025,8 @@ DOLPHIN_SOURCES_C += \
|
||||
$(CORE_DIR)/Source/Core/Common/CallGate.c \
|
||||
$(CORE_DIR)/Source/Core/Common/FrameClock.c \
|
||||
$(CORE_DIR)/Source/Core/Common/ParallelFor.c \
|
||||
$(CORE_DIR)/Source/Core/Common/Quiesce.c
|
||||
$(CORE_DIR)/Source/Core/Common/Quiesce.c \
|
||||
$(CORE_DIR)/Source/Core/Core/HW/GBAVFile.c
|
||||
DOLPHIN_SOURCES_CXX += \
|
||||
$(CORE_DIR)/Source/Core/Common/Assembler/AssemblerShared.cpp \
|
||||
$(CORE_DIR)/Source/Core/Common/Assembler/AssemblerTables.cpp \
|
||||
|
||||
+23
-130
@@ -18,12 +18,12 @@
|
||||
|
||||
|
||||
#include "AudioCommon/FieldAudio.h"
|
||||
#include "Core/HW/GBAVFile.h"
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/Crypto/SHA1.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IOFile.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MinizipUtil.h"
|
||||
#include "Common/ScopeGuard.h"
|
||||
@@ -35,10 +35,6 @@
|
||||
#include "Core/Host.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
#ifdef ANDROID
|
||||
#include "jni/AndroidCommon/AndroidCommon.h"
|
||||
#endif
|
||||
|
||||
namespace HW::GBA
|
||||
{
|
||||
namespace
|
||||
@@ -65,14 +61,20 @@ static VFile* OpenROM_Archive(const char* path)
|
||||
VDirFindFirst(archive, [](VFile* vf_) { return mCoreIsCompatible(vf_) != mPLATFORM_NONE; });
|
||||
if (vf_archive)
|
||||
{
|
||||
size_t size = static_cast<size_t>(vf_archive->size(vf_archive));
|
||||
|
||||
std::vector<u8> buffer(size);
|
||||
vf_archive->seek(vf_archive, 0, SEEK_SET);
|
||||
vf_archive->read(vf_archive, buffer.data(), size);
|
||||
// read straight into the memory file the core will map
|
||||
const size_t size = static_cast<size_t>(vf_archive->size(vf_archive));
|
||||
vf = VFileMemChunk(nullptr, size);
|
||||
if (vf)
|
||||
{
|
||||
void* dst = vf->map(vf, size, MAP_WRITE);
|
||||
vf_archive->seek(vf_archive, 0, SEEK_SET);
|
||||
if (!dst || vf_archive->read(vf_archive, dst, size) != static_cast<ssize_t>(size))
|
||||
{
|
||||
vf->close(vf);
|
||||
vf = nullptr;
|
||||
}
|
||||
}
|
||||
vf_archive->close(vf_archive);
|
||||
|
||||
vf = VFileMemChunk(buffer.data(), size);
|
||||
}
|
||||
archive->close(archive);
|
||||
return vf;
|
||||
@@ -96,12 +98,13 @@ static VFile* OpenROM_Zip(const char* path)
|
||||
if (mz_zip_reader_entry_get_info(zip_reader, &info) != MZ_OK || !info->uncompressed_size)
|
||||
continue;
|
||||
|
||||
std::vector<u8> buffer(info->uncompressed_size);
|
||||
if (!Common::ReadFileFromZip(zip_reader, &buffer))
|
||||
// inflated straight into the memory file the core will map
|
||||
vf = VFileMemChunk(nullptr, static_cast<size_t>(info->uncompressed_size));
|
||||
if (!vf)
|
||||
continue;
|
||||
|
||||
vf = VFileMemChunk(buffer.data(), info->uncompressed_size);
|
||||
if (mCoreIsCompatible(vf) != mPLATFORM_NONE)
|
||||
void* dst = vf->map(vf, static_cast<size_t>(info->uncompressed_size), MAP_WRITE);
|
||||
if (dst && Common::ReadFileFromZip(zip_reader, static_cast<u8*>(dst), info->uncompressed_size) &&
|
||||
mCoreIsCompatible(vf) != mPLATFORM_NONE)
|
||||
{
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
break;
|
||||
@@ -115,11 +118,7 @@ static VFile* OpenROM_Zip(const char* path)
|
||||
|
||||
static VFile* OpenReadOnlyFile(const char* path)
|
||||
{
|
||||
#ifdef ANDROID
|
||||
if (IsPathAndroidContent(path))
|
||||
return VFileFromFD(OpenAndroidContent(path, "r"));
|
||||
#endif
|
||||
return VFileOpen(path, O_RDONLY);
|
||||
return gba_vfile_open(path, 0);
|
||||
}
|
||||
|
||||
static VFile* OpenROM(const char* rom_path)
|
||||
@@ -337,23 +336,6 @@ void Core::SetHost(std::weak_ptr<GBAHostInterface> host)
|
||||
m_host = std::move(host);
|
||||
}
|
||||
|
||||
void Core::SetForceDisconnect(bool force_disconnect)
|
||||
{
|
||||
m_force_disconnect = force_disconnect;
|
||||
}
|
||||
|
||||
void Core::EReaderQueueCard(std::string_view card_path)
|
||||
{
|
||||
Flush();
|
||||
if (!GetCoreInfo().has_ereader)
|
||||
return;
|
||||
|
||||
File::IOFile file(std::string(card_path), "rb");
|
||||
std::vector<u8> core_state(file.GetSize());
|
||||
file.ReadBytes(core_state.data(), core_state.size());
|
||||
GBACartEReaderQueueCard(static_cast<::GBA*>(m_core->board), core_state.data(), core_state.size());
|
||||
}
|
||||
|
||||
bool Core::LoadBIOS(const char* bios_path)
|
||||
{
|
||||
VFile* vf = OpenReadOnlyFile(bios_path);
|
||||
@@ -375,7 +357,7 @@ bool Core::LoadBIOS(const char* bios_path)
|
||||
|
||||
bool Core::LoadSave(const char* save_path)
|
||||
{
|
||||
VFile* vf = VFileOpen(save_path, O_CREAT | O_RDWR);
|
||||
VFile* vf = gba_vfile_open(save_path, 1);
|
||||
if (!vf)
|
||||
{
|
||||
PanicAlertFmtT("Error: GBA{0} failed to open the save in {1}", m_device_number + 1, save_path);
|
||||
@@ -592,7 +574,7 @@ void Core::HandleEvent(SyncEvent event)
|
||||
if (event.event_type != SyncEventType::RunCommand)
|
||||
return;
|
||||
|
||||
if (m_link_enabled && !m_force_disconnect)
|
||||
if (m_link_enabled)
|
||||
{
|
||||
m_response_size =
|
||||
u8(GBASIOJOYSendCommand(&m_sio_driver, m_joybus_command, m_joybus_buffer.data()));
|
||||
@@ -635,66 +617,6 @@ void Core::RunFor(u64 gc_ticks)
|
||||
RunUntil(m_last_gc_ticks + gc_ticks);
|
||||
}
|
||||
|
||||
void Core::ImportState(std::string_view state_path)
|
||||
{
|
||||
Flush();
|
||||
if (!IsStarted())
|
||||
return;
|
||||
|
||||
std::vector<u8> core_state(m_core->stateSize(m_core));
|
||||
File::IOFile file(std::string(state_path), "rb");
|
||||
if (core_state.size() != file.GetSize())
|
||||
return;
|
||||
|
||||
file.ReadBytes(core_state.data(), core_state.size());
|
||||
m_core->loadState(m_core, core_state.data());
|
||||
}
|
||||
|
||||
void Core::ExportState(std::string_view state_path)
|
||||
{
|
||||
Flush();
|
||||
if (!IsStarted())
|
||||
return;
|
||||
|
||||
std::vector<u8> core_state(m_core->stateSize(m_core));
|
||||
m_core->saveState(m_core, core_state.data());
|
||||
|
||||
File::IOFile file(std::string(state_path), "wb");
|
||||
file.WriteBytes(core_state.data(), core_state.size());
|
||||
}
|
||||
|
||||
void Core::ImportSave(std::string_view save_path)
|
||||
{
|
||||
Flush();
|
||||
if (!IsStarted())
|
||||
return;
|
||||
|
||||
File::IOFile file(std::string(save_path), "rb");
|
||||
std::vector<u8> save_file(file.GetSize());
|
||||
if (!file.ReadBytes(save_file.data(), save_file.size()))
|
||||
return;
|
||||
|
||||
m_core->savedataRestore(m_core, save_file.data(), save_file.size(), true);
|
||||
m_core->reset(m_core);
|
||||
}
|
||||
|
||||
void Core::ExportSave(std::string_view save_path)
|
||||
{
|
||||
Flush();
|
||||
if (!IsStarted())
|
||||
return;
|
||||
|
||||
File::IOFile file(std::string(save_path), "wb");
|
||||
|
||||
void* sram = nullptr;
|
||||
size_t size = m_core->savedataClone(m_core, &sram);
|
||||
if (!sram)
|
||||
return;
|
||||
|
||||
file.WriteBytes(sram, size);
|
||||
free(sram);
|
||||
}
|
||||
|
||||
void Core::DoState(PointerWrap& p)
|
||||
{
|
||||
Flush();
|
||||
@@ -753,35 +675,6 @@ void Core::DoState(PointerWrap& p)
|
||||
}
|
||||
}
|
||||
|
||||
bool Core::GetRomInfo(const char* rom_path, std::array<u8, 20>& hash, std::string& title)
|
||||
{
|
||||
VFile* rom = OpenROM(rom_path);
|
||||
if (!rom)
|
||||
return false;
|
||||
|
||||
hash = GetROMHash(rom);
|
||||
|
||||
mCore* core = mCoreFindVF(rom);
|
||||
if (!core)
|
||||
{
|
||||
rom->close(rom);
|
||||
return false;
|
||||
}
|
||||
core->init(core);
|
||||
if (!core->loadROM(core, rom))
|
||||
{
|
||||
rom->close(rom);
|
||||
return false;
|
||||
}
|
||||
|
||||
mGameInfo info;
|
||||
core->getGameInfo(core, &info);
|
||||
title = info.title;
|
||||
|
||||
core->deinit(core);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Core::GetSavePath(std::string_view rom_path, int device_number)
|
||||
{
|
||||
// This is done to make the save file compatible with the save from the mGBA core in RA as it
|
||||
|
||||
@@ -78,8 +78,6 @@ public:
|
||||
CoreInfo GetCoreInfo() const;
|
||||
|
||||
void SetHost(std::weak_ptr<GBAHostInterface> host);
|
||||
void SetForceDisconnect(bool force_disconnect);
|
||||
void EReaderQueueCard(std::string_view card_path);
|
||||
|
||||
void SyncJoybus(u64 gc_ticks, u16 keys);
|
||||
void SendJoybusCommand(u64 gc_ticks, int transfer_time, u8* buffer, u16 keys);
|
||||
@@ -97,13 +95,8 @@ public:
|
||||
u32 GetAudioSampleRate() const { return m_core->audioSampleRate(m_core); }
|
||||
int GetDeviceNumber() const { return m_device_number; }
|
||||
|
||||
void ImportState(std::string_view state_path);
|
||||
void ExportState(std::string_view state_path);
|
||||
void ImportSave(std::string_view save_path);
|
||||
void ExportSave(std::string_view save_path);
|
||||
void DoState(PointerWrap& p);
|
||||
|
||||
static bool GetRomInfo(const char* rom_path, std::array<u8, 20>& hash, std::string& title);
|
||||
static std::string GetSavePath(std::string_view rom_path, int device_number);
|
||||
|
||||
private:
|
||||
@@ -154,7 +147,6 @@ private:
|
||||
u64 m_gc_ticks_remainder = 0;
|
||||
u16 m_keys = 0;
|
||||
bool m_link_enabled = false;
|
||||
bool m_force_disconnect = false;
|
||||
|
||||
std::weak_ptr<GBAHostInterface> m_host;
|
||||
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
/* Copyright 2026 Dolphin Emulator Project
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* See GBAVFile.h. */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <mgba-util/vfs.h>
|
||||
#include <streams/file_stream.h>
|
||||
|
||||
#include "Core/HW/GBAVFile.h"
|
||||
|
||||
/* A writable map: its own buffer, until it is unmapped. */
|
||||
struct gba_vfile_map
|
||||
{
|
||||
struct gba_vfile_map *next;
|
||||
size_t size;
|
||||
/* the octets follow */
|
||||
};
|
||||
|
||||
struct gba_vfile
|
||||
{
|
||||
struct VFile d;
|
||||
RFILE *file;
|
||||
const unsigned char *map; /* the whole file, lent, or NULL */
|
||||
int64_t map_size;
|
||||
struct gba_vfile_map *maps; /* writable maps outstanding */
|
||||
};
|
||||
|
||||
static bool gba_vfile_close(struct VFile *vf)
|
||||
{
|
||||
struct gba_vfile *f = (struct gba_vfile*)vf;
|
||||
int ok = filestream_close(f->file) == 0;
|
||||
|
||||
while (f->maps)
|
||||
{
|
||||
struct gba_vfile_map *next = f->maps->next;
|
||||
free(f->maps);
|
||||
f->maps = next;
|
||||
}
|
||||
free(f);
|
||||
return ok != 0;
|
||||
}
|
||||
|
||||
static off_t gba_vfile_seek(struct VFile *vf, off_t offset, int whence)
|
||||
{
|
||||
struct gba_vfile *f = (struct gba_vfile*)vf;
|
||||
int mode;
|
||||
|
||||
switch (whence)
|
||||
{
|
||||
case SEEK_CUR:
|
||||
mode = RETRO_VFS_SEEK_POSITION_CURRENT;
|
||||
break;
|
||||
case SEEK_END:
|
||||
mode = RETRO_VFS_SEEK_POSITION_END;
|
||||
break;
|
||||
default:
|
||||
mode = RETRO_VFS_SEEK_POSITION_START;
|
||||
break;
|
||||
}
|
||||
if (filestream_seek(f->file, (int64_t)offset, mode) < 0)
|
||||
return -1;
|
||||
return (off_t)filestream_tell(f->file);
|
||||
}
|
||||
|
||||
static ssize_t gba_vfile_read(struct VFile *vf, void *buffer, size_t size)
|
||||
{
|
||||
struct gba_vfile *f = (struct gba_vfile*)vf;
|
||||
return (ssize_t)filestream_read(f->file, buffer, (int64_t)size);
|
||||
}
|
||||
|
||||
static ssize_t gba_vfile_write(struct VFile *vf, const void *buffer, size_t size)
|
||||
{
|
||||
struct gba_vfile *f = (struct gba_vfile*)vf;
|
||||
return (ssize_t)filestream_write(f->file, buffer, (int64_t)size);
|
||||
}
|
||||
|
||||
static ssize_t gba_vfile_size(struct VFile *vf)
|
||||
{
|
||||
struct gba_vfile *f = (struct gba_vfile*)vf;
|
||||
return (ssize_t)filestream_get_size(f->file);
|
||||
}
|
||||
|
||||
static void *gba_vfile_map(struct VFile *vf, size_t size, int flags)
|
||||
{
|
||||
struct gba_vfile *f = (struct gba_vfile*)vf;
|
||||
struct gba_vfile_map *m;
|
||||
unsigned char *data;
|
||||
int64_t have;
|
||||
|
||||
if (!size)
|
||||
return NULL;
|
||||
/* Read-only, and all there: the mapping, lent. mGBA writes to a
|
||||
* read-only map nowhere; a ROM it patches is copied first. */
|
||||
if (!(flags & MAP_WRITE) && f->map && (int64_t)size <= f->map_size)
|
||||
return (void*)f->map;
|
||||
|
||||
m = (struct gba_vfile_map*)malloc(sizeof(*m) + size);
|
||||
if (!m)
|
||||
return NULL;
|
||||
data = (unsigned char*)(m + 1);
|
||||
have = filestream_get_size(f->file);
|
||||
if (have < 0)
|
||||
have = 0;
|
||||
if ((uint64_t)have > size)
|
||||
have = (int64_t)size;
|
||||
if (have && (filestream_seek(f->file, 0, RETRO_VFS_SEEK_POSITION_START) < 0
|
||||
|| filestream_read(f->file, data, have) != have))
|
||||
{
|
||||
free(m);
|
||||
return NULL;
|
||||
}
|
||||
/* what lies past the end of the file reads as zeroes, as a mapping
|
||||
* of a file grown to the size would */
|
||||
memset(data + have, 0, size - (size_t)have);
|
||||
m->size = size;
|
||||
m->next = f->maps;
|
||||
f->maps = m;
|
||||
return data;
|
||||
}
|
||||
|
||||
static void gba_vfile_unmap(struct VFile *vf, void *memory, size_t size)
|
||||
{
|
||||
struct gba_vfile *f = (struct gba_vfile*)vf;
|
||||
struct gba_vfile_map **link = &f->maps;
|
||||
|
||||
(void)size;
|
||||
while (*link)
|
||||
{
|
||||
if ((void*)(*link + 1) == memory)
|
||||
{
|
||||
struct gba_vfile_map *m = *link;
|
||||
*link = m->next;
|
||||
free(m);
|
||||
return;
|
||||
}
|
||||
link = &(*link)->next;
|
||||
}
|
||||
/* the lent mapping: nothing to give back */
|
||||
}
|
||||
|
||||
static void gba_vfile_truncate(struct VFile *vf, size_t size)
|
||||
{
|
||||
struct gba_vfile *f = (struct gba_vfile*)vf;
|
||||
filestream_truncate(f->file, (int64_t)size);
|
||||
}
|
||||
|
||||
/* What was written to a map, written to the file. */
|
||||
static bool gba_vfile_sync(struct VFile *vf, void *buffer, size_t size)
|
||||
{
|
||||
struct gba_vfile *f = (struct gba_vfile*)vf;
|
||||
|
||||
if (buffer && size)
|
||||
{
|
||||
if (filestream_seek(f->file, 0, RETRO_VFS_SEEK_POSITION_START) < 0
|
||||
|| filestream_write(f->file, buffer, (int64_t)size) != (int64_t)size)
|
||||
return false;
|
||||
}
|
||||
return filestream_flush(f->file) == 0;
|
||||
}
|
||||
|
||||
struct VFile *gba_vfile_open(const char *path, int writable)
|
||||
{
|
||||
struct gba_vfile *f;
|
||||
RFILE *file;
|
||||
unsigned mode;
|
||||
|
||||
if (!path || !*path)
|
||||
return NULL;
|
||||
if (!writable)
|
||||
mode = RETRO_VFS_FILE_ACCESS_READ;
|
||||
else if (filestream_exists(path))
|
||||
mode = RETRO_VFS_FILE_ACCESS_READ_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING;
|
||||
else
|
||||
mode = RETRO_VFS_FILE_ACCESS_READ_WRITE;
|
||||
file = filestream_open(path, mode, writable ? RETRO_VFS_FILE_ACCESS_HINT_NONE
|
||||
: RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS);
|
||||
if (!file)
|
||||
return NULL;
|
||||
|
||||
f = (struct gba_vfile*)calloc(1, sizeof(*f));
|
||||
if (!f)
|
||||
{
|
||||
filestream_close(file);
|
||||
return NULL;
|
||||
}
|
||||
f->file = file;
|
||||
f->map = writable ? NULL : filestream_get_mapped_ptr(file, &f->map_size);
|
||||
f->d.close = gba_vfile_close;
|
||||
f->d.seek = gba_vfile_seek;
|
||||
f->d.read = gba_vfile_read;
|
||||
f->d.readline = VFileReadline;
|
||||
f->d.write = gba_vfile_write;
|
||||
f->d.map = gba_vfile_map;
|
||||
f->d.unmap = gba_vfile_unmap;
|
||||
f->d.truncate = gba_vfile_truncate;
|
||||
f->d.size = gba_vfile_size;
|
||||
f->d.sync = gba_vfile_sync;
|
||||
return &f->d;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/* Copyright 2026 Dolphin Emulator Project
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/* An mGBA VFile over a libretro-common filestream, so the GBA core opens
|
||||
* its ROM, BIOS and saves through the frontend's VFS like everything
|
||||
* else. A read-only map of a mapped file lends the mapping itself: the
|
||||
* ROM and the BIOS are read where they lie. A writable map is a buffer
|
||||
* of its own, read in once and written back on sync. */
|
||||
|
||||
#ifndef GBA_VFILE_H
|
||||
#define GBA_VFILE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct VFile;
|
||||
|
||||
/* writable: open for reading and writing, made if it is not there yet.
|
||||
* Returns NULL when the file cannot be opened. */
|
||||
struct VFile* gba_vfile_open(const char* path, int writable);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user