mirror of
https://github.com/melonDS-emu/melonDS.git
synced 2025-08-01 10:39:53 -06:00
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:
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <vector>
|
||||
|
||||
namespace GL {
|
||||
using namespace melonDS;
|
||||
class Context
|
||||
{
|
||||
public:
|
||||
|
@ -13,6 +13,7 @@ struct NSView;
|
||||
|
||||
namespace GL {
|
||||
|
||||
using namespace melonDS;
|
||||
class ContextAGL final : public Context
|
||||
{
|
||||
public:
|
||||
|
@ -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
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <X11/Xutil.h>
|
||||
|
||||
namespace GL {
|
||||
using namespace melonDS;
|
||||
class X11Window
|
||||
{
|
||||
public:
|
||||
|
@ -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
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2021-2022 melonDS team
|
||||
Copyright 2021-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
||||
}
|
||||
|
91
src/frontend/qt_sdl/DateTimeDialog.cpp
Normal file
91
src/frontend/qt_sdl/DateTimeDialog.cpp
Normal 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);
|
||||
}
|
||||
}
|
70
src/frontend/qt_sdl/DateTimeDialog.h
Normal file
70
src/frontend/qt_sdl/DateTimeDialog.h
Normal 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
|
148
src/frontend/qt_sdl/DateTimeDialog.ui
Normal file
148
src/frontend/qt_sdl/DateTimeDialog.ui
Normal 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>
|
@ -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()
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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>
|
||||
|
752
src/frontend/qt_sdl/EmuThread.cpp
Normal file
752
src/frontend/qt_sdl/EmuThread.cpp
Normal 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);
|
||||
}
|
134
src/frontend/qt_sdl/EmuThread.h
Normal file
134
src/frontend/qt_sdl/EmuThread.h
Normal 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
|
@ -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;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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};
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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];
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
//
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
|
@ -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 });
|
||||
|
@ -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
|
||||
|
@ -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]]);
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
@ -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
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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
|
||||
|
1072
src/frontend/qt_sdl/Screen.cpp
Normal file
1072
src/frontend/qt_sdl/Screen.cpp
Normal file
File diff suppressed because it is too large
Load Diff
196
src/frontend/qt_sdl/Screen.h
Normal file
196
src/frontend/qt_sdl/Screen.h
Normal 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
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
|
2058
src/frontend/qt_sdl/Window.cpp
Normal file
2058
src/frontend/qt_sdl/Window.cpp
Normal file
File diff suppressed because it is too large
Load Diff
299
src/frontend/qt_sdl/Window.h
Normal file
299
src/frontend/qt_sdl/Window.h
Normal 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
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
|
836
src/frontend/qt_sdl/gif-h/gif.h
Normal file
836
src/frontend/qt_sdl/gif-h/gif.h
Normal 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
@ -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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016-2022 melonDS team
|
||||
Copyright 2016-2023 melonDS team
|
||||
|
||||
This file is part of melonDS.
|
||||
|
||||
|
Reference in New Issue
Block a user