Core: Implement GBA Core using libmgba
This commit is contained in:
parent
2d744da68c
commit
9a22ff653f
|
@ -440,8 +440,8 @@ add_library(core
|
|||
PowerPC/Interpreter/Interpreter_Tables.cpp
|
||||
PowerPC/Interpreter/Interpreter.cpp
|
||||
PowerPC/Interpreter/Interpreter.h
|
||||
PowerPC/JitCommon/DivUtils.cpp
|
||||
PowerPC/JitCommon/DivUtils.h
|
||||
PowerPC/JitCommon/DivUtils.cpp
|
||||
PowerPC/JitCommon/DivUtils.h
|
||||
PowerPC/JitCommon/JitAsmCommon.cpp
|
||||
PowerPC/JitCommon/JitAsmCommon.h
|
||||
PowerPC/JitCommon/JitBase.cpp
|
||||
|
@ -621,6 +621,10 @@ if(ENABLE_VULKAN)
|
|||
endif()
|
||||
|
||||
if(USE_MGBA)
|
||||
target_sources(core PRIVATE
|
||||
HW/GBACore.cpp
|
||||
HW/GBACore.h
|
||||
)
|
||||
target_link_libraries(core PUBLIC mGBA::mgba)
|
||||
target_compile_definitions(core PUBLIC -DHAS_LIBMGBA)
|
||||
endif()
|
||||
|
|
|
@ -0,0 +1,714 @@
|
|||
// Copyright 2021 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/HW/GBACore.h"
|
||||
|
||||
#include <mbedtls/sha1.h>
|
||||
|
||||
#define PYCPARSE // Remove static functions from the header
|
||||
#include <mgba/core/interface.h>
|
||||
#undef PYCPARSE
|
||||
#include <mgba-util/vfs.h>
|
||||
#include <mgba/core/blip_buf.h>
|
||||
#include <mgba/core/log.h>
|
||||
#include <mgba/core/timing.h>
|
||||
#include <mgba/internal/gb/gb.h>
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
|
||||
#include "AudioCommon/AudioCommon.h"
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IOFile.h"
|
||||
#include "Common/MinizipUtil.h"
|
||||
#include "Common/ScopeGuard.h"
|
||||
#include "Common/Thread.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/HW/SystemTimers.h"
|
||||
#include "Core/Host.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
|
||||
namespace HW::GBA
|
||||
{
|
||||
namespace
|
||||
{
|
||||
mLogger s_stub_logger = {
|
||||
[](mLogger*, int category, mLogLevel level, const char* format, va_list args) {}, nullptr};
|
||||
} // namespace
|
||||
|
||||
constexpr auto SAMPLES = 512;
|
||||
constexpr auto SAMPLE_RATE = 48000;
|
||||
|
||||
// libmGBA does not return the correct frequency for some GB models
|
||||
static u32 GetCoreFrequency(mCore* core)
|
||||
{
|
||||
if (core->platform(core) != mPLATFORM_GB)
|
||||
return static_cast<u32>(core->frequency(core));
|
||||
|
||||
switch (static_cast<::GB*>(core->board)->model)
|
||||
{
|
||||
case GB_MODEL_CGB:
|
||||
case GB_MODEL_SCGB:
|
||||
case GB_MODEL_AGB:
|
||||
return CGB_SM83_FREQUENCY;
|
||||
case GB_MODEL_SGB:
|
||||
return SGB_SM83_FREQUENCY;
|
||||
default:
|
||||
return DMG_SM83_FREQUENCY;
|
||||
}
|
||||
}
|
||||
|
||||
static VFile* OpenROM_Archive(const char* path)
|
||||
{
|
||||
VFile* vf{};
|
||||
VDir* archive = VDirOpenArchive(path);
|
||||
if (!archive)
|
||||
return nullptr;
|
||||
VFile* vf_archive =
|
||||
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);
|
||||
vf_archive->close(vf_archive);
|
||||
|
||||
vf = VFileMemChunk(buffer.data(), size);
|
||||
}
|
||||
archive->close(archive);
|
||||
return vf;
|
||||
}
|
||||
|
||||
static VFile* OpenROM_Zip(const char* path)
|
||||
{
|
||||
VFile* vf{};
|
||||
unzFile zip = unzOpen(path);
|
||||
if (!zip)
|
||||
return nullptr;
|
||||
do
|
||||
{
|
||||
unz_file_info info{};
|
||||
if (unzGetCurrentFileInfo(zip, &info, nullptr, 0, nullptr, 0, nullptr, 0) != UNZ_OK ||
|
||||
!info.uncompressed_size)
|
||||
continue;
|
||||
|
||||
std::vector<u8> buffer(info.uncompressed_size);
|
||||
if (!Common::ReadFileFromZip(zip, &buffer))
|
||||
continue;
|
||||
|
||||
vf = VFileMemChunk(buffer.data(), info.uncompressed_size);
|
||||
if (mCoreIsCompatible(vf) == mPLATFORM_GBA)
|
||||
{
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
break;
|
||||
}
|
||||
|
||||
vf->close(vf);
|
||||
vf = nullptr;
|
||||
} while (unzGoToNextFile(zip) == UNZ_OK);
|
||||
unzClose(zip);
|
||||
return vf;
|
||||
}
|
||||
|
||||
static VFile* OpenROM(const char* rom_path)
|
||||
{
|
||||
VFile* vf{};
|
||||
|
||||
vf = OpenROM_Archive(rom_path);
|
||||
if (!vf)
|
||||
vf = OpenROM_Zip(rom_path);
|
||||
if (!vf)
|
||||
vf = VFileOpen(rom_path, O_RDONLY);
|
||||
if (!vf)
|
||||
return nullptr;
|
||||
|
||||
if (mCoreIsCompatible(vf) == mPLATFORM_NONE)
|
||||
{
|
||||
vf->close(vf);
|
||||
return nullptr;
|
||||
}
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
|
||||
return vf;
|
||||
}
|
||||
|
||||
static std::array<u8, 20> GetROMHash(VFile* rom)
|
||||
{
|
||||
size_t size = rom->size(rom);
|
||||
u8* buffer = static_cast<u8*>(rom->map(rom, size, MAP_READ));
|
||||
|
||||
std::array<u8, 20> hash;
|
||||
mbedtls_sha1_ret(buffer, size, hash.data());
|
||||
rom->unmap(rom, buffer, size);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
Core::Core(int device_number) : m_device_number(device_number)
|
||||
{
|
||||
mLogSetDefaultLogger(&s_stub_logger);
|
||||
}
|
||||
|
||||
Core::~Core()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool Core::Start(u64 gc_ticks)
|
||||
{
|
||||
if (IsStarted())
|
||||
return false;
|
||||
|
||||
Common::ScopeGuard start_guard{[&] { Stop(); }};
|
||||
|
||||
VFile* rom{};
|
||||
Common::ScopeGuard rom_guard{[&] {
|
||||
if (rom)
|
||||
rom->close(rom);
|
||||
}};
|
||||
|
||||
m_rom_path = Config::Get(Config::MAIN_GBA_ROM_PATHS[m_device_number]);
|
||||
if (!m_rom_path.empty())
|
||||
{
|
||||
rom = OpenROM(m_rom_path.c_str());
|
||||
if (!rom)
|
||||
{
|
||||
PanicAlertFmtT("Error: GBA{0} failed to open the ROM in {1}", m_device_number + 1,
|
||||
m_rom_path);
|
||||
return false;
|
||||
}
|
||||
m_rom_hash = GetROMHash(rom);
|
||||
}
|
||||
|
||||
m_core = rom ? mCoreFindVF(rom) : mCoreCreate(mPLATFORM_GBA);
|
||||
if (!m_core)
|
||||
{
|
||||
PanicAlertFmtT("Error: GBA{0} failed to create core", m_device_number + 1);
|
||||
return false;
|
||||
}
|
||||
m_core->init(m_core);
|
||||
|
||||
mCoreInitConfig(m_core, "dolphin");
|
||||
mCoreConfigSetValue(&m_core->config, "idleOptimization", "detect");
|
||||
mCoreConfigSetIntValue(&m_core->config, "useBios", 0);
|
||||
mCoreConfigSetIntValue(&m_core->config, "skipBios", 0);
|
||||
|
||||
if (m_core->platform(m_core) == mPLATFORM_GBA &&
|
||||
!LoadBIOS(File::GetUserPath(F_GBABIOS_IDX).c_str()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rom)
|
||||
{
|
||||
if (!m_core->loadROM(m_core, rom))
|
||||
{
|
||||
PanicAlertFmtT("Error: GBA{0} failed to load the ROM in {1}", m_device_number + 1,
|
||||
m_rom_path);
|
||||
return false;
|
||||
}
|
||||
rom_guard.Dismiss();
|
||||
|
||||
std::array<char, 17> game_title{};
|
||||
m_core->getGameTitle(m_core, game_title.data());
|
||||
m_game_title = game_title.data();
|
||||
|
||||
m_save_path = GetSavePath(m_rom_path, m_device_number);
|
||||
if (!m_save_path.empty() && !LoadSave(m_save_path.c_str()))
|
||||
return false;
|
||||
}
|
||||
|
||||
m_last_gc_ticks = gc_ticks;
|
||||
m_gc_ticks_remainder = 0;
|
||||
m_keys = 0;
|
||||
|
||||
SetSIODriver();
|
||||
SetVideoBuffer();
|
||||
SetSampleRates();
|
||||
AddCallbacks();
|
||||
SetAVStream();
|
||||
SetupEvent();
|
||||
|
||||
m_core->reset(m_core);
|
||||
m_started = true;
|
||||
start_guard.Dismiss();
|
||||
// Notify the host and handle a dimension change if that happened after reset()
|
||||
SetVideoBuffer();
|
||||
|
||||
if (Config::Get(Config::MAIN_GBA_THREADS))
|
||||
{
|
||||
m_idle = true;
|
||||
m_exit_loop = false;
|
||||
m_thread = std::make_unique<std::thread>([this] { ThreadLoop(); });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Core::Stop()
|
||||
{
|
||||
if (m_thread)
|
||||
{
|
||||
Flush();
|
||||
m_exit_loop = true;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_queue_mutex);
|
||||
m_command_cv.notify_one();
|
||||
}
|
||||
m_thread->join();
|
||||
m_thread.reset();
|
||||
}
|
||||
if (m_core)
|
||||
{
|
||||
mCoreConfigDeinit(&m_core->config);
|
||||
m_core->deinit(m_core);
|
||||
m_core = nullptr;
|
||||
}
|
||||
m_started = false;
|
||||
m_rom_path = {};
|
||||
m_save_path = {};
|
||||
m_rom_hash = {};
|
||||
m_game_title = {};
|
||||
}
|
||||
|
||||
void Core::Reset()
|
||||
{
|
||||
Flush();
|
||||
if (!IsStarted())
|
||||
return;
|
||||
|
||||
m_core->reset(m_core);
|
||||
}
|
||||
|
||||
bool Core::IsStarted() const
|
||||
{
|
||||
return m_started;
|
||||
}
|
||||
|
||||
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 (!IsStarted() || m_core->platform(m_core) != mPlatform::mPLATFORM_GBA)
|
||||
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 = VFileOpen(bios_path, O_RDONLY);
|
||||
if (!vf)
|
||||
{
|
||||
PanicAlertFmtT("Error: GBA{0} failed to open the BIOS in {1}", m_device_number + 1, bios_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_core->loadBIOS(m_core, vf, 0))
|
||||
{
|
||||
PanicAlertFmtT("Error: GBA{0} failed to load the BIOS in {1}", m_device_number + 1, bios_path);
|
||||
vf->close(vf);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Core::LoadSave(const char* save_path)
|
||||
{
|
||||
VFile* vf = VFileOpen(save_path, O_CREAT | O_RDWR);
|
||||
if (!vf)
|
||||
{
|
||||
PanicAlertFmtT("Error: GBA{0} failed to open the save in {1}", m_device_number + 1, save_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_core->loadSave(m_core, vf))
|
||||
{
|
||||
PanicAlertFmtT("Error: GBA{0} failed to load the save in {1}", m_device_number + 1, save_path);
|
||||
vf->close(vf);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Core::SetSIODriver()
|
||||
{
|
||||
if (m_core->platform(m_core) != mPLATFORM_GBA)
|
||||
return;
|
||||
|
||||
GBASIOJOYCreate(&m_sio_driver);
|
||||
GBASIOSetDriver(&static_cast<::GBA*>(m_core->board)->sio, &m_sio_driver, SIO_JOYBUS);
|
||||
|
||||
m_sio_driver.core = this;
|
||||
m_sio_driver.load = [](GBASIODriver* driver) {
|
||||
static_cast<SIODriver*>(driver)->core->m_link_enabled = true;
|
||||
return true;
|
||||
};
|
||||
m_sio_driver.unload = [](GBASIODriver* driver) {
|
||||
static_cast<SIODriver*>(driver)->core->m_link_enabled = false;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
void Core::SetVideoBuffer()
|
||||
{
|
||||
u32 width, height;
|
||||
m_core->desiredVideoDimensions(m_core, &width, &height);
|
||||
m_video_buffer.resize(width * height);
|
||||
m_core->setVideoBuffer(m_core, m_video_buffer.data(), width);
|
||||
if (auto host = m_host.lock())
|
||||
host->GameChanged();
|
||||
}
|
||||
|
||||
void Core::SetSampleRates()
|
||||
{
|
||||
m_core->setAudioBufferSize(m_core, SAMPLES);
|
||||
blip_set_rates(m_core->getAudioChannel(m_core, 0), m_core->frequency(m_core), SAMPLE_RATE);
|
||||
blip_set_rates(m_core->getAudioChannel(m_core, 1), m_core->frequency(m_core), SAMPLE_RATE);
|
||||
g_sound_stream->GetMixer()->SetGBAInputSampleRates(m_device_number, SAMPLE_RATE);
|
||||
}
|
||||
|
||||
void Core::AddCallbacks()
|
||||
{
|
||||
mCoreCallbacks callbacks{};
|
||||
callbacks.context = this;
|
||||
callbacks.keysRead = [](void* context) {
|
||||
auto core = static_cast<Core*>(context);
|
||||
core->m_core->setKeys(core->m_core, core->m_keys);
|
||||
};
|
||||
callbacks.videoFrameEnded = [](void* context) {
|
||||
auto core = static_cast<Core*>(context);
|
||||
if (auto host = core->m_host.lock())
|
||||
host->FrameEnded(core->m_video_buffer);
|
||||
};
|
||||
m_core->addCoreCallbacks(m_core, &callbacks);
|
||||
}
|
||||
|
||||
void Core::SetAVStream()
|
||||
{
|
||||
m_stream = {};
|
||||
m_stream.core = this;
|
||||
m_stream.videoDimensionsChanged = [](mAVStream* stream, unsigned width, unsigned height) {
|
||||
auto core = static_cast<AVStream*>(stream)->core;
|
||||
core->SetVideoBuffer();
|
||||
};
|
||||
m_stream.postAudioBuffer = [](mAVStream* stream, blip_t* left, blip_t* right) {
|
||||
auto core = static_cast<AVStream*>(stream)->core;
|
||||
std::vector<s16> buffer(SAMPLES * 2);
|
||||
blip_read_samples(left, &buffer[0], SAMPLES, 1);
|
||||
blip_read_samples(right, &buffer[1], SAMPLES, 1);
|
||||
g_sound_stream->GetMixer()->PushGBASamples(core->m_device_number, &buffer[0], SAMPLES);
|
||||
};
|
||||
m_core->setAVStream(m_core, &m_stream);
|
||||
}
|
||||
|
||||
void Core::SetupEvent()
|
||||
{
|
||||
m_event.context = this;
|
||||
m_event.name = "Dolphin Sync";
|
||||
m_event.callback = [](mTiming* timing, void* context, u32 cycles_late) {
|
||||
Core* core = static_cast<Core*>(context);
|
||||
if (core->m_core->platform(core->m_core) == mPLATFORM_GBA)
|
||||
static_cast<::GBA*>(core->m_core->board)->earlyExit = true;
|
||||
else if (core->m_core->platform(core->m_core) == mPLATFORM_GB)
|
||||
static_cast<::GB*>(core->m_core->board)->earlyExit = true;
|
||||
core->m_waiting_for_event = false;
|
||||
};
|
||||
m_event.priority = 0x80;
|
||||
}
|
||||
|
||||
int Core::GetDeviceNumber() const
|
||||
{
|
||||
return m_device_number;
|
||||
}
|
||||
|
||||
void Core::GetVideoDimensions(u32* width, u32* height) const
|
||||
{
|
||||
if (!IsStarted())
|
||||
{
|
||||
*width = GBA_VIDEO_HORIZONTAL_PIXELS;
|
||||
*height = GBA_VIDEO_VERTICAL_PIXELS;
|
||||
return;
|
||||
}
|
||||
|
||||
m_core->desiredVideoDimensions(m_core, width, height);
|
||||
}
|
||||
|
||||
std::string Core::GetGameTitle() const
|
||||
{
|
||||
return m_game_title;
|
||||
}
|
||||
|
||||
void Core::SendJoybusCommand(u64 gc_ticks, int transfer_time, u8* buffer, u16 keys)
|
||||
{
|
||||
if (!IsStarted())
|
||||
return;
|
||||
|
||||
Command command{};
|
||||
command.ticks = gc_ticks;
|
||||
command.transfer_time = transfer_time;
|
||||
command.sync_only = buffer == nullptr;
|
||||
if (buffer)
|
||||
std::copy_n(buffer, command.buffer.size(), command.buffer.begin());
|
||||
command.keys = keys;
|
||||
|
||||
if (m_thread)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_queue_mutex);
|
||||
m_command_queue.push(command);
|
||||
m_idle = false;
|
||||
m_command_cv.notify_one();
|
||||
}
|
||||
else
|
||||
{
|
||||
RunCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u8> Core::GetJoybusResponse()
|
||||
{
|
||||
if (!IsStarted())
|
||||
return {};
|
||||
|
||||
if (m_thread)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_response_mutex);
|
||||
m_response_cv.wait(lock, [&] { return m_response_ready; });
|
||||
}
|
||||
m_response_ready = false;
|
||||
return m_response;
|
||||
}
|
||||
|
||||
void Core::Flush()
|
||||
{
|
||||
if (!IsStarted() || !m_thread)
|
||||
return;
|
||||
std::unique_lock<std::mutex> lock(m_queue_mutex);
|
||||
m_response_cv.wait(lock, [&] { return m_idle; });
|
||||
}
|
||||
|
||||
void Core::ThreadLoop()
|
||||
{
|
||||
Common::SetCurrentThreadName(fmt::format("GBA{}", m_device_number + 1).c_str());
|
||||
std::unique_lock<std::mutex> queue_lock(m_queue_mutex);
|
||||
while (true)
|
||||
{
|
||||
m_command_cv.wait(queue_lock, [&] { return !m_command_queue.empty() || m_exit_loop; });
|
||||
if (m_exit_loop)
|
||||
break;
|
||||
Command command{m_command_queue.front()};
|
||||
m_command_queue.pop();
|
||||
queue_lock.unlock();
|
||||
|
||||
RunCommand(command);
|
||||
|
||||
queue_lock.lock();
|
||||
if (m_command_queue.empty())
|
||||
m_idle = true;
|
||||
m_response_cv.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void Core::RunCommand(Command& command)
|
||||
{
|
||||
m_keys = command.keys;
|
||||
RunUntil(command.ticks);
|
||||
if (!command.sync_only)
|
||||
{
|
||||
m_response.clear();
|
||||
if (m_link_enabled && !m_force_disconnect)
|
||||
{
|
||||
int recvd = GBASIOJOYSendCommand(
|
||||
&m_sio_driver, static_cast<GBASIOJOYCommand>(command.buffer[0]), &command.buffer[1]);
|
||||
std::copy(command.buffer.begin() + 1, command.buffer.begin() + 1 + recvd,
|
||||
std::back_inserter(m_response));
|
||||
}
|
||||
|
||||
if (m_thread && !m_response_ready)
|
||||
{
|
||||
std::lock_guard<std::mutex> response_lock(m_response_mutex);
|
||||
m_response_ready = true;
|
||||
m_response_cv.notify_one();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_response_ready = true;
|
||||
}
|
||||
}
|
||||
if (command.transfer_time)
|
||||
RunFor(command.transfer_time);
|
||||
}
|
||||
|
||||
void Core::RunUntil(u64 gc_ticks)
|
||||
{
|
||||
if (static_cast<s64>(gc_ticks - m_last_gc_ticks) <= 0)
|
||||
return;
|
||||
|
||||
const u64 gc_frequency = SystemTimers::GetTicksPerSecond();
|
||||
const u32 core_frequency = GetCoreFrequency(m_core);
|
||||
|
||||
mTimingSchedule(m_core->timing, &m_event,
|
||||
static_cast<s32>((gc_ticks - m_last_gc_ticks) * core_frequency / gc_frequency));
|
||||
m_waiting_for_event = true;
|
||||
|
||||
s32 begin_time = mTimingCurrentTime(m_core->timing);
|
||||
while (m_waiting_for_event)
|
||||
m_core->runLoop(m_core);
|
||||
s32 end_time = mTimingCurrentTime(m_core->timing);
|
||||
|
||||
u64 d = (static_cast<u64>(end_time - begin_time) * gc_frequency) + m_gc_ticks_remainder;
|
||||
m_last_gc_ticks += d / core_frequency;
|
||||
m_gc_ticks_remainder = d % core_frequency;
|
||||
}
|
||||
|
||||
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::DoState(PointerWrap& p)
|
||||
{
|
||||
Flush();
|
||||
if (!IsStarted())
|
||||
{
|
||||
::Core::DisplayMessage(fmt::format("GBA{} core not started. Aborting.", m_device_number + 1),
|
||||
3000);
|
||||
p.SetMode(PointerWrap::MODE_VERIFY);
|
||||
return;
|
||||
}
|
||||
|
||||
bool has_rom = !m_rom_path.empty();
|
||||
p.Do(has_rom);
|
||||
auto old_hash = m_rom_hash;
|
||||
p.Do(m_rom_hash);
|
||||
auto old_title = m_game_title;
|
||||
p.Do(m_game_title);
|
||||
|
||||
if (p.GetMode() == PointerWrap::MODE_READ &&
|
||||
(has_rom != !m_rom_path.empty() ||
|
||||
(has_rom && (old_hash != m_rom_hash || old_title != m_game_title))))
|
||||
{
|
||||
::Core::DisplayMessage(
|
||||
fmt::format("Incompatible ROM state in GBA{}. Aborting load state.", m_device_number + 1),
|
||||
3000);
|
||||
p.SetMode(PointerWrap::MODE_VERIFY);
|
||||
return;
|
||||
}
|
||||
|
||||
p.Do(m_video_buffer);
|
||||
p.Do(m_last_gc_ticks);
|
||||
p.Do(m_gc_ticks_remainder);
|
||||
p.Do(m_keys);
|
||||
p.Do(m_link_enabled);
|
||||
p.Do(m_response_ready);
|
||||
p.Do(m_response);
|
||||
|
||||
std::vector<u8> core_state;
|
||||
core_state.resize(m_core->stateSize(m_core));
|
||||
|
||||
if (p.GetMode() == PointerWrap::MODE_WRITE || p.GetMode() == PointerWrap::MODE_VERIFY)
|
||||
{
|
||||
m_core->saveState(m_core, core_state.data());
|
||||
}
|
||||
|
||||
p.Do(core_state);
|
||||
|
||||
if (p.GetMode() == PointerWrap::MODE_READ && m_core->stateSize(m_core) == core_state.size())
|
||||
{
|
||||
m_core->loadState(m_core, core_state.data());
|
||||
if (auto host = m_host.lock())
|
||||
host->FrameEnded(m_video_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::array<char, 17> game_title{};
|
||||
core->getGameTitle(core, game_title.data());
|
||||
title = game_title.data();
|
||||
|
||||
core->deinit(core);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Core::GetSavePath(std::string_view rom_path, int device_number)
|
||||
{
|
||||
std::string save_path =
|
||||
fmt::format("{}-{}.sav", rom_path.substr(0, rom_path.find_last_of('.')), device_number + 1);
|
||||
|
||||
if (!Config::Get(Config::MAIN_GBA_SAVES_IN_ROM_PATH))
|
||||
{
|
||||
save_path =
|
||||
File::GetUserPath(D_GBASAVES_IDX) + save_path.substr(save_path.find_last_of("\\/") + 1);
|
||||
}
|
||||
|
||||
return save_path;
|
||||
}
|
||||
} // namespace HW::GBA
|
|
@ -0,0 +1,129 @@
|
|||
// Copyright 2021 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#define PYCPARSE // Remove static functions from the header
|
||||
#include <mgba/core/interface.h>
|
||||
#undef PYCPARSE
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba/gba/interface.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class GBAHostInterface;
|
||||
class PointerWrap;
|
||||
|
||||
namespace HW::GBA
|
||||
{
|
||||
class Core;
|
||||
struct SIODriver : GBASIODriver
|
||||
{
|
||||
Core* core;
|
||||
};
|
||||
struct AVStream : mAVStream
|
||||
{
|
||||
Core* core;
|
||||
};
|
||||
|
||||
class Core final
|
||||
{
|
||||
public:
|
||||
explicit Core(int device_number);
|
||||
~Core();
|
||||
|
||||
bool Start(u64 gc_ticks);
|
||||
void Stop();
|
||||
void Reset();
|
||||
bool IsStarted() const;
|
||||
|
||||
void SetHost(std::weak_ptr<GBAHostInterface> host);
|
||||
void SetForceDisconnect(bool force_disconnect);
|
||||
void EReaderQueueCard(std::string_view card_path);
|
||||
|
||||
int GetDeviceNumber() const;
|
||||
void GetVideoDimensions(u32* width, u32* height) const;
|
||||
std::string GetGameTitle() const;
|
||||
|
||||
void SendJoybusCommand(u64 gc_ticks, int transfer_time, u8* buffer, u16 keys);
|
||||
std::vector<u8> GetJoybusResponse();
|
||||
|
||||
void ImportState(std::string_view state_path);
|
||||
void ExportState(std::string_view state_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:
|
||||
void ThreadLoop();
|
||||
void RunUntil(u64 gc_ticks);
|
||||
void RunFor(u64 gc_ticks);
|
||||
void Flush();
|
||||
|
||||
struct Command
|
||||
{
|
||||
u64 ticks;
|
||||
int transfer_time;
|
||||
bool sync_only;
|
||||
std::array<u8, 6> buffer;
|
||||
u16 keys;
|
||||
};
|
||||
void RunCommand(Command& command);
|
||||
|
||||
bool LoadBIOS(const char* bios_path);
|
||||
bool LoadSave(const char* save_path);
|
||||
|
||||
void SetSIODriver();
|
||||
void SetVideoBuffer();
|
||||
void SetSampleRates();
|
||||
void AddCallbacks();
|
||||
void SetAVStream();
|
||||
void SetupEvent();
|
||||
|
||||
const int m_device_number;
|
||||
|
||||
bool m_started = false;
|
||||
std::string m_rom_path;
|
||||
std::string m_save_path;
|
||||
std::array<u8, 20> m_rom_hash{};
|
||||
std::string m_game_title;
|
||||
|
||||
mCore* m_core{};
|
||||
mTimingEvent m_event{};
|
||||
bool m_waiting_for_event = false;
|
||||
SIODriver m_sio_driver{};
|
||||
AVStream m_stream{};
|
||||
std::vector<u32> m_video_buffer;
|
||||
|
||||
u64 m_last_gc_ticks = 0;
|
||||
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;
|
||||
|
||||
std::unique_ptr<std::thread> m_thread;
|
||||
bool m_exit_loop = false;
|
||||
bool m_idle = false;
|
||||
std::mutex m_queue_mutex;
|
||||
std::condition_variable m_command_cv;
|
||||
std::queue<Command> m_command_queue;
|
||||
|
||||
std::mutex m_response_mutex;
|
||||
std::condition_variable m_response_cv;
|
||||
bool m_response_ready = false;
|
||||
std::vector<u8> m_response;
|
||||
};
|
||||
} // namespace HW::GBA
|
|
@ -264,6 +264,7 @@
|
|||
<ClInclude Include="Core\HW\EXI\EXI_DeviceMemoryCard.h" />
|
||||
<ClInclude Include="Core\HW\EXI\EXI_DeviceMic.h" />
|
||||
<ClInclude Include="Core\HW\EXI\EXI.h" />
|
||||
<ClInclude Include="Core\HW\GBACore.h" />
|
||||
<ClInclude Include="Core\HW\GBAPad.h" />
|
||||
<ClInclude Include="Core\HW\GBAPadEmu.h" />
|
||||
<ClInclude Include="Core\HW\GCKeyboard.h" />
|
||||
|
@ -846,6 +847,7 @@
|
|||
<ClCompile Include="Core\HW\EXI\EXI_DeviceMemoryCard.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI_DeviceMic.cpp" />
|
||||
<ClCompile Include="Core\HW\EXI\EXI.cpp" />
|
||||
<ClCompile Include="Core\HW\GBACore.cpp" />
|
||||
<ClCompile Include="Core\HW\GBAPad.cpp" />
|
||||
<ClCompile Include="Core\HW\GBAPadEmu.cpp" />
|
||||
<ClCompile Include="Core\HW\GCKeyboard.cpp" />
|
||||
|
|
Loading…
Reference in New Issue