Files
melonDS/src/frontend/qt_sdl/main.cpp
2020-05-20 03:01:09 +02:00

1163 lines
30 KiB
C++

/*
Copyright 2016-2020 Arisotura
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 <QApplication>
#include <QMessageBox>
#include <QMenuBar>
#include <QFileDialog>
#include <QPaintEvent>
#include <QPainter>
#include <QKeyEvent>
#include <QMimeData>
#include <SDL2/SDL.h>
#include "main.h"
#include "Input.h"
#include "EmuSettingsDialog.h"
#include "InputConfigDialog.h"
#include "types.h"
#include "version.h"
#include "FrontendUtil.h"
#include "NDS.h"
#include "GBACart.h"
#include "GPU.h"
#include "SPU.h"
#include "Wifi.h"
#include "Platform.h"
#include "Config.h"
#include "PlatformConfig.h"
#include "Savestate.h"
// TODO: uniform variable spelling
bool RunningSomething;
MainWindow* mainWindow;
EmuThread* emuThread;
SDL_AudioDeviceID audioDevice;
int audioFreq;
SDL_cond* audioSync;
SDL_mutex* audioSyncLock;
void audioCallback(void* data, Uint8* stream, int len)
{
len /= (sizeof(s16) * 2);
// resample incoming audio to match the output sample rate
int len_in = Frontend::AudioOut_GetNumSamples(len);
s16 buf_in[1024*2];
int num_in;
SDL_LockMutex(audioSyncLock);
num_in = SPU::ReadOutput(buf_in, len_in);
SDL_CondSignal(audioSync);
SDL_UnlockMutex(audioSyncLock);
if (num_in < 1)
{
memset(stream, 0, len*sizeof(s16)*2);
return;
}
int margin = 6;
if (num_in < len_in-margin)
{
int last = num_in-1;
if (last < 0) last = 0;
for (int i = num_in; i < len_in-margin; i++)
((u32*)buf_in)[i] = ((u32*)buf_in)[last];
num_in = len_in-margin;
}
Frontend::AudioOut_Resample(buf_in, num_in, (s16*)stream, len);
}
EmuThread::EmuThread(QObject* parent) : QThread(parent)
{
EmuStatus = 0;
EmuRunning = 2;
RunningSomething = false;
connect(this, SIGNAL(windowTitleChange(QString)), mainWindow, SLOT(onTitleUpdate(QString)));
connect(this, SIGNAL(windowEmuStart()), mainWindow, SLOT(onEmuStart()));
connect(this, SIGNAL(windowEmuStop()), mainWindow, SLOT(onEmuStop()));
emit windowEmuStop();
}
void EmuThread::run()
{
NDS::Init();
/*MainScreenPos[0] = 0;
MainScreenPos[1] = 0;
MainScreenPos[2] = 0;
AutoScreenSizing = 0;*/
/*if (Screen_UseGL)
{
uiGLMakeContextCurrent(GLContext);
GPU3D::InitRenderer(true);
uiGLMakeContextCurrent(NULL);
}
else*/
{
GPU3D::InitRenderer(false);
}
Input::Init();
/*Touching = false;*/
u32 nframes = 0;
u32 starttick = SDL_GetTicks();
u32 lasttick = starttick;
u32 lastmeasuretick = lasttick;
u32 fpslimitcount = 0;
char melontitle[100];
while (EmuRunning != 0)
{
Input::Process();
/*if (Input::HotkeyPressed(HK_FastForwardToggle))
{
Config::LimitFPS = !Config::LimitFPS;
// TODO: reflect in UI!
}*/
//if (Input::HotkeyPressed(HK_Pause)) uiQueueMain(TogglePause, NULL);
//if (Input::HotkeyPressed(HK_Reset)) uiQueueMain(Reset, NULL);
if (GBACart::CartInserted && GBACart::HasSolarSensor)
{
if (Input::HotkeyPressed(HK_SolarSensorDecrease))
{
if (GBACart_SolarSensor::LightLevel > 0) GBACart_SolarSensor::LightLevel--;
//char msg[64];
//sprintf(msg, "Solar sensor level set to %d", GBACart_SolarSensor::LightLevel);
//OSD::AddMessage(0, msg);
}
if (Input::HotkeyPressed(HK_SolarSensorIncrease))
{
if (GBACart_SolarSensor::LightLevel < 10) GBACart_SolarSensor::LightLevel++;
//char msg[64];
//sprintf(msg, "Solar sensor level set to %d", GBACart_SolarSensor::LightLevel);
//OSD::AddMessage(0, msg);
}
}
if (EmuRunning == 1)
{
EmuStatus = 1;
// process input and hotkeys
NDS::SetKeyMask(Input::InputMask);
if (Input::HotkeyPressed(HK_Lid))
{
bool lid = !NDS::IsLidClosed();
NDS::SetLidClosed(lid);
//OSD::AddMessage(0, lid ? "Lid closed" : "Lid opened");
}
// microphone input
/*FeedMicInput();
if (Screen_UseGL)
{
uiGLBegin(GLContext);
uiGLMakeContextCurrent(GLContext);
}*/
// auto screen layout
/*{
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 = 0;
}
else
{
if (MainScreenPos[0] == 1)
guess = 1;
else
guess = 2;
}
if (guess != AutoScreenSizing)
{
AutoScreenSizing = guess;
SetupScreenRects(WindowWidth, WindowHeight);
}
}*/
// emulate
u32 nlines = NDS::RunFrame();
#ifdef MELONCAP
MelonCap::Update();
#endif // MELONCAP
if (EmuRunning == 0) break;
/*if (Screen_UseGL)
{
GLScreen_DrawScreen();
uiGLEnd(GLContext);
}
uiAreaQueueRedrawAll(MainDrawArea);*/
mainWindow->update();
bool fastforward = Input::HotkeyDown(HK_FastForward);
if (Config::AudioSync && (!fastforward) && audioDevice)
{
SDL_LockMutex(audioSyncLock);
while (SPU::GetOutputSize() > 1024)
{
int ret = SDL_CondWaitTimeout(audioSync, audioSyncLock, 500);
if (ret == SDL_MUTEX_TIMEDOUT) break;
}
SDL_UnlockMutex(audioSyncLock);
}
float framerate = (1000.0f * nlines) / (60.0f * 263.0f);
{
u32 curtick = SDL_GetTicks();
u32 delay = curtick - lasttick;
bool limitfps = Config::LimitFPS && !fastforward;
if (limitfps)
{
float wantedtickF = starttick + (framerate * (fpslimitcount+1));
u32 wantedtick = (u32)ceil(wantedtickF);
if (curtick < wantedtick) SDL_Delay(wantedtick - curtick);
lasttick = SDL_GetTicks();
fpslimitcount++;
if ((abs(wantedtickF - (float)wantedtick) < 0.001312) || (fpslimitcount > 60))
{
fpslimitcount = 0;
starttick = lasttick;
}
}
else
{
if (delay < 1) SDL_Delay(1);
lasttick = SDL_GetTicks();
}
}
nframes++;
if (nframes >= 30)
{
u32 tick = SDL_GetTicks();
u32 diff = tick - lastmeasuretick;
lastmeasuretick = tick;
u32 fps;
if (diff < 1) fps = 77777;
else fps = (nframes * 1000) / diff;
nframes = 0;
float fpstarget;
if (framerate < 1) fpstarget = 999;
else fpstarget = 1000.0f/framerate;
sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget);
changeWindowTitle(melontitle);
}
}
else
{
// paused
nframes = 0;
lasttick = SDL_GetTicks();
starttick = lasttick;
lastmeasuretick = lasttick;
fpslimitcount = 0;
if (EmuRunning == 2)
{
/*if (Screen_UseGL)
{
uiGLBegin(GLContext);
uiGLMakeContextCurrent(GLContext);
GLScreen_DrawScreen();
uiGLEnd(GLContext);
}
uiAreaQueueRedrawAll(MainDrawArea);*/
mainWindow->update();
}
//if (Screen_UseGL) uiGLMakeContextCurrent(NULL);
EmuStatus = EmuRunning;
sprintf(melontitle, "melonDS " MELONDS_VERSION);
changeWindowTitle(melontitle);
SDL_Delay(100);
}
}
EmuStatus = 0;
//if (Screen_UseGL) uiGLMakeContextCurrent(GLContext);
NDS::DeInit();
//Platform::LAN_DeInit();
/*if (Screen_UseGL)
{
OSD::DeInit(true);
GLScreen_DeInit();
}
else
OSD::DeInit(false);*/
//if (Screen_UseGL) uiGLMakeContextCurrent(NULL);
}
void EmuThread::changeWindowTitle(char* title)
{
emit windowTitleChange(QString(title));
}
void EmuThread::emuRun()
{
EmuRunning = 1;
RunningSomething = true;
// checkme
emit windowEmuStart();
if (audioDevice) SDL_PauseAudioDevice(audioDevice, 0);
}
void EmuThread::emuPause(bool refresh)
{
int status = refresh ? 2:3;
PrevEmuStatus = EmuRunning;
EmuRunning = status;
while (EmuStatus != status);
//emit windowEmuPause();
if (audioDevice) SDL_PauseAudioDevice(audioDevice, 1);
}
void EmuThread::emuUnpause()
{
EmuRunning = PrevEmuStatus;
//emit windowEmuUnpause();
if (audioDevice) SDL_PauseAudioDevice(audioDevice, 0);
}
void EmuThread::emuStop()
{
EmuRunning = 0;
if (audioDevice) SDL_PauseAudioDevice(audioDevice, 1);
}
bool EmuThread::emuIsRunning()
{
return (EmuRunning == 1);
}
MainWindowPanel::MainWindowPanel(QWidget* parent) : QWidget(parent)
{
screen[0] = new QImage(256, 192, QImage::Format_RGB32);
screen[1] = new QImage(256, 192, QImage::Format_RGB32);
touching = false;
}
MainWindowPanel::~MainWindowPanel()
{
delete screen[0];
delete screen[1];
}
void MainWindowPanel::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
// fill background
painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0));
int frontbuf = GPU::FrontBuffer;
if (!GPU::Framebuffer[frontbuf][0] || !GPU::Framebuffer[frontbuf][1]) return;
memcpy(screen[0]->scanLine(0), GPU::Framebuffer[frontbuf][0], 256*192*4);
memcpy(screen[1]->scanLine(0), GPU::Framebuffer[frontbuf][1], 256*192*4);
QRect src = QRect(0, 0, 256, 192);
QRect dstTop = QRect(0, 0, 256, 192); // TODO
QRect dstBot = QRect(0, 192, 256, 192); // TODO
painter.drawImage(dstTop, *screen[0]);
painter.drawImage(dstBot, *screen[1]);
}
void MainWindowPanel::transformTSCoords(int& x, int& y)
{
// TODO: actual screen de-transform taking screen layout/rotation/etc into account
y -= 192;
// clamp to screen range
if (x < 0) x = 0;
else if (x > 255) x = 255;
if (y < 0) y = 0;
else if (y > 191) y = 191;
}
void MainWindowPanel::mousePressEvent(QMouseEvent* event)
{
event->accept();
if (event->button() != Qt::LeftButton) return;
int x = event->pos().x();
int y = event->pos().y();
if (x >= 0 && x < 256 && y >= 192 && y < 384)
{
touching = true;
transformTSCoords(x, y);
NDS::TouchScreen(x, y);
}
}
void MainWindowPanel::mouseReleaseEvent(QMouseEvent* event)
{
event->accept();
if (event->button() != Qt::LeftButton) return;
if (touching)
{
touching = false;
NDS::ReleaseScreen();
}
}
void MainWindowPanel::mouseMoveEvent(QMouseEvent* event)
{
event->accept();
if (!(event->buttons() & Qt::LeftButton)) return;
if (!touching) return;
int x = event->pos().x();
int y = event->pos().y();
transformTSCoords(x, y);
NDS::TouchScreen(x, y);
}
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
{
setWindowTitle("melonDS " MELONDS_VERSION);
setAttribute(Qt::WA_DeleteOnClose);
setAcceptDrops(true);
QMenuBar* menubar = new QMenuBar();
{
QMenu* menu = menubar->addMenu("File");
actOpenROM = menu->addAction("Open ROM...");
connect(actOpenROM, &QAction::triggered, this, &MainWindow::onOpenFile);
//actBootFirmware = menu->addAction("Launch DS menu");
actBootFirmware = menu->addAction("Boot firmware");
connect(actBootFirmware, &QAction::triggered, this, &MainWindow::onBootFirmware);
menu->addSeparator();
{
QMenu* submenu = menu->addMenu("Save state");
for (int i = 1; i < 9; i++)
{
char title[16];
sprintf(title, "%d", i);
actSaveState[i] = submenu->addAction(title);
actSaveState[i]->setShortcut(QKeySequence(Qt::ShiftModifier | (Qt::Key_F1+i-1)));
actSaveState[i]->setData(QVariant(i));
connect(actSaveState[i], &QAction::triggered, this, &MainWindow::onSaveState);
}
actSaveState[0] = submenu->addAction("File...");
actSaveState[0]->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_F9));
actSaveState[0]->setData(QVariant(0));
connect(actSaveState[0], &QAction::triggered, this, &MainWindow::onSaveState);
}
{
QMenu* submenu = menu->addMenu("Load state");
for (int i = 1; i < 9; i++)
{
char title[16];
sprintf(title, "%d", i);
actLoadState[i] = submenu->addAction(title);
actLoadState[i]->setShortcut(QKeySequence(Qt::Key_F1+i-1));
actLoadState[i]->setData(QVariant(i));
connect(actLoadState[i], &QAction::triggered, this, &MainWindow::onLoadState);
}
actLoadState[0] = submenu->addAction("File...");
actLoadState[0]->setShortcut(QKeySequence(Qt::Key_F9));
actLoadState[0]->setData(QVariant(0));
connect(actLoadState[0], &QAction::triggered, this, &MainWindow::onLoadState);
}
actUndoStateLoad = menu->addAction("Undo state load");
actUndoStateLoad->setShortcut(QKeySequence(Qt::Key_F12));
connect(actUndoStateLoad, &QAction::triggered, this, &MainWindow::onUndoStateLoad);
menu->addSeparator();
actQuit = menu->addAction("Quit");
connect(actQuit, &QAction::triggered, this, &MainWindow::onQuit);
}
{
QMenu* menu = menubar->addMenu("System");
actPause = menu->addAction("Pause");
actPause->setCheckable(true);
connect(actPause, &QAction::triggered, this, &MainWindow::onPause);
actReset = menu->addAction("Reset");
connect(actReset, &QAction::triggered, this, &MainWindow::onReset);
actStop = menu->addAction("Stop");
connect(actStop, &QAction::triggered, this, &MainWindow::onStop);
}
{
QMenu* menu = menubar->addMenu("Config");
actEmuSettings = menu->addAction("Emu settings");
connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings);
actInputConfig = menu->addAction("Input and hotkeys");
connect(actInputConfig, &QAction::triggered, this, &MainWindow::onOpenInputConfig);
}
setMenuBar(menubar);
panel = new MainWindowPanel(this);
setCentralWidget(panel);
panel->setMinimumSize(256, 384);
}
MainWindow::~MainWindow()
{
}
void MainWindow::keyPressEvent(QKeyEvent* event)
{
if (event->isAutoRepeat()) return;
Input::KeyPress(event);
}
void MainWindow::keyReleaseEvent(QKeyEvent* event)
{
if (event->isAutoRepeat()) return;
Input::KeyRelease(event);
}
void MainWindow::dragEnterEvent(QDragEnterEvent* event)
{
if (!event->mimeData()->hasUrls()) return;
QList<QUrl> urls = event->mimeData()->urls();
if (urls.count() > 1) return; // not handling more than one file at once
QString filename = urls.at(0).toLocalFile();
QString ext = filename.right(3);
if (ext == "nds" || ext == "srl" || (ext == "gba" && RunningSomething))
event->acceptProposedAction();
}
void MainWindow::dropEvent(QDropEvent* event)
{
if (!event->mimeData()->hasUrls()) return;
QList<QUrl> urls = event->mimeData()->urls();
if (urls.count() > 1) return; // not handling more than one file at once
emuThread->emuPause(true);
QString filename = urls.at(0).toLocalFile();
QString ext = filename.right(3);
char _filename[1024];
strncpy(_filename, filename.toStdString().c_str(), 1023); _filename[1023] = '\0';
int slot; int res;
if (ext == "gba")
{
slot = 1;
res = Frontend::LoadROM(_filename, Frontend::ROMSlot_GBA);
}
else
{
slot = 0;
res = Frontend::LoadROM(_filename, Frontend::ROMSlot_NDS);
}
if (res != Frontend::Load_OK)
{
QMessageBox::critical(this,
"melonDS",
loadErrorStr(res));
emuThread->emuUnpause();
}
else if (slot == 1)
{
// checkme
emuThread->emuUnpause();
}
else
{
emuThread->emuRun();
}
}
QString MainWindow::loadErrorStr(int error)
{
switch (error)
{
case Frontend::Load_BIOS9Missing: return "DS ARM9 BIOS was not found or could not be accessed.";
case Frontend::Load_BIOS9Bad: return "DS ARM9 BIOS is not a valid BIOS dump.";
case Frontend::Load_BIOS7Missing: return "DS ARM7 BIOS was not found or could not be accessed.";
case Frontend::Load_BIOS7Bad: return "DS ARM7 BIOS is not a valid BIOS dump.";
case Frontend::Load_FirmwareMissing: return "DS firmware was not found or could not be accessed.";
case Frontend::Load_FirmwareBad: return "DS firmware is not a valid firmware dump.";
case Frontend::Load_FirmwareNotBootable: return "DS firmware is not bootable.";
case Frontend::Load_ROMLoadError: return "Failed to load the ROM. Make sure the file is accessible and isn't used by another application.";
default: return "Unknown error during launch; smack Arisotura.";
}
}
void MainWindow::onOpenFile()
{
emuThread->emuPause(true);
QString filename = QFileDialog::getOpenFileName(this,
"Open ROM",
Config::LastROMFolder,
"DS ROMs (*.nds *.srl);;GBA ROMs (*.gba);;Any file (*.*)");
if (filename.isEmpty())
{
emuThread->emuUnpause();
return;
}
// TODO: validate the input file!!
// * check that it is a proper ROM
// * ensure the binary offsets are sane
// * etc
// this shit is stupid
char file[1024];
strncpy(file, filename.toStdString().c_str(), 1023); file[1023] = '\0';
int pos = strlen(file)-1;
while (file[pos] != '/' && file[pos] != '\\' && pos > 0) pos--;
strncpy(Config::LastROMFolder, file, pos);
Config::LastROMFolder[pos] = '\0';
char* ext = &file[strlen(file)-3];
int slot; int res;
if (!strcasecmp(ext, "gba"))
{
slot = 1;
res = Frontend::LoadROM(file, Frontend::ROMSlot_GBA);
}
else
{
slot = 0;
res = Frontend::LoadROM(file, Frontend::ROMSlot_NDS);
}
if (res != Frontend::Load_OK)
{
QMessageBox::critical(this,
"melonDS",
loadErrorStr(res));
emuThread->emuUnpause();
}
else if (slot == 1)
{
// checkme
emuThread->emuUnpause();
}
else
{
emuThread->emuRun();
}
}
void MainWindow::onBootFirmware()
{
// TODO: ensure the firmware is actually bootable!!
// TODO: check the whole GBA cart shito
emuThread->emuPause(true);
int res = Frontend::LoadBIOS();
if (res != Frontend::Load_OK)
{
QMessageBox::critical(this,
"melonDS",
loadErrorStr(res));
emuThread->emuUnpause();
}
else
{
emuThread->emuRun();
}
}
void MainWindow::onSaveState()
{
int slot = ((QAction*)sender())->data().toInt();
emuThread->emuPause(true);
char filename[1024];
if (slot > 0)
{
Frontend::GetSavestateName(slot, filename, 1024);
}
else
{
// TODO: specific 'last directory' for savestate files?
QString qfilename = QFileDialog::getSaveFileName(this,
"Save state",
Config::LastROMFolder,
"melonDS savestates (*.mln);;Any file (*.*)");
if (qfilename.isEmpty())
{
emuThread->emuUnpause();
return;
}
strncpy(filename, qfilename.toStdString().c_str(), 1023); filename[1023] = '\0';
}
if (Frontend::SaveState(filename))
{
// TODO: OSD message
actLoadState[slot]->setEnabled(true);
}
else
{
// TODO: OSD error message?
}
emuThread->emuUnpause();
}
void MainWindow::onLoadState()
{
int slot = ((QAction*)sender())->data().toInt();
emuThread->emuPause(true);
char filename[1024];
if (slot > 0)
{
Frontend::GetSavestateName(slot, filename, 1024);
}
else
{
// TODO: specific 'last directory' for savestate files?
QString qfilename = QFileDialog::getOpenFileName(this,
"Load state",
Config::LastROMFolder,
"melonDS savestates (*.ml*);;Any file (*.*)");
if (qfilename.isEmpty())
{
emuThread->emuUnpause();
return;
}
strncpy(filename, qfilename.toStdString().c_str(), 1023); filename[1023] = '\0';
}
if (!Platform::FileExists(filename))
{
/*char msg[64];
if (slot > 0) sprintf(msg, "State slot %d is empty", slot);
else sprintf(msg, "State file does not exist");
OSD::AddMessage(0xFFA0A0, msg);*/
emuThread->emuUnpause();
return;
}
if (Frontend::LoadState(filename))
{
// TODO: OSD message
}
else
{
// TODO: OSD error message?
}
emuThread->emuUnpause();
}
void MainWindow::onUndoStateLoad()
{
emuThread->emuPause(true);
Frontend::UndoStateLoad();
emuThread->emuUnpause();
}
void MainWindow::onQuit()
{
QApplication::quit();
}
void MainWindow::onPause(bool checked)
{
if (emuThread->emuIsRunning())
{
emuThread->emuPause(true);
}
else
{
emuThread->emuUnpause();
}
}
void MainWindow::onReset()
{
//
}
void MainWindow::onStop()
{
//
}
void MainWindow::onTitleUpdate(QString title)
{
setWindowTitle(title);
}
void MainWindow::onEmuStart()
{
for (int i = 1; i < 9; i++)
{
actSaveState[i]->setEnabled(true);
actLoadState[i]->setEnabled(Frontend::SavestateExists(i));
}
actSaveState[0]->setEnabled(true);
actLoadState[0]->setEnabled(true);
actUndoStateLoad->setEnabled(true);
actPause->setEnabled(true);
actPause->setChecked(false);
actReset->setEnabled(true);
actStop->setEnabled(true);
}
void MainWindow::onEmuStop()
{
for (int i = 0; i < 9; i++)
{
actSaveState[i]->setEnabled(false);
actLoadState[i]->setEnabled(false);
}
actUndoStateLoad->setEnabled(false);
actPause->setEnabled(false);
actReset->setEnabled(false);
actStop->setEnabled(false);
}
void MainWindow::onEmuPause()
{
//
}
void MainWindow::onEmuUnpause()
{
//
}
void MainWindow::onOpenEmuSettings()
{
EmuSettingsDialog::openDlg(this);
}
void MainWindow::onOpenInputConfig()
{
emuThread->emuPause(true);
InputConfigDialog* dlg = InputConfigDialog::openDlg(this);
connect(dlg, &InputConfigDialog::finished, this, &MainWindow::onInputConfigFinished);
}
void MainWindow::onInputConfigFinished(int res)
{
emuThread->emuUnpause();
}
int main(int argc, char** argv)
{
srand(time(NULL));
printf("melonDS " MELONDS_VERSION "\n");
printf(MELONDS_URL "\n");
Platform::Init(argc, argv);
QApplication melon(argc, argv);
melon.setWindowIcon(QIcon(":/melon-icon"));
// http://stackoverflow.com/questions/14543333/joystick-wont-work-using-sdl
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
if (SDL_Init(SDL_INIT_HAPTIC) < 0)
{
printf("SDL couldn't init rumble\n");
}
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) < 0)
{
QMessageBox::critical(NULL, "melonDS", "SDL shat itself :(");
return 1;
}
SDL_JoystickEventState(SDL_ENABLE);
Config::Load();
if (Config::AudioVolume < 0) Config::AudioVolume = 0;
else if (Config::AudioVolume > 256) Config::AudioVolume = 256;
// TODO: this should be checked before running anything
#if 0
{
const char* romlist_missing = "Save memory type detection will not work correctly.\n\n"
"You should use the latest version of romlist.bin (provided in melonDS release packages).";
#if !defined(UNIX_PORTABLE) && !defined(__WIN32__)
std::string missingstr = std::string(romlist_missing) +
"\n\nThe ROM list should be placed in " + g_get_user_data_dir() + "/melonds/, otherwise "
"melonDS will search for it in the current working directory.";
const char* romlist_missing_text = missingstr.c_str();
#else
const char* romlist_missing_text = romlist_missing;
#endif
FILE* f = Platform::OpenDataFile("romlist.bin");
if (f)
{
u32 data;
fread(&data, 4, 1, f);
fclose(f);
if ((data >> 24) == 0) // old CRC-based list
{
uiMsgBoxError(NULL, "Your version of romlist.bin is outdated.", romlist_missing_text);
}
}
else
{
uiMsgBoxError(NULL, "romlist.bin not found.", romlist_missing_text);
}
}
#endif
audioSync = SDL_CreateCond();
audioSyncLock = SDL_CreateMutex();
audioFreq = 48000; // TODO: make configurable?
SDL_AudioSpec whatIwant, whatIget;
memset(&whatIwant, 0, sizeof(SDL_AudioSpec));
whatIwant.freq = audioFreq;
whatIwant.format = AUDIO_S16LSB;
whatIwant.channels = 2;
whatIwant.samples = 1024;
whatIwant.callback = audioCallback;
audioDevice = SDL_OpenAudioDevice(NULL, 0, &whatIwant, &whatIget, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
if (!audioDevice)
{
printf("Audio init failed: %s\n", SDL_GetError());
}
else
{
audioFreq = whatIget.freq;
printf("Audio output frequency: %d Hz\n", audioFreq);
SDL_PauseAudioDevice(audioDevice, 1);
}
Frontend::Init_ROM();
Frontend::Init_Audio(audioFreq);
Input::JoystickID = Config::JoystickID;
Input::OpenJoystick();
mainWindow = new MainWindow();
mainWindow->show();
emuThread = new EmuThread();
emuThread->start();
emuThread->emuPause(true);
if (argc > 1)
{
char* file = argv[1];
char* ext = &file[strlen(file)-3];
if (!strcasecmp(ext, "nds") || !strcasecmp(ext, "srl"))
{
int res = Frontend::LoadROM(file, Frontend::ROMSlot_NDS);
if (res == Frontend::Load_OK)
{
if (argc > 2)
{
file = argv[2];
ext = &file[strlen(file)-3];
if (!strcasecmp(ext, "gba"))
{
Frontend::LoadROM(file, Frontend::ROMSlot_GBA);
}
}
emuThread->emuRun();
}
}
}
int ret = melon.exec();
emuThread->emuStop();
emuThread->wait();
delete emuThread;
Input::CloseJoystick();
if (audioDevice) SDL_CloseAudioDevice(audioDevice);
//if (MicDevice) SDL_CloseAudioDevice(MicDevice);
SDL_DestroyCond(audioSync);
SDL_DestroyMutex(audioSyncLock);
//if (MicWavBuffer) delete[] MicWavBuffer;
Config::Save();
SDL_Quit();
Platform::DeInit();
return ret;
}
#ifdef __WIN32__
#include <windows.h>
int CALLBACK WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int cmdshow)
{
int argc = 0;
wchar_t** argv_w = CommandLineToArgvW(GetCommandLineW(), &argc);
char* nullarg = "";
char** argv = new char*[argc];
for (int i = 0; i < argc; i++)
{
if (!argv_w) { argv[i] = nullarg; continue; }
int len = WideCharToMultiByte(CP_UTF8, 0, argv_w[i], -1, NULL, 0, NULL, NULL);
if (len < 1) { argv[i] = nullarg; continue; }
argv[i] = new char[len];
int res = WideCharToMultiByte(CP_UTF8, 0, argv_w[i], -1, argv[i], len, NULL, NULL);
if (res != len) { delete[] argv[i]; argv[i] = nullarg; }
}
if (argv_w) LocalFree(argv_w);
if (AttachConsole(ATTACH_PARENT_PROCESS))
{
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
printf("\n");
}
int ret = main(argc, argv);
printf("\n\n>");
for (int i = 0; i < argc; i++) if (argv[i] != nullarg) delete[] argv[i];
delete[] argv;
return ret;
}
#endif