Merge branch 'master' into compute-shader-renderer

# Conflicts:
#	src/GPU.cpp
#	src/GPU.h
#	src/GPU2D_Soft.cpp
#	src/GPU3D.h
#	src/GPU3D_OpenGL.h
#	src/GPU_OpenGL.cpp
#	src/GPU_OpenGL.h
#	src/NDS.cpp
#	src/OpenGLSupport.cpp
#	src/frontend/qt_sdl/OSD.cpp
#	src/frontend/qt_sdl/main.cpp
#	src/frontend/qt_sdl/main.h
This commit is contained in:
RSDuck
2024-05-12 17:06:42 +02:00
256 changed files with 27552 additions and 18641 deletions

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -24,8 +24,13 @@
#include <string>
#include <vector>
namespace melonDS
{
class NDS;
}
namespace Frontend
{
using namespace melonDS;
enum ScreenLayout
{
@ -104,14 +109,14 @@ int AudioOut_GetNumSamples(int outlen);
void AudioOut_Resample(s16* inbuf, int inlen, s16* outbuf, int outlen, int volume);
// feed silence to the microphone input
void Mic_FeedSilence();
void Mic_FeedSilence(NDS& nds);
// feed random noise to the microphone input
void Mic_FeedNoise();
void Mic_FeedNoise(NDS& nds);
// feed an external buffer to the microphone input
// buffer should be mono
void Mic_FeedExternalBuffer();
void Mic_FeedExternalBuffer(NDS& nds);
void Mic_SetExternalBuffer(s16* buffer, u32 len);
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -27,6 +27,7 @@
#include "mic_blow.h"
using namespace melonDS;
namespace Frontend
{
@ -62,32 +63,39 @@ int AudioOut_GetNumSamples(int outlen)
void AudioOut_Resample(s16* inbuf, int inlen, s16* outbuf, int outlen, int volume)
{
float res_incr = inlen / (float)outlen;
float res_timer = 0;
int res_pos = 0;
double factor = (double) inlen / (double) outlen;
double inpos = -(factor / 2);
double vol = (double) volume / 256.f;
for (int i = 0; i < outlen; i++)
for (int i = 0; i < outlen * 2; i += 2)
{
outbuf[i*2 ] = (inbuf[res_pos*2 ] * volume) >> 8;
outbuf[i*2+1] = (inbuf[res_pos*2+1] * volume) >> 8;
double intpart_d;
double frac = modf(inpos, &intpart_d);
int intpart = (int) intpart_d;
res_timer += res_incr;
while (res_timer >= 1.0)
{
res_timer -= 1.0;
res_pos++;
}
double l1 = inbuf[ intpart * 2];
double l2 = inbuf[(intpart * 2) + 2];
double r1 = inbuf[(intpart * 2) + 1];
double r2 = inbuf[(intpart * 2) + 3];
double ldiff = l2 - l1;
double rdiff = r2 - r1;
outbuf[i] = (s16) round((l1 + ldiff * frac) * vol);
outbuf[i+1] = (s16) round((r1 + rdiff * frac) * vol);
inpos += factor;
}
}
void Mic_FeedSilence()
void Mic_FeedSilence(NDS& nds)
{
MicBufferReadPos = 0;
NDS::MicInputFrame(NULL, 0);
nds.MicInputFrame(NULL, 0);
}
void Mic_FeedNoise()
void Mic_FeedNoise(NDS& nds)
{
int sample_len = sizeof(mic_blow) / sizeof(u16);
static int sample_pos = 0;
@ -101,12 +109,12 @@ void Mic_FeedNoise()
if (sample_pos >= sample_len) sample_pos = 0;
}
NDS::MicInputFrame(tmp, 735);
nds.MicInputFrame(tmp, 735);
}
void Mic_FeedExternalBuffer()
void Mic_FeedExternalBuffer(NDS& nds)
{
if (!MicBuffer) return Mic_FeedSilence();
if (!MicBuffer) return Mic_FeedSilence(nds);
if ((MicBufferReadPos + 735) > MicBufferLength)
{
@ -115,12 +123,12 @@ void Mic_FeedExternalBuffer()
memcpy(&tmp[0], &MicBuffer[MicBufferReadPos], len1*sizeof(s16));
memcpy(&tmp[len1], &MicBuffer[0], (735 - len1)*sizeof(s16));
NDS::MicInputFrame(tmp, 735);
nds.MicInputFrame(tmp, 735);
MicBufferReadPos = 735 - len1;
}
else
{
NDS::MicInputFrame(&MicBuffer[MicBufferReadPos], 735);
nds.MicInputFrame(&MicBuffer[MicBufferReadPos], 735);
MicBufferReadPos += 735;
}
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -471,7 +471,7 @@ int GetScreenTransforms(float* out, int* kind)
bool GetTouchCoords(int& x, int& y, bool clamp)
{
if (HybEnable && HybScreen == 1)
if (HybEnable && HybScreen == 1)
{
float vx = x;
float vy = y;
@ -487,7 +487,7 @@ bool GetTouchCoords(int& x, int& y, bool clamp)
{
x = std::clamp((int)vx, 0, 255);
y = std::clamp((int)vy, 0, 191);
return true;
}
if (HybPrevTouchScreen == 2)

View File

@ -3,11 +3,7 @@
#include "loader.h"
#include <cstdlib>
#include <cstring>
#ifdef __APPLE__
#include <stdlib.h>
#else
#include <malloc.h>
#endif
Log_SetChannel(GL::Context);
#if defined(_WIN32)

View File

@ -6,6 +6,7 @@
#include <vector>
namespace GL {
using namespace melonDS;
class Context
{
public:

View File

@ -13,6 +13,7 @@ struct NSView;
namespace GL {
using namespace melonDS;
class ContextAGL final : public Context
{
public:

View File

@ -3,6 +3,7 @@
#include "../log.h"
#include "../scoped_guard.h"
#include "loader.h"
using namespace melonDS;
Log_SetChannel(GL::ContextWGL);
// TODO: get rid of this

View File

@ -4,6 +4,7 @@
#include <X11/Xutil.h>
namespace GL {
using namespace melonDS;
class X11Window
{
public:

View File

@ -28,8 +28,8 @@ struct WindowInfo
Type type = Type::Surfaceless;
void* display_connection = nullptr;
void* window_handle = nullptr;
u32 surface_width = 0;
u32 surface_height = 0;
melonDS::u32 surface_width = 0;
melonDS::u32 surface_height = 0;
float surface_refresh_rate = 0.0f;
float surface_scale = 1.0f;
SurfaceFormat surface_format = SurfaceFormat::RGB8;

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -19,6 +19,7 @@
#include "ArchiveUtil.h"
#include "Platform.h"
using namespace melonDS;
using Platform::Log;
using Platform::LogLevel;
@ -119,13 +120,12 @@ QVector<QString> ExtractFileFromArchive(QString path, QString wantedFile, QByteA
}
u32 ExtractFileFromArchive(QString path, QString wantedFile, u8** filedata, u32* filesize)
s32 ExtractFileFromArchive(QString path, QString wantedFile, std::unique_ptr<u8[]>& filedata, u32* filesize)
{
struct archive *a = archive_read_new();
struct archive_entry *entry;
int r;
if (!filedata) return -1;
archive_read_support_format_all(a);
archive_read_support_filter_all(a);
@ -147,8 +147,8 @@ u32 ExtractFileFromArchive(QString path, QString wantedFile, u8** filedata, u32*
size_t bytesToRead = archive_entry_size(entry);
if (filesize) *filesize = bytesToRead;
*filedata = new u8[bytesToRead];
ssize_t bytesRead = archive_read_data(a, *filedata, bytesToRead);
filedata = std::make_unique<u8[]>(bytesToRead);
ssize_t bytesRead = archive_read_data(a, filedata.get(), bytesToRead);
archive_read_close(a);
archive_read_free(a);

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -35,8 +35,9 @@
namespace Archive
{
using namespace melonDS;
QVector<QString> ListArchive(QString path);
u32 ExtractFileFromArchive(QString path, QString wantedFile, u8** filedata, u32* filesize);
s32 ExtractFileFromArchive(QString path, QString wantedFile, std::unique_ptr<u8[]>& filedata, u32* filesize);
//QVector<QString> ExtractFileFromArchive(QString path, QString wantedFile, QByteArray *romBuffer);
//u32 ExtractFileFromArchive(const char* path, const char* wantedFile, u8 **romdata);

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -22,11 +22,13 @@
#include "FrontendUtil.h"
#include "Config.h"
#include "NDS.h"
#include "SPU.h"
#include "Platform.h"
#include "Input.h"
#include "main.h"
using namespace melonDS;
namespace AudioInOut
{
@ -53,8 +55,9 @@ void AudioCallback(void* data, Uint8* stream, int len)
s16 buf_in[1024*2];
int num_in;
EmuThread* emuThread = (EmuThread*)data;
SDL_LockMutex(audioSyncLock);
num_in = SPU::ReadOutput(buf_in, len_in);
num_in = emuThread->NDS->SPU.ReadOutput(buf_in, len_in);
SDL_CondSignal(audioSync);
SDL_UnlockMutex(audioSyncLock);
@ -242,7 +245,7 @@ void MicLoadWav(const std::string& name)
SDL_FreeWAV(buf);
}
void MicProcess()
void MicProcess(melonDS::NDS& nds)
{
int type = Config::MicInputType;
bool cmd = Input::HotkeyDown(HK_Mic);
@ -255,16 +258,16 @@ void MicProcess()
switch (type)
{
case micInputType_Silence: // no mic
Frontend::Mic_FeedSilence();
Frontend::Mic_FeedSilence(nds);
break;
case micInputType_External: // host mic
case micInputType_Wav: // WAV
Frontend::Mic_FeedExternalBuffer();
Frontend::Mic_FeedExternalBuffer(nds);
break;
case micInputType_Noise: // blowing noise
Frontend::Mic_FeedNoise();
Frontend::Mic_FeedNoise(nds);
break;
}
}
@ -294,7 +297,7 @@ void SetupMicInputData()
}
}
void Init()
void Init(EmuThread* thread)
{
audioMuted = false;
audioSync = SDL_CreateCond();
@ -308,6 +311,7 @@ void Init()
whatIwant.channels = 2;
whatIwant.samples = 1024;
whatIwant.callback = AudioCallback;
whatIwant.userdata = thread;
audioDevice = SDL_OpenAudioDevice(NULL, 0, &whatIwant, &whatIget, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
if (!audioDevice)
{
@ -347,12 +351,12 @@ void DeInit()
micWavBuffer = nullptr;
}
void AudioSync()
void AudioSync(NDS& nds)
{
if (audioDevice)
{
SDL_LockMutex(audioSyncLock);
while (SPU::GetOutputSize() > 1024)
while (nds.SPU.GetOutputSize() > 1024)
{
int ret = SDL_CondWaitTimeout(audioSync, audioSyncLock, 500);
if (ret == SDL_MUTEX_TIMEDOUT) break;
@ -361,11 +365,11 @@ void AudioSync()
}
}
void UpdateSettings()
void UpdateSettings(NDS& nds)
{
MicClose();
SPU::SetInterpolation(Config::AudioInterp);
nds.SPU.SetInterpolation(static_cast<AudioInterpolation>(Config::AudioInterp));
SetupMicInputData();
MicOpen();
@ -383,4 +387,4 @@ void Disable()
MicClose();
}
}
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -23,22 +23,27 @@
#include <QMainWindow>
class EmuThread;
namespace melonDS
{
class NDS;
}
namespace AudioInOut
{
void Init();
void Init(EmuThread* thread);
void DeInit();
void MicProcess();
void MicProcess(melonDS::NDS& nds);
void AudioMute(QMainWindow* mainWindow);
void AudioSync();
void AudioSync(melonDS::NDS& nds);
void UpdateSettings();
void UpdateSettings(melonDS::NDS& nds);
void Enable();
void Disable();
}
#endif
#endif

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -24,18 +24,20 @@
#include "Platform.h"
#include "Config.h"
#include "NDS.h"
#include "DSi.h"
#include "DSi_I2C.h"
#include "AudioSettingsDialog.h"
#include "ui_AudioSettingsDialog.h"
#include "main.h"
using namespace melonDS;
AudioSettingsDialog* AudioSettingsDialog::currentDlg = nullptr;
extern std::string EmuDirectory;
AudioSettingsDialog::AudioSettingsDialog(QWidget* parent, bool emuActive) : QDialog(parent), ui(new Ui::AudioSettingsDialog)
AudioSettingsDialog::AudioSettingsDialog(QWidget* parent, bool emuActive, EmuThread* emuThread) : QDialog(parent), ui(new Ui::AudioSettingsDialog), emuThread(emuThread)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
@ -49,6 +51,7 @@ AudioSettingsDialog::AudioSettingsDialog(QWidget* parent, bool emuActive) : QDia
ui->cbInterpolation->addItem("Linear");
ui->cbInterpolation->addItem("Cosine");
ui->cbInterpolation->addItem("Cubic");
ui->cbInterpolation->addItem("Gaussian (SNES)");
ui->cbInterpolation->setCurrentIndex(Config::AudioInterp);
ui->cbBitDepth->addItem("Automatic");
@ -63,7 +66,7 @@ AudioSettingsDialog::AudioSettingsDialog(QWidget* parent, bool emuActive) : QDia
ui->chkSyncDSiVolume->setChecked(Config::DSiVolumeSync);
// Setup volume slider accordingly
if (emuActive && NDS::ConsoleType == 1)
if (emuActive && emuThread->NDS->ConsoleType == 1)
{
on_chkSyncDSiVolume_clicked(Config::DSiVolumeSync);
}
@ -77,15 +80,15 @@ AudioSettingsDialog::AudioSettingsDialog(QWidget* parent, bool emuActive) : QDia
const int count = SDL_GetNumAudioDevices(true);
for (int i = 0; i < count; i++)
{
ui->cbMic->addItem(SDL_GetAudioDeviceName(i, true));
ui->cbMic->addItem(SDL_GetAudioDeviceName(i, true));
}
if (Config::MicDevice == "" && count > 0)
{
{
Config::MicDevice = SDL_GetAudioDeviceName(0, true);
}
ui->cbMic->setCurrentText(QString::fromStdString(Config::MicDevice));
ui->cbMic->setCurrentText(QString::fromStdString(Config::MicDevice));
grpMicMode = new QButtonGroup(this);
grpMicMode->addButton(ui->rbMicNone, micInputType_Silence);
grpMicMode->addButton(ui->rbMicExternal, micInputType_External);
@ -123,10 +126,11 @@ AudioSettingsDialog::~AudioSettingsDialog()
void AudioSettingsDialog::onSyncVolumeLevel()
{
if (Config::DSiVolumeSync && NDS::ConsoleType == 1)
if (Config::DSiVolumeSync && emuThread->NDS->ConsoleType == 1)
{
auto& dsi = static_cast<DSi&>(*emuThread->NDS);
bool state = ui->slVolume->blockSignals(true);
ui->slVolume->setValue(DSi_BPTWL::GetVolumeLevel());
ui->slVolume->setValue(dsi.I2C.GetBPTWL()->GetVolumeLevel());
ui->slVolume->blockSignals(state);
}
}
@ -134,7 +138,7 @@ void AudioSettingsDialog::onSyncVolumeLevel()
void AudioSettingsDialog::onConsoleReset()
{
on_chkSyncDSiVolume_clicked(Config::DSiVolumeSync);
ui->chkSyncDSiVolume->setEnabled(NDS::ConsoleType == 1);
ui->chkSyncDSiVolume->setEnabled(emuThread->NDS->ConsoleType == 1);
}
void AudioSettingsDialog::on_AudioSettingsDialog_accepted()
@ -170,7 +174,7 @@ void AudioSettingsDialog::on_cbBitDepth_currentIndexChanged(int idx)
void AudioSettingsDialog::on_cbInterpolation_currentIndexChanged(int idx)
{
// prevent a spurious change
if (ui->cbInterpolation->count() < 4) return;
if (ui->cbInterpolation->count() < 5) return;
Config::AudioInterp = ui->cbInterpolation->currentIndex();
@ -179,9 +183,10 @@ void AudioSettingsDialog::on_cbInterpolation_currentIndexChanged(int idx)
void AudioSettingsDialog::on_slVolume_valueChanged(int val)
{
if (Config::DSiVolumeSync && NDS::ConsoleType == 1)
if (Config::DSiVolumeSync && emuThread->NDS->ConsoleType == 1)
{
DSi_BPTWL::SetVolumeLevel(val);
auto& dsi = static_cast<DSi&>(*emuThread->NDS);
dsi.I2C.GetBPTWL()->SetVolumeLevel(val);
return;
}
@ -193,10 +198,11 @@ void AudioSettingsDialog::on_chkSyncDSiVolume_clicked(bool checked)
Config::DSiVolumeSync = checked;
bool state = ui->slVolume->blockSignals(true);
if (Config::DSiVolumeSync && NDS::ConsoleType == 1)
if (Config::DSiVolumeSync && emuThread->NDS->ConsoleType == 1)
{
auto& dsi = static_cast<DSi&>(*emuThread->NDS);
ui->slVolume->setMaximum(31);
ui->slVolume->setValue(DSi_BPTWL::GetVolumeLevel());
ui->slVolume->setValue(dsi.I2C.GetBPTWL()->GetVolumeLevel());
ui->slVolume->setPageStep(4);
ui->slVolume->setTickPosition(QSlider::TicksBelow);
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -24,17 +24,18 @@
namespace Ui { class AudioSettingsDialog; }
class AudioSettingsDialog;
class EmuThread;
class AudioSettingsDialog : public QDialog
{
Q_OBJECT
public:
explicit AudioSettingsDialog(QWidget* parent, bool emuActive);
explicit AudioSettingsDialog(QWidget* parent, bool emuActive, EmuThread* emuThread);
~AudioSettingsDialog();
static AudioSettingsDialog* currentDlg;
static AudioSettingsDialog* openDlg(QWidget* parent, bool emuActive)
static AudioSettingsDialog* openDlg(QWidget* parent, bool emuActive, EmuThread* emuThread)
{
if (currentDlg)
{
@ -42,7 +43,7 @@ public:
return currentDlg;
}
currentDlg = new AudioSettingsDialog(parent, emuActive);
currentDlg = new AudioSettingsDialog(parent, emuActive, emuThread);
currentDlg->show();
return currentDlg;
}
@ -69,6 +70,7 @@ private slots:
void on_btnMicWavBrowse_clicked();
private:
EmuThread* emuThread;
Ui::AudioSettingsDialog* ui;
int oldInterp;

View File

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 melonDS team
Copyright 2021-2023 melonDS team
This file is part of melonDS.
@ -27,8 +27,8 @@
#include "CLI.h"
#include "Platform.h"
using Platform::Log;
using Platform::LogLevel;
using melonDS::Platform::Log;
using melonDS::Platform::LogLevel;
namespace CLI
{

View File

@ -1,5 +1,5 @@
/*
Copyright 2021-2022 melonDS team
Copyright 2021-2023 melonDS team
This file is part of melonDS.

View File

@ -5,8 +5,12 @@ include(FixInterfaceIncludes)
set(SOURCES_QT_SDL
main.cpp
main_shaders.h
Screen.cpp
Window.cpp
EmuThread.cpp
CheatsDialog.cpp
Config.cpp
DateTimeDialog.cpp
EmuSettingsDialog.cpp
PowerManagement/PowerManagementDialog.cpp
PowerManagement/resources/battery.qrc
@ -28,7 +32,6 @@ set(SOURCES_QT_SDL
LAN_PCap.cpp
LAN_Socket.cpp
LocalMP.cpp
OSD.cpp
OSD_shaders.h
font.h
Platform.cpp
@ -37,7 +40,7 @@ set(SOURCES_QT_SDL
SaveManager.cpp
CameraManager.cpp
AudioInOut.cpp
ArchiveUtil.h
ArchiveUtil.cpp
@ -81,11 +84,11 @@ if (BUILD_STATIC)
endif()
pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2)
pkg_check_modules(Slirp REQUIRED IMPORTED_TARGET slirp)
pkg_check_modules(Slirp REQUIRED slirp)
pkg_check_modules(LibArchive REQUIRED IMPORTED_TARGET libarchive)
pkg_check_modules(Zstd REQUIRED IMPORTED_TARGET libzstd)
fix_interface_includes(PkgConfig::SDL2 PkgConfig::Slirp PkgConfig::LibArchive)
fix_interface_includes(PkgConfig::SDL2 PkgConfig::LibArchive)
add_compile_definitions(ARCHIVE_SUPPORT_ENABLED)
@ -139,6 +142,7 @@ else()
)
target_link_libraries(melonDS PRIVATE "${X11_LIBRARIES}" "${EGL_LIBRARIES}")
target_include_directories(melonDS PRIVATE "${X11_INCLUDE_DIR}")
add_compile_definitions(QAPPLICATION_CLASS=QApplication)
endif()
@ -156,14 +160,19 @@ else()
target_include_directories(melonDS PUBLIC ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif()
target_link_libraries(melonDS PRIVATE core)
target_link_libraries(melonDS PRIVATE PkgConfig::SDL2 PkgConfig::Slirp PkgConfig::LibArchive PkgConfig::Zstd)
target_link_libraries(melonDS PRIVATE PkgConfig::SDL2 PkgConfig::LibArchive PkgConfig::Zstd)
target_link_libraries(melonDS PRIVATE ${QT_LINK_LIBS} ${CMAKE_DL_LIBS})
if (UNIX)
option(PORTABLE "Make a portable build that looks for its configuration in the current directory" OFF)
elseif (WIN32)
target_include_directories(melonDS PRIVATE "${Slirp_INCLUDE_DIRS}")
target_link_libraries(melonDS PRIVATE "${Slirp_LINK_LIBRARIES}")
if (WIN32)
option(PORTABLE "Make a portable build that looks for its configuration in the current directory" ON)
if (PORTABLE)
target_compile_definitions(melonDS PRIVATE WIN32_PORTABLE)
endif()
configure_file("${CMAKE_SOURCE_DIR}/res/melon.rc.in" "${CMAKE_BINARY_DIR}/res/melon.rc")
target_sources(melonDS PUBLIC "${CMAKE_BINARY_DIR}/res/melon.rc")
target_include_directories(melonDS PRIVATE "${CMAKE_BINARY_DIR}/res")
@ -182,10 +191,6 @@ elseif (WIN32)
set_target_properties(melonDS PROPERTIES LINK_FLAGS_DEBUG "-mconsole")
endif()
if (PORTABLE)
target_compile_definitions(melonDS PRIVATE PORTABLE)
endif()
if (APPLE)
target_sources(melonDS PRIVATE sem_timedwait.cpp)

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -16,9 +16,12 @@
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#include <QEventLoop>
#include "CameraManager.h"
#include "Config.h"
using namespace melonDS;
#if QT_VERSION >= 0x060000
@ -144,6 +147,7 @@ CameraManager::~CameraManager()
// save settings here?
delete[] frameBuffer;
delete[] tempFrameBuffer;
}
void CameraManager::init()
@ -256,6 +260,12 @@ void CameraManager::init()
if (camDevice)
{
camDevice->load();
if (camDevice->status() == QCamera::LoadingStatus)
{
QEventLoop loop;
connect(camDevice, &QCamera::statusChanged, &loop, &QEventLoop::quit);
loop.exec();
}
const QList<QCameraViewfinderSettings> supported = camDevice->supportedViewfinderSettings();
bool good = false;

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -89,11 +89,11 @@ public:
void setXFlip(bool flip);
void captureFrame(u32* frame, int width, int height, bool yuv);
void captureFrame(melonDS::u32* frame, int width, int height, bool yuv);
void feedFrame(u32* frame, int width, int height, bool yuv);
void feedFrame_UYVY(u32* frame, int width, int height);
void feedFrame_NV12(u8* planeY, u8* planeUV, int width, int height);
void feedFrame(melonDS::u32* frame, int width, int height, bool yuv);
void feedFrame_UYVY(melonDS::u32* frame, int width, int height);
void feedFrame_NV12(melonDS::u8* planeY, melonDS::u8* planeUV, int width, int height);
signals:
void camStartSignal();
@ -120,15 +120,15 @@ private:
int frameWidth, frameHeight;
bool frameFormatYUV;
u32* frameBuffer;
u32* tempFrameBuffer;
melonDS::u32* frameBuffer;
melonDS::u32* tempFrameBuffer;
QMutex frameMutex;
bool xFlip;
void copyFrame_Straight(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip, bool yuv);
void copyFrame_RGBtoYUV(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip);
void copyFrame_YUVtoRGB(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip);
void copyFrame_Straight(melonDS::u32* src, int swidth, int sheight, melonDS::u32* dst, int dwidth, int dheight, bool xflip, bool yuv);
void copyFrame_RGBtoYUV(melonDS::u32* src, int swidth, int sheight, melonDS::u32* dst, int dwidth, int dheight, bool xflip);
void copyFrame_YUVtoRGB(melonDS::u32* src, int swidth, int sheight, melonDS::u32* dst, int dwidth, int dheight, bool xflip);
};
#endif // CAMERAMANAGER_H

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -26,6 +26,7 @@
#include "CameraSettingsDialog.h"
#include "ui_CameraSettingsDialog.h"
using namespace melonDS;
CameraSettingsDialog* CameraSettingsDialog::currentDlg = nullptr;

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -29,6 +29,7 @@
#include "CheatsDialog.h"
#include "ui_CheatsDialog.h"
using namespace melonDS;
using Platform::Log;
using Platform::LogLevel;

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -27,8 +27,8 @@
#include "ARCodeFile.h"
Q_DECLARE_METATYPE(ARCodeList::iterator)
Q_DECLARE_METATYPE(ARCodeCatList::iterator)
Q_DECLARE_METATYPE(melonDS::ARCodeList::iterator)
Q_DECLARE_METATYPE(melonDS::ARCodeCatList::iterator)
namespace Ui { class CheatsDialog; }
class CheatsDialog;
@ -87,7 +87,7 @@ private slots:
private:
Ui::CheatsDialog* ui;
ARCodeFile* codeFile;
melonDS::ARCodeFile* codeFile;
ARCodeChecker* codeChecker;
};

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -19,6 +19,7 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include "Platform.h"
#include "Config.h"
#include "GPU.h"
@ -26,6 +27,7 @@
namespace Config
{
using namespace melonDS;
int KeyMapping[12];
int JoyMapping[12];
@ -61,6 +63,7 @@ bool GL_BetterPolygons;
bool GL_HiresCoordinates;
bool LimitFPS;
int MaxFPS;
bool AudioSync;
bool ShowOSD;
@ -141,6 +144,9 @@ bool MouseHide;
int MouseHideSeconds;
bool PauseLostFocus;
std::string UITheme;
int64_t RTCOffset;
bool DSBatteryLevelOkay;
int DSiBatteryLevel;
@ -148,6 +154,14 @@ bool DSiBatteryCharging;
bool DSiFullBIOSBoot;
#ifdef GDBSTUB_ENABLED
bool GdbEnabled;
int GdbPortARM7;
int GdbPortARM9;
bool GdbARM7BreakOnStartup;
bool GdbARM9BreakOnStartup;
#endif
CameraConfig Camera[2];
@ -242,6 +256,7 @@ ConfigEntry ConfigFile[] =
{"GL_HiresCoordinates", 1, &GL_HiresCoordinates, true, false},
{"LimitFPS", 1, &LimitFPS, true, false},
{"MaxFPS", 0, &MaxFPS, 1000, false},
{"AudioSync", 1, &AudioSync, false},
{"ShowOSD", 1, &ShowOSD, true, false},
@ -333,6 +348,9 @@ ConfigEntry ConfigFile[] =
{"MouseHide", 1, &MouseHide, false, false},
{"MouseHideSeconds", 0, &MouseHideSeconds, 5, false},
{"PauseLostFocus", 1, &PauseLostFocus, false, false},
{"UITheme", 2, &UITheme, (std::string)"", false},
{"RTCOffset", 3, &RTCOffset, (int64_t)0, true},
{"DSBatteryLevelOkay", 1, &DSBatteryLevelOkay, true, true},
{"DSiBatteryLevel", 0, &DSiBatteryLevel, 0xF, true},
@ -340,6 +358,14 @@ ConfigEntry ConfigFile[] =
{"DSiFullBIOSBoot", 1, &DSiFullBIOSBoot, false, true},
#ifdef GDBSTUB_ENABLED
{"GdbEnabled", 1, &GdbEnabled, false, false},
{"GdbPortARM7", 0, &GdbPortARM7, 3334, true},
{"GdbPortARM9", 0, &GdbPortARM9, 3333, true},
{"GdbARM7BreakOnStartup", 1, &GdbARM7BreakOnStartup, false, true},
{"GdbARM9BreakOnStartup", 1, &GdbARM9BreakOnStartup, false, true},
#endif
// TODO!!
// we need a more elegant way to deal with this
{"Camera0_InputType", 0, &Camera[0].InputType, 0, false},
@ -355,7 +381,7 @@ ConfigEntry ConfigFile[] =
};
void LoadFile(int inst)
bool LoadFile(int inst, int actualinst)
{
Platform::FileHandle* f;
if (inst > 0)
@ -363,11 +389,17 @@ void LoadFile(int inst)
char name[100] = {0};
snprintf(name, 99, kUniqueConfigFile, inst+1);
f = Platform::OpenLocalFile(name, Platform::FileMode::ReadText);
if (!Platform::CheckLocalFileWritable(name)) return false;
}
else
{
f = Platform::OpenLocalFile(kConfigFile, Platform::FileMode::ReadText);
if (!f) return;
if (actualinst == 0 && !Platform::CheckLocalFileWritable(kConfigFile)) return false;
}
if (!f) return true;
char linebuf[1024];
char entryname[32];
@ -393,6 +425,7 @@ void LoadFile(int inst)
case 0: *(int*)entry->Value = strtol(entryval, NULL, 10); break;
case 1: *(bool*)entry->Value = strtol(entryval, NULL, 10) ? true:false; break;
case 2: *(std::string*)entry->Value = entryval; break;
case 3: *(int64_t*)entry->Value = strtoll(entryval, NULL, 10); break;
}
break;
@ -401,9 +434,10 @@ void LoadFile(int inst)
}
CloseFile(f);
return true;
}
void Load()
bool Load()
{
for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++)
@ -413,14 +447,17 @@ void Load()
case 0: *(int*)entry->Value = std::get<int>(entry->Default); break;
case 1: *(bool*)entry->Value = std::get<bool>(entry->Default); break;
case 2: *(std::string*)entry->Value = std::get<std::string>(entry->Default); break;
case 3: *(int64_t*)entry->Value = std::get<int64_t>(entry->Default); break;
}
}
LoadFile(0);
int inst = Platform::InstanceID();
bool ret = LoadFile(0, inst);
if (inst > 0)
LoadFile(inst);
ret = LoadFile(inst, inst);
return ret;
}
void Save()
@ -446,9 +483,10 @@ void Save()
switch (entry->Type)
{
case 0: Platform::FileWriteFormatted(f, "%s=%d\r\n", entry->Name, *(int*)entry->Value); break;
case 1: Platform::FileWriteFormatted(f, "%s=%d\r\n", entry->Name, *(bool*)entry->Value ? 1:0); break;
case 2: Platform::FileWriteFormatted(f, "%s=%s\r\n", entry->Name, (*(std::string*)entry->Value).c_str()); break;
case 0: Platform::FileWriteFormatted(f, "%s=%d\n", entry->Name, *(int*)entry->Value); break;
case 1: Platform::FileWriteFormatted(f, "%s=%d\n", entry->Name, *(bool*)entry->Value ? 1:0); break;
case 2: Platform::FileWriteFormatted(f, "%s=%s\n", entry->Name, (*(std::string*)entry->Value).c_str()); break;
case 3: Platform::FileWriteFormatted(f, "%s=%" PRId64 "\n", entry->Name, *(int64_t*)entry->Value); break;
}
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -57,9 +57,9 @@ namespace Config
struct ConfigEntry
{
char Name[32];
int Type; // 0=int 1=bool 2=string
int Type; // 0=int 1=bool 2=string 3=64bit int
void* Value; // pointer to the value variable
std::variant<int, bool, std::string> Default;
std::variant<int, bool, std::string, int64_t> Default;
bool InstanceUnique; // whether the setting can exist individually for each instance in multiplayer
};
@ -106,6 +106,7 @@ extern bool GL_BetterPolygons;
extern bool GL_HiresCoordinates;
extern bool LimitFPS;
extern int MaxFPS;
extern bool AudioSync;
extern bool ShowOSD;
@ -185,6 +186,9 @@ extern bool EnableCheats;
extern bool MouseHide;
extern int MouseHideSeconds;
extern bool PauseLostFocus;
extern std::string UITheme;
extern int64_t RTCOffset;
extern bool DSBatteryLevelOkay;
extern int DSiBatteryLevel;
@ -194,8 +198,14 @@ extern bool DSiFullBIOSBoot;
extern CameraConfig Camera[2];
extern bool GdbEnabled;
extern int GdbPortARM7;
extern int GdbPortARM9;
extern bool GdbARM7BreakOnStartup;
extern bool GdbARM9BreakOnStartup;
void Load();
bool Load();
void Save();
}

View File

@ -0,0 +1,91 @@
/*
Copyright 2016-2023 melonDS team
This file is part of melonDS.
melonDS is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#include <stdio.h>
#include "types.h"
#include "Config.h"
#include "DateTimeDialog.h"
#include "ui_DateTimeDialog.h"
DateTimeDialog* DateTimeDialog::currentDlg = nullptr;
DateTimeDialog::DateTimeDialog(QWidget* parent) : QDialog(parent), ui(new Ui::DateTimeDialog)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
QDateTime now = QDateTime::currentDateTime();
customTime = now.addSecs(Config::RTCOffset);
ui->chkChangeTime->setChecked(false);
ui->chkResetTime->setChecked(false);
ui->lblCustomTime->setText(customTime.toString(ui->txtNewCustomTime->displayFormat()));
startTimer(1000);
ui->txtNewCustomTime->setEnabled(ui->chkChangeTime->isChecked());
}
DateTimeDialog::~DateTimeDialog()
{
delete ui;
}
void DateTimeDialog::timerEvent(QTimerEvent* event)
{
customTime = customTime.addSecs(1);
ui->lblCustomTime->setText(customTime.toString(ui->txtNewCustomTime->displayFormat()));
}
void DateTimeDialog::done(int r)
{
if (r == QDialog::Accepted)
{
if (ui->chkChangeTime->isChecked())
{
QDateTime now = QDateTime::currentDateTime();
Config::RTCOffset = now.secsTo(ui->txtNewCustomTime->dateTime());
}
else if (ui->chkResetTime->isChecked())
Config::RTCOffset = 0;
Config::Save();
}
QDialog::done(r);
closeDlg();
}
void DateTimeDialog::on_chkChangeTime_clicked(bool checked)
{
if (checked) ui->chkResetTime->setChecked(false);
ui->txtNewCustomTime->setEnabled(checked);
}
void DateTimeDialog::on_chkResetTime_clicked(bool checked)
{
if (checked)
{
ui->chkChangeTime->setChecked(false);
ui->txtNewCustomTime->setEnabled(false);
}
}

View File

@ -0,0 +1,70 @@
/*
Copyright 2016-2023 melonDS team
This file is part of melonDS.
melonDS is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#ifndef DATETIMEDIALOG_H
#define DATETIMEDIALOG_H
#include <QDialog>
#include <QButtonGroup>
#include <QDateTime>
namespace Ui {class DateTimeDialog; }
class DateTimeDialog;
class DateTimeDialog : public QDialog
{
Q_OBJECT
public:
explicit DateTimeDialog(QWidget* parent);
~DateTimeDialog();
static DateTimeDialog* currentDlg;
static DateTimeDialog* openDlg(QWidget* parent)
{
if (currentDlg)
{
currentDlg->activateWindow();
return currentDlg;
}
currentDlg = new DateTimeDialog(parent);
currentDlg->open();
return currentDlg;
}
static void closeDlg()
{
currentDlg = nullptr;
}
protected:
void timerEvent(QTimerEvent* event) override;
private slots:
void done(int r);
void on_chkChangeTime_clicked(bool checked);
void on_chkResetTime_clicked(bool checked);
private:
Ui::DateTimeDialog* ui;
QDateTime customTime;
};
#endif // DATETIMEDIALOG_H

View File

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DateTimeDialog</class>
<widget class="QDialog" name="DateTimeDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>357</width>
<height>161</height>
</rect>
</property>
<property name="windowTitle">
<string>Date and time - melonDS</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Date and time</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Current value:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="lblCustomTime">
<property name="text">
<string>[placeholder]</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="chkChangeTime">
<property name="text">
<string>Change to:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QDateTimeEdit" name="txtNewCustomTime">
<property name="dateTime">
<datetime>
<hour>0</hour>
<minute>0</minute>
<second>0</second>
<year>2000</year>
<month>1</month>
<day>1</day>
</datetime>
</property>
<property name="maximumDateTime">
<datetime>
<hour>23</hour>
<minute>59</minute>
<second>59</second>
<year>2099</year>
<month>12</month>
<day>31</day>
</datetime>
</property>
<property name="minimumDateTime">
<datetime>
<hour>0</hour>
<minute>0</minute>
<second>0</second>
<year>2000</year>
<month>1</month>
<day>1</day>
</datetime>
</property>
<property name="displayFormat">
<string>dd/MM/yyyy HH:mm:ss</string>
</property>
<property name="calendarPopup">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="chkResetTime">
<property name="text">
<string>Reset to system date and time</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>DateTimeDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>DateTimeDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -29,7 +29,8 @@
#include "EmuSettingsDialog.h"
#include "ui_EmuSettingsDialog.h"
using namespace Platform;
using namespace melonDS::Platform;
using namespace melonDS;
EmuSettingsDialog* EmuSettingsDialog::currentDlg = nullptr;
@ -89,7 +90,22 @@ EmuSettingsDialog::EmuSettingsDialog(QWidget* parent) : QDialog(parent), ui(new
ui->spnJITMaximumBlockSize->setDisabled(true);
#endif
#ifdef GDBSTUB_ENABLED
ui->cbGdbEnabled->setChecked(Config::GdbEnabled);
ui->intGdbPortA7->setValue(Config::GdbPortARM7);
ui->intGdbPortA9->setValue(Config::GdbPortARM9);
ui->cbGdbBOSA7->setChecked(Config::GdbARM7BreakOnStartup);
ui->cbGdbBOSA9->setChecked(Config::GdbARM9BreakOnStartup);
#else
ui->cbGdbEnabled->setDisabled(true);
ui->intGdbPortA7->setDisabled(true);
ui->intGdbPortA9->setDisabled(true);
ui->cbGdbBOSA7->setDisabled(true);
ui->cbGdbBOSA9->setDisabled(true);
#endif
on_chkEnableJIT_toggled();
on_cbGdbEnabled_toggled();
on_chkExternalBIOS_toggled();
const int imgsizes[] = {256, 512, 1024, 2048, 4096, 0};
@ -223,6 +239,12 @@ void EmuSettingsDialog::done(int r)
bool dsiSDFolderSync = ui->cbDSiSDFolder->isChecked();
std::string dsiSDFolderPath = ui->txtDSiSDFolder->text().toStdString();
bool gdbEnabled = ui->cbGdbEnabled->isChecked();
int gdbPortA7 = ui->intGdbPortA7->value();
int gdbPortA9 = ui->intGdbPortA9->value();
bool gdbBOSA7 = ui->cbGdbBOSA7->isChecked();
bool gdbBOSA9 = ui->cbGdbBOSA9->isChecked();
if (consoleType != Config::ConsoleType
|| directBoot != Config::DirectBoot
#ifdef JIT_ENABLED
@ -231,6 +253,13 @@ void EmuSettingsDialog::done(int r)
|| jitBranchOptimisations != Config::JIT_BranchOptimisations
|| jitLiteralOptimisations != Config::JIT_LiteralOptimisations
|| jitFastMemory != Config::JIT_FastMemory
#endif
#ifdef GDBSTUB_ENABLED
|| gdbEnabled != Config::GdbEnabled
|| gdbPortA7 != Config::GdbPortARM7
|| gdbPortA9 != Config::GdbPortARM9
|| gdbBOSA7 != Config::GdbARM7BreakOnStartup
|| gdbBOSA9 != Config::GdbARM9BreakOnStartup
#endif
|| externalBiosEnable != Config::ExternalBIOSEnable
|| bios9Path != Config::BIOS9Path
@ -285,13 +314,20 @@ void EmuSettingsDialog::done(int r)
Config::DSiSDFolderSync = dsiSDFolderSync;
Config::DSiSDFolderPath = dsiSDFolderPath;
#ifdef JIT_ENABLED
#ifdef JIT_ENABLED
Config::JIT_Enable = jitEnable;
Config::JIT_MaxBlockSize = jitMaxBlockSize;
Config::JIT_BranchOptimisations = jitBranchOptimisations;
Config::JIT_LiteralOptimisations = jitLiteralOptimisations;
Config::JIT_FastMemory = jitFastMemory;
#endif
#endif
#ifdef GDBSTUB_ENABLED
Config::GdbEnabled = gdbEnabled;
Config::GdbPortARM7 = gdbPortA7;
Config::GdbPortARM9 = gdbPortA9;
Config::GdbARM7BreakOnStartup = gdbBOSA7;
Config::GdbARM9BreakOnStartup = gdbBOSA9;
#endif
Config::ConsoleType = consoleType;
Config::DirectBoot = directBoot;
@ -344,6 +380,12 @@ void EmuSettingsDialog::on_btnFirmwareBrowse_clicked()
if (file.isEmpty()) return;
if (!Platform::CheckFileWritable(file.toStdString()))
{
QMessageBox::critical(this, "melonDS", "Unable to write to firmware file.\nPlease check file/folder write permissions.");
return;
}
updateLastBIOSFolder(file);
ui->txtFirmwarePath->setText(file);
@ -400,6 +442,12 @@ void EmuSettingsDialog::on_btnDLDISDBrowse_clicked()
if (file.isEmpty()) return;
if (!Platform::CheckFileWritable(file.toStdString()))
{
QMessageBox::critical(this, "melonDS", "Unable to write to DLDI SD image.\nPlease check file/folder write permissions.");
return;
}
updateLastBIOSFolder(file);
ui->txtDLDISDPath->setText(file);
@ -432,6 +480,13 @@ void EmuSettingsDialog::on_btnDSiFirmwareBrowse_clicked()
if (file.isEmpty()) return;
if (!Platform::CheckFileWritable(file.toStdString()))
{
QMessageBox::critical(this, "melonDS", "Unable to write to DSi firmware file.\nPlease check file/folder write permissions.");
return;
}
updateLastBIOSFolder(file);
ui->txtDSiFirmwarePath->setText(file);
@ -446,6 +501,13 @@ void EmuSettingsDialog::on_btnDSiNANDBrowse_clicked()
if (file.isEmpty()) return;
if (!Platform::CheckFileWritable(file.toStdString()))
{
QMessageBox::critical(this, "melonDS", "Unable to write to DSi NAND image.\nPlease check file/folder write permissions.");
return;
}
updateLastBIOSFolder(file);
ui->txtDSiNANDPath->setText(file);
@ -474,6 +536,12 @@ void EmuSettingsDialog::on_btnDSiSDBrowse_clicked()
if (file.isEmpty()) return;
if (!Platform::CheckFileWritable(file.toStdString()))
{
QMessageBox::critical(this, "melonDS", "Unable to write to DSi SD image.\nPlease check file/folder write permissions.");
return;
}
updateLastBIOSFolder(file);
ui->txtDSiSDPath->setText(file);
@ -506,6 +574,31 @@ void EmuSettingsDialog::on_chkEnableJIT_toggled()
ui->chkJITFastMemory->setDisabled(disabled);
#endif
ui->spnJITMaximumBlockSize->setDisabled(disabled);
on_cbGdbEnabled_toggled();
}
void EmuSettingsDialog::on_cbGdbEnabled_toggled()
{
#ifdef GDBSTUB_ENABLED
bool disabled = !ui->cbGdbEnabled->isChecked();
bool jitenable = ui->chkEnableJIT->isChecked();
if (jitenable && !disabled) {
ui->cbGdbEnabled->setChecked(false);
disabled = true;
}
#else
bool disabled = true;
bool jitenable = true;
ui->cbGdbEnabled->setChecked(false);
#endif
ui->cbGdbEnabled->setDisabled(jitenable);
ui->intGdbPortA7->setDisabled(disabled);
ui->intGdbPortA9->setDisabled(disabled);
ui->cbGdbBOSA7->setDisabled(disabled);
ui->cbGdbBOSA9->setDisabled(disabled);
}
void EmuSettingsDialog::on_chkExternalBIOS_toggled()

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -77,6 +77,8 @@ private slots:
void on_chkEnableJIT_toggled();
void on_chkExternalBIOS_toggled();
void on_cbGdbEnabled_toggled();
private:
void verifyFirmware();

View File

@ -568,6 +568,101 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_6">
<attribute name="title">
<string>Devtools</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>ARM9 port</string>
</property>
</widget>
</item>
<item row="5" column="0">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>ARM7 port</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<widget class="QCheckBox" name="cbGdbEnabled">
<property name="text">
<string>Enable GDB stub</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="7">
<widget class="QLabel" name="label_18">
<property name="text">
<string>Note: melonDS must be restarted in order for these changes to have effect</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="7">
<widget class="QLabel" name="label_19">
<property name="text">
<string>Note: GDB stub cannot be used together with the JIT recompiler</string>
</property>
</widget>
</item>
<item row="1" column="6">
<widget class="QCheckBox" name="cbGdbBOSA9">
<property name="text">
<string>Break on startup</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="5">
<widget class="QSpinBox" name="intGdbPortA9">
<property name="minimum">
<number>1000</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>3333</number>
</property>
</widget>
</item>
<item row="2" column="1" colspan="5">
<widget class="QSpinBox" name="intGdbPortA7">
<property name="minimum">
<number>1000</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>3334</number>
</property>
</widget>
</item>
<item row="2" column="6">
<widget class="QCheckBox" name="cbGdbBOSA7">
<property name="text">
<string>Break on startup</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
@ -590,7 +685,6 @@
</customwidget>
</customwidgets>
<tabstops>
<tabstop>tabWidget</tabstop>
<tabstop>cbxConsoleType</tabstop>
<tabstop>chkDirectBoot</tabstop>
<tabstop>chkExternalBIOS</tabstop>
@ -639,8 +733,8 @@
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>257</x>
<y>349</y>
<x>266</x>
<y>379</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
@ -655,8 +749,8 @@
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>325</x>
<y>349</y>
<x>334</x>
<y>379</y>
</hint>
<hint type="destinationlabel">
<x>286</x>

View File

@ -0,0 +1,752 @@
/*
Copyright 2016-2023 melonDS team
This file is part of melonDS.
melonDS is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <optional>
#include <vector>
#include <string>
#include <algorithm>
#include <SDL2/SDL.h>
#include "main.h"
#include "Input.h"
#include "AudioInOut.h"
#include "types.h"
#include "version.h"
#include "FrontendUtil.h"
#include "Args.h"
#include "NDS.h"
#include "NDSCart.h"
#include "GBACart.h"
#include "GPU.h"
#include "SPU.h"
#include "Wifi.h"
#include "Platform.h"
#include "LocalMP.h"
#include "Config.h"
#include "RTC.h"
#include "DSi.h"
#include "DSi_I2C.h"
#include "GPU3D_Soft.h"
#include "GPU3D_OpenGL.h"
#include "Savestate.h"
#include "ROMManager.h"
//#include "ArchiveUtil.h"
//#include "CameraManager.h"
//#include "CLI.h"
// TODO: uniform variable spelling
using namespace melonDS;
// TEMP
extern bool RunningSomething;
extern MainWindow* mainWindow;
extern int autoScreenSizing;
extern int videoRenderer;
extern bool videoSettingsDirty;
EmuThread::EmuThread(QObject* parent) : QThread(parent)
{
EmuStatus = emuStatus_Exit;
EmuRunning = emuStatus_Paused;
EmuPauseStack = EmuPauseStackRunning;
RunningSomething = false;
connect(this, SIGNAL(windowUpdate()), mainWindow->panel, SLOT(repaint()));
connect(this, SIGNAL(windowTitleChange(QString)), mainWindow, SLOT(onTitleUpdate(QString)));
connect(this, SIGNAL(windowEmuStart()), mainWindow, SLOT(onEmuStart()));
connect(this, SIGNAL(windowEmuStop()), mainWindow, SLOT(onEmuStop()));
connect(this, SIGNAL(windowEmuPause()), mainWindow->actPause, SLOT(trigger()));
connect(this, SIGNAL(windowEmuReset()), mainWindow->actReset, SLOT(trigger()));
connect(this, SIGNAL(windowEmuFrameStep()), mainWindow->actFrameStep, SLOT(trigger()));
connect(this, SIGNAL(windowLimitFPSChange()), mainWindow->actLimitFramerate, SLOT(trigger()));
connect(this, SIGNAL(screenLayoutChange()), mainWindow->panel, SLOT(onScreenLayoutChanged()));
connect(this, SIGNAL(windowFullscreenToggle()), mainWindow, SLOT(onFullscreenToggled()));
connect(this, SIGNAL(swapScreensToggle()), mainWindow->actScreenSwap, SLOT(trigger()));
connect(this, SIGNAL(screenEmphasisToggle()), mainWindow, SLOT(onScreenEmphasisToggled()));
}
std::unique_ptr<NDS> EmuThread::CreateConsole(
std::unique_ptr<melonDS::NDSCart::CartCommon>&& ndscart,
std::unique_ptr<melonDS::GBACart::CartCommon>&& gbacart
) noexcept
{
auto arm7bios = ROMManager::LoadARM7BIOS();
if (!arm7bios)
return nullptr;
auto arm9bios = ROMManager::LoadARM9BIOS();
if (!arm9bios)
return nullptr;
auto firmware = ROMManager::LoadFirmware(Config::ConsoleType);
if (!firmware)
return nullptr;
#ifdef JIT_ENABLED
JITArgs jitargs {
static_cast<unsigned>(Config::JIT_MaxBlockSize),
Config::JIT_LiteralOptimisations,
Config::JIT_BranchOptimisations,
Config::JIT_FastMemory,
};
#endif
#ifdef GDBSTUB_ENABLED
GDBArgs gdbargs {
static_cast<u16>(Config::GdbPortARM7),
static_cast<u16>(Config::GdbPortARM9),
Config::GdbARM7BreakOnStartup,
Config::GdbARM9BreakOnStartup,
};
#endif
NDSArgs ndsargs {
std::move(ndscart),
std::move(gbacart),
std::move(arm9bios),
std::move(arm7bios),
std::move(*firmware),
#ifdef JIT_ENABLED
Config::JIT_Enable ? std::make_optional(jitargs) : std::nullopt,
#else
std::nullopt,
#endif
static_cast<AudioBitDepth>(Config::AudioBitDepth),
static_cast<AudioInterpolation>(Config::AudioInterp),
#ifdef GDBSTUB_ENABLED
Config::GdbEnabled ? std::make_optional(gdbargs) : std::nullopt,
#else
std::nullopt,
#endif
};
if (Config::ConsoleType == 1)
{
auto arm7ibios = ROMManager::LoadDSiARM7BIOS();
if (!arm7ibios)
return nullptr;
auto arm9ibios = ROMManager::LoadDSiARM9BIOS();
if (!arm9ibios)
return nullptr;
auto nand = ROMManager::LoadNAND(*arm7ibios);
if (!nand)
return nullptr;
auto sdcard = ROMManager::LoadDSiSDCard();
DSiArgs args {
std::move(ndsargs),
std::move(arm9ibios),
std::move(arm7ibios),
std::move(*nand),
std::move(sdcard),
Config::DSiFullBIOSBoot,
};
args.GBAROM = nullptr;
return std::make_unique<melonDS::DSi>(std::move(args));
}
return std::make_unique<melonDS::NDS>(std::move(ndsargs));
}
bool EmuThread::UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept
{
// Let's get the cart we want to use;
// if we wnat to keep the cart, we'll eject it from the existing console first.
std::unique_ptr<NDSCart::CartCommon> nextndscart;
if (std::holds_alternative<Keep>(ndsargs))
{ // If we want to keep the existing cart (if any)...
nextndscart = NDS ? NDS->EjectCart() : nullptr;
ndsargs = {};
}
else if (const auto ptr = std::get_if<std::unique_ptr<NDSCart::CartCommon>>(&ndsargs))
{
nextndscart = std::move(*ptr);
ndsargs = {};
}
if (auto* cartsd = dynamic_cast<NDSCart::CartSD*>(nextndscart.get()))
{
// LoadDLDISDCard will return nullopt if the SD card is disabled;
// SetSDCard will accept nullopt, which means no SD card
cartsd->SetSDCard(ROMManager::GetDLDISDCardArgs());
}
std::unique_ptr<GBACart::CartCommon> nextgbacart;
if (std::holds_alternative<Keep>(gbaargs))
{
nextgbacart = NDS ? NDS->EjectGBACart() : nullptr;
}
else if (const auto ptr = std::get_if<std::unique_ptr<GBACart::CartCommon>>(&gbaargs))
{
nextgbacart = std::move(*ptr);
gbaargs = {};
}
if (!NDS || NDS->ConsoleType != Config::ConsoleType)
{ // If we're switching between DS and DSi mode, or there's no console...
// To ensure the destructor is called before a new one is created,
// as the presence of global signal handlers still complicates things a bit
NDS = nullptr;
NDS::Current = nullptr;
NDS = CreateConsole(std::move(nextndscart), std::move(nextgbacart));
if (NDS == nullptr)
return false;
NDS->Reset();
NDS::Current = NDS.get();
return true;
}
auto arm9bios = ROMManager::LoadARM9BIOS();
if (!arm9bios)
return false;
auto arm7bios = ROMManager::LoadARM7BIOS();
if (!arm7bios)
return false;
auto firmware = ROMManager::LoadFirmware(NDS->ConsoleType);
if (!firmware)
return false;
if (NDS->ConsoleType == 1)
{ // If the console we're updating is a DSi...
DSi& dsi = static_cast<DSi&>(*NDS);
auto arm9ibios = ROMManager::LoadDSiARM9BIOS();
if (!arm9ibios)
return false;
auto arm7ibios = ROMManager::LoadDSiARM7BIOS();
if (!arm7ibios)
return false;
auto nandimage = ROMManager::LoadNAND(*arm7ibios);
if (!nandimage)
return false;
auto dsisdcard = ROMManager::LoadDSiSDCard();
dsi.SetFullBIOSBoot(Config::DSiFullBIOSBoot);
dsi.ARM7iBIOS = *arm7ibios;
dsi.ARM9iBIOS = *arm9ibios;
dsi.SetNAND(std::move(*nandimage));
dsi.SetSDCard(std::move(dsisdcard));
// We're moving the optional, not the card
// (inserting std::nullopt here is okay, it means no card)
dsi.EjectGBACart();
}
if (NDS->ConsoleType == 0)
{
NDS->SetGBACart(std::move(nextgbacart));
}
#ifdef JIT_ENABLED
JITArgs jitargs {
static_cast<unsigned>(Config::JIT_MaxBlockSize),
Config::JIT_LiteralOptimisations,
Config::JIT_BranchOptimisations,
Config::JIT_FastMemory,
};
NDS->SetJITArgs(Config::JIT_Enable ? std::make_optional(jitargs) : std::nullopt);
#endif
NDS->SetARM7BIOS(*arm7bios);
NDS->SetARM9BIOS(*arm9bios);
NDS->SetFirmware(std::move(*firmware));
NDS->SetNDSCart(std::move(nextndscart));
NDS->SPU.SetInterpolation(static_cast<AudioInterpolation>(Config::AudioInterp));
NDS->SPU.SetDegrade10Bit(static_cast<AudioBitDepth>(Config::AudioBitDepth));
NDS::Current = NDS.get();
return true;
}
void EmuThread::run()
{
u32 mainScreenPos[3];
Platform::FileHandle* file;
UpdateConsole(nullptr, nullptr);
// No carts are inserted when melonDS first boots
mainScreenPos[0] = 0;
mainScreenPos[1] = 0;
mainScreenPos[2] = 0;
autoScreenSizing = 0;
videoSettingsDirty = false;
if (mainWindow->hasOGL)
{
screenGL = static_cast<ScreenPanelGL*>(mainWindow->panel);
screenGL->initOpenGL();
videoRenderer = Config::_3DRenderer;
}
else
{
screenGL = nullptr;
videoRenderer = 0;
}
if (videoRenderer == 0)
{ // If we're using the software renderer...
NDS->GPU.SetRenderer3D(std::make_unique<SoftRenderer>(Config::Threaded3D != 0));
}
else
{
auto glrenderer = melonDS::GLRenderer::New();
glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor);
NDS->GPU.SetRenderer3D(std::move(glrenderer));
}
Input::Init();
u32 nframes = 0;
double perfCountsSec = 1.0 / SDL_GetPerformanceFrequency();
double lastTime = SDL_GetPerformanceCounter() * perfCountsSec;
double frameLimitError = 0.0;
double lastMeasureTime = lastTime;
u32 winUpdateCount = 0, winUpdateFreq = 1;
u8 dsiVolumeLevel = 0x1F;
file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Read);
if (file)
{
RTC::StateData state;
Platform::FileRead(&state, sizeof(state), 1, file);
Platform::CloseFile(file);
NDS->RTC.SetState(state);
}
char melontitle[100];
while (EmuRunning != emuStatus_Exit)
{
Input::Process();
if (Input::HotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange();
if (Input::HotkeyPressed(HK_Pause)) emit windowEmuPause();
if (Input::HotkeyPressed(HK_Reset)) emit windowEmuReset();
if (Input::HotkeyPressed(HK_FrameStep)) emit windowEmuFrameStep();
if (Input::HotkeyPressed(HK_FullscreenToggle)) emit windowFullscreenToggle();
if (Input::HotkeyPressed(HK_SwapScreens)) emit swapScreensToggle();
if (Input::HotkeyPressed(HK_SwapScreenEmphasis)) emit screenEmphasisToggle();
if (EmuRunning == emuStatus_Running || EmuRunning == emuStatus_FrameStep)
{
EmuStatus = emuStatus_Running;
if (EmuRunning == emuStatus_FrameStep) EmuRunning = emuStatus_Paused;
if (Input::HotkeyPressed(HK_SolarSensorDecrease))
{
int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorDown, true);
if (level != -1)
{
mainWindow->osdAddMessage(0, "Solar sensor level: %d", level);
}
}
if (Input::HotkeyPressed(HK_SolarSensorIncrease))
{
int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorUp, true);
if (level != -1)
{
mainWindow->osdAddMessage(0, "Solar sensor level: %d", level);
}
}
if (NDS->ConsoleType == 1)
{
DSi& dsi = static_cast<DSi&>(*NDS);
double currentTime = SDL_GetPerformanceCounter() * perfCountsSec;
// Handle power button
if (Input::HotkeyDown(HK_PowerButton))
{
dsi.I2C.GetBPTWL()->SetPowerButtonHeld(currentTime);
}
else if (Input::HotkeyReleased(HK_PowerButton))
{
dsi.I2C.GetBPTWL()->SetPowerButtonReleased(currentTime);
}
// Handle volume buttons
if (Input::HotkeyDown(HK_VolumeUp))
{
dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Up);
}
else if (Input::HotkeyReleased(HK_VolumeUp))
{
dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Up);
}
if (Input::HotkeyDown(HK_VolumeDown))
{
dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Down);
}
else if (Input::HotkeyReleased(HK_VolumeDown))
{
dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Down);
}
dsi.I2C.GetBPTWL()->ProcessVolumeSwitchInput(currentTime);
}
// update render settings if needed
// HACK:
// once the fast forward hotkey is released, we need to update vsync
// to the old setting again
if (videoSettingsDirty || Input::HotkeyReleased(HK_FastForward))
{
if (screenGL)
{
screenGL->setSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0);
videoRenderer = Config::_3DRenderer;
}
#ifdef OGLRENDERER_ENABLED
else
#endif
{
videoRenderer = 0;
}
videoRenderer = screenGL ? Config::_3DRenderer : 0;
videoSettingsDirty = false;
if (videoRenderer == 0)
{ // If we're using the software renderer...
NDS->GPU.SetRenderer3D(std::make_unique<SoftRenderer>(Config::Threaded3D != 0));
}
else
{
auto glrenderer = melonDS::GLRenderer::New();
glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor);
NDS->GPU.SetRenderer3D(std::move(glrenderer));
}
}
// process input and hotkeys
NDS->SetKeyMask(Input::InputMask);
if (Input::HotkeyPressed(HK_Lid))
{
bool lid = !NDS->IsLidClosed();
NDS->SetLidClosed(lid);
mainWindow->osdAddMessage(0, lid ? "Lid closed" : "Lid opened");
}
// microphone input
AudioInOut::MicProcess(*NDS);
// auto screen layout
if (Config::ScreenSizing == Frontend::screenSizing_Auto)
{
mainScreenPos[2] = mainScreenPos[1];
mainScreenPos[1] = mainScreenPos[0];
mainScreenPos[0] = NDS->PowerControl9 >> 15;
int guess;
if (mainScreenPos[0] == mainScreenPos[2] &&
mainScreenPos[0] != mainScreenPos[1])
{
// constant flickering, likely displaying 3D on both screens
// TODO: when both screens are used for 2D only...???
guess = Frontend::screenSizing_Even;
}
else
{
if (mainScreenPos[0] == 1)
guess = Frontend::screenSizing_EmphTop;
else
guess = Frontend::screenSizing_EmphBot;
}
if (guess != autoScreenSizing)
{
autoScreenSizing = guess;
emit screenLayoutChange();
}
}
// emulate
u32 nlines = NDS->RunFrame();
if (ROMManager::NDSSave)
ROMManager::NDSSave->CheckFlush();
if (ROMManager::GBASave)
ROMManager::GBASave->CheckFlush();
if (ROMManager::FirmwareSave)
ROMManager::FirmwareSave->CheckFlush();
if (!screenGL)
{
FrontBufferLock.lock();
FrontBuffer = NDS->GPU.FrontBuffer;
FrontBufferLock.unlock();
}
else
{
FrontBuffer = NDS->GPU.FrontBuffer;
screenGL->drawScreenGL();
}
#ifdef MELONCAP
MelonCap::Update();
#endif // MELONCAP
if (EmuRunning == emuStatus_Exit) break;
winUpdateCount++;
if (winUpdateCount >= winUpdateFreq && !screenGL)
{
emit windowUpdate();
winUpdateCount = 0;
}
bool fastforward = Input::HotkeyDown(HK_FastForward);
if (fastforward && screenGL && Config::ScreenVSync)
{
screenGL->setSwapInterval(0);
}
if (Config::DSiVolumeSync && NDS->ConsoleType == 1)
{
DSi& dsi = static_cast<DSi&>(*NDS);
u8 volumeLevel = dsi.I2C.GetBPTWL()->GetVolumeLevel();
if (volumeLevel != dsiVolumeLevel)
{
dsiVolumeLevel = volumeLevel;
emit syncVolumeLevel();
}
Config::AudioVolume = volumeLevel * (256.0 / 31.0);
}
if (Config::AudioSync && !fastforward)
AudioInOut::AudioSync(*this->NDS);
double frametimeStep = nlines / (60.0 * 263.0);
{
bool limitfps = Config::LimitFPS && !fastforward;
double practicalFramelimit = limitfps ? frametimeStep : 1.0 / Config::MaxFPS;
double curtime = SDL_GetPerformanceCounter() * perfCountsSec;
frameLimitError += practicalFramelimit - (curtime - lastTime);
if (frameLimitError < -practicalFramelimit)
frameLimitError = -practicalFramelimit;
if (frameLimitError > practicalFramelimit)
frameLimitError = practicalFramelimit;
if (round(frameLimitError * 1000.0) > 0.0)
{
SDL_Delay(round(frameLimitError * 1000.0));
double timeBeforeSleep = curtime;
curtime = SDL_GetPerformanceCounter() * perfCountsSec;
frameLimitError -= curtime - timeBeforeSleep;
}
lastTime = curtime;
}
nframes++;
if (nframes >= 30)
{
double time = SDL_GetPerformanceCounter() * perfCountsSec;
double dt = time - lastMeasureTime;
lastMeasureTime = time;
u32 fps = round(nframes / dt);
nframes = 0;
float fpstarget = 1.0/frametimeStep;
winUpdateFreq = fps / (u32)round(fpstarget);
if (winUpdateFreq < 1)
winUpdateFreq = 1;
int inst = Platform::InstanceID();
if (inst == 0)
sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget);
else
sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1);
changeWindowTitle(melontitle);
}
}
else
{
// paused
nframes = 0;
lastTime = SDL_GetPerformanceCounter() * perfCountsSec;
lastMeasureTime = lastTime;
emit windowUpdate();
EmuStatus = EmuRunning;
int inst = Platform::InstanceID();
if (inst == 0)
sprintf(melontitle, "melonDS " MELONDS_VERSION);
else
sprintf(melontitle, "melonDS (%d)", inst+1);
changeWindowTitle(melontitle);
SDL_Delay(75);
if (screenGL)
screenGL->drawScreenGL();
ContextRequestKind contextRequest = ContextRequest;
if (contextRequest == contextRequest_InitGL)
{
screenGL = static_cast<ScreenPanelGL*>(mainWindow->panel);
screenGL->initOpenGL();
ContextRequest = contextRequest_None;
}
else if (contextRequest == contextRequest_DeInitGL)
{
screenGL->deinitOpenGL();
screenGL = nullptr;
ContextRequest = contextRequest_None;
}
}
}
file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Write);
if (file)
{
RTC::StateData state;
NDS->RTC.GetState(state);
Platform::FileWrite(&state, sizeof(state), 1, file);
Platform::CloseFile(file);
}
EmuStatus = emuStatus_Exit;
NDS::Current = nullptr;
// nds is out of scope, so unique_ptr cleans it up for us
}
void EmuThread::changeWindowTitle(char* title)
{
emit windowTitleChange(QString(title));
}
void EmuThread::emuRun()
{
EmuRunning = emuStatus_Running;
EmuPauseStack = EmuPauseStackRunning;
RunningSomething = true;
// checkme
emit windowEmuStart();
AudioInOut::Enable();
}
void EmuThread::initContext()
{
ContextRequest = contextRequest_InitGL;
while (ContextRequest != contextRequest_None);
}
void EmuThread::deinitContext()
{
ContextRequest = contextRequest_DeInitGL;
while (ContextRequest != contextRequest_None);
}
void EmuThread::emuPause()
{
EmuPauseStack++;
if (EmuPauseStack > EmuPauseStackPauseThreshold) return;
PrevEmuStatus = EmuRunning;
EmuRunning = emuStatus_Paused;
while (EmuStatus != emuStatus_Paused);
AudioInOut::Disable();
}
void EmuThread::emuUnpause()
{
if (EmuPauseStack < EmuPauseStackPauseThreshold) return;
EmuPauseStack--;
if (EmuPauseStack >= EmuPauseStackPauseThreshold) return;
EmuRunning = PrevEmuStatus;
AudioInOut::Enable();
}
void EmuThread::emuStop()
{
EmuRunning = emuStatus_Exit;
EmuPauseStack = EmuPauseStackRunning;
AudioInOut::Disable();
}
void EmuThread::emuFrameStep()
{
if (EmuPauseStack < EmuPauseStackPauseThreshold) emit windowEmuPause();
EmuRunning = emuStatus_FrameStep;
}
bool EmuThread::emuIsRunning()
{
return EmuRunning == emuStatus_Running;
}
bool EmuThread::emuIsActive()
{
return (RunningSomething == 1);
}

View File

@ -0,0 +1,134 @@
/*
Copyright 2016-2023 melonDS team
This file is part of melonDS.
melonDS is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#ifndef EMUTHREAD_H
#define EMUTHREAD_H
#include <QThread>
#include <QMutex>
#include <atomic>
#include <variant>
#include <optional>
#include "NDSCart.h"
#include "GBACart.h"
using Keep = std::monostate;
using UpdateConsoleNDSArgs = std::variant<Keep, std::unique_ptr<melonDS::NDSCart::CartCommon>>;
using UpdateConsoleGBAArgs = std::variant<Keep, std::unique_ptr<melonDS::GBACart::CartCommon>>;
namespace melonDS
{
class NDS;
}
class ScreenPanelGL;
class EmuThread : public QThread
{
Q_OBJECT
void run() override;
public:
explicit EmuThread(QObject* parent = nullptr);
void changeWindowTitle(char* title);
// to be called from the UI thread
void emuRun();
void emuPause();
void emuUnpause();
void emuStop();
void emuFrameStep();
bool emuIsRunning();
bool emuIsActive();
void initContext();
void deinitContext();
int FrontBuffer = 0;
QMutex FrontBufferLock;
/// Applies the config in args.
/// Creates a new NDS console if needed,
/// modifies the existing one if possible.
/// @return \c true if the console was updated.
/// If this returns \c false, then the existing NDS console is not modified.
bool UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept;
std::unique_ptr<melonDS::NDS> NDS; // TODO: Proper encapsulation and synchronization
signals:
void windowUpdate();
void windowTitleChange(QString title);
void windowEmuStart();
void windowEmuStop();
void windowEmuPause();
void windowEmuReset();
void windowEmuFrameStep();
void windowLimitFPSChange();
void screenLayoutChange();
void windowFullscreenToggle();
void swapScreensToggle();
void screenEmphasisToggle();
void syncVolumeLevel();
private:
std::unique_ptr<melonDS::NDS> CreateConsole(
std::unique_ptr<melonDS::NDSCart::CartCommon>&& ndscart,
std::unique_ptr<melonDS::GBACart::CartCommon>&& gbacart
) noexcept;
enum EmuStatusKind
{
emuStatus_Exit,
emuStatus_Running,
emuStatus_Paused,
emuStatus_FrameStep,
};
std::atomic<EmuStatusKind> EmuStatus;
EmuStatusKind PrevEmuStatus;
EmuStatusKind EmuRunning;
constexpr static int EmuPauseStackRunning = 0;
constexpr static int EmuPauseStackPauseThreshold = 1;
int EmuPauseStack;
enum ContextRequestKind
{
contextRequest_None = 0,
contextRequest_InitGL,
contextRequest_DeInitGL
};
std::atomic<ContextRequestKind> ContextRequest = contextRequest_None;
ScreenPanelGL* screenGL;
int autoScreenSizing;
int videoRenderer;
bool videoSettingsDirty;
};
#endif // EMUTHREAD_H

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -24,6 +24,8 @@
#include "FirmwareSettingsDialog.h"
#include "ui_FirmwareSettingsDialog.h"
using namespace melonDS::Platform;
namespace Platform = melonDS::Platform;
FirmwareSettingsDialog* FirmwareSettingsDialog::currentDlg = nullptr;

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -22,6 +22,7 @@
#include "Input.h"
#include "Config.h"
using namespace melonDS;
namespace Input
{
@ -127,6 +128,11 @@ void KeyRelease(QKeyEvent* event)
KeyHotkeyMask &= ~(1<<i);
}
void KeyReleaseAll()
{
KeyInputMask = 0xFFF;
KeyHotkeyMask = 0;
}
bool JoystickButtonDown(int val)
{
@ -203,16 +209,22 @@ void Process()
}
JoyInputMask = 0xFFF;
for (int i = 0; i < 12; i++)
if (JoystickButtonDown(Config::JoyMapping[i]))
JoyInputMask &= ~(1<<i);
if (Joystick)
{
for (int i = 0; i < 12; i++)
if (JoystickButtonDown(Config::JoyMapping[i]))
JoyInputMask &= ~(1 << i);
}
InputMask = KeyInputMask & JoyInputMask;
JoyHotkeyMask = 0;
for (int i = 0; i < HK_MAX; i++)
if (JoystickButtonDown(Config::HKJoyMapping[i]))
JoyHotkeyMask |= (1<<i);
if (Joystick)
{
for (int i = 0; i < HK_MAX; i++)
if (JoystickButtonDown(Config::HKJoyMapping[i]))
JoyHotkeyMask |= (1 << i);
}
HotkeyMask = KeyHotkeyMask | JoyHotkeyMask;
HotkeyPress = HotkeyMask & ~LastHotkeyMask;

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -19,11 +19,14 @@
#ifndef INPUT_H
#define INPUT_H
#include <SDL2/SDL.h>
#include "types.h"
namespace Input
{
using namespace melonDS;
extern int JoystickID;
extern SDL_Joystick* Joystick;
@ -37,6 +40,7 @@ void CloseJoystick();
void KeyPress(QKeyEvent* event);
void KeyRelease(QKeyEvent* event);
void KeyReleaseAll();
void Process();

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -32,6 +32,7 @@
#include "ui_InputConfigDialog.h"
using namespace melonDS;
InputConfigDialog* InputConfigDialog::currentDlg = nullptr;
const int dskeyorder[12] = {0, 1, 10, 11, 5, 4, 6, 7, 9, 8, 2, 3};

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -52,7 +52,7 @@ protected:
{
if (!isChecked()) return QPushButton::keyPressEvent(event);
Platform::Log(Platform::Debug, "KEY PRESSED = %08X %08X | %08X %08X %08X\n", event->key(), (int)event->modifiers(), event->nativeVirtualKey(), event->nativeModifiers(), event->nativeScanCode());
Log(melonDS::Platform::Debug, "KEY PRESSED = %08X %08X | %08X %08X %08X\n", event->key(), (int)event->modifiers(), event->nativeVirtualKey(), event->nativeModifiers(), event->nativeScanCode());
int key = event->key();
int mod = event->modifiers();

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -16,15 +16,16 @@
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#include <QStyleFactory>
#include "InterfaceSettingsDialog.h"
#include "ui_InterfaceSettingsDialog.h"
#include "types.h"
#include "Platform.h"
#include "Config.h"
#include "main.h"
InterfaceSettingsDialog* InterfaceSettingsDialog::currentDlg = nullptr;
InterfaceSettingsDialog::InterfaceSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::InterfaceSettingsDialog)
{
ui->setupUi(this);
@ -34,6 +35,19 @@ InterfaceSettingsDialog::InterfaceSettingsDialog(QWidget* parent) : QDialog(pare
ui->spinMouseHideSeconds->setEnabled(Config::MouseHide != 0);
ui->spinMouseHideSeconds->setValue(Config::MouseHideSeconds);
ui->cbPauseLostFocus->setChecked(Config::PauseLostFocus != 0);
ui->spinMaxFPS->setValue(Config::MaxFPS);
const QList<QString> themeKeys = QStyleFactory::keys();
const QString currentTheme = qApp->style()->objectName();
ui->cbxUITheme->addItem("System default", "");
for (int i = 0; i < themeKeys.length(); i++)
{
ui->cbxUITheme->addItem(themeKeys[i], themeKeys[i]);
if (!Config::UITheme.empty() && themeKeys[i].compare(currentTheme, Qt::CaseInsensitive) == 0)
ui->cbxUITheme->setCurrentIndex(i + 1);
}
}
InterfaceSettingsDialog::~InterfaceSettingsDialog()
@ -60,9 +74,18 @@ void InterfaceSettingsDialog::done(int r)
Config::MouseHide = ui->cbMouseHide->isChecked() ? 1:0;
Config::MouseHideSeconds = ui->spinMouseHideSeconds->value();
Config::PauseLostFocus = ui->cbPauseLostFocus->isChecked() ? 1:0;
Config::MaxFPS = ui->spinMaxFPS->value();
QString themeName = ui->cbxUITheme->currentData().toString();
Config::UITheme = themeName.toStdString();
Config::Save();
if (!Config::UITheme.empty())
qApp->setStyle(themeName);
else
qApp->setStyle(*systemThemeName);
emit updateMouseTimer();
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>262</width>
<height>113</height>
<width>337</width>
<height>275</height>
</rect>
</property>
<property name="sizePolicy">
@ -19,32 +19,113 @@
<property name="windowTitle">
<string>Interface settings - melonDS</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0" alignment="Qt::AlignLeft">
<widget class="QLabel" name="label">
<property name="text">
<string>Hide after</string>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>User interface</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Theme</string>
</property>
<property name="buddy">
<cstring>cbxUITheme</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cbxUITheme"/>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="cbMouseHide">
<property name="text">
<string>Hide mouse after inactivity</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,1">
<property name="leftMargin">
<number>18</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>After</string>
</property>
<property name="buddy">
<cstring>spinMouseHideSeconds</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinMouseHideSeconds"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>seconds</string>
</property>
<property name="buddy">
<cstring>spinMouseHideSeconds</cstring>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="cbPauseLostFocus">
<property name="text">
<string>Pause emulation when window is not in focus</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="4">
<widget class="QCheckBox" name="cbPauseLostFocus">
<property name="text">
<string>Pause emulation when window is not in focus</string>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Framerate </string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,0">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Fast-forward limit</string>
</property>
<property name="buddy">
<cstring>spinMaxFPS</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinMaxFPS">
<property name="suffix">
<string> FPS</string>
</property>
<property name="minimum">
<number>60</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
<property name="value">
<number>1000</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0" colspan="5">
<widget class="QCheckBox" name="cbMouseHide">
<property name="text">
<string>Hide mouse after inactivity</string>
</property>
</widget>
</item>
<item row="1" column="1" alignment="Qt::AlignLeft">
<widget class="QSpinBox" name="spinMouseHideSeconds"/>
</item>
<item row="3" column="0" colspan="5">
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -54,20 +135,8 @@
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_2">
<property name="text">
<string>seconds of inactivity</string>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>cbMouseHide</tabstop>
<tabstop>spinMouseHideSeconds</tabstop>
<tabstop>cbPauseLostFocus</tabstop>
</tabstops>
<resources/>
<connections>
<connection>

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -41,6 +41,7 @@
#endif
#endif
using namespace melonDS;
using Platform::Log;
using Platform::LogLevel;

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -24,6 +24,7 @@
namespace LAN_PCap
{
using namespace melonDS;
struct AdapterData
{
char DeviceName[128];

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -37,6 +37,7 @@
#include <time.h>
#endif
using namespace melonDS;
namespace LAN_Socket
{

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -23,6 +23,7 @@
namespace LAN_Socket
{
using namespace melonDS;
//

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -38,6 +38,9 @@
#include "LocalMP.h"
#include "Platform.h"
using namespace melonDS;
using namespace melonDS::Platform;
using Platform::Log;
using Platform::LogLevel;
@ -127,7 +130,7 @@ bool SemInit(int num)
char semname[64];
sprintf(semname, "Local\\melonNIFI_Sem%02d", num);
HANDLE sem = CreateSemaphore(nullptr, 0, 64, semname);
HANDLE sem = CreateSemaphoreA(nullptr, 0, 64, semname);
SemPool[num] = sem;
SemInited[num] = true;
return sem != INVALID_HANDLE_VALUE;
@ -245,7 +248,9 @@ bool Init()
Log(LogLevel::Info, "MP sharedmem doesn't exist. creating\n");
if (!MPQueue->create(kQueueSize))
{
Log(LogLevel::Error, "MP sharedmem create failed :(\n");
Log(LogLevel::Error, "MP sharedmem create failed :( (%d)\n", MPQueue->error());
delete MPQueue;
MPQueue = nullptr;
return false;
}
@ -300,10 +305,13 @@ void DeInit()
if (MPQueue)
{
MPQueue->lock();
MPQueueHeader* header = (MPQueueHeader*)MPQueue->data();
header->ConnectedBitmask &= ~(1 << InstanceID);
header->InstanceBitmask &= ~(1 << InstanceID);
header->NumInstances--;
if (MPQueue->data() != nullptr)
{
MPQueueHeader *header = (MPQueueHeader *) MPQueue->data();
header->ConnectedBitmask &= ~(1 << InstanceID);
header->InstanceBitmask &= ~(1 << InstanceID);
header->NumInstances--;
}
MPQueue->unlock();
SemPoolDeinit();
@ -311,8 +319,8 @@ void DeInit()
MPQueue->detach();
}
MPQueue = nullptr;
delete MPQueue;
MPQueue = nullptr;
}
void SetRecvTimeout(int timeout)
@ -322,6 +330,7 @@ void SetRecvTimeout(int timeout)
void Begin()
{
if (!MPQueue) return;
MPQueue->lock();
MPQueueHeader* header = (MPQueueHeader*)MPQueue->data();
PacketReadOffset = header->PacketWriteOffset;
@ -334,6 +343,7 @@ void Begin()
void End()
{
if (!MPQueue) return;
MPQueue->lock();
MPQueueHeader* header = (MPQueueHeader*)MPQueue->data();
//SemReset(InstanceID);
@ -415,6 +425,7 @@ void FIFOWrite(int fifo, void* buf, int len)
int SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp)
{
if (!MPQueue) return 0;
MPQueue->lock();
u8* data = (u8*)MPQueue->data();
MPQueueHeader* header = (MPQueueHeader*)&data[0];
@ -470,6 +481,7 @@ int SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp)
int RecvPacketGeneric(u8* packet, bool block, u64* timestamp)
{
if (!MPQueue) return 0;
for (;;)
{
if (!SemWait(InstanceID, block ? RecvTimeout : 0))
@ -546,6 +558,8 @@ int SendAck(u8* packet, int len, u64 timestamp)
int RecvHostPacket(u8* packet, u64* timestamp)
{
if (!MPQueue) return -1;
if (LastHostID != -1)
{
// check if the host is still connected
@ -565,6 +579,8 @@ int RecvHostPacket(u8* packet, u64* timestamp)
u16 RecvReplies(u8* packets, u64 timestamp, u16 aidmask)
{
if (!MPQueue) return 0;
u16 ret = 0;
u16 myinstmask = (1 << InstanceID);
u16 curinstmask;

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -24,6 +24,7 @@
namespace LocalMP
{
using namespace melonDS;
bool Init();
void DeInit();

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.

View File

@ -1,472 +0,0 @@
/*
Copyright 2016-2022 melonDS team
This file is part of melonDS.
melonDS is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#include <stdio.h>
#include <string.h>
#include <deque>
#include <SDL2/SDL.h>
#include "../types.h"
#include "main.h"
#include "OpenGLSupport.h"
#include <QPainter>
#include "OSD.h"
#include "OSD_shaders.h"
#include "font.h"
#include "Config.h"
extern MainWindow* mainWindow;
namespace OSD
{
const u32 kOSDMargin = 6;
struct Item
{
Uint32 Timestamp;
char Text[256];
u32 Color;
u32 Width, Height;
u32* Bitmap;
bool NativeBitmapLoaded;
QImage NativeBitmap;
bool GLTextureLoaded;
GLuint GLTexture;
};
std::deque<Item> ItemQueue;
GLuint Shader;
GLint uScreenSize, uOSDPos, uOSDSize;
GLfloat uScaleFactor;
GLuint OSDVertexArray;
GLuint OSDVertexBuffer;
QMutex Rendering;
bool Init(bool openGL)
{
if (openGL)
{
OpenGL::CompileVertexFragmentProgram(Shader,
kScreenVS_OSD, kScreenFS_OSD,
"OSDShader",
{{"vPosition", 0}},
{{"oColor", 0}});
glUseProgram(Shader);
glUniform1i(glGetUniformLocation(Shader, "OSDTex"), 0);
uScreenSize = glGetUniformLocation(Shader, "uScreenSize");
uOSDPos = glGetUniformLocation(Shader, "uOSDPos");
uOSDSize = glGetUniformLocation(Shader, "uOSDSize");
uScaleFactor = glGetUniformLocation(Shader, "uScaleFactor");
float vertices[6*2] =
{
0, 0,
1, 1,
1, 0,
0, 0,
0, 1,
1, 1
};
glGenBuffers(1, &OSDVertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, OSDVertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glGenVertexArrays(1, &OSDVertexArray);
glBindVertexArray(OSDVertexArray);
glEnableVertexAttribArray(0); // position
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)(0));
}
return true;
}
void DeInit()
{
for (auto it = ItemQueue.begin(); it != ItemQueue.end(); )
{
Item& item = *it;
if (item.GLTextureLoaded) glDeleteTextures(1, &item.GLTexture);
if (item.Bitmap) delete[] item.Bitmap;
it = ItemQueue.erase(it);
}
}
int FindBreakPoint(const char* text, int i)
{
// i = character that went out of bounds
for (int j = i; j >= 0; j--)
{
if (text[j] == ' ')
return j;
}
return i;
}
void LayoutText(const char* text, u32* width, u32* height, int* breaks)
{
u32 w = 0;
u32 h = 14;
u32 totalw = 0;
u32 maxw = mainWindow->panelWidget->width() - (kOSDMargin*2);
int lastbreak = -1;
int numbrk = 0;
u16* ptr;
memset(breaks, 0, sizeof(int)*64);
for (int i = 0; text[i] != '\0'; )
{
int glyphsize;
if (text[i] == ' ')
{
glyphsize = 6;
}
else
{
u32 ch = text[i];
if (ch < 0x10 || ch > 0x7E) ch = 0x7F;
ptr = &font[(ch-0x10) << 4];
glyphsize = ptr[0];
if (!glyphsize) glyphsize = 6;
else glyphsize += 2; // space around the character
}
w += glyphsize;
if (w > maxw)
{
// wrap shit as needed
if (text[i] == ' ')
{
if (numbrk >= 64) break;
breaks[numbrk++] = i;
i++;
}
else
{
int brk = FindBreakPoint(text, i);
if (brk != lastbreak) i = brk;
if (numbrk >= 64) break;
breaks[numbrk++] = i;
lastbreak = brk;
}
w = 0;
h += 14;
}
else
i++;
if (w > totalw) totalw = w;
}
*width = totalw;
*height = h;
}
u32 RainbowColor(u32 inc)
{
// inspired from Acmlmboard
if (inc < 100) return 0xFFFF9B9B + (inc << 8);
else if (inc < 200) return 0xFFFFFF9B - ((inc-100) << 16);
else if (inc < 300) return 0xFF9BFF9B + (inc-200);
else if (inc < 400) return 0xFF9BFFFF - ((inc-300) << 8);
else if (inc < 500) return 0xFF9B9BFF + ((inc-400) << 16);
else return 0xFFFF9BFF - (inc-500);
}
void RenderText(u32 color, const char* text, Item* item)
{
u32 w, h;
int breaks[64];
bool rainbow = (color == 0);
u32 rainbowinc = ((text[0] * 17) + (SDL_GetTicks() * 13)) % 600;
color |= 0xFF000000;
const u32 shadow = 0xE0000000;
LayoutText(text, &w, &h, breaks);
item->Width = w;
item->Height = h;
item->Bitmap = new u32[w*h];
memset(item->Bitmap, 0, w*h*sizeof(u32));
u32 x = 0, y = 1;
u32 maxw = mainWindow->panelWidget->width() - (kOSDMargin*2);
int curline = 0;
u16* ptr;
for (int i = 0; text[i] != '\0'; )
{
int glyphsize;
if (text[i] == ' ')
{
x += 6;
}
else
{
u32 ch = text[i];
if (ch < 0x10 || ch > 0x7E) ch = 0x7F;
ptr = &font[(ch-0x10) << 4];
int glyphsize = ptr[0];
if (!glyphsize) x += 6;
else
{
x++;
if (rainbow)
{
color = RainbowColor(rainbowinc);
rainbowinc = (rainbowinc + 30) % 600;
}
// draw character
for (int cy = 0; cy < 12; cy++)
{
u16 val = ptr[4+cy];
for (int cx = 0; cx < glyphsize; cx++)
{
if (val & (1<<cx))
item->Bitmap[((y+cy) * w) + x+cx] = color;
}
}
x += glyphsize;
x++;
}
}
i++;
if (breaks[curline] && i >= breaks[curline])
{
i = breaks[curline++];
if (text[i] == ' ') i++;
x = 0;
y += 14;
}
}
// shadow
for (y = 0; y < h; y++)
{
for (x = 0; x < w; x++)
{
u32 val;
val = item->Bitmap[(y * w) + x];
if ((val >> 24) == 0xFF) continue;
if (x > 0) val = item->Bitmap[(y * w) + x-1];
if (x < w-1) val |= item->Bitmap[(y * w) + x+1];
if (y > 0)
{
if (x > 0) val |= item->Bitmap[((y-1) * w) + x-1];
val |= item->Bitmap[((y-1) * w) + x];
if (x < w-1) val |= item->Bitmap[((y-1) * w) + x+1];
}
if (y < h-1)
{
if (x > 0) val |= item->Bitmap[((y+1) * w) + x-1];
val |= item->Bitmap[((y+1) * w) + x];
if (x < w-1) val |= item->Bitmap[((y+1) * w) + x+1];
}
if ((val >> 24) == 0xFF)
item->Bitmap[(y * w) + x] = shadow;
}
}
}
void AddMessage(u32 color, const char* text)
{
if (!Config::ShowOSD) return;
Rendering.lock();
Item item;
item.Timestamp = SDL_GetTicks();
strncpy(item.Text, text, 255); item.Text[255] = '\0';
item.Color = color;
item.Bitmap = nullptr;
item.NativeBitmapLoaded = false;
item.GLTextureLoaded = false;
ItemQueue.push_back(item);
Rendering.unlock();
}
void Update()
{
if (!Config::ShowOSD)
{
Rendering.lock();
for (auto it = ItemQueue.begin(); it != ItemQueue.end(); )
{
Item& item = *it;
if (item.GLTextureLoaded) glDeleteTextures(1, &item.GLTexture);
if (item.Bitmap) delete[] item.Bitmap;
it = ItemQueue.erase(it);
}
Rendering.unlock();
return;
}
Rendering.lock();
Uint32 tick_now = SDL_GetTicks();
Uint32 tick_min = tick_now - 2500;
for (auto it = ItemQueue.begin(); it != ItemQueue.end(); )
{
Item& item = *it;
if (item.Timestamp < tick_min)
{
if (item.GLTextureLoaded) glDeleteTextures(1, &item.GLTexture);
if (item.Bitmap) delete[] item.Bitmap;
it = ItemQueue.erase(it);
continue;
}
if (!item.Bitmap)
{
RenderText(item.Color, item.Text, &item);
}
it++;
}
Rendering.unlock();
}
void DrawNative(QPainter& painter)
{
if (!Config::ShowOSD) return;
Rendering.lock();
u32 y = kOSDMargin;
painter.resetTransform();
for (auto it = ItemQueue.begin(); it != ItemQueue.end(); )
{
Item& item = *it;
if (!item.NativeBitmapLoaded)
{
item.NativeBitmap = QImage((const uchar*)item.Bitmap, item.Width, item.Height, QImage::Format_ARGB32_Premultiplied);
item.NativeBitmapLoaded = true;
}
painter.drawImage(kOSDMargin, y, item.NativeBitmap);
y += item.Height;
it++;
}
Rendering.unlock();
}
void DrawGL(float w, float h)
{
if (!Config::ShowOSD) return;
if (!mainWindow || !mainWindow->panel) return;
Rendering.lock();
u32 y = kOSDMargin;
glUseProgram(Shader);
glUniform2f(uScreenSize, w, h);
glUniform1f(uScaleFactor, mainWindow->devicePixelRatioF());
glBindBuffer(GL_ARRAY_BUFFER, OSDVertexBuffer);
glBindVertexArray(OSDVertexArray);
glActiveTexture(GL_TEXTURE0);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
for (auto it = ItemQueue.begin(); it != ItemQueue.end(); )
{
Item& item = *it;
if (!item.GLTextureLoaded)
{
glGenTextures(1, &item.GLTexture);
glBindTexture(GL_TEXTURE_2D, item.GLTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, item.Width, item.Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, item.Bitmap);
item.GLTextureLoaded = true;
}
glBindTexture(GL_TEXTURE_2D, item.GLTexture);
glUniform2i(uOSDPos, kOSDMargin, y);
glUniform2i(uOSDSize, item.Width, item.Height);
glDrawArrays(GL_TRIANGLES, 0, 2*3);
y += item.Height;
it++;
}
glDisable(GL_BLEND);
glUseProgram(0);
Rendering.unlock();
}
}

View File

@ -1,36 +0,0 @@
/*
Copyright 2016-2022 melonDS team
This file is part of melonDS.
melonDS is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#ifndef OSD_H
#define OSD_H
namespace OSD
{
bool Init(bool openGL);
void DeInit();
void AddMessage(u32 color, const char* text);
void Update();
void DrawNative(QPainter& painter);
void DrawGL(float w, float h);
}
#endif // OSD_H

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -19,6 +19,7 @@
#include <stdio.h>
#include <QFileDialog>
#include <QMessageBox>
#include <QTemporaryFile>
#include "types.h"
#include "Config.h"
@ -27,6 +28,8 @@
#include "PathSettingsDialog.h"
#include "ui_PathSettingsDialog.h"
using namespace melonDS::Platform;
namespace Platform = melonDS::Platform;
PathSettingsDialog* PathSettingsDialog::currentDlg = nullptr;
@ -35,6 +38,7 @@ extern bool RunningSomething;
bool PathSettingsDialog::needsReset = false;
constexpr char errordialog[] = "melonDS cannot write to that directory.";
PathSettingsDialog::PathSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::PathSettingsDialog)
{
@ -99,6 +103,12 @@ void PathSettingsDialog::on_btnSaveFileBrowse_clicked()
QString::fromStdString(EmuDirectory));
if (dir.isEmpty()) return;
if (!QTemporaryFile(dir).open())
{
QMessageBox::critical(this, "melonDS", errordialog);
return;
}
ui->txtSaveFilePath->setText(dir);
}
@ -110,6 +120,12 @@ void PathSettingsDialog::on_btnSavestateBrowse_clicked()
QString::fromStdString(EmuDirectory));
if (dir.isEmpty()) return;
if (!QTemporaryFile(dir).open())
{
QMessageBox::critical(this, "melonDS", errordialog);
return;
}
ui->txtSavestatePath->setText(dir);
}
@ -121,6 +137,12 @@ void PathSettingsDialog::on_btnCheatFileBrowse_clicked()
QString::fromStdString(EmuDirectory));
if (dir.isEmpty()) return;
if (!QTemporaryFile(dir).open())
{
QMessageBox::critical(this, "melonDS", errordialog);
return;
}
ui->txtCheatFilePath->setText(dir);
}

View File

@ -1,6 +1,6 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -21,14 +21,17 @@
#include <string.h>
#include <string>
#include <QCoreApplication>
#include <QStandardPaths>
#include <QString>
#include <QDateTime>
#include <QDir>
#include <QThread>
#include <QSemaphore>
#include <QMutex>
#include <QOpenGLContext>
#include <QSharedMemory>
#include <QTemporaryFile>
#include <SDL_loadso.h>
#include "Platform.h"
@ -38,7 +41,6 @@
#include "LAN_Socket.h"
#include "LAN_PCap.h"
#include "LocalMP.h"
#include "OSD.h"
#include "SPI_Firmware.h"
#ifdef __WIN32__
@ -52,10 +54,50 @@ extern CameraManager* camManager[2];
void emuStop();
// TEMP
//#include "main.h"
//extern MainWindow* mainWindow;
namespace Platform
namespace melonDS::Platform
{
void PathInit(int argc, char** argv)
{
// First, check for the portable directory next to the executable.
QString appdirpath = QCoreApplication::applicationDirPath();
QString portablepath = appdirpath + QDir::separator() + "portable";
#if defined(__APPLE__)
// On Apple platforms we may need to navigate outside an app bundle.
// The executable directory would be "melonDS.app/Contents/MacOS", so we need to go a total of three steps up.
QDir bundledir(appdirpath);
if (bundledir.cd("..") && bundledir.cd("..") && bundledir.dirName().endsWith(".app") && bundledir.cd(".."))
{
portablepath = bundledir.absolutePath() + QDir::separator() + "portable";
}
#endif
QDir portabledir(portablepath);
if (portabledir.exists())
{
EmuDirectory = portabledir.absolutePath().toStdString();
}
else
{
// If no overrides are specified, use the default path.
#if defined(__WIN32__) && defined(WIN32_PORTABLE)
EmuDirectory = appdirpath.toStdString();
#else
QString confdir;
QDir config(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation));
config.mkdir("melonDS");
confdir = config.absolutePath() + QDir::separator() + "melonDS";
EmuDirectory = confdir.toStdString();
#endif
}
}
QSharedMemory* IPCBuffer = nullptr;
int IPCInstanceID;
@ -65,12 +107,25 @@ void IPCInit()
IPCBuffer = new QSharedMemory("melonIPC");
#if !defined(Q_OS_WINDOWS)
// QSharedMemory instances can be left over from crashed processes on UNIX platforms.
// To prevent melonDS thinking there's another instance, we attach and then immediately detach from the
// shared memory. If no other process was actually using it, it'll be destroyed and we'll have a clean
// shared memory buffer after creating it again below.
if (IPCBuffer->attach())
{
IPCBuffer->detach();
delete IPCBuffer;
IPCBuffer = new QSharedMemory("melonIPC");
}
#endif
if (!IPCBuffer->attach())
{
Log(LogLevel::Info, "IPC sharedmem doesn't exist. creating\n");
if (!IPCBuffer->create(1024))
{
Log(LogLevel::Error, "IPC sharedmem create failed :(\n");
Log(LogLevel::Error, "IPC sharedmem create failed: %s\n", IPCBuffer->errorString().toStdString().c_str());
delete IPCBuffer;
IPCBuffer = nullptr;
return;
@ -116,38 +171,7 @@ void IPCDeInit()
void Init(int argc, char** argv)
{
#if defined(__WIN32__) || defined(PORTABLE)
if (argc > 0 && strlen(argv[0]) > 0)
{
int len = strlen(argv[0]);
while (len > 0)
{
if (argv[0][len] == '/') break;
if (argv[0][len] == '\\') break;
len--;
}
if (len > 0)
{
std::string emudir = argv[0];
EmuDirectory = emudir.substr(0, len);
}
else
{
EmuDirectory = ".";
}
}
else
{
EmuDirectory = ".";
}
#else
QString confdir;
QDir config(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation));
config.mkdir("melonDS");
confdir = config.absolutePath() + "/melonDS/";
EmuDirectory = confdir.toStdString();
#endif
PathInit(argc, argv);
IPCInit();
}
@ -163,14 +187,14 @@ void SignalStop(StopReason reason)
{
case StopReason::GBAModeNotSupported:
Log(LogLevel::Error, "!! GBA MODE NOT SUPPORTED\n");
OSD::AddMessage(0xFFA0A0, "GBA mode not supported.");
//mainWindow->osdAddMessage(0xFFA0A0, "GBA mode not supported.");
break;
case StopReason::BadExceptionRegion:
OSD::AddMessage(0xFFA0A0, "Internal error.");
//mainWindow->osdAddMessage(0xFFA0A0, "Internal error.");
break;
case StopReason::PowerOff:
case StopReason::External:
OSD::AddMessage(0xFFC040, "Shutdown");
//mainWindow->osdAddMessage(0xFFC040, "Shutdown");
default:
break;
}
@ -192,119 +216,33 @@ std::string InstanceFileSuffix()
return suffix;
}
int GetConfigInt(ConfigEntry entry)
static QIODevice::OpenMode GetQMode(FileMode mode)
{
const int imgsizes[] = {0, 256, 512, 1024, 2048, 4096};
QIODevice::OpenMode qmode = QIODevice::OpenModeFlag::NotOpen;
if (mode & FileMode::Read)
qmode |= QIODevice::OpenModeFlag::ReadOnly;
if (mode & FileMode::Write)
qmode |= QIODevice::OpenModeFlag::WriteOnly;
if (mode & FileMode::Append)
qmode |= QIODevice::OpenModeFlag::Append;
switch (entry)
{
#ifdef JIT_ENABLED
case JIT_MaxBlockSize: return Config::JIT_MaxBlockSize;
#endif
if ((mode & FileMode::Write) && !(mode & FileMode::Preserve))
qmode |= QIODevice::OpenModeFlag::Truncate;
case DLDI_ImageSize: return imgsizes[Config::DLDISize];
if (mode & FileMode::NoCreate)
qmode |= QIODevice::OpenModeFlag::ExistingOnly;
case DSiSD_ImageSize: return imgsizes[Config::DSiSDSize];
if (mode & FileMode::Text)
qmode |= QIODevice::OpenModeFlag::Text;
case Firm_Language: return Config::FirmwareLanguage;
case Firm_BirthdayMonth: return Config::FirmwareBirthdayMonth;
case Firm_BirthdayDay: return Config::FirmwareBirthdayDay;
case Firm_Color: return Config::FirmwareFavouriteColour;
case AudioBitDepth: return Config::AudioBitDepth;
}
return 0;
}
bool GetConfigBool(ConfigEntry entry)
{
switch (entry)
{
#ifdef JIT_ENABLED
case JIT_Enable: return Config::JIT_Enable != 0;
case JIT_LiteralOptimizations: return Config::JIT_LiteralOptimisations != 0;
case JIT_BranchOptimizations: return Config::JIT_BranchOptimisations != 0;
case JIT_FastMemory: return Config::JIT_FastMemory != 0;
#endif
case ExternalBIOSEnable: return Config::ExternalBIOSEnable != 0;
case DLDI_Enable: return Config::DLDIEnable != 0;
case DLDI_ReadOnly: return Config::DLDIReadOnly != 0;
case DLDI_FolderSync: return Config::DLDIFolderSync != 0;
case DSiSD_Enable: return Config::DSiSDEnable != 0;
case DSiSD_ReadOnly: return Config::DSiSDReadOnly != 0;
case DSiSD_FolderSync: return Config::DSiSDFolderSync != 0;
case Firm_OverrideSettings: return Config::FirmwareOverrideSettings != 0;
case DSi_FullBIOSBoot: return Config::DSiFullBIOSBoot != 0;
}
return false;
}
std::string GetConfigString(ConfigEntry entry)
{
switch (entry)
{
case DSi_NANDPath: return Config::DSiNANDPath;
case DLDI_ImagePath: return Config::DLDISDPath;
case DLDI_FolderPath: return Config::DLDIFolderPath;
case DSiSD_ImagePath: return Config::DSiSDPath;
case DSiSD_FolderPath: return Config::DSiSDFolderPath;
case Firm_Username: return Config::FirmwareUsername;
case Firm_Message: return Config::FirmwareMessage;
case WifiSettingsPath: return Config::WifiSettingsPath;
}
return "";
}
bool GetConfigArray(ConfigEntry entry, void* data)
{
switch (entry)
{
case Firm_MAC:
{
std::string& mac_in = Config::FirmwareMAC;
u8* mac_out = (u8*)data;
int o = 0;
u8 tmp = 0;
for (int i = 0; i < 18; i++)
{
char c = mac_in[i];
if (c == '\0') break;
int n;
if (c >= '0' && c <= '9') n = c - '0';
else if (c >= 'a' && c <= 'f') n = c - 'a' + 10;
else if (c >= 'A' && c <= 'F') n = c - 'A' + 10;
else continue;
if (!(o & 1))
tmp = n;
else
mac_out[o >> 1] = n | (tmp << 4);
o++;
if (o >= 12) return true;
}
}
return false;
}
return false;
return qmode;
}
constexpr char AccessMode(FileMode mode, bool file_exists)
{
if (mode & FileMode::Append)
return 'a';
if (!(mode & FileMode::Write))
// If we're only opening the file for reading...
return 'r';
@ -343,16 +281,21 @@ static std::string GetModeString(FileMode mode, bool file_exists)
FileHandle* OpenFile(const std::string& path, FileMode mode)
{
if ((mode & FileMode::ReadWrite) == FileMode::None)
if ((mode & (FileMode::ReadWrite | FileMode::Append)) == FileMode::None)
{ // If we aren't reading or writing, then we can't open the file
Log(LogLevel::Error, "Attempted to open \"%s\" in neither read nor write mode (FileMode 0x%x)\n", path.c_str(), mode);
return nullptr;
}
bool file_exists = QFile::exists(QString::fromStdString(path));
std::string modeString = GetModeString(mode, file_exists);
QString qpath{QString::fromStdString(path)};
std::string modeString = GetModeString(mode, QFile::exists(qpath));
QIODevice::OpenMode qmode = GetQMode(mode);
QFile qfile{qpath};
qfile.open(qmode);
FILE* file = fdopen(dup(qfile.handle()), modeString.c_str());
qfile.close();
FILE* file = fopen(path.c_str(), modeString.c_str());
if (file)
{
Log(LogLevel::Debug, "Opened \"%s\" with FileMode 0x%x (effective mode \"%s\")\n", path.c_str(), mode, modeString.c_str());
@ -378,15 +321,7 @@ FileHandle* OpenLocalFile(const std::string& path, FileMode mode)
}
else
{
#ifdef PORTABLE
fullpath = QString::fromStdString(EmuDirectory) + QDir::separator() + qpath;
#else
// Check user configuration directory
QDir config(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation));
config.mkdir("melonDS");
fullpath = config.absolutePath() + "/melonDS/";
fullpath.append(qpath);
#endif
}
return OpenFile(fullpath.toStdString(), mode);
@ -423,6 +358,44 @@ bool LocalFileExists(const std::string& name)
return true;
}
bool CheckFileWritable(const std::string& filepath)
{
FileHandle* file = Platform::OpenFile(filepath.c_str(), FileMode::Read);
if (file)
{
// if the file exists, check if it can be opened for writing.
Platform::CloseFile(file);
file = Platform::OpenFile(filepath.c_str(), FileMode::Append);
if (file)
{
Platform::CloseFile(file);
return true;
}
else return false;
}
else
{
// if the file does not exist, create a temporary file to check, to avoid creating an empty file.
if (QTemporaryFile(filepath.c_str()).open())
{
return true;
}
else return false;
}
}
bool CheckLocalFileWritable(const std::string& name)
{
FileHandle* file = Platform::OpenLocalFile(name.c_str(), FileMode::Append);
if (file)
{
Platform::CloseFile(file);
return true;
}
else return false;
}
bool FileSeek(FileHandle* file, s64 offset, FileSeekOrigin origin)
{
int stdorigin;
@ -578,37 +551,46 @@ void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen
ROMManager::GBASave->RequestFlush(savedata, savelen, writeoffset, writelen);
}
void WriteFirmware(const SPI_Firmware::Firmware& firmware, u32 writeoffset, u32 writelen)
void WriteFirmware(const Firmware& firmware, u32 writeoffset, u32 writelen)
{
if (!ROMManager::FirmwareSave)
return;
if (firmware.Header().Identifier != SPI_Firmware::GENERATED_FIRMWARE_IDENTIFIER)
if (firmware.GetHeader().Identifier != GENERATED_FIRMWARE_IDENTIFIER)
{ // If this is not the default built-in firmware...
// ...then write the whole thing back.
ROMManager::FirmwareSave->RequestFlush(firmware.Buffer(), firmware.Length(), writeoffset, writelen);
}
else
{
u32 eapstart = firmware.ExtendedAccessPointOffset();
u32 eapend = eapstart + sizeof(firmware.ExtendedAccessPoints());
u32 eapstart = firmware.GetExtendedAccessPointOffset();
u32 eapend = eapstart + sizeof(firmware.GetExtendedAccessPoints());
u32 apstart = firmware.WifiAccessPointOffset();
u32 apend = apstart + sizeof(firmware.AccessPoints());
u32 apstart = firmware.GetWifiAccessPointOffset();
u32 apend = apstart + sizeof(firmware.GetAccessPoints());
// assert that the extended access points come just before the regular ones
assert(eapend == apstart);
if (eapstart <= writeoffset && writeoffset < apend)
{ // If we're writing to the access points...
const u8* buffer = firmware.ExtendedAccessPointPosition();
u32 length = sizeof(firmware.ExtendedAccessPoints()) + sizeof(firmware.AccessPoints());
const u8* buffer = firmware.GetExtendedAccessPointPosition();
u32 length = sizeof(firmware.GetExtendedAccessPoints()) + sizeof(firmware.GetAccessPoints());
ROMManager::FirmwareSave->RequestFlush(buffer, length, writeoffset - eapstart, writelen);
}
}
}
void WriteDateTime(int year, int month, int day, int hour, int minute, int second)
{
QDateTime hosttime = QDateTime::currentDateTime();
QDateTime time = QDateTime(QDate(year, month, day), QTime(hour, minute, second));
Config::RTCOffset = hosttime.secsTo(time);
Config::Save();
}
bool MP_Init()
{
return LocalMP::Init();

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -22,49 +22,66 @@
#include "SPI.h"
#include "DSi_I2C.h"
#include "NDS.h"
#include "DSi.h"
#include "Config.h"
#include "Platform.h"
#include "types.h"
#include <QtDebug>
#include "main.h"
using namespace melonDS;
PowerManagementDialog* PowerManagementDialog::currentDlg = nullptr;
PowerManagementDialog::PowerManagementDialog(QWidget* parent) : QDialog(parent), ui(new Ui::PowerManagementDialog)
PowerManagementDialog::PowerManagementDialog(QWidget* parent, EmuThread* emuThread) : QDialog(parent), emuThread(emuThread), ui(new Ui::PowerManagementDialog)
{
inited = false;
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
if (NDS::ConsoleType == 1)
if (emuThread->NDS->ConsoleType == 1)
{
ui->grpDSBattery->setEnabled(false);
oldDSiBatteryLevel = DSi_BPTWL::GetBatteryLevel();
oldDSiBatteryCharging = DSi_BPTWL::GetBatteryCharging();
auto& dsi = static_cast<DSi&>(*emuThread->NDS);
oldDSiBatteryLevel = dsi.I2C.GetBPTWL()->GetBatteryLevel();
oldDSiBatteryCharging = dsi.I2C.GetBPTWL()->GetBatteryCharging();
}
else
{
ui->grpDSiBattery->setEnabled(false);
oldDSBatteryLevel = SPI_Powerman::GetBatteryLevelOkay();
oldDSBatteryLevel = emuThread->NDS->SPI.GetPowerMan()->GetBatteryLevelOkay();
}
updateDSBatteryLevelControls();
ui->cbDSiBatteryCharging->setChecked(DSi_BPTWL::GetBatteryCharging());
int dsiBatterySliderPos;
switch (DSi_BPTWL::GetBatteryLevel())
bool defaultDSiBatteryCharging = (emuThread->NDS->ConsoleType == 1) ? Config::DSiBatteryCharging : false;
if (emuThread->NDS->ConsoleType == 1)
{
auto& dsi = static_cast<DSi&>(*emuThread->NDS);
ui->cbDSiBatteryCharging->setChecked(dsi.I2C.GetBPTWL()->GetBatteryCharging());
int dsiBatterySliderPos = 4;
switch (dsi.I2C.GetBPTWL()->GetBatteryLevel())
{
case DSi_BPTWL::batteryLevel_AlmostEmpty: dsiBatterySliderPos = 0; break;
case DSi_BPTWL::batteryLevel_Low: dsiBatterySliderPos = 1; break;
case DSi_BPTWL::batteryLevel_Half: dsiBatterySliderPos = 2; break;
case DSi_BPTWL::batteryLevel_ThreeQuarters: dsiBatterySliderPos = 3; break;
case DSi_BPTWL::batteryLevel_Full: dsiBatterySliderPos = 4; break;
}
ui->sliderDSiBatteryLevel->setValue(dsiBatterySliderPos);
}
ui->sliderDSiBatteryLevel->setValue(dsiBatterySliderPos);
else
{
ui->cbDSiBatteryCharging->setChecked(Config::DSiBatteryCharging);
ui->sliderDSiBatteryLevel->setValue(Config::DSiBatteryLevel);
}
int inst = Platform::InstanceID();
if (inst > 0)
@ -84,26 +101,28 @@ void PowerManagementDialog::done(int r)
{
if (r == QDialog::Accepted)
{
if (NDS::ConsoleType == 1)
if (emuThread->NDS->ConsoleType == 1)
{
Config::DSiBatteryLevel = DSi_BPTWL::GetBatteryLevel();
Config::DSiBatteryCharging = DSi_BPTWL::GetBatteryCharging();
auto& dsi = static_cast<DSi&>(*emuThread->NDS);
Config::DSiBatteryLevel = dsi.I2C.GetBPTWL()->GetBatteryLevel();
Config::DSiBatteryCharging = dsi.I2C.GetBPTWL()->GetBatteryCharging();
}
else
{
Config::DSBatteryLevelOkay = SPI_Powerman::GetBatteryLevelOkay();
Config::DSBatteryLevelOkay = emuThread->NDS->SPI.GetPowerMan()->GetBatteryLevelOkay();
}
}
else
{
if (NDS::ConsoleType == 1)
if (emuThread->NDS->ConsoleType == 1)
{
DSi_BPTWL::SetBatteryLevel(oldDSiBatteryLevel);
DSi_BPTWL::SetBatteryCharging(oldDSiBatteryCharging);
auto& dsi = static_cast<DSi&>(*emuThread->NDS);
dsi.I2C.GetBPTWL()->SetBatteryLevel(oldDSiBatteryLevel);
dsi.I2C.GetBPTWL()->SetBatteryCharging(oldDSiBatteryCharging);
}
else
{
SPI_Powerman::SetBatteryLevelOkay(oldDSBatteryLevel);
emuThread->NDS->SPI.GetPowerMan()->SetBatteryLevelOkay(oldDSBatteryLevel);
}
}
@ -114,17 +133,17 @@ void PowerManagementDialog::done(int r)
void PowerManagementDialog::on_rbDSBatteryLow_clicked()
{
SPI_Powerman::SetBatteryLevelOkay(false);
emuThread->NDS->SPI.GetPowerMan()->SetBatteryLevelOkay(false);
}
void PowerManagementDialog::on_rbDSBatteryOkay_clicked()
{
SPI_Powerman::SetBatteryLevelOkay(true);
emuThread->NDS->SPI.GetPowerMan()->SetBatteryLevelOkay(true);
}
void PowerManagementDialog::updateDSBatteryLevelControls()
{
if (SPI_Powerman::GetBatteryLevelOkay())
if (emuThread->NDS->SPI.GetPowerMan()->GetBatteryLevelOkay())
ui->rbDSBatteryOkay->setChecked(true);
else
ui->rbDSBatteryLow->setChecked(true);
@ -132,23 +151,32 @@ void PowerManagementDialog::updateDSBatteryLevelControls()
void PowerManagementDialog::on_cbDSiBatteryCharging_toggled()
{
DSi_BPTWL::SetBatteryCharging(ui->cbDSiBatteryCharging->isChecked());
if (emuThread->NDS->ConsoleType == 1)
{
auto& dsi = static_cast<DSi&>(*emuThread->NDS);
dsi.I2C.GetBPTWL()->SetBatteryCharging(ui->cbDSiBatteryCharging->isChecked());
}
}
void PowerManagementDialog::on_sliderDSiBatteryLevel_valueChanged(int value)
{
if (!inited) return;
u8 newBatteryLevel;
switch (value)
if (emuThread->NDS->ConsoleType == 1)
{
auto& dsi = static_cast<DSi&>(*emuThread->NDS);
u8 newBatteryLevel = DSi_BPTWL::batteryLevel_Full;
switch (value)
{
case 0: newBatteryLevel = DSi_BPTWL::batteryLevel_AlmostEmpty; break;
case 1: newBatteryLevel = DSi_BPTWL::batteryLevel_Low; break;
case 2: newBatteryLevel = DSi_BPTWL::batteryLevel_Half; break;
case 3: newBatteryLevel = DSi_BPTWL::batteryLevel_ThreeQuarters; break;
case 4: newBatteryLevel = DSi_BPTWL::batteryLevel_Full; break;
}
dsi.I2C.GetBPTWL()->SetBatteryLevel(newBatteryLevel);
}
DSi_BPTWL::SetBatteryLevel(newBatteryLevel);
updateDSBatteryLevelControls();
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -25,6 +25,7 @@
#include "types.h"
namespace Ui { class PowerManagementDialog; }
class EmuThread;
class PowerManagementDialog;
class PowerManagementDialog : public QDialog
@ -32,11 +33,11 @@ class PowerManagementDialog : public QDialog
Q_OBJECT
public:
explicit PowerManagementDialog(QWidget* parent);
explicit PowerManagementDialog(QWidget* parent, EmuThread* emu_thread);
~PowerManagementDialog();
static PowerManagementDialog* currentDlg;
static PowerManagementDialog* openDlg(QWidget* parent)
static PowerManagementDialog* openDlg(QWidget* parent, EmuThread* emu_thread)
{
if (currentDlg)
{
@ -44,7 +45,7 @@ public:
return currentDlg;
}
currentDlg = new PowerManagementDialog(parent);
currentDlg = new PowerManagementDialog(parent, emu_thread);
currentDlg->open();
return currentDlg;
}
@ -64,10 +65,11 @@ private slots:
private:
Ui::PowerManagementDialog* ui;
EmuThread* emuThread;
bool inited;
bool oldDSBatteryLevel;
u8 oldDSiBatteryLevel;
melonDS::u8 oldDSiBatteryLevel;
bool oldDSiBatteryCharging;
void updateDSBatteryLevelControls();

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.

View File

@ -21,18 +21,19 @@
#include "main.h"
using namespace melonDS;
extern EmuThread* emuThread;
s32 GetMainRAMValue(const u32& addr, const ramInfo_ByteType& byteType)
s32 GetMainRAMValue(NDS& nds, const u32& addr, const ramInfo_ByteType& byteType)
{
switch (byteType)
{
case ramInfo_OneByte:
return *(s8*)(NDS::MainRAM + (addr&NDS::MainRAMMask));
return *(s8*)(nds.MainRAM + (addr&nds.MainRAMMask));
case ramInfo_TwoBytes:
return *(s16*)(NDS::MainRAM + (addr&NDS::MainRAMMask));
return *(s16*)(nds.MainRAM + (addr&nds.MainRAMMask));
case ramInfo_FourBytes:
return *(s32*)(NDS::MainRAM + (addr&NDS::MainRAMMask));
return *(s32*)(nds.MainRAM + (addr&nds.MainRAMMask));
default:
return 0;
}
@ -40,7 +41,7 @@ s32 GetMainRAMValue(const u32& addr, const ramInfo_ByteType& byteType)
RAMInfoDialog* RAMInfoDialog::currentDlg = nullptr;
RAMInfoDialog::RAMInfoDialog(QWidget* parent) : QDialog(parent), ui(new Ui::RAMInfoDialog)
RAMInfoDialog::RAMInfoDialog(QWidget* parent, EmuThread* emuThread) : QDialog(parent), emuThread(emuThread), ui(new Ui::RAMInfoDialog)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
@ -90,7 +91,7 @@ void RAMInfoDialog::ShowRowsInTable()
for (u32 row = scrollValue; row < std::min<u32>(scrollValue+25, RowDataVector->size()); row++)
{
ramInfo_RowData& rowData = RowDataVector->at(row);
rowData.Update(SearchThread->GetSearchByteType());
rowData.Update(*emuThread->NDS, SearchThread->GetSearchByteType());
if (ui->ramTable->item(row, ramInfo_Address) == nullptr)
{
@ -185,7 +186,7 @@ void RAMInfoDialog::on_ramTable_itemChanged(QTableWidgetItem *item)
s32 itemValue = item->text().toInt();
if (rowData.Value != itemValue)
rowData.SetValue(itemValue);
rowData.SetValue(*emuThread->NDS, itemValue);
}
/**
@ -240,14 +241,14 @@ void RAMSearchThread::run()
if (SearchMode == ramInfoSTh_SearchAll || RowDataVector->size() == 0)
{
// First search mode
for (u32 addr = 0x02000000; SearchRunning && addr < 0x02000000+NDS::MainRAMMaxSize; addr += SearchByteType)
for (u32 addr = 0x02000000; SearchRunning && addr < 0x02000000+MainRAMMaxSize; addr += SearchByteType)
{
const s32& value = GetMainRAMValue(addr, SearchByteType);
const s32& value = GetMainRAMValue(*emuThread->NDS, addr, SearchByteType);
RowDataVector->push_back({ addr, value, value });
// A solution to prevent to call too many slot.
u32 newProgress = (int)((addr-0x02000000) / (NDS::MainRAMMaxSize-1.0f) * 100);
u32 newProgress = (int)((addr-0x02000000) / (MainRAMMaxSize-1.0f) * 100);
if (progress < newProgress)
{
progress = newProgress;
@ -263,7 +264,7 @@ void RAMSearchThread::run()
for (u32 row = 0; SearchRunning && row < RowDataVector->size(); row++)
{
const u32& addr = RowDataVector->at(row).Address;
const s32& value = GetMainRAMValue(addr, SearchByteType);
const s32& value = GetMainRAMValue(*emuThread->NDS, addr, SearchByteType);
if (SearchValue == value)
newRowDataVector->push_back({ addr, value, value });

View File

@ -32,6 +32,7 @@ namespace Ui { class RAMInfoDialog; }
class RAMInfoDialog;
class RAMSearchThread;
class RAMUpdateThread;
class EmuThread;
enum ramInfo_ByteType
{
@ -53,22 +54,22 @@ enum
ramInfo_Previous
};
s32 GetMainRAMValue(const u32& addr, const ramInfo_ByteType& byteType);
melonDS::s32 GetMainRAMValue(melonDS::NDS& nds, const melonDS::u32& addr, const ramInfo_ByteType& byteType);
struct ramInfo_RowData
{
u32 Address;
s32 Value;
s32 Previous;
melonDS::u32 Address;
melonDS::s32 Value;
melonDS::s32 Previous;
void Update(const ramInfo_ByteType& byteType)
void Update(melonDS::NDS& nds, const ramInfo_ByteType& byteType)
{
Value = GetMainRAMValue(Address, byteType);
Value = GetMainRAMValue(nds, Address, byteType);
}
void SetValue(const s32& value)
void SetValue(melonDS::NDS& nds, const melonDS::s32& value)
{
NDS::MainRAM[Address&NDS::MainRAMMask] = (u32)value;
nds.MainRAM[Address&nds.MainRAMMask] = (melonDS::u32)value;
Value = value;
}
};
@ -78,11 +79,11 @@ class RAMInfoDialog : public QDialog
Q_OBJECT
public:
explicit RAMInfoDialog(QWidget* parent);
explicit RAMInfoDialog(QWidget* parent, EmuThread* emuThread);
~RAMInfoDialog();
static RAMInfoDialog* currentDlg;
static RAMInfoDialog* openDlg(QWidget* parent)
static RAMInfoDialog* openDlg(QWidget* parent, EmuThread* emuThread)
{
if (currentDlg)
{
@ -90,7 +91,7 @@ public:
return currentDlg;
}
currentDlg = new RAMInfoDialog(parent);
currentDlg = new RAMInfoDialog(parent, emuThread);
currentDlg->show();
return currentDlg;
}
@ -99,7 +100,7 @@ public:
currentDlg = nullptr;
}
s32 SearchValue = 0;
melonDS::s32 SearchValue = 0;
void ClearTableContents();
@ -115,9 +116,10 @@ private slots:
void OnSearchFinished();
void ShowRowsInTable();
void SetProgressbarValue(const u32& value);
void SetProgressbarValue(const melonDS::u32& value);
private:
EmuThread* emuThread;
Ui::RAMInfoDialog* ui;
RAMSearchThread* SearchThread;
@ -132,7 +134,7 @@ public:
explicit RAMSearchThread(RAMInfoDialog* dialog);
~RAMSearchThread() override;
void Start(const s32& searchValue, const ramInfoSTh_SearchMode& searchMode = ramInfoSTh_Default);
void Start(const melonDS::s32& searchValue, const ramInfoSTh_SearchMode& searchMode = ramInfoSTh_Default);
void Start(const ramInfoSTh_SearchMode& searchMode);
void SetSearchByteType(const ramInfo_ByteType& bytetype);
@ -148,14 +150,14 @@ private:
bool SearchRunning = false;
ramInfoSTh_SearchMode SearchMode;
s32 SearchValue;
melonDS::s32 SearchValue;
ramInfo_ByteType SearchByteType = ramInfo_OneByte;
std::vector<ramInfo_RowData>* RowDataVector = nullptr;
void ClearTableContents();
signals:
void SetProgressbarValue(const u32& value);
void SetProgressbarValue(const melonDS::u32& value);
};
#endif // RAMINFODIALOG_H

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -21,11 +21,15 @@
#include <QFileDialog>
#include "gif-h/gif.h"
#include "NDS.h"
#include "NDSCart.h"
#include "Platform.h"
#include "Config.h"
using namespace melonDS;
QString IntToHex(u64 num)
{
return ("0x" + QString::number(num, 16).toUpper());
@ -38,30 +42,30 @@ QString QStringBytes(u64 num)
ROMInfoDialog* ROMInfoDialog::currentDlg = nullptr;
ROMInfoDialog::ROMInfoDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ROMInfoDialog)
ROMInfoDialog::ROMInfoDialog(QWidget* parent, const melonDS::NDSCart::CartCommon& rom) : QDialog(parent), ui(new Ui::ROMInfoDialog)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
const NDSBanner* banner = NDSCart::Cart->Banner();
const NDSHeader& header = NDSCart::Cart->GetHeader();
const NDSBanner* banner = rom.Banner();
const NDSHeader& header = rom.GetHeader();
u32 iconData[32 * 32];
ROMManager::ROMIcon(banner->Icon, banner->Palette, iconData);
iconImage = QImage(reinterpret_cast<unsigned char*>(iconData), 32, 32, QImage::Format_ARGB32).copy();
iconImage = QImage(reinterpret_cast<u8*>(iconData), 32, 32, QImage::Format_RGBA8888).copy();
ui->iconImage->setPixmap(QPixmap::fromImage(iconImage));
if (banner->Version == 0x103)
{
u32 animatedIconData[32 * 32 * 64] = {0};
ui->saveAnimatedIconButton->setEnabled(true);
ROMManager::AnimatedROMIcon(banner->DSiIcon, banner->DSiPalette, banner->DSiSequence, animatedIconData, animatedSequence);
for (int i = 0; i < 64; i++)
for (u32* image: animatedIconData)
{
if (animatedIconData[32 * 32 * i] == 0)
if (!image)
break;
animatedIconImages.push_back(QPixmap::fromImage(QImage(reinterpret_cast<unsigned char*>(&animatedIconData[32 * 32 * i]), 32, 32, QImage::Format_ARGB32).copy()));
animatedIconImages.push_back(QPixmap::fromImage(QImage(reinterpret_cast<u8*>(image), 32, 32, QImage::Format_RGBA8888).copy()));
}
iconTimeline = new QTimeLine(animatedSequence.size() / 60 * 1000, this);
iconTimeline->setFrameRange(0, animatedSequence.size() - 1);
iconTimeline->setLoopCount(0);
@ -139,6 +143,30 @@ void ROMInfoDialog::on_saveIconButton_clicked()
iconImage.save(filename, "PNG");
}
void ROMInfoDialog::on_saveAnimatedIconButton_clicked()
{
QString filename = QFileDialog::getSaveFileName(this,
"Save Animated Icon",
QString::fromStdString(Config::LastROMFolder),
"GIF Images (*.gif)");
if (filename.isEmpty())
return;
GifWriter writer;
// The GIF format only supports delays of 0.01 seconds, so 0.0166... (60fps)
// is rounded up to 0.02 (50fps)
GifBegin(&writer, filename.toStdString().c_str(), 32, 32, 2);
for (int i: animatedSequence)
{
if (animatedIconData[i] == 0)
break;
GifWriteFrame(&writer, reinterpret_cast<u8*>(animatedIconData[i]), 32, 32, 2);
}
GifEnd(&writer);
}
void ROMInfoDialog::iconSetFrame(int frame)
{
ui->dsiIconImage->setPixmap(animatedIconImages[animatedSequence[frame]]);

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -29,17 +29,17 @@
namespace Ui { class ROMInfoDialog; }
class ROMInfoDialog;
namespace melonDS::NDSCart { class CartCommon; }
class ROMInfoDialog : public QDialog
{
Q_OBJECT
public:
explicit ROMInfoDialog(QWidget* parent);
explicit ROMInfoDialog(QWidget* parent, const melonDS::NDSCart::CartCommon& rom);
~ROMInfoDialog();
static ROMInfoDialog* currentDlg;
static ROMInfoDialog* openDlg(QWidget* parent)
static ROMInfoDialog* openDlg(QWidget* parent, const melonDS::NDSCart::CartCommon& rom)
{
if (currentDlg)
{
@ -47,7 +47,7 @@ public:
return currentDlg;
}
currentDlg = new ROMInfoDialog(parent);
currentDlg = new ROMInfoDialog(parent, rom);
currentDlg->open();
return currentDlg;
}
@ -60,6 +60,7 @@ private slots:
void done(int r);
void on_saveIconButton_clicked();
void on_saveAnimatedIconButton_clicked();
void iconSetFrame(int frame);
@ -68,6 +69,7 @@ private:
QImage iconImage;
QTimeLine* iconTimeline;
melonDS::u32 animatedIconData[64][32*32] = {0};
std::vector<QPixmap> animatedIconImages;
std::vector<int> animatedSequence;
};

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>559</width>
<height>532</height>
<width>557</width>
<height>547</height>
</rect>
</property>
<property name="sizePolicy">
@ -525,10 +525,17 @@
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_2">
<layout class="QGridLayout" name="gridLayout_9">
<item row="1" column="1">
<widget class="QPushButton" name="saveIconButton">
<property name="text">
<string>Save icon</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QGroupBox" name="iconBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -619,7 +626,7 @@
<property name="bottomMargin">
<number>1</number>
</property>
<item row="0" column="0">
<item row="0" column="1">
<widget class="QLabel" name="iconTitle">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
@ -644,16 +651,42 @@
</property>
</widget>
</item>
<item row="0" column="2">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="2">
<item row="0" column="2" alignment="Qt::AlignHCenter">
<widget class="QGroupBox" name="dsiIconBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -724,25 +757,40 @@
</layout>
</widget>
</item>
<item row="0" column="3">
<item row="1" column="2">
<widget class="QPushButton" name="saveAnimatedIconButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Save animated icon</string>
</property>
</widget>
</item>
<item row="0" column="3" rowspan="2">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="saveIconButton">
<property name="text">
<string>Save icon</string>
</property>
</widget>
</item>
<item row="0" column="0">
<item row="0" column="0" rowspan="2">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -22,49 +22,83 @@
#include "types.h"
#include "SaveManager.h"
#include "AREngine.h"
#include "DSi_NAND.h"
#include <QMainWindow>
#include "MemConstants.h"
#include <Args.h>
#include <optional>
#include <string>
#include <memory>
#include <vector>
namespace melonDS
{
class NDS;
class DSi;
class FATStorage;
class FATStorageArgs;
}
class EmuThread;
namespace ROMManager
{
extern SaveManager* NDSSave;
extern SaveManager* GBASave;
using namespace melonDS;
extern std::unique_ptr<SaveManager> NDSSave;
extern std::unique_ptr<SaveManager> GBASave;
extern std::unique_ptr<SaveManager> FirmwareSave;
QString VerifySetup();
void Reset();
bool LoadBIOS();
void Reset(EmuThread* thread);
/// Boots the emulated console into its system menu without starting a game.
bool BootToMenu(EmuThread* thread);
void ClearBackupState();
bool InstallFirmware();
bool LoadROM(QStringList filepath, bool reset);
void EjectCart();
/// Returns the configured ARM9 BIOS loaded from disk,
/// the FreeBIOS if external BIOS is disabled and we're in NDS mode,
/// or nullptr if loading failed.
std::unique_ptr<ARM9BIOSImage> LoadARM9BIOS() noexcept;
std::unique_ptr<ARM7BIOSImage> LoadARM7BIOS() noexcept;
std::unique_ptr<DSiBIOSImage> LoadDSiARM9BIOS() noexcept;
std::unique_ptr<DSiBIOSImage> LoadDSiARM7BIOS() noexcept;
std::optional<FATStorageArgs> GetDSiSDCardArgs() noexcept;
std::optional<FATStorage> LoadDSiSDCard() noexcept;
std::optional<FATStorageArgs> GetDLDISDCardArgs() noexcept;
std::optional<FATStorage> LoadDLDISDCard() noexcept;
void CustomizeFirmware(Firmware& firmware, bool overridesettings) noexcept;
Firmware GenerateFirmware(int type) noexcept;
/// Loads and customizes a firmware image based on the values in Config
std::optional<Firmware> LoadFirmware(int type) noexcept;
/// Loads and customizes a NAND image based on the values in Config
std::optional<DSi_NAND::NANDImage> LoadNAND(const std::array<u8, DSiBIOSSize>& arm7ibios) noexcept;
/// Inserts a ROM into the emulated console.
bool LoadROM(QMainWindow* mainWindow, EmuThread*, QStringList filepath, bool reset);
void EjectCart(NDS& nds);
bool CartInserted();
QString CartLabel();
bool LoadGBAROM(QStringList filepath);
void LoadGBAAddon(int type);
void EjectGBACart();
bool LoadGBAROM(QMainWindow* mainWindow, NDS& nds, QStringList filepath);
void LoadGBAAddon(NDS& nds, int type);
void EjectGBACart(NDS& nds);
bool GBACartInserted();
QString GBACartLabel();
std::string GetSavestateName(int slot);
bool SavestateExists(int slot);
bool LoadState(const std::string& filename);
bool SaveState(const std::string& filename);
void UndoStateLoad();
bool LoadState(NDS& nds, const std::string& filename);
bool SaveState(NDS& nds, const std::string& filename);
void UndoStateLoad(NDS& nds);
void EnableCheats(bool enable);
void EnableCheats(NDS& nds, bool enable);
ARCodeFile* GetCheatFile();
void ROMIcon(const u8 (&data)[512], const u16 (&palette)[16], u32* iconRef);
void ROMIcon(const u8 (&data)[512], const u16 (&palette)[16], u32 (&iconRef)[32*32]);
void AnimatedROMIcon(const u8 (&data)[8][512], const u16 (&palette)[8][16],
const u16 (&sequence)[64], u32 (&animatedTexRef)[32 * 32 * 64],
const u16 (&sequence)[64], u32 (&animatedIconRef)[64][32*32],
std::vector<int> &animatedSequenceRef);
}
#endif // ROMMANAGER_H

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -22,7 +22,8 @@
#include "SaveManager.h"
#include "Platform.h"
using namespace Platform;
using namespace melonDS;
using namespace melonDS::Platform;
SaveManager::SaveManager(const std::string& path) : QThread()
{

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -41,31 +41,31 @@ public:
std::string GetPath();
void SetPath(const std::string& path, bool reload);
void RequestFlush(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen);
void RequestFlush(const melonDS::u8* savedata, melonDS::u32 savelen, melonDS::u32 writeoffset, melonDS::u32 writelen);
void CheckFlush();
bool NeedsFlush();
void FlushSecondaryBuffer(u8* dst = nullptr, u32 dstLength = 0);
void FlushSecondaryBuffer(melonDS::u8* dst = nullptr, melonDS::u32 dstLength = 0);
private:
std::string Path;
std::atomic_bool Running;
std::unique_ptr<u8[]> Buffer;
u32 Length;
std::unique_ptr<melonDS::u8[]> Buffer;
melonDS::u32 Length;
bool FlushRequested;
QMutex* SecondaryBufferLock;
std::unique_ptr<u8[]> SecondaryBuffer;
u32 SecondaryBufferLength;
std::unique_ptr<melonDS::u8[]> SecondaryBuffer;
melonDS::u32 SecondaryBufferLength;
time_t TimeAtLastFlushRequest;
// We keep versions in case the user closes the application before
// a flush cycle is finished.
u32 PreviousFlushVersion;
u32 FlushVersion;
melonDS::u32 PreviousFlushVersion;
melonDS::u32 FlushVersion;
};
#endif // SAVEMANAGER_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,196 @@
/*
Copyright 2016-2023 melonDS team
This file is part of melonDS.
melonDS is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#ifndef SCREEN_H
#define SCREEN_H
#include <optional>
#include <deque>
#include <map>
#include <QWidget>
#include <QImage>
#include <QMutex>
#include <QScreen>
#include <QCloseEvent>
#include <QTimer>
#include "glad/glad.h"
#include "FrontendUtil.h"
#include "duckstation/gl/context.h"
class EmuThread;
const struct { int id; float ratio; const char* label; } aspectRatios[] =
{
{ 0, 1, "4:3 (native)" },
{ 4, (5.f / 3) / (4.f / 3), "5:3 (3DS)"},
{ 1, (16.f / 9) / (4.f / 3), "16:9" },
{ 2, (21.f / 9) / (4.f / 3), "21:9" },
{ 3, 0, "window" }
};
constexpr int AspectRatiosNum = sizeof(aspectRatios) / sizeof(aspectRatios[0]);
class ScreenPanel : public QWidget
{
Q_OBJECT
public:
explicit ScreenPanel(QWidget* parent);
virtual ~ScreenPanel();
QTimer* setupMouseTimer();
void updateMouseTimer();
QTimer* mouseTimer;
QSize screenGetMinSize(int factor);
void osdSetEnabled(bool enabled);
void osdAddMessage(unsigned int color, const char* msg);
private slots:
void onScreenLayoutChanged();
protected:
struct OSDItem
{
unsigned int id;
qint64 timestamp;
char text[256];
unsigned int color;
bool rendered;
QImage bitmap;
};
QMutex osdMutex;
bool osdEnabled;
unsigned int osdID;
std::deque<OSDItem> osdItems;
virtual void setupScreenLayout();
void resizeEvent(QResizeEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void tabletEvent(QTabletEvent* event) override;
void touchEvent(QTouchEvent* event);
bool event(QEvent* event) override;
float screenMatrix[Frontend::MaxScreenTransforms][6];
int screenKind[Frontend::MaxScreenTransforms];
int numScreens;
bool touching = false;
void showCursor();
int osdFindBreakPoint(const char* text, int i);
void osdLayoutText(const char* text, int* width, int* height, int* breaks);
unsigned int osdRainbowColor(int inc);
virtual void osdRenderItem(OSDItem* item);
virtual void osdDeleteItem(OSDItem* item);
void osdUpdate();
};
class ScreenPanelNative : public ScreenPanel
{
Q_OBJECT
public:
explicit ScreenPanelNative(QWidget* parent);
virtual ~ScreenPanelNative();
protected:
void paintEvent(QPaintEvent* event) override;
private:
void setupScreenLayout() override;
QImage screen[2];
QTransform screenTrans[Frontend::MaxScreenTransforms];
};
class ScreenPanelGL : public ScreenPanel
{
Q_OBJECT
public:
explicit ScreenPanelGL(QWidget* parent);
virtual ~ScreenPanelGL();
std::optional<WindowInfo> getWindowInfo();
bool createContext();
void setSwapInterval(int intv);
void initOpenGL();
void deinitOpenGL();
void drawScreenGL();
GL::Context* getContext() { return glContext.get(); }
void transferLayout();
protected:
qreal devicePixelRatioFromScreen() const;
int scaledWindowWidth() const;
int scaledWindowHeight() const;
QPaintEngine* paintEngine() const override;
private:
void setupScreenLayout() override;
std::unique_ptr<GL::Context> glContext;
GLuint screenVertexBuffer, screenVertexArray;
GLuint screenTexture;
GLuint screenShaderProgram[3];
GLuint screenShaderTransformULoc, screenShaderScreenSizeULoc;
QMutex screenSettingsLock;
WindowInfo windowInfo;
bool filter;
int lastScreenWidth = -1, lastScreenHeight = -1;
GLuint osdShader[3];
GLint osdScreenSizeULoc, osdPosULoc, osdSizeULoc;
GLfloat osdScaleFactorULoc;
GLuint osdVertexArray;
GLuint osdVertexBuffer;
std::map<unsigned int, GLuint> osdTextures;
void osdRenderItem(OSDItem* item) override;
void osdDeleteItem(OSDItem* item) override;
};
#endif // SCREEN_H

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -30,15 +30,16 @@
#include "ui_TitleManagerDialog.h"
#include "ui_TitleImportDialog.h"
using namespace Platform;
using namespace melonDS;
using namespace melonDS::Platform;
bool TitleManagerDialog::NANDInited = false;
std::unique_ptr<DSi_NAND::NANDImage> TitleManagerDialog::nand = nullptr;
TitleManagerDialog* TitleManagerDialog::currentDlg = nullptr;
extern std::string EmuDirectory;
TitleManagerDialog::TitleManagerDialog(QWidget* parent) : QDialog(parent), ui(new Ui::TitleManagerDialog)
TitleManagerDialog::TitleManagerDialog(QWidget* parent, DSi_NAND::NANDImage& image) : QDialog(parent), ui(new Ui::TitleManagerDialog), nandmount(image)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
@ -47,7 +48,7 @@ TitleManagerDialog::TitleManagerDialog(QWidget* parent) : QDialog(parent), ui(ne
const u32 category = 0x00030004;
std::vector<u32> titlelist;
DSi_NAND::ListTitles(category, titlelist);
nandmount.ListTitles(category, titlelist);
for (std::vector<u32>::iterator it = titlelist.begin(); it != titlelist.end(); it++)
{
@ -109,11 +110,11 @@ void TitleManagerDialog::createTitleItem(u32 category, u32 titleid)
NDSHeader header;
NDSBanner banner;
DSi_NAND::GetTitleInfo(category, titleid, version, &header, &banner);
nandmount.GetTitleInfo(category, titleid, version, &header, &banner);
u32 icondata[32*32];
ROMManager::ROMIcon(banner.Icon, banner.Palette, icondata);
QImage iconimg((const uchar*)icondata, 32, 32, QImage::Format_ARGB32);
QImage iconimg((const uchar*)icondata, 32, 32, QImage::Format_RGBA8888);
QIcon icon(QPixmap::fromImage(iconimg.copy()));
// TODO: make it possible to select other languages?
@ -137,7 +138,7 @@ void TitleManagerDialog::createTitleItem(u32 category, u32 titleid)
bool TitleManagerDialog::openNAND()
{
NANDInited = false;
nand = nullptr;
FileHandle* bios7i = Platform::OpenLocalFile(Config::DSiBIOS7Path, FileMode::Read);
if (!bios7i)
@ -148,22 +149,25 @@ bool TitleManagerDialog::openNAND()
FileRead(es_keyY, 16, 1, bios7i);
CloseFile(bios7i);
if (!DSi_NAND::Init(es_keyY))
{
FileHandle* nandfile = Platform::OpenLocalFile(Config::DSiNANDPath, FileMode::ReadWriteExisting);
if (!nandfile)
return false;
nand = std::make_unique<DSi_NAND::NANDImage>(nandfile, es_keyY);
if (!*nand)
{ // If loading and mounting the NAND image failed...
nand = nullptr;
return false;
// NOTE: The NANDImage takes ownership of the FileHandle,
// so it will be closed even if the NANDImage constructor fails.
}
NANDInited = true;
return true;
}
void TitleManagerDialog::closeNAND()
{
if (NANDInited)
{
DSi_NAND::DeInit();
NANDInited = false;
}
nand = nullptr;
}
void TitleManagerDialog::done(int r)
@ -175,7 +179,7 @@ void TitleManagerDialog::done(int r)
void TitleManagerDialog::on_btnImportTitle_clicked()
{
TitleImportDialog* importdlg = new TitleImportDialog(this, importAppPath, &importTmdData, importReadOnly);
TitleImportDialog* importdlg = new TitleImportDialog(this, importAppPath, &importTmdData, importReadOnly, nandmount);
importdlg->open();
connect(importdlg, &TitleImportDialog::finished, this, &TitleManagerDialog::onImportTitleFinished);
@ -190,14 +194,16 @@ void TitleManagerDialog::onImportTitleFinished(int res)
titleid[0] = importTmdData.GetCategory();
titleid[1] = importTmdData.GetID();
assert(nand != nullptr);
assert(*nand);
// remove anything that might hinder the install
DSi_NAND::DeleteTitle(titleid[0], titleid[1]);
nandmount.DeleteTitle(titleid[0], titleid[1]);
bool importres = DSi_NAND::ImportTitle(importAppPath.toStdString().c_str(), importTmdData, importReadOnly);
bool importres = nandmount.ImportTitle(importAppPath.toStdString().c_str(), importTmdData, importReadOnly);
if (!importres)
{
// remove a potential half-completed install
DSi_NAND::DeleteTitle(titleid[0], titleid[1]);
nandmount.DeleteTitle(titleid[0], titleid[1]);
QMessageBox::critical(this,
"Import title - melonDS",
@ -224,7 +230,7 @@ void TitleManagerDialog::on_btnDeleteTitle_clicked()
return;
u64 titleid = cur->data(Qt::UserRole).toULongLong();
DSi_NAND::DeleteTitle((u32)(titleid >> 32), (u32)titleid);
nandmount.DeleteTitle((u32)(titleid >> 32), (u32)titleid);
delete cur;
}
@ -317,7 +323,7 @@ void TitleManagerDialog::onImportTitleData()
}
u64 titleid = cur->data(Qt::UserRole).toULongLong();
bool res = DSi_NAND::ImportTitleData((u32)(titleid >> 32), (u32)titleid, type, file.toStdString().c_str());
bool res = nandmount.ImportTitleData((u32)(titleid >> 32), (u32)titleid, type, file.toStdString().c_str());
if (!res)
{
QMessageBox::critical(this,
@ -370,7 +376,7 @@ void TitleManagerDialog::onExportTitleData()
if (file.isEmpty()) return;
u64 titleid = cur->data(Qt::UserRole).toULongLong();
bool res = DSi_NAND::ExportTitleData((u32)(titleid >> 32), (u32)titleid, type, file.toStdString().c_str());
bool res = nandmount.ExportTitleData((u32)(titleid >> 32), (u32)titleid, type, file.toStdString().c_str());
if (!res)
{
QMessageBox::critical(this,
@ -380,8 +386,8 @@ void TitleManagerDialog::onExportTitleData()
}
TitleImportDialog::TitleImportDialog(QWidget* parent, QString& apppath, const DSi_TMD::TitleMetadata* tmd, bool& readonly)
: QDialog(parent), ui(new Ui::TitleImportDialog), appPath(apppath), tmdData(tmd), readOnly(readonly)
TitleImportDialog::TitleImportDialog(QWidget* parent, QString& apppath, const DSi_TMD::TitleMetadata* tmd, bool& readonly, DSi_NAND::NANDMount& nandmount)
: QDialog(parent), ui(new Ui::TitleImportDialog), appPath(apppath), tmdData(tmd), readOnly(readonly), nandmount(nandmount)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
@ -455,7 +461,7 @@ void TitleImportDialog::accept()
}
}
if (DSi_NAND::TitleExists(titleid[1], titleid[0]))
if (nandmount.TitleExists(titleid[1], titleid[0]))
{
if (QMessageBox::question(this,
"Import title - melonDS",

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -19,6 +19,7 @@
#ifndef TITLEMANAGERDIALOG_H
#define TITLEMANAGERDIALOG_H
#include <memory>
#include <QDialog>
#include <QMessageBox>
#include <QListWidget>
@ -30,6 +31,7 @@
#include <QNetworkAccessManager>
#include "DSi_TMD.h"
#include "DSi_NAND.h"
namespace Ui
{
@ -44,10 +46,10 @@ class TitleManagerDialog : public QDialog
Q_OBJECT
public:
explicit TitleManagerDialog(QWidget* parent);
explicit TitleManagerDialog(QWidget* parent, melonDS::DSi_NAND::NANDImage& image);
~TitleManagerDialog();
static bool NANDInited;
static std::unique_ptr<melonDS::DSi_NAND::NANDImage> nand;
static bool openNAND();
static void closeNAND();
@ -68,7 +70,10 @@ public:
return nullptr;
}
currentDlg = new TitleManagerDialog(parent);
assert(nand != nullptr);
assert(*nand);
currentDlg = new TitleManagerDialog(parent, *nand);
currentDlg->open();
return currentDlg;
}
@ -89,16 +94,17 @@ private slots:
void onExportTitleData();
private:
melonDS::DSi_NAND::NANDMount nandmount;
Ui::TitleManagerDialog* ui;
QString importAppPath;
DSi_TMD::TitleMetadata importTmdData;
melonDS::DSi_TMD::TitleMetadata importTmdData;
bool importReadOnly;
QAction* actImportTitleData[3];
QAction* actExportTitleData[3];
void createTitleItem(u32 category, u32 titleid);
void createTitleItem(melonDS::u32 category, melonDS::u32 titleid);
};
class TitleImportDialog : public QDialog
@ -106,7 +112,7 @@ class TitleImportDialog : public QDialog
Q_OBJECT
public:
explicit TitleImportDialog(QWidget* parent, QString& apppath, const DSi_TMD::TitleMetadata* tmd, bool& readonly);
explicit TitleImportDialog(QWidget* parent, QString& apppath, const melonDS::DSi_TMD::TitleMetadata* tmd, bool& readonly, melonDS::DSi_NAND::NANDMount& nand);
~TitleImportDialog();
private slots:
@ -119,6 +125,7 @@ private slots:
private:
Ui::TitleImportDialog* ui;
melonDS::DSi_NAND::NANDMount& nandmount;
QButtonGroup* grpTmdSource;
@ -126,10 +133,10 @@ private:
QNetworkReply* netreply;
QString& appPath;
const DSi_TMD::TitleMetadata* tmdData;
const melonDS::DSi_TMD::TitleMetadata* tmdData;
bool& readOnly;
u32 titleid[2];
melonDS::u32 titleid[2];
};
#endif // TITLEMANAGERDIALOG_H

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,299 @@
/*
Copyright 2016-2023 melonDS team
This file is part of melonDS.
melonDS is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#ifndef WINDOW_H
#define WINDOW_H
#include "glad/glad.h"
#include "FrontendUtil.h"
#include "duckstation/gl/context.h"
#include <QWidget>
#include <QWindow>
#include <QMainWindow>
#include <QImage>
#include <QActionGroup>
#include <QTimer>
#include <QMutex>
#include <QScreen>
#include <QCloseEvent>
#include "Screen.h"
class EmuThread;
/*
class WindowBase : public QMainWindow
{
Q_OBJECT
public:
explicit WindowBase(QWidget* parent = nullptr);
~WindowBase();
bool hasOGL;
GL::Context* getOGLContext();
//void onAppStateChanged(Qt::ApplicationState state);
protected:
void resizeEvent(QResizeEvent* event) override;
void changeEvent(QEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override;
void dropEvent(QDropEvent* event) override;
void focusInEvent(QFocusEvent* event) override;
void focusOutEvent(QFocusEvent* event) override;
signals:
void screenLayoutChange();
private slots:
//void onQuit();
//void onTitleUpdate(QString title);
//void onEmuStart();
//void onEmuStop();
//void onUpdateVideoSettings(bool glchange);
void onFullscreenToggled();
void onScreenEmphasisToggled();
private:
virtual void closeEvent(QCloseEvent* event) override;
void createScreenPanel();
//bool pausedManually = false;
int oldW, oldH;
bool oldMax;
public:
ScreenPanel* panel;
};*/
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget* parent = nullptr);
~MainWindow();
bool hasOGL;
GL::Context* getOGLContext();
/*void initOpenGL();
void deinitOpenGL();
void drawScreenGL();*/
bool preloadROMs(QStringList file, QStringList gbafile, bool boot);
QStringList splitArchivePath(const QString& filename, bool useMemberSyntax);
void onAppStateChanged(Qt::ApplicationState state);
void osdAddMessage(unsigned int color, const char* fmt, ...);
protected:
void resizeEvent(QResizeEvent* event) override;
void changeEvent(QEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override;
void dropEvent(QDropEvent* event) override;
void focusInEvent(QFocusEvent* event) override;
void focusOutEvent(QFocusEvent* event) override;
signals:
void screenLayoutChange();
private slots:
void onOpenFile();
void onClickRecentFile();
void onClearRecentFiles();
void onBootFirmware();
void onInsertCart();
void onEjectCart();
void onInsertGBACart();
void onInsertGBAAddon();
void onEjectGBACart();
void onSaveState();
void onLoadState();
void onUndoStateLoad();
void onImportSavefile();
void onQuit();
void onPause(bool checked);
void onReset();
void onStop();
void onFrameStep();
void onOpenPowerManagement();
void onOpenDateTime();
void onEnableCheats(bool checked);
void onSetupCheats();
void onCheatsDialogFinished(int res);
void onROMInfo();
void onRAMInfo();
void onOpenTitleManager();
void onMPNewInstance();
void onOpenEmuSettings();
void onEmuSettingsDialogFinished(int res);
void onOpenInputConfig();
void onInputConfigFinished(int res);
void onOpenVideoSettings();
void onOpenCameraSettings();
void onCameraSettingsFinished(int res);
void onOpenAudioSettings();
void onUpdateAudioSettings();
void onAudioSettingsFinished(int res);
void onOpenMPSettings();
void onMPSettingsFinished(int res);
void onOpenWifiSettings();
void onWifiSettingsFinished(int res);
void onOpenFirmwareSettings();
void onFirmwareSettingsFinished(int res);
void onOpenPathSettings();
void onPathSettingsFinished(int res);
void onOpenInterfaceSettings();
void onInterfaceSettingsFinished(int res);
void onUpdateMouseTimer();
void onChangeSavestateSRAMReloc(bool checked);
void onChangeScreenSize();
void onChangeScreenRotation(QAction* act);
void onChangeScreenGap(QAction* act);
void onChangeScreenLayout(QAction* act);
void onChangeScreenSwap(bool checked);
void onChangeScreenSizing(QAction* act);
void onChangeScreenAspect(QAction* act);
void onChangeIntegerScaling(bool checked);
void onChangeScreenFiltering(bool checked);
void onChangeShowOSD(bool checked);
void onChangeLimitFramerate(bool checked);
void onChangeAudioSync(bool checked);
void onTitleUpdate(QString title);
void onEmuStart();
void onEmuStop();
void onUpdateVideoSettings(bool glchange);
void onFullscreenToggled();
void onScreenEmphasisToggled();
private:
virtual void closeEvent(QCloseEvent* event) override;
QStringList currentROM;
QStringList currentGBAROM;
QList<QString> recentFileList;
QMenu *recentMenu;
void updateRecentFilesMenu();
bool verifySetup();
QString pickFileFromArchive(QString archiveFileName);
QStringList pickROM(bool gba);
void updateCartInserted(bool gba);
void createScreenPanel();
bool pausedManually = false;
int oldW, oldH;
bool oldMax;
public:
ScreenPanel* panel;
QAction* actOpenROM;
QAction* actBootFirmware;
QAction* actCurrentCart;
QAction* actInsertCart;
QAction* actEjectCart;
QAction* actCurrentGBACart;
QAction* actInsertGBACart;
QAction* actInsertGBAAddon[1];
QAction* actEjectGBACart;
QAction* actImportSavefile;
QAction* actSaveState[9];
QAction* actLoadState[9];
QAction* actUndoStateLoad;
QAction* actQuit;
QAction* actPause;
QAction* actReset;
QAction* actStop;
QAction* actFrameStep;
QAction* actPowerManagement;
QAction* actDateTime;
QAction* actEnableCheats;
QAction* actSetupCheats;
QAction* actROMInfo;
QAction* actRAMInfo;
QAction* actTitleManager;
QAction* actMPNewInstance;
QAction* actEmuSettings;
#ifdef __APPLE__
QAction* actPreferences;
#endif
QAction* actInputConfig;
QAction* actVideoSettings;
QAction* actCameraSettings;
QAction* actAudioSettings;
QAction* actMPSettings;
QAction* actWifiSettings;
QAction* actFirmwareSettings;
QAction* actPathSettings;
QAction* actInterfaceSettings;
QAction* actSavestateSRAMReloc;
QAction* actScreenSize[4];
QActionGroup* grpScreenRotation;
QAction* actScreenRotation[Frontend::screenRot_MAX];
QActionGroup* grpScreenGap;
QAction* actScreenGap[6];
QActionGroup* grpScreenLayout;
QAction* actScreenLayout[Frontend::screenLayout_MAX];
QAction* actScreenSwap;
QActionGroup* grpScreenSizing;
QAction* actScreenSizing[Frontend::screenSizing_MAX];
QAction* actIntegerScaling;
QActionGroup* grpScreenAspectTop;
QAction** actScreenAspectTop;
QActionGroup* grpScreenAspectBot;
QAction** actScreenAspectBot;
QAction* actScreenFiltering;
QAction* actShowOSD;
QAction* actLimitFramerate;
QAction* actAudioSync;
};
void ToggleFullscreen(MainWindow* mainWindow);
#endif // WINDOW_H

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.

View File

@ -0,0 +1,836 @@
//
// gif.h
// by Charlie Tangora
// Public domain.
// Email me : ctangora -at- gmail -dot- com
//
// This file offers a simple, very limited way to create animated GIFs directly in code.
//
// Those looking for particular cleverness are likely to be disappointed; it's pretty
// much a straight-ahead implementation of the GIF format with optional Floyd-Steinberg
// dithering. (It does at least use delta encoding - only the changed portions of each
// frame are saved.)
//
// So resulting files are often quite large. The hope is that it will be handy nonetheless
// as a quick and easily-integrated way for programs to spit out animations.
//
// Only RGBA8 is currently supported as an input format. (The alpha is ignored.)
//
// If capturing a buffer with a bottom-left origin (such as OpenGL), define GIF_FLIP_VERT
// to automatically flip the buffer data when writing the image (the buffer itself is
// unchanged.
//
// USAGE:
// Create a GifWriter struct. Pass it to GifBegin() to initialize and write the header.
// Pass subsequent frames to GifWriteFrame().
// Finally, call GifEnd() to close the file handle and free memory.
//
#ifndef gif_h
#define gif_h
#include <stdio.h> // for FILE*
#include <string.h> // for memcpy and bzero
#include <stdint.h> // for integer typedefs
#include <stdbool.h> // for bool macros
// Define these macros to hook into a custom memory allocator.
// TEMP_MALLOC and TEMP_FREE will only be called in stack fashion - frees in the reverse order of mallocs
// and any temp memory allocated by a function will be freed before it exits.
// MALLOC and FREE are used only by GifBegin and GifEnd respectively (to allocate a buffer the size of the image, which
// is used to find changed pixels for delta-encoding.)
#ifndef GIF_TEMP_MALLOC
#include <stdlib.h>
#define GIF_TEMP_MALLOC malloc
#endif
#ifndef GIF_TEMP_FREE
#include <stdlib.h>
#define GIF_TEMP_FREE free
#endif
#ifndef GIF_MALLOC
#include <stdlib.h>
#define GIF_MALLOC malloc
#endif
#ifndef GIF_FREE
#include <stdlib.h>
#define GIF_FREE free
#endif
const int kGifTransIndex = 0;
typedef struct
{
int bitDepth;
uint8_t r[256];
uint8_t g[256];
uint8_t b[256];
// k-d tree over RGB space, organized in heap fashion
// i.e. left child of node i is node i*2, right child is node i*2+1
// nodes 256-511 are implicitly the leaves, containing a color
uint8_t treeSplitElt[256];
uint8_t treeSplit[256];
} GifPalette;
// max, min, and abs functions
int GifIMax(int l, int r) { return l>r?l:r; }
int GifIMin(int l, int r) { return l<r?l:r; }
int GifIAbs(int i) { return i<0?-i:i; }
// walks the k-d tree to pick the palette entry for a desired color.
// Takes as in/out parameters the current best color and its error -
// only changes them if it finds a better color in its subtree.
// this is the major hotspot in the code at the moment.
void GifGetClosestPaletteColor( GifPalette* pPal, int r, int g, int b, int* bestInd, int* bestDiff, int treeRoot )
{
// base case, reached the bottom of the tree
if(treeRoot > (1<<pPal->bitDepth)-1)
{
int ind = treeRoot-(1<<pPal->bitDepth);
if(ind == kGifTransIndex) return;
// check whether this color is better than the current winner
int r_err = r - ((int32_t)pPal->r[ind]);
int g_err = g - ((int32_t)pPal->g[ind]);
int b_err = b - ((int32_t)pPal->b[ind]);
int diff = GifIAbs(r_err)+GifIAbs(g_err)+GifIAbs(b_err);
if(diff < *bestDiff)
{
*bestInd = ind;
*bestDiff = diff;
}
return;
}
// take the appropriate color (r, g, or b) for this node of the k-d tree
int comps[3]; comps[0] = r; comps[1] = g; comps[2] = b;
int splitComp = comps[pPal->treeSplitElt[treeRoot]];
int splitPos = pPal->treeSplit[treeRoot];
if(splitPos > splitComp)
{
// check the left subtree
GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2);
if( *bestDiff > splitPos - splitComp )
{
// cannot prove there's not a better value in the right subtree, check that too
GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2+1);
}
}
else
{
GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2+1);
if( *bestDiff > splitComp - splitPos )
{
GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2);
}
}
}
void GifSwapPixels(uint8_t* image, int pixA, int pixB)
{
uint8_t rA = image[pixA*4];
uint8_t gA = image[pixA*4+1];
uint8_t bA = image[pixA*4+2];
uint8_t aA = image[pixA*4+3];
uint8_t rB = image[pixB*4];
uint8_t gB = image[pixB*4+1];
uint8_t bB = image[pixB*4+2];
uint8_t aB = image[pixA*4+3];
image[pixA*4] = rB;
image[pixA*4+1] = gB;
image[pixA*4+2] = bB;
image[pixA*4+3] = aB;
image[pixB*4] = rA;
image[pixB*4+1] = gA;
image[pixB*4+2] = bA;
image[pixB*4+3] = aA;
}
// just the partition operation from quicksort
int GifPartition(uint8_t* image, const int left, const int right, const int elt, int pivotIndex)
{
const int pivotValue = image[(pivotIndex)*4+elt];
GifSwapPixels(image, pivotIndex, right-1);
int storeIndex = left;
bool split = 0;
for(int ii=left; ii<right-1; ++ii)
{
int arrayVal = image[ii*4+elt];
if( arrayVal < pivotValue )
{
GifSwapPixels(image, ii, storeIndex);
++storeIndex;
}
else if( arrayVal == pivotValue )
{
if(split)
{
GifSwapPixels(image, ii, storeIndex);
++storeIndex;
}
split = !split;
}
}
GifSwapPixels(image, storeIndex, right-1);
return storeIndex;
}
// Perform an incomplete sort, finding all elements above and below the desired median
void GifPartitionByMedian(uint8_t* image, int left, int right, int com, int neededCenter)
{
if(left < right-1)
{
int pivotIndex = left + (right-left)/2;
pivotIndex = GifPartition(image, left, right, com, pivotIndex);
// Only "sort" the section of the array that contains the median
if(pivotIndex > neededCenter)
GifPartitionByMedian(image, left, pivotIndex, com, neededCenter);
if(pivotIndex < neededCenter)
GifPartitionByMedian(image, pivotIndex+1, right, com, neededCenter);
}
}
// Builds a palette by creating a balanced k-d tree of all pixels in the image
void GifSplitPalette(uint8_t* image, int numPixels, int firstElt, int lastElt, int splitElt, int splitDist, int treeNode, bool buildForDither, GifPalette* pal)
{
if(lastElt <= firstElt || numPixels == 0)
return;
// base case, bottom of the tree
if(lastElt == firstElt+1)
{
if(buildForDither)
{
// Dithering needs at least one color as dark as anything
// in the image and at least one brightest color -
// otherwise it builds up error and produces strange artifacts
if( firstElt == 1 )
{
// special case: the darkest color in the image
uint32_t r=255, g=255, b=255;
for(int ii=0; ii<numPixels; ++ii)
{
r = (uint32_t)GifIMin((int32_t)r, image[ii * 4 + 0]);
g = (uint32_t)GifIMin((int32_t)g, image[ii * 4 + 1]);
b = (uint32_t)GifIMin((int32_t)b, image[ii * 4 + 2]);
}
pal->r[firstElt] = (uint8_t)r;
pal->g[firstElt] = (uint8_t)g;
pal->b[firstElt] = (uint8_t)b;
return;
}
if( firstElt == (1 << pal->bitDepth)-1 )
{
// special case: the lightest color in the image
uint32_t r=0, g=0, b=0;
for(int ii=0; ii<numPixels; ++ii)
{
r = (uint32_t)GifIMax((int32_t)r, image[ii * 4 + 0]);
g = (uint32_t)GifIMax((int32_t)g, image[ii * 4 + 1]);
b = (uint32_t)GifIMax((int32_t)b, image[ii * 4 + 2]);
}
pal->r[firstElt] = (uint8_t)r;
pal->g[firstElt] = (uint8_t)g;
pal->b[firstElt] = (uint8_t)b;
return;
}
}
// otherwise, take the average of all colors in this subcube
uint64_t r=0, g=0, b=0;
for(int ii=0; ii<numPixels; ++ii)
{
r += image[ii*4+0];
g += image[ii*4+1];
b += image[ii*4+2];
}
r += (uint64_t)numPixels / 2; // round to nearest
g += (uint64_t)numPixels / 2;
b += (uint64_t)numPixels / 2;
r /= (uint64_t)numPixels;
g /= (uint64_t)numPixels;
b /= (uint64_t)numPixels;
pal->r[firstElt] = (uint8_t)r;
pal->g[firstElt] = (uint8_t)g;
pal->b[firstElt] = (uint8_t)b;
return;
}
// Find the axis with the largest range
int minR = 255, maxR = 0;
int minG = 255, maxG = 0;
int minB = 255, maxB = 0;
for(int ii=0; ii<numPixels; ++ii)
{
int r = image[ii*4+0];
int g = image[ii*4+1];
int b = image[ii*4+2];
if(r > maxR) maxR = r;
if(r < minR) minR = r;
if(g > maxG) maxG = g;
if(g < minG) minG = g;
if(b > maxB) maxB = b;
if(b < minB) minB = b;
}
int rRange = maxR - minR;
int gRange = maxG - minG;
int bRange = maxB - minB;
// and split along that axis. (incidentally, this means this isn't a "proper" k-d tree but I don't know what else to call it)
int splitCom = 1;
if(bRange > gRange) splitCom = 2;
if(rRange > bRange && rRange > gRange) splitCom = 0;
int subPixelsA = numPixels * (splitElt - firstElt) / (lastElt - firstElt);
int subPixelsB = numPixels-subPixelsA;
GifPartitionByMedian(image, 0, numPixels, splitCom, subPixelsA);
pal->treeSplitElt[treeNode] = (uint8_t)splitCom;
pal->treeSplit[treeNode] = image[subPixelsA*4+splitCom];
GifSplitPalette(image, subPixelsA, firstElt, splitElt, splitElt-splitDist, splitDist/2, treeNode*2, buildForDither, pal);
GifSplitPalette(image+subPixelsA*4, subPixelsB, splitElt, lastElt, splitElt+splitDist, splitDist/2, treeNode*2+1, buildForDither, pal);
}
// Finds all pixels that have changed from the previous image and
// moves them to the fromt of th buffer.
// This allows us to build a palette optimized for the colors of the
// changed pixels only.
int GifPickChangedPixels( const uint8_t* lastFrame, uint8_t* frame, int numPixels )
{
int numChanged = 0;
uint8_t* writeIter = frame;
for (int ii=0; ii<numPixels; ++ii)
{
if(lastFrame[0] != frame[0] ||
lastFrame[1] != frame[1] ||
lastFrame[2] != frame[2])
{
writeIter[0] = frame[0];
writeIter[1] = frame[1];
writeIter[2] = frame[2];
++numChanged;
writeIter += 4;
}
lastFrame += 4;
frame += 4;
}
return numChanged;
}
// Creates a palette by placing all the image pixels in a k-d tree and then averaging the blocks at the bottom.
// This is known as the "modified median split" technique
void GifMakePalette( const uint8_t* lastFrame, const uint8_t* nextFrame, uint32_t width, uint32_t height, int bitDepth, bool buildForDither, GifPalette* pPal )
{
pPal->bitDepth = bitDepth;
// SplitPalette is destructive (it sorts the pixels by color) so
// we must create a copy of the image for it to destroy
size_t imageSize = (size_t)(width * height * 4 * sizeof(uint8_t));
uint8_t* destroyableImage = (uint8_t*)GIF_TEMP_MALLOC(imageSize);
memcpy(destroyableImage, nextFrame, imageSize);
int numPixels = (int)(width * height);
if(lastFrame)
numPixels = GifPickChangedPixels(lastFrame, destroyableImage, numPixels);
const int lastElt = 1 << bitDepth;
const int splitElt = lastElt/2;
const int splitDist = splitElt/2;
GifSplitPalette(destroyableImage, numPixels, 1, lastElt, splitElt, splitDist, 1, buildForDither, pPal);
GIF_TEMP_FREE(destroyableImage);
// add the bottom node for the transparency index
pPal->treeSplit[1 << (bitDepth-1)] = 0;
pPal->treeSplitElt[1 << (bitDepth-1)] = 0;
pPal->r[0] = pPal->g[0] = pPal->b[0] = 0;
}
// Implements Floyd-Steinberg dithering, writes palette value to alpha
void GifDitherImage( const uint8_t* lastFrame, const uint8_t* nextFrame, uint8_t* outFrame, uint32_t width, uint32_t height, GifPalette* pPal )
{
int numPixels = (int)(width * height);
// quantPixels initially holds color*256 for all pixels
// The extra 8 bits of precision allow for sub-single-color error values
// to be propagated
int32_t *quantPixels = (int32_t *)GIF_TEMP_MALLOC(sizeof(int32_t) * (size_t)numPixels * 4);
for( int ii=0; ii<numPixels*4; ++ii )
{
uint8_t pix = nextFrame[ii];
int32_t pix16 = (int32_t)(pix) * 256;
quantPixels[ii] = pix16;
}
for( uint32_t yy=0; yy<height; ++yy )
{
for( uint32_t xx=0; xx<width; ++xx )
{
int32_t* nextPix = quantPixels + 4*(yy*width+xx);
const uint8_t* lastPix = lastFrame? lastFrame + 4*(yy*width+xx) : NULL;
// Compute the colors we want (rounding to nearest)
int32_t rr = (nextPix[0] + 127) / 256;
int32_t gg = (nextPix[1] + 127) / 256;
int32_t bb = (nextPix[2] + 127) / 256;
// if it happens that we want the color from last frame, then just write out
// a transparent pixel
if( lastFrame &&
lastPix[0] == rr &&
lastPix[1] == gg &&
lastPix[2] == bb )
{
nextPix[0] = rr;
nextPix[1] = gg;
nextPix[2] = bb;
nextPix[3] = kGifTransIndex;
continue;
}
int32_t bestDiff = 1000000;
int32_t bestInd = kGifTransIndex;
// Search the palete
GifGetClosestPaletteColor(pPal, rr, gg, bb, &bestInd, &bestDiff, 1);
// Write the result to the temp buffer
int32_t r_err = nextPix[0] - (int32_t)(pPal->r[bestInd]) * 256;
int32_t g_err = nextPix[1] - (int32_t)(pPal->g[bestInd]) * 256;
int32_t b_err = nextPix[2] - (int32_t)(pPal->b[bestInd]) * 256;
nextPix[0] = pPal->r[bestInd];
nextPix[1] = pPal->g[bestInd];
nextPix[2] = pPal->b[bestInd];
nextPix[3] = bestInd;
// Propagate the error to the four adjacent locations
// that we haven't touched yet
int quantloc_7 = (int)(yy * width + xx + 1);
int quantloc_3 = (int)(yy * width + width + xx - 1);
int quantloc_5 = (int)(yy * width + width + xx);
int quantloc_1 = (int)(yy * width + width + xx + 1);
if(quantloc_7 < numPixels)
{
int32_t* pix7 = quantPixels+4*quantloc_7;
pix7[0] += GifIMax( -pix7[0], r_err * 7 / 16 );
pix7[1] += GifIMax( -pix7[1], g_err * 7 / 16 );
pix7[2] += GifIMax( -pix7[2], b_err * 7 / 16 );
}
if(quantloc_3 < numPixels)
{
int32_t* pix3 = quantPixels+4*quantloc_3;
pix3[0] += GifIMax( -pix3[0], r_err * 3 / 16 );
pix3[1] += GifIMax( -pix3[1], g_err * 3 / 16 );
pix3[2] += GifIMax( -pix3[2], b_err * 3 / 16 );
}
if(quantloc_5 < numPixels)
{
int32_t* pix5 = quantPixels+4*quantloc_5;
pix5[0] += GifIMax( -pix5[0], r_err * 5 / 16 );
pix5[1] += GifIMax( -pix5[1], g_err * 5 / 16 );
pix5[2] += GifIMax( -pix5[2], b_err * 5 / 16 );
}
if(quantloc_1 < numPixels)
{
int32_t* pix1 = quantPixels+4*quantloc_1;
pix1[0] += GifIMax( -pix1[0], r_err / 16 );
pix1[1] += GifIMax( -pix1[1], g_err / 16 );
pix1[2] += GifIMax( -pix1[2], b_err / 16 );
}
}
}
// Copy the palettized result to the output buffer
for( int ii=0; ii<numPixels*4; ++ii )
{
outFrame[ii] = (uint8_t)quantPixels[ii];
}
GIF_TEMP_FREE(quantPixels);
}
// Picks palette colors for the image using simple thresholding, no dithering
void GifThresholdImage( const uint8_t* lastFrame, const uint8_t* nextFrame, uint8_t* outFrame, uint32_t width, uint32_t height, GifPalette* pPal )
{
uint32_t numPixels = width*height;
for( uint32_t ii=0; ii<numPixels; ++ii )
{
// if a previous color is available, and it matches the current color,
// set the pixel to transparent
if(lastFrame &&
lastFrame[0] == nextFrame[0] &&
lastFrame[1] == nextFrame[1] &&
lastFrame[2] == nextFrame[2])
{
outFrame[0] = lastFrame[0];
outFrame[1] = lastFrame[1];
outFrame[2] = lastFrame[2];
outFrame[3] = kGifTransIndex;
}
else
{
// palettize the pixel
int32_t bestDiff = 1000000;
int32_t bestInd = 1;
GifGetClosestPaletteColor(pPal, nextFrame[0], nextFrame[1], nextFrame[2], &bestInd, &bestDiff, 1);
// Write the resulting color to the output buffer
outFrame[0] = pPal->r[bestInd];
outFrame[1] = pPal->g[bestInd];
outFrame[2] = pPal->b[bestInd];
outFrame[3] = (uint8_t)bestInd;
}
if(lastFrame) lastFrame += 4;
outFrame += 4;
nextFrame += 4;
}
}
// Simple structure to write out the LZW-compressed portion of the image
// one bit at a time
typedef struct
{
uint8_t bitIndex; // how many bits in the partial byte written so far
uint8_t byte; // current partial byte
uint32_t chunkIndex;
uint8_t chunk[256]; // bytes are written in here until we have 256 of them, then written to the file
} GifBitStatus;
// insert a single bit
void GifWriteBit( GifBitStatus* stat, uint32_t bit )
{
bit = bit & 1;
bit = bit << stat->bitIndex;
stat->byte |= bit;
++stat->bitIndex;
if( stat->bitIndex > 7 )
{
// move the newly-finished byte to the chunk buffer
stat->chunk[stat->chunkIndex++] = stat->byte;
// and start a new byte
stat->bitIndex = 0;
stat->byte = 0;
}
}
// write all bytes so far to the file
void GifWriteChunk( FILE* f, GifBitStatus* stat )
{
fputc((int)stat->chunkIndex, f);
fwrite(stat->chunk, 1, stat->chunkIndex, f);
stat->bitIndex = 0;
stat->byte = 0;
stat->chunkIndex = 0;
}
void GifWriteCode( FILE* f, GifBitStatus* stat, uint32_t code, uint32_t length )
{
for( uint32_t ii=0; ii<length; ++ii )
{
GifWriteBit(stat, code);
code = code >> 1;
if( stat->chunkIndex == 255 )
{
GifWriteChunk(f, stat);
}
}
}
// The LZW dictionary is a 256-ary tree constructed as the file is encoded,
// this is one node
typedef struct
{
uint16_t m_next[256];
} GifLzwNode;
// write a 256-color (8-bit) image palette to the file
void GifWritePalette( const GifPalette* pPal, FILE* f )
{
fputc(0, f); // first color: transparency
fputc(0, f);
fputc(0, f);
for(int ii=1; ii<(1 << pPal->bitDepth); ++ii)
{
uint32_t r = pPal->r[ii];
uint32_t g = pPal->g[ii];
uint32_t b = pPal->b[ii];
fputc((int)r, f);
fputc((int)g, f);
fputc((int)b, f);
}
}
// write the image header, LZW-compress and write out the image
void GifWriteLzwImage(FILE* f, uint8_t* image, uint32_t left, uint32_t top, uint32_t width, uint32_t height, uint32_t delay, GifPalette* pPal)
{
// graphics control extension
fputc(0x21, f);
fputc(0xf9, f);
fputc(0x04, f);
fputc(0x05, f); // leave prev frame in place, this frame has transparency
fputc(delay & 0xff, f);
fputc((delay >> 8) & 0xff, f);
fputc(kGifTransIndex, f); // transparent color index
fputc(0, f);
fputc(0x2c, f); // image descriptor block
fputc(left & 0xff, f); // corner of image in canvas space
fputc((left >> 8) & 0xff, f);
fputc(top & 0xff, f);
fputc((top >> 8) & 0xff, f);
fputc(width & 0xff, f); // width and height of image
fputc((width >> 8) & 0xff, f);
fputc(height & 0xff, f);
fputc((height >> 8) & 0xff, f);
//fputc(0, f); // no local color table, no transparency
//fputc(0x80, f); // no local color table, but transparency
fputc(0x80 + pPal->bitDepth-1, f); // local color table present, 2 ^ bitDepth entries
GifWritePalette(pPal, f);
const int minCodeSize = pPal->bitDepth;
const uint32_t clearCode = 1 << pPal->bitDepth;
fputc(minCodeSize, f); // min code size 8 bits
GifLzwNode* codetree = (GifLzwNode*)GIF_TEMP_MALLOC(sizeof(GifLzwNode)*4096);
memset(codetree, 0, sizeof(GifLzwNode)*4096);
int32_t curCode = -1;
uint32_t codeSize = (uint32_t)minCodeSize + 1;
uint32_t maxCode = clearCode+1;
GifBitStatus stat;
stat.byte = 0;
stat.bitIndex = 0;
stat.chunkIndex = 0;
GifWriteCode(f, &stat, clearCode, codeSize); // start with a fresh LZW dictionary
for(uint32_t yy=0; yy<height; ++yy)
{
for(uint32_t xx=0; xx<width; ++xx)
{
#ifdef GIF_FLIP_VERT
// bottom-left origin image (such as an OpenGL capture)
uint8_t nextValue = image[((height-1-yy)*width+xx)*4+3];
#else
// top-left origin
uint8_t nextValue = image[(yy*width+xx)*4+3];
#endif
// "loser mode" - no compression, every single code is followed immediately by a clear
//WriteCode( f, stat, nextValue, codeSize );
//WriteCode( f, stat, 256, codeSize );
if( curCode < 0 )
{
// first value in a new run
curCode = nextValue;
}
else if( codetree[curCode].m_next[nextValue] )
{
// current run already in the dictionary
curCode = codetree[curCode].m_next[nextValue];
}
else
{
// finish the current run, write a code
GifWriteCode(f, &stat, (uint32_t)curCode, codeSize);
// insert the new run into the dictionary
codetree[curCode].m_next[nextValue] = (uint16_t)++maxCode;
if( maxCode >= (1ul << codeSize) )
{
// dictionary entry count has broken a size barrier,
// we need more bits for codes
codeSize++;
}
if( maxCode == 4095 )
{
// the dictionary is full, clear it out and begin anew
GifWriteCode(f, &stat, clearCode, codeSize); // clear tree
memset(codetree, 0, sizeof(GifLzwNode)*4096);
codeSize = (uint32_t)(minCodeSize + 1);
maxCode = clearCode+1;
}
curCode = nextValue;
}
}
}
// compression footer
GifWriteCode(f, &stat, (uint32_t)curCode, codeSize);
GifWriteCode(f, &stat, clearCode, codeSize);
GifWriteCode(f, &stat, clearCode + 1, (uint32_t)minCodeSize + 1);
// write out the last partial chunk
while( stat.bitIndex ) GifWriteBit(&stat, 0);
if( stat.chunkIndex ) GifWriteChunk(f, &stat);
fputc(0, f); // image block terminator
GIF_TEMP_FREE(codetree);
}
typedef struct
{
FILE* f;
uint8_t* oldImage;
bool firstFrame;
} GifWriter;
// Creates a gif file.
// The input GIFWriter is assumed to be uninitialized.
// The delay value is the time between frames in hundredths of a second - note that not all viewers pay much attention to this value.
bool GifBegin( GifWriter* writer, const char* filename, uint32_t width, uint32_t height, uint32_t delay, int32_t bitDepth = 8, bool dither = false )
{
(void)bitDepth; (void)dither; // Mute "Unused argument" warnings
#if defined(_MSC_VER) && (_MSC_VER >= 1400)
writer->f = 0;
fopen_s(&writer->f, filename, "wb");
#else
writer->f = fopen(filename, "wb");
#endif
if(!writer->f) return false;
writer->firstFrame = true;
// allocate
writer->oldImage = (uint8_t*)GIF_MALLOC(width*height*4);
fputs("GIF89a", writer->f);
// screen descriptor
fputc(width & 0xff, writer->f);
fputc((width >> 8) & 0xff, writer->f);
fputc(height & 0xff, writer->f);
fputc((height >> 8) & 0xff, writer->f);
fputc(0xf0, writer->f); // there is an unsorted global color table of 2 entries
fputc(0, writer->f); // background color
fputc(0, writer->f); // pixels are square (we need to specify this because it's 1989)
// now the "global" palette (really just a dummy palette)
// color 0: black
fputc(0, writer->f);
fputc(0, writer->f);
fputc(0, writer->f);
// color 1: also black
fputc(0, writer->f);
fputc(0, writer->f);
fputc(0, writer->f);
if( delay != 0 )
{
// animation header
fputc(0x21, writer->f); // extension
fputc(0xff, writer->f); // application specific
fputc(11, writer->f); // length 11
fputs("NETSCAPE2.0", writer->f); // yes, really
fputc(3, writer->f); // 3 bytes of NETSCAPE2.0 data
fputc(1, writer->f); // JUST BECAUSE
fputc(0, writer->f); // loop infinitely (byte 0)
fputc(0, writer->f); // loop infinitely (byte 1)
fputc(0, writer->f); // block terminator
}
return true;
}
// Writes out a new frame to a GIF in progress.
// The GIFWriter should have been created by GIFBegin.
// AFAIK, it is legal to use different bit depths for different frames of an image -
// this may be handy to save bits in animations that don't change much.
bool GifWriteFrame( GifWriter* writer, const uint8_t* image, uint32_t width, uint32_t height, uint32_t delay, int bitDepth = 8, bool dither = false )
{
if(!writer->f) return false;
const uint8_t* oldImage = writer->firstFrame? NULL : writer->oldImage;
writer->firstFrame = false;
GifPalette pal;
GifMakePalette((dither? NULL : oldImage), image, width, height, bitDepth, dither, &pal);
if(dither)
GifDitherImage(oldImage, image, writer->oldImage, width, height, &pal);
else
GifThresholdImage(oldImage, image, writer->oldImage, width, height, &pal);
GifWriteLzwImage(writer->f, writer->oldImage, 0, 0, width, height, delay, &pal);
return true;
}
// Writes the EOF code, closes the file handle, and frees temp memory used by a GIF.
// Many if not most viewers will still display a GIF properly if the EOF code is missing,
// but it's still a good idea to write it out.
bool GifEnd( GifWriter* writer )
{
if(!writer->f) return false;
fputc(0x3b, writer->f); // end of file
fclose(writer->f);
GIF_FREE(writer->oldImage);
writer->f = NULL;
writer->oldImage = NULL;
return true;
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.
@ -22,221 +22,18 @@
#include "glad/glad.h"
#include <QApplication>
#include <QThread>
#include <QWidget>
#include <QWindow>
#include <QMainWindow>
#include <QImage>
#include <QActionGroup>
#include <QTimer>
#include <QMutex>
#include <QScreen>
#include <QCloseEvent>
#include <atomic>
#include <optional>
#include "Window.h"
#include "EmuThread.h"
#include "FrontendUtil.h"
#include "duckstation/gl/context.h"
class EmuThread : public QThread
{
Q_OBJECT
void run() override;
public:
explicit EmuThread(QObject* parent = nullptr);
void changeWindowTitle(char* title);
// to be called from the UI thread
void emuRun();
void emuPause();
void emuUnpause();
void emuStop();
void emuFrameStep();
bool emuIsRunning();
bool emuIsActive();
void initContext();
void deinitContext();
int FrontBuffer = 0;
QMutex FrontBufferLock;
void updateScreenSettings(bool filter, const WindowInfo& windowInfo, int numScreens, int* screenKind, float* screenMatrix);
signals:
void windowUpdate();
void windowTitleChange(QString title);
void windowEmuStart();
void windowEmuStop();
void windowEmuPause();
void windowEmuReset();
void windowEmuFrameStep();
void windowLimitFPSChange();
void screenLayoutChange();
void windowFullscreenToggle();
void swapScreensToggle();
void screenEmphasisToggle();
void syncVolumeLevel();
private:
void drawScreenGL();
void initOpenGL();
void deinitOpenGL();
enum EmuStatusKind
{
emuStatus_Exit,
emuStatus_Running,
emuStatus_Paused,
emuStatus_FrameStep,
};
std::atomic<EmuStatusKind> EmuStatus;
EmuStatusKind PrevEmuStatus;
EmuStatusKind EmuRunning;
constexpr static int EmuPauseStackRunning = 0;
constexpr static int EmuPauseStackPauseThreshold = 1;
int EmuPauseStack;
enum ContextRequestKind
{
contextRequest_None = 0,
contextRequest_InitGL,
contextRequest_DeInitGL
};
std::atomic<ContextRequestKind> ContextRequest = contextRequest_None;
GL::Context* oglContext = nullptr;
GLuint screenVertexBuffer, screenVertexArray;
GLuint screenTexture;
GLuint screenShaderProgram;
GLuint screenShaderTransformULoc, screenShaderScreenSizeULoc;
QMutex screenSettingsLock;
WindowInfo windowInfo;
float screenMatrix[Frontend::MaxScreenTransforms][6];
int screenKind[Frontend::MaxScreenTransforms];
int numScreens;
bool filter;
int lastScreenWidth = -1, lastScreenHeight = -1;
};
class ScreenHandler
{
Q_GADGET
public:
ScreenHandler(QWidget* widget);
virtual ~ScreenHandler();
QTimer* setupMouseTimer();
void updateMouseTimer();
QTimer* mouseTimer;
QSize screenGetMinSize(int factor);
protected:
void screenSetupLayout(int w, int h);
void screenOnMousePress(QMouseEvent* event);
void screenOnMouseRelease(QMouseEvent* event);
void screenOnMouseMove(QMouseEvent* event);
void screenHandleTablet(QTabletEvent* event);
void screenHandleTouch(QTouchEvent* event);
float screenMatrix[Frontend::MaxScreenTransforms][6];
int screenKind[Frontend::MaxScreenTransforms];
int numScreens;
bool touching = false;
void showCursor();
};
class ScreenPanelNative : public QWidget, public ScreenHandler
{
Q_OBJECT
public:
explicit ScreenPanelNative(QWidget* parent);
virtual ~ScreenPanelNative();
protected:
void paintEvent(QPaintEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void tabletEvent(QTabletEvent* event) override;
bool event(QEvent* event) override;
private slots:
void onScreenLayoutChanged();
private:
void setupScreenLayout();
QImage screen[2];
QTransform screenTrans[Frontend::MaxScreenTransforms];
};
class ScreenPanelGL : public QWidget, public ScreenHandler
{
Q_OBJECT
public:
explicit ScreenPanelGL(QWidget* parent);
virtual ~ScreenPanelGL();
std::optional<WindowInfo> getWindowInfo();
bool createContext();
GL::Context* getContext() { return glContext.get(); }
void transferLayout(EmuThread* thread);
protected:
qreal devicePixelRatioFromScreen() const;
int scaledWindowWidth() const;
int scaledWindowHeight() const;
QPaintEngine* paintEngine() const override;
void resizeEvent(QResizeEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void tabletEvent(QTabletEvent* event) override;
bool event(QEvent* event) override;
private slots:
void onScreenLayoutChanged();
private:
void setupScreenLayout();
std::unique_ptr<GL::Context> glContext;
};
class MelonApplication : public QApplication
{
@ -247,197 +44,6 @@ public:
bool event(QEvent* event) override;
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget* parent = nullptr);
~MainWindow();
bool hasOGL;
GL::Context* getOGLContext();
bool preloadROMs(QStringList file, QStringList gbafile, bool boot);
QStringList splitArchivePath(const QString& filename, bool useMemberSyntax);
void onAppStateChanged(Qt::ApplicationState state);
protected:
void resizeEvent(QResizeEvent* event) override;
void changeEvent(QEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override;
void dropEvent(QDropEvent* event) override;
void focusInEvent(QFocusEvent* event) override;
void focusOutEvent(QFocusEvent* event) override;
signals:
void screenLayoutChange();
private slots:
void onOpenFile();
void onClickRecentFile();
void onClearRecentFiles();
void onBootFirmware();
void onInsertCart();
void onEjectCart();
void onInsertGBACart();
void onInsertGBAAddon();
void onEjectGBACart();
void onSaveState();
void onLoadState();
void onUndoStateLoad();
void onImportSavefile();
void onQuit();
void onPause(bool checked);
void onReset();
void onStop();
void onFrameStep();
void onEnableCheats(bool checked);
void onSetupCheats();
void onCheatsDialogFinished(int res);
void onROMInfo();
void onRAMInfo();
void onOpenTitleManager();
void onMPNewInstance();
void onOpenEmuSettings();
void onEmuSettingsDialogFinished(int res);
void onOpenPowerManagement();
void onOpenInputConfig();
void onInputConfigFinished(int res);
void onOpenVideoSettings();
void onOpenCameraSettings();
void onCameraSettingsFinished(int res);
void onOpenAudioSettings();
void onUpdateAudioSettings();
void onAudioSettingsFinished(int res);
void onOpenMPSettings();
void onMPSettingsFinished(int res);
void onOpenWifiSettings();
void onWifiSettingsFinished(int res);
void onOpenFirmwareSettings();
void onFirmwareSettingsFinished(int res);
void onOpenPathSettings();
void onPathSettingsFinished(int res);
void onOpenInterfaceSettings();
void onInterfaceSettingsFinished(int res);
void onUpdateMouseTimer();
void onChangeSavestateSRAMReloc(bool checked);
void onChangeScreenSize();
void onChangeScreenRotation(QAction* act);
void onChangeScreenGap(QAction* act);
void onChangeScreenLayout(QAction* act);
void onChangeScreenSwap(bool checked);
void onChangeScreenSizing(QAction* act);
void onChangeScreenAspect(QAction* act);
void onChangeIntegerScaling(bool checked);
void onChangeScreenFiltering(bool checked);
void onChangeShowOSD(bool checked);
void onChangeLimitFramerate(bool checked);
void onChangeAudioSync(bool checked);
void onTitleUpdate(QString title);
void onEmuStart();
void onEmuStop();
void onUpdateVideoSettings(bool glchange);
void onFullscreenToggled();
void onScreenEmphasisToggled();
private:
virtual void closeEvent(QCloseEvent* event) override;
QStringList currentROM;
QStringList currentGBAROM;
QList<QString> recentFileList;
QMenu *recentMenu;
void updateRecentFilesMenu();
bool verifySetup();
QString pickFileFromArchive(QString archiveFileName);
QStringList pickROM(bool gba);
void updateCartInserted(bool gba);
void createScreenPanel();
bool pausedManually = false;
int oldW, oldH;
bool oldMax;
public:
ScreenHandler* panel;
QWidget* panelWidget;
QAction* actOpenROM;
QAction* actBootFirmware;
QAction* actCurrentCart;
QAction* actInsertCart;
QAction* actEjectCart;
QAction* actCurrentGBACart;
QAction* actInsertGBACart;
QAction* actInsertGBAAddon[1];
QAction* actEjectGBACart;
QAction* actImportSavefile;
QAction* actSaveState[9];
QAction* actLoadState[9];
QAction* actUndoStateLoad;
QAction* actQuit;
QAction* actPause;
QAction* actReset;
QAction* actStop;
QAction* actFrameStep;
QAction* actEnableCheats;
QAction* actSetupCheats;
QAction* actROMInfo;
QAction* actRAMInfo;
QAction* actTitleManager;
QAction* actMPNewInstance;
QAction* actEmuSettings;
#ifdef __APPLE__
QAction* actPreferences;
#endif
QAction* actPowerManagement;
QAction* actInputConfig;
QAction* actVideoSettings;
QAction* actCameraSettings;
QAction* actAudioSettings;
QAction* actMPSettings;
QAction* actWifiSettings;
QAction* actFirmwareSettings;
QAction* actPathSettings;
QAction* actInterfaceSettings;
QAction* actSavestateSRAMReloc;
QAction* actScreenSize[4];
QActionGroup* grpScreenRotation;
QAction* actScreenRotation[Frontend::screenRot_MAX];
QActionGroup* grpScreenGap;
QAction* actScreenGap[6];
QActionGroup* grpScreenLayout;
QAction* actScreenLayout[Frontend::screenLayout_MAX];
QAction* actScreenSwap;
QActionGroup* grpScreenSizing;
QAction* actScreenSizing[Frontend::screenSizing_MAX];
QAction* actIntegerScaling;
QActionGroup* grpScreenAspectTop;
QAction** actScreenAspectTop;
QActionGroup* grpScreenAspectBot;
QAction** actScreenAspectBot;
QAction* actScreenFiltering;
QAction* actShowOSD;
QAction* actLimitFramerate;
QAction* actAudioSync;
};
extern QString* systemThemeName;
#endif // MAIN_H

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2022 melonDS team
Copyright 2016-2023 melonDS team
This file is part of melonDS.