aboutsummaryrefslogtreecommitdiff
path: root/src/libui_sdl
diff options
context:
space:
mode:
Diffstat (limited to 'src/libui_sdl')
-rw-r--r--src/libui_sdl/DlgEmuSettings.cpp252
-rw-r--r--src/libui_sdl/libui/ui.h764
-rw-r--r--src/libui_sdl/libui/unix/stddialogs.c126
-rw-r--r--src/libui_sdl/libui/windows/stddialogs.cpp180
-rw-r--r--src/libui_sdl/main.cpp3061
5 files changed, 4383 insertions, 0 deletions
diff --git a/src/libui_sdl/DlgEmuSettings.cpp b/src/libui_sdl/DlgEmuSettings.cpp
new file mode 100644
index 0000000..0df9c6c
--- /dev/null
+++ b/src/libui_sdl/DlgEmuSettings.cpp
@@ -0,0 +1,252 @@
+/*
+ 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 <stdio.h>
+
+#include "libui/ui.h"
+
+#include "../types.h"
+#include "PlatformConfig.h"
+
+#include "DlgEmuSettings.h"
+
+
+void ApplyNewSettings(int type);
+
+extern bool RunningSomething;
+
+namespace DlgEmuSettings
+{
+
+bool opened;
+uiWindow* win;
+
+uiCheckbox* cbDirectBoot;
+
+#ifdef JIT_ENABLED
+uiCheckbox* cbJITEnabled;
+uiEntry* enJITMaxBlockSize;
+uiCheckbox* cbJITBranchOptimisations;
+uiCheckbox* cbJITLiteralOptimisations;
+#endif
+
+int OnCloseWindow(uiWindow* window, void* blarg)
+{
+ opened = false;
+ return 1;
+}
+
+void OnCancel(uiButton* btn, void* blarg)
+{
+ uiControlDestroy(uiControl(win));
+ opened = false;
+}
+
+void OnOk(uiButton* btn, void* blarg)
+{
+#ifdef JIT_ENABLED
+ bool restart = false;
+
+ bool enableJit = uiCheckboxChecked(cbJITEnabled);
+ char* maxBlockSizeStr = uiEntryText(enJITMaxBlockSize);
+ long blockSize = strtol(maxBlockSizeStr, NULL, 10);
+ bool branchOptimisations = uiCheckboxChecked(cbJITBranchOptimisations);
+ bool literalOptimisations = uiCheckboxChecked(cbJITLiteralOptimisations);
+ uiFreeText(maxBlockSizeStr);
+ if (blockSize < 1)
+ blockSize = 1;
+ if (blockSize > 32)
+ blockSize = 32;
+
+ if (enableJit != Config::JIT_Enable || blockSize != Config::JIT_MaxBlockSize
+ || branchOptimisations != Config::JIT_BrancheOptimisations
+ || literalOptimisations != Config::JIT_LiteralOptimisations)
+ {
+ if (RunningSomething &&
+ !uiMsgBoxConfirm(win, "Reset emulator",
+ "Changing JIT settings requires a reset.\n\nDo you want to continue?"))
+ return;
+
+ Config::JIT_Enable = enableJit;
+ Config::JIT_MaxBlockSize = blockSize;
+ Config::JIT_BrancheOptimisations = branchOptimisations;
+ Config::JIT_LiteralOptimisations = literalOptimisations;
+
+ restart = true;
+ }
+#endif
+
+ Config::DirectBoot = uiCheckboxChecked(cbDirectBoot);
+
+ Config::Save();
+
+ uiControlDestroy(uiControl(win));
+ opened = false;
+
+#ifdef JIT_ENABLED
+ if (restart)
+ ApplyNewSettings(4);
+#endif
+}
+
+#ifdef JIT_ENABLED
+void OnJITStateChanged(uiCheckbox* cb, void* blarg)
+{
+ if (uiCheckboxChecked(cb))
+ {
+ uiControlEnable(uiControl(enJITMaxBlockSize));
+ uiControlEnable(uiControl(cbJITBranchOptimisations));
+ uiControlEnable(uiControl(cbJITLiteralOptimisations));
+ }
+ else
+ {
+ uiControlDisable(uiControl(enJITMaxBlockSize));
+ uiControlDisable(uiControl(cbJITBranchOptimisations));
+ uiControlDisable(uiControl(cbJITLiteralOptimisations));
+ }
+}
+#endif
+
+void Open()
+{
+ if (opened)
+ {
+ uiControlSetFocus(uiControl(win));
+ return;
+ }
+
+ opened = true;
+ win = uiNewWindow("Emu settings - melonDS", 300, 50, 0, 0, 0);
+ uiWindowSetMargined(win, 1);
+ uiWindowOnClosing(win, OnCloseWindow, NULL);
+
+ uiBox* top = uiNewVerticalBox();
+ uiWindowSetChild(win, uiControl(top));
+
+ {
+ uiBox* in_ctrl = uiNewVerticalBox();
+ uiBoxAppend(top, uiControl(in_ctrl), 0);
+
+ cbDirectBoot = uiNewCheckbox("Boot game directly");
+ uiBoxAppend(in_ctrl, uiControl(cbDirectBoot), 0);
+ }
+
+#ifdef JIT_ENABLED
+ {
+ uiLabel* dummy = uiNewLabel("");
+ uiBoxAppend(top, uiControl(dummy), 0);
+ }
+
+ {
+ uiGroup* grp = uiNewGroup("JIT");
+ uiBoxAppend(top, uiControl(grp), 1);
+
+ uiBox* in_ctrl = uiNewVerticalBox();
+ uiGroupSetChild(grp, uiControl(in_ctrl));
+
+ cbJITEnabled = uiNewCheckbox("Enable JIT recompiler");
+ uiBoxAppend(in_ctrl, uiControl(cbJITEnabled), 0);
+
+ uiCheckboxOnToggled(cbJITEnabled, OnJITStateChanged, NULL);
+
+ {
+ uiBox* row = uiNewHorizontalBox();
+ uiBoxAppend(in_ctrl, uiControl(row), 0);
+
+ uiLabel* lbl = uiNewLabel("Maximum block size (1-32): ");
+ uiBoxAppend(row, uiControl(lbl), 0);
+
+ enJITMaxBlockSize = uiNewEntry();
+ uiBoxAppend(row, uiControl(enJITMaxBlockSize), 0);
+ }
+
+ {
+ uiBox* row = uiNewHorizontalBox();
+ uiBoxAppend(in_ctrl, uiControl(row), 0);
+
+ uiLabel* lbl = uiNewLabel("If you experience problems with a certain game, you can try disabling these options:");
+ uiBoxAppend(row, uiControl(lbl), 0);
+ }
+
+ {
+ uiBox* row = uiNewHorizontalBox();
+ uiBoxAppend(in_ctrl, uiControl(row), 0);
+
+ cbJITBranchOptimisations = uiNewCheckbox("Branch optimisations");
+ uiBoxAppend(row, uiControl(cbJITBranchOptimisations), 0);
+ }
+
+ {
+ uiBox* row = uiNewHorizontalBox();
+ uiBoxAppend(in_ctrl, uiControl(row), 0);
+
+ cbJITLiteralOptimisations = uiNewCheckbox("Literal optimisations");
+ uiBoxAppend(row, uiControl(cbJITLiteralOptimisations), 0);
+ }
+ }
+#endif
+
+ {
+ uiLabel* dummy = uiNewLabel("");
+ uiBoxAppend(top, uiControl(dummy), 0);
+ }
+
+ {
+ uiBox* in_ctrl = uiNewHorizontalBox();
+ uiBoxSetPadded(in_ctrl, 1);
+ uiBoxAppend(top, uiControl(in_ctrl), 0);
+
+ uiLabel* dummy = uiNewLabel("");
+ uiBoxAppend(in_ctrl, uiControl(dummy), 1);
+
+ uiButton* btncancel = uiNewButton("Cancel");
+ uiButtonOnClicked(btncancel, OnCancel, NULL);
+ uiBoxAppend(in_ctrl, uiControl(btncancel), 0);
+
+ uiButton* btnok = uiNewButton("Ok");
+ uiButtonOnClicked(btnok, OnOk, NULL);
+ uiBoxAppend(in_ctrl, uiControl(btnok), 0);
+ }
+
+ uiCheckboxSetChecked(cbDirectBoot, Config::DirectBoot);
+
+#ifdef JIT_ENABLED
+ uiCheckboxSetChecked(cbJITEnabled, Config::JIT_Enable);
+ {
+ char maxBlockSizeStr[10];
+ sprintf(maxBlockSizeStr, "%d", Config::JIT_MaxBlockSize);
+ uiEntrySetText(enJITMaxBlockSize, maxBlockSizeStr);
+ }
+ OnJITStateChanged(cbJITEnabled, NULL);
+
+ uiCheckboxSetChecked(cbJITBranchOptimisations, Config::JIT_BrancheOptimisations);
+ uiCheckboxSetChecked(cbJITLiteralOptimisations, Config::JIT_LiteralOptimisations);
+#endif
+
+ uiControlShow(uiControl(win));
+}
+
+void Close()
+{
+ if (!opened) return;
+ uiControlDestroy(uiControl(win));
+ opened = false;
+}
+
+}
diff --git a/src/libui_sdl/libui/ui.h b/src/libui_sdl/libui/ui.h
new file mode 100644
index 0000000..e45fe91
--- /dev/null
+++ b/src/libui_sdl/libui/ui.h
@@ -0,0 +1,764 @@
+// 6 april 2015
+
+// TODO add a uiVerifyControlType() function that can be used by control implementations to verify controls
+
+#ifndef __LIBUI_UI_H__
+#define __LIBUI_UI_H__
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// this macro is generated by cmake
+#ifdef libui_EXPORTS
+#ifdef _WIN32
+#define _UI_EXTERN __declspec(dllexport) extern
+#else
+#define _UI_EXTERN __attribute__((visibility("default"))) extern
+#endif
+#else
+// TODO add __declspec(dllimport) on windows, but only if not static
+#define _UI_EXTERN extern
+#endif
+
+// C++ is really really really really really really dumb about enums, so screw that and just make them anonymous
+// This has the advantage of being ABI-able should we ever need an ABI...
+#define _UI_ENUM(s) typedef unsigned int s; enum
+
+// This constant is provided because M_PI is nonstandard.
+// This comes from Go's math.Pi, which in turn comes from http://oeis.org/A000796.
+#define uiPi 3.14159265358979323846264338327950288419716939937510582097494459
+
+// TODO uiBool?
+
+typedef struct uiInitOptions uiInitOptions;
+
+struct uiInitOptions {
+ size_t Size;
+};
+
+_UI_EXTERN const char *uiInit(uiInitOptions *options);
+_UI_EXTERN void uiUninit(void);
+_UI_EXTERN void uiFreeInitError(const char *err);
+
+_UI_EXTERN void uiMain(void);
+_UI_EXTERN void uiMainSteps(void);
+_UI_EXTERN int uiMainStep(int wait);
+_UI_EXTERN void uiQuit(void);
+
+_UI_EXTERN void uiQueueMain(void (*f)(void *data), void *data);
+
+_UI_EXTERN void uiOnShouldQuit(int (*f)(void *data), void *data);
+
+_UI_EXTERN void uiFreeText(char *text);
+
+typedef struct uiControl uiControl;
+
+struct uiControl {
+ uint32_t Signature;
+ uint32_t OSSignature;
+ uint32_t TypeSignature;
+ void (*Destroy)(uiControl *);
+ uintptr_t (*Handle)(uiControl *);
+ uiControl *(*Parent)(uiControl *);
+ void (*SetParent)(uiControl *, uiControl *);
+ int (*Toplevel)(uiControl *);
+ int (*Visible)(uiControl *);
+ void (*Show)(uiControl *);
+ void (*Hide)(uiControl *);
+ int (*Enabled)(uiControl *);
+ void (*Enable)(uiControl *);
+ void (*Disable)(uiControl *);
+ void (*SetFocus)(uiControl *);
+ void (*SetMinSize)(uiControl*, int, int);
+
+ int MinWidth, MinHeight;
+
+ void* UserData;
+};
+// TOOD add argument names to all arguments
+#define uiControl(this) ((uiControl *) (this))
+_UI_EXTERN void uiControlDestroy(uiControl *);
+_UI_EXTERN uintptr_t uiControlHandle(uiControl *);
+_UI_EXTERN uiControl *uiControlParent(uiControl *);
+_UI_EXTERN void uiControlSetParent(uiControl *, uiControl *);
+_UI_EXTERN int uiControlToplevel(uiControl *);
+_UI_EXTERN int uiControlVisible(uiControl *);
+_UI_EXTERN void uiControlShow(uiControl *);
+_UI_EXTERN void uiControlHide(uiControl *);
+_UI_EXTERN int uiControlEnabled(uiControl *);
+_UI_EXTERN void uiControlEnable(uiControl *);
+_UI_EXTERN void uiControlDisable(uiControl *);
+_UI_EXTERN void uiControlSetFocus(uiControl *);
+_UI_EXTERN void uiControlSetMinSize(uiControl *, int w, int h); // -1 = no minimum
+
+_UI_EXTERN uiControl *uiAllocControl(size_t n, uint32_t OSsig, uint32_t typesig, const char *typenamestr);
+_UI_EXTERN void uiFreeControl(uiControl *);
+
+// TODO make sure all controls have these
+_UI_EXTERN void uiControlVerifySetParent(uiControl *, uiControl *);
+_UI_EXTERN int uiControlEnabledToUser(uiControl *);
+
+_UI_EXTERN void uiUserBugCannotSetParentOnToplevel(const char *type);
+
+typedef struct uiWindow uiWindow;
+#define uiWindow(this) ((uiWindow *) (this))
+_UI_EXTERN char *uiWindowTitle(uiWindow *w);
+_UI_EXTERN void uiWindowSetTitle(uiWindow *w, const char *title);
+_UI_EXTERN void uiWindowPosition(uiWindow *w, int *x, int *y);
+_UI_EXTERN void uiWindowSetPosition(uiWindow *w, int x, int y);
+_UI_EXTERN void uiWindowContentSize(uiWindow *w, int *width, int *height);
+_UI_EXTERN void uiWindowSetContentSize(uiWindow *w, int width, int height);
+_UI_EXTERN int uiWindowMinimized(uiWindow *w);
+_UI_EXTERN void uiWindowSetMinimized(uiWindow *w, int minimized);
+_UI_EXTERN int uiWindowMaximized(uiWindow *w);
+_UI_EXTERN void uiWindowSetMaximized(uiWindow *w, int maximized);
+_UI_EXTERN int uiWindowFullscreen(uiWindow *w);
+_UI_EXTERN void uiWindowSetFullscreen(uiWindow *w, int fullscreen);
+_UI_EXTERN int uiWindowBorderless(uiWindow *w);
+_UI_EXTERN void uiWindowSetBorderless(uiWindow *w, int borderless);
+_UI_EXTERN void uiWindowSetChild(uiWindow *w, uiControl *child);
+_UI_EXTERN int uiWindowMargined(uiWindow *w);
+_UI_EXTERN void uiWindowSetMargined(uiWindow *w, int margined);
+_UI_EXTERN void uiWindowSetDropTarget(uiWindow* w, int drop);
+_UI_EXTERN uiWindow *uiNewWindow(const char *title, int width, int height, int maximized, int hasMenubar, int resizable);
+
+_UI_EXTERN void uiWindowOnContentSizeChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data);
+_UI_EXTERN void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *w, void *data), void *data);
+_UI_EXTERN void uiWindowOnDropFile(uiWindow *w, void (*f)(uiWindow *w, char *file, void *data), void *data);
+_UI_EXTERN void uiWindowOnGetFocus(uiWindow *w, void (*f)(uiWindow *w, void *data), void *data);
+_UI_EXTERN void uiWindowOnLoseFocus(uiWindow *w, void (*f)(uiWindow *w, void *data), void *data);
+
+typedef struct uiButton uiButton;
+#define uiButton(this) ((uiButton *) (this))
+_UI_EXTERN char *uiButtonText(uiButton *b);
+_UI_EXTERN void uiButtonSetText(uiButton *b, const char *text);
+_UI_EXTERN void uiButtonOnClicked(uiButton *b, void (*f)(uiButton *b, void *data), void *data);
+_UI_EXTERN uiButton *uiNewButton(const char *text);
+
+typedef struct uiBox uiBox;
+#define uiBox(this) ((uiBox *) (this))
+_UI_EXTERN void uiBoxAppend(uiBox *b, uiControl *child, int stretchy);
+_UI_EXTERN void uiBoxDelete(uiBox *b, int index);
+_UI_EXTERN int uiBoxPadded(uiBox *b);
+_UI_EXTERN void uiBoxSetPadded(uiBox *b, int padded);
+_UI_EXTERN uiBox *uiNewHorizontalBox(void);
+_UI_EXTERN uiBox *uiNewVerticalBox(void);
+
+typedef struct uiCheckbox uiCheckbox;
+#define uiCheckbox(this) ((uiCheckbox *) (this))
+_UI_EXTERN char *uiCheckboxText(uiCheckbox *c);
+_UI_EXTERN void uiCheckboxSetText(uiCheckbox *c, const char *text);
+_UI_EXTERN void uiCheckboxOnToggled(uiCheckbox *c, void (*f)(uiCheckbox *c, void *data), void *data);
+_UI_EXTERN int uiCheckboxChecked(uiCheckbox *c);
+_UI_EXTERN void uiCheckboxSetChecked(uiCheckbox *c, int checked);
+_UI_EXTERN uiCheckbox *uiNewCheckbox(const char *text);
+
+typedef struct uiEntry uiEntry;
+#define uiEntry(this) ((uiEntry *) (this))
+_UI_EXTERN char *uiEntryText(uiEntry *e);
+_UI_EXTERN void uiEntrySetText(uiEntry *e, const char *text);
+_UI_EXTERN void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *e, void *data), void *data);
+_UI_EXTERN int uiEntryReadOnly(uiEntry *e);
+_UI_EXTERN void uiEntrySetReadOnly(uiEntry *e, int readonly);
+_UI_EXTERN uiEntry *uiNewEntry(void);
+_UI_EXTERN uiEntry *uiNewPasswordEntry(void);
+_UI_EXTERN uiEntry *uiNewSearchEntry(void);
+
+typedef struct uiLabel uiLabel;
+#define uiLabel(this) ((uiLabel *) (this))
+_UI_EXTERN char *uiLabelText(uiLabel *l);
+_UI_EXTERN void uiLabelSetText(uiLabel *l, const char *text);
+_UI_EXTERN uiLabel *uiNewLabel(const char *text);
+
+typedef struct uiTab uiTab;
+#define uiTab(this) ((uiTab *) (this))
+_UI_EXTERN void uiTabAppend(uiTab *t, const char *name, uiControl *c);
+_UI_EXTERN void uiTabInsertAt(uiTab *t, const char *name, int before, uiControl *c);
+_UI_EXTERN void uiTabDelete(uiTab *t, int index);
+_UI_EXTERN int uiTabNumPages(uiTab *t);
+_UI_EXTERN int uiTabMargined(uiTab *t, int page);
+_UI_EXTERN void uiTabSetMargined(uiTab *t, int page, int margined);
+_UI_EXTERN uiTab *uiNewTab(void);
+
+typedef struct uiGroup uiGroup;
+#define uiGroup(this) ((uiGroup *) (this))
+_UI_EXTERN char *uiGroupTitle(uiGroup *g);
+_UI_EXTERN void uiGroupSetTitle(uiGroup *g, const char *title);
+_UI_EXTERN void uiGroupSetChild(uiGroup *g, uiControl *c);
+_UI_EXTERN int uiGroupMargined(uiGroup *g);
+_UI_EXTERN void uiGroupSetMargined(uiGroup *g, int margined);
+_UI_EXTERN uiGroup *uiNewGroup(const char *title);
+
+// spinbox/slider rules:
+// setting value outside of range will automatically clamp
+// initial value is minimum
+// complaint if min >= max?
+
+typedef struct uiSpinbox uiSpinbox;
+#define uiSpinbox(this) ((uiSpinbox *) (this))
+_UI_EXTERN int uiSpinboxValue(uiSpinbox *s);
+_UI_EXTERN void uiSpinboxSetValue(uiSpinbox *s, int value);
+_UI_EXTERN void uiSpinboxOnChanged(uiSpinbox *s, void (*f)(uiSpinbox *s, void *data), void *data);
+_UI_EXTERN uiSpinbox *uiNewSpinbox(int min, int max);
+
+typedef struct uiSlider uiSlider;
+#define uiSlider(this) ((uiSlider *) (this))
+_UI_EXTERN int uiSliderValue(uiSlider *s);
+_UI_EXTERN void uiSliderSetValue(uiSlider *s, int value);
+_UI_EXTERN void uiSliderOnChanged(uiSlider *s, void (*f)(uiSlider *s, void *data), void *data);
+_UI_EXTERN uiSlider *uiNewSlider(int min, int max);
+
+typedef struct uiProgressBar uiProgressBar;
+#define uiProgressBar(this) ((uiProgressBar *) (this))
+_UI_EXTERN int uiProgressBarValue(uiProgressBar *p);
+_UI_EXTERN void uiProgressBarSetValue(uiProgressBar *p, int n);
+_UI_EXTERN uiProgressBar *uiNewProgressBar(void);
+
+typedef struct uiSeparator uiSeparator;
+#define uiSeparator(this) ((uiSeparator *) (this))
+_UI_EXTERN uiSeparator *uiNewHorizontalSeparator(void);
+_UI_EXTERN uiSeparator *uiNewVerticalSeparator(void);
+
+typedef struct uiCombobox uiCombobox;
+#define uiCombobox(this) ((uiCombobox *) (this))
+_UI_EXTERN void uiComboboxAppend(uiCombobox *c, const char *text);
+_UI_EXTERN int uiComboboxSelected(uiCombobox *c);
+_UI_EXTERN void uiComboboxSetSelected(uiCombobox *c, int n);
+_UI_EXTERN void uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *c, void *data), void *data);
+_UI_EXTERN uiCombobox *uiNewCombobox(void);
+
+typedef struct uiEditableCombobox uiEditableCombobox;
+#define uiEditableCombobox(this) ((uiEditableCombobox *) (this))
+_UI_EXTERN void uiEditableComboboxAppend(uiEditableCombobox *c, const char *text);
+_UI_EXTERN char *uiEditableComboboxText(uiEditableCombobox *c);
+_UI_EXTERN void uiEditableComboboxSetText(uiEditableCombobox *c, const char *text);
+// TODO what do we call a function that sets the currently selected item and fills the text field with it? editable comboboxes have no consistent concept of selected item
+_UI_EXTERN void uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *c, void *data), void *data);
+_UI_EXTERN uiEditableCombobox *uiNewEditableCombobox(void);
+
+typedef struct uiRadioButtons uiRadioButtons;
+#define uiRadioButtons(this) ((uiRadioButtons *) (this))
+_UI_EXTERN void uiRadioButtonsAppend(uiRadioButtons *r, const char *text);
+_UI_EXTERN int uiRadioButtonsSelected(uiRadioButtons *r);
+_UI_EXTERN void uiRadioButtonsSetSelected(uiRadioButtons *r, int n);
+_UI_EXTERN void uiRadioButtonsOnSelected(uiRadioButtons *r, void (*f)(uiRadioButtons *, void *), void *data);
+_UI_EXTERN uiRadioButtons *uiNewRadioButtons(void);
+
+typedef struct uiDateTimePicker uiDateTimePicker;
+#define uiDateTimePicker(this) ((uiDateTimePicker *) (this))
+_UI_EXTERN uiDateTimePicker *uiNewDateTimePicker(void);
+_UI_EXTERN uiDateTimePicker *uiNewDatePicker(void);
+_UI_EXTERN uiDateTimePicker *uiNewTimePicker(void);
+
+// TODO provide a facility for entering tab stops?
+typedef struct uiMultilineEntry uiMultilineEntry;
+#define uiMultilineEntry(this) ((uiMultilineEntry *) (this))
+_UI_EXTERN char *uiMultilineEntryText(uiMultilineEntry *e);
+_UI_EXTERN void uiMultilineEntrySetText(uiMultilineEntry *e, const char *text);
+_UI_EXTERN void uiMultilineEntryAppend(uiMultilineEntry *e, const char *text);
+_UI_EXTERN void uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMultilineEntry *e, void *data), void *data);
+_UI_EXTERN int uiMultilineEntryReadOnly(uiMultilineEntry *e);
+_UI_EXTERN void uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly);
+_UI_EXTERN uiMultilineEntry *uiNewMultilineEntry(void);
+_UI_EXTERN uiMultilineEntry *uiNewNonWrappingMultilineEntry(void);
+
+typedef struct uiMenuItem uiMenuItem;
+#define uiMenuItem(this) ((uiMenuItem *) (this))
+_UI_EXTERN void uiMenuItemEnable(uiMenuItem *m);
+_UI_EXTERN void uiMenuItemDisable(uiMenuItem *m);
+_UI_EXTERN void uiMenuItemOnClicked(uiMenuItem *m, void (*f)(uiMenuItem *sender, uiWindow *window, void *data), void *data);
+_UI_EXTERN int uiMenuItemChecked(uiMenuItem *m);
+_UI_EXTERN void uiMenuItemSetChecked(uiMenuItem *m, int checked);
+
+typedef struct uiMenu uiMenu;
+#define uiMenu(this) ((uiMenu *) (this))
+_UI_EXTERN uiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name);
+_UI_EXTERN uiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name);
+_UI_EXTERN uiMenuItem *uiMenuAppendQuitItem(uiMenu *m);
+_UI_EXTERN uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m);
+_UI_EXTERN uiMenuItem *uiMenuAppendAboutItem(uiMenu *m);
+_UI_EXTERN uiMenuItem *uiMenuAppendSubmenu(uiMenu *m, uiMenu* child);
+_UI_EXTERN void uiMenuAppendSeparator(uiMenu *m);
+_UI_EXTERN uiMenu *uiNewMenu(const char *name);
+
+_UI_EXTERN char *uiOpenFile(uiWindow *parent, const char* filter, const char* initpath);
+_UI_EXTERN char *uiSaveFile(uiWindow *parent, const char* filter, const char* initpath);
+_UI_EXTERN void uiMsgBox(uiWindow *parent, const char *title, const char *description);
+_UI_EXTERN void uiMsgBoxError(uiWindow *parent, const char *title, const char *description);
+_UI_EXTERN int uiMsgBoxConfirm(uiWindow * parent, const char *title, const char *description);
+
+typedef struct uiArea uiArea;
+typedef struct uiAreaHandler uiAreaHandler;
+typedef struct uiAreaDrawParams uiAreaDrawParams;
+typedef struct uiAreaMouseEvent uiAreaMouseEvent;
+typedef struct uiAreaKeyEvent uiAreaKeyEvent;
+
+typedef struct uiDrawContext uiDrawContext;
+
+// TO CONSIDER: the uiAreaHandler param there seems useless
+// (might use individual callbacks instead of handler struct?)
+struct uiAreaHandler {
+ void (*Draw)(uiAreaHandler *, uiArea *, uiAreaDrawParams *);
+ // TODO document that resizes cause a full redraw for non-scrolling areas; implementation-defined for scrolling areas
+ void (*MouseEvent)(uiAreaHandler *, uiArea *, uiAreaMouseEvent *);
+ // TODO document that on first show if the mouse is already in the uiArea then one gets sent with left=0
+ // TODO what about when the area is hidden and then shown again?
+ void (*MouseCrossed)(uiAreaHandler *, uiArea *, int left);
+ void (*DragBroken)(uiAreaHandler *, uiArea *);
+ int (*KeyEvent)(uiAreaHandler *, uiArea *, uiAreaKeyEvent *);
+ void (*Resize)(uiAreaHandler *, uiArea *, int, int);
+};
+
+// TODO RTL layouts?
+// TODO reconcile edge and corner naming
+_UI_ENUM(uiWindowResizeEdge) {
+ uiWindowResizeEdgeLeft,
+ uiWindowResizeEdgeTop,
+ uiWindowResizeEdgeRight,
+ uiWindowResizeEdgeBottom,
+ uiWindowResizeEdgeTopLeft,
+ uiWindowResizeEdgeTopRight,
+ uiWindowResizeEdgeBottomLeft,
+ uiWindowResizeEdgeBottomRight,
+ // TODO have one for keyboard resizes?
+ // TODO GDK doesn't seem to have any others, including for keyboards...
+ // TODO way to bring up the system menu instead?
+};
+
+#define uiGLVersion(major, minor) ((major) | ((minor)<<16))
+#define uiGLVerMajor(ver) ((ver) & 0xFFFF)
+#define uiGLVerMinor(ver) ((ver) >> 16)
+
+#define uiArea(this) ((uiArea *) (this))
+// TODO give a better name
+// TODO document the types of width and height
+_UI_EXTERN void uiAreaSetSize(uiArea *a, int width, int height);
+// TODO uiAreaQueueRedraw()
+_UI_EXTERN void uiAreaQueueRedrawAll(uiArea *a);
+_UI_EXTERN void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height);
+// TODO document these can only be called within Mouse() handlers
+// TODO should these be allowed on scrolling areas?
+// TODO decide which mouse events should be accepted; Down is the only one guaranteed to work right now
+// TODO what happens to events after calling this up to and including the next mouse up?
+// TODO release capture?
+_UI_EXTERN void uiAreaBeginUserWindowMove(uiArea *a);
+_UI_EXTERN void uiAreaBeginUserWindowResize(uiArea *a, uiWindowResizeEdge edge);
+_UI_EXTERN void uiAreaSetBackgroundColor(uiArea *a, int r, int g, int b);
+_UI_EXTERN uiArea *uiNewArea(uiAreaHandler *ah);
+_UI_EXTERN uiArea *uiNewGLArea(uiAreaHandler *ah, const unsigned int* req_versions);
+_UI_EXTERN uiArea *uiNewScrollingArea(uiAreaHandler *ah, int width, int height);
+
+struct uiAreaDrawParams {
+ uiDrawContext *Context;
+
+ // TODO document that this is only defined for nonscrolling areas
+ double AreaWidth;
+ double AreaHeight;
+
+ double ClipX;
+ double ClipY;
+ double ClipWidth;
+ double ClipHeight;
+};
+
+typedef struct uiDrawPath uiDrawPath;
+typedef struct uiDrawBrush uiDrawBrush;
+typedef struct uiDrawStrokeParams uiDrawStrokeParams;
+typedef struct uiDrawMatrix uiDrawMatrix;
+
+typedef struct uiDrawBrushGradientStop uiDrawBrushGradientStop;
+
+typedef struct uiDrawBitmap uiDrawBitmap;
+
+_UI_ENUM(uiDrawBrushType) {
+ uiDrawBrushTypeSolid,
+ uiDrawBrushTypeLinearGradient,
+ uiDrawBrushTypeRadialGradient,
+ uiDrawBrushTypeImage,
+};
+
+_UI_ENUM(uiDrawLineCap) {
+ uiDrawLineCapFlat,
+ uiDrawLineCapRound,
+ uiDrawLineCapSquare,
+};
+
+_UI_ENUM(uiDrawLineJoin) {
+ uiDrawLineJoinMiter,
+ uiDrawLineJoinRound,
+ uiDrawLineJoinBevel,
+};
+
+// this is the default for botoh cairo and Direct2D (in the latter case, from the C++ helper functions)
+// Core Graphics doesn't explicitly specify a default, but NSBezierPath allows you to choose one, and this is the initial value
+// so we're good to use it too!
+#define uiDrawDefaultMiterLimit 10.0
+
+_UI_ENUM(uiDrawFillMode) {
+ uiDrawFillModeWinding,
+ uiDrawFillModeAlternate,
+};
+
+struct uiDrawMatrix {
+ double M11;
+ double M12;
+ double M21;
+ double M22;
+ double M31;
+ double M32;
+};
+
+struct uiDrawBrush {
+ uiDrawBrushType Type;
+
+ // solid brushes
+ double R;
+ double G;
+ double B;
+ double A;
+
+ // gradient brushes
+ double X0; // linear: start X, radial: start X
+ double Y0; // linear: start Y, radial: start Y
+ double X1; // linear: end X, radial: outer circle center X
+ double Y1; // linear: end Y, radial: outer circle center Y
+ double OuterRadius; // radial gradients only
+ uiDrawBrushGradientStop *Stops;
+ size_t NumStops;
+ // TODO extend mode
+ // cairo: none, repeat, reflect, pad; no individual control
+ // Direct2D: repeat, reflect, pad; no individual control
+ // Core Graphics: none, pad; before and after individually
+ // TODO cairo documentation is inconsistent about pad
+
+ // TODO images
+
+ // TODO transforms
+};
+
+struct uiDrawBrushGradientStop {
+ double Pos;
+ double R;
+ double G;
+ double B;
+ double A;
+};
+
+struct uiDrawStrokeParams {
+ uiDrawLineCap Cap;
+ uiDrawLineJoin Join;
+ // TODO what if this is 0? on windows there will be a crash with dashing
+ double Thickness;
+ double MiterLimit;
+ double *Dashes;
+ // TOOD what if this is 1 on Direct2D?
+ // TODO what if a dash is 0 on Cairo or Quartz?
+ size_t NumDashes;
+ double DashPhase;
+};
+
+struct uiRect {
+ int X;
+ int Y;
+ int Width;
+ int Height;
+};
+
+typedef struct uiRect uiRect;
+
+_UI_EXTERN uiDrawPath *uiDrawNewPath(uiDrawFillMode fillMode);
+_UI_EXTERN void uiDrawFreePath(uiDrawPath *p);
+
+_UI_EXTERN void uiDrawPathNewFigure(uiDrawPath *p, double x, double y);
+_UI_EXTERN void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative);
+_UI_EXTERN void uiDrawPathLineTo(uiDrawPath *p, double x, double y);
+// notes: angles are both relative to 0 and go counterclockwise
+// TODO is the initial line segment on cairo and OS X a proper join?
+// TODO what if sweep < 0?
+_UI_EXTERN void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative);
+_UI_EXTERN void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY);
+// TODO quadratic bezier
+_UI_EXTERN void uiDrawPathCloseFigure(uiDrawPath *p);
+
+// TODO effect of these when a figure is already started
+_UI_EXTERN void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height);
+
+_UI_EXTERN void uiDrawPathEnd(uiDrawPath *p);
+
+_UI_EXTERN void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStrokeParams *p);
+_UI_EXTERN void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b);
+
+// TODO primitives:
+// - rounded rectangles
+// - elliptical arcs
+// - quadratic bezier curves
+
+_UI_EXTERN void uiDrawMatrixSetIdentity(uiDrawMatrix *m);
+_UI_EXTERN void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y);
+_UI_EXTERN void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y);
+_UI_EXTERN void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount);
+_UI_EXTERN void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount);
+_UI_EXTERN void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src);
+_UI_EXTERN int uiDrawMatrixInvertible(uiDrawMatrix *m);
+_UI_EXTERN int uiDrawMatrixInvert(uiDrawMatrix *m);
+_UI_EXTERN void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y);
+_UI_EXTERN void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y);
+
+_UI_EXTERN void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m);
+
+// TODO add a uiDrawPathStrokeToFill() or something like that
+_UI_EXTERN void uiDrawClip(uiDrawContext *c, uiDrawPath *path);
+
+_UI_EXTERN void uiDrawSave(uiDrawContext *c);
+_UI_EXTERN void uiDrawRestore(uiDrawContext *c);
+
+// bitmap API
+_UI_EXTERN uiDrawBitmap* uiDrawNewBitmap(uiDrawContext* c, int width, int height, int alpha);
+_UI_EXTERN void uiDrawBitmapUpdate(uiDrawBitmap* bmp, const void* data);
+_UI_EXTERN void uiDrawBitmapDraw(uiDrawContext* c, uiDrawBitmap* bmp, uiRect* srcrect, uiRect* dstrect, int filter);
+_UI_EXTERN void uiDrawFreeBitmap(uiDrawBitmap* bmp);
+
+// TODO manage the use of Text, Font, and TextFont, and of the uiDrawText prefix in general
+
+///// TODO reconsider this
+typedef struct uiDrawFontFamilies uiDrawFontFamilies;
+
+_UI_EXTERN uiDrawFontFamilies *uiDrawListFontFamilies(void);
+_UI_EXTERN int uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff);
+_UI_EXTERN char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n);
+_UI_EXTERN void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff);
+///// END TODO
+
+typedef struct uiDrawTextLayout uiDrawTextLayout;
+typedef struct uiDrawTextFont uiDrawTextFont;
+typedef struct uiDrawTextFontDescriptor uiDrawTextFontDescriptor;
+typedef struct uiDrawTextFontMetrics uiDrawTextFontMetrics;
+
+_UI_ENUM(uiDrawTextWeight) {
+ uiDrawTextWeightThin,
+ uiDrawTextWeightUltraLight,
+ uiDrawTextWeightLight,
+ uiDrawTextWeightBook,
+ uiDrawTextWeightNormal,
+ uiDrawTextWeightMedium,
+ uiDrawTextWeightSemiBold,
+ uiDrawTextWeightBold,
+ uiDrawTextWeightUltraBold,
+ uiDrawTextWeightHeavy,
+ uiDrawTextWeightUltraHeavy,
+};
+
+_UI_ENUM(uiDrawTextItalic) {
+ uiDrawTextItalicNormal,
+ uiDrawTextItalicOblique,
+ uiDrawTextItalicItalic,
+};
+
+_UI_ENUM(uiDrawTextStretch) {
+ uiDrawTextStretchUltraCondensed,
+ uiDrawTextStretchExtraCondensed,
+ uiDrawTextStretchCondensed,
+ uiDrawTextStretchSemiCondensed,
+ uiDrawTextStretchNormal,
+ uiDrawTextStretchSemiExpanded,
+ uiDrawTextStretchExpanded,
+ uiDrawTextStretchExtraExpanded,
+ uiDrawTextStretchUltraExpanded,
+};
+
+struct uiDrawTextFontDescriptor {
+ const char *Family;
+ double Size;
+ uiDrawTextWeight Weight;
+ uiDrawTextItalic Italic;
+ uiDrawTextStretch Stretch;
+};
+
+struct uiDrawTextFontMetrics {
+ double Ascent;
+ double Descent;
+ double Leading;
+ // TODO do these two mean the same across all platforms?
+ double UnderlinePos;
+ double UnderlineThickness;
+};
+
+_UI_EXTERN uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc);
+_UI_EXTERN void uiDrawFreeTextFont(uiDrawTextFont *font);
+_UI_EXTERN uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font);
+_UI_EXTERN void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc);
+// TODO make copy with given attributes methods?
+// TODO yuck this name
+_UI_EXTERN void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics);
+
+// TODO initial line spacing? and what about leading?
+_UI_EXTERN uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width);
+_UI_EXTERN void uiDrawFreeTextLayout(uiDrawTextLayout *layout);
+// TODO get width
+_UI_EXTERN void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width);
+_UI_EXTERN void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height);
+
+// and the attributes that you can set on a text layout
+_UI_EXTERN void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a);
+
+_UI_EXTERN void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout);
+
+
+// OpenGL support
+
+typedef struct uiGLContext uiGLContext;
+
+_UI_EXTERN uiGLContext *uiAreaGetGLContext(uiArea* a);
+_UI_EXTERN void uiGLMakeContextCurrent(uiGLContext* ctx);
+_UI_EXTERN void uiGLBegin(uiGLContext* ctx);
+_UI_EXTERN void uiGLEnd(uiGLContext* ctx);
+_UI_EXTERN unsigned int uiGLGetVersion(uiGLContext* ctx);
+_UI_EXTERN void *uiGLGetProcAddress(const char* proc);
+_UI_EXTERN int uiGLGetFramebuffer(uiGLContext* ctx);
+_UI_EXTERN float uiGLGetFramebufferScale(uiGLContext* ctx);
+_UI_EXTERN void uiGLSwapBuffers(uiGLContext* ctx);
+_UI_EXTERN void uiGLSetVSync(int sync);
+
+
+_UI_ENUM(uiModifiers) {
+ uiModifierCtrl = 1 << 0,
+ uiModifierAlt = 1 << 1,
+ uiModifierShift = 1 << 2,
+ uiModifierSuper = 1 << 3,
+};
+
+// TODO document drag captures
+struct uiAreaMouseEvent {
+ // TODO document what these mean for scrolling areas
+ double X;
+ double Y;
+
+ // TODO see draw above
+ double AreaWidth;
+ double AreaHeight;
+
+ int Down;
+ int Up;
+
+ int Count;
+
+ uiModifiers Modifiers;
+
+ uint64_t Held1To64;
+};
+
+_UI_ENUM(uiExtKey) {
+ uiExtKeyEscape = 1,
+ uiExtKeyInsert, // equivalent to "Help" on Apple keyboards
+ uiExtKeyDelete,
+ uiExtKeyHome,
+ uiExtKeyEnd,
+ uiExtKeyPageUp,
+ uiExtKeyPageDown,
+ uiExtKeyUp,
+ uiExtKeyDown,
+ uiExtKeyLeft,
+ uiExtKeyRight,
+ uiExtKeyF1, // F1..F12 are guaranteed to be consecutive
+ uiExtKeyF2,
+ uiExtKeyF3,
+ uiExtKeyF4,
+ uiExtKeyF5,
+ uiExtKeyF6,
+ uiExtKeyF7,
+ uiExtKeyF8,
+ uiExtKeyF9,
+ uiExtKeyF10,
+ uiExtKeyF11,
+ uiExtKeyF12,
+ uiExtKeyN0, // numpad keys; independent of Num Lock state
+ uiExtKeyN1, // N0..N9 are guaranteed to be consecutive
+ uiExtKeyN2,
+ uiExtKeyN3,
+ uiExtKeyN4,
+ uiExtKeyN5,
+ uiExtKeyN6,
+ uiExtKeyN7,
+ uiExtKeyN8,
+ uiExtKeyN9,
+ uiExtKeyNDot,
+ uiExtKeyNEnter,
+ uiExtKeyNAdd,
+ uiExtKeyNSubtract,
+ uiExtKeyNMultiply,
+ uiExtKeyNDivide,
+};
+
+struct uiAreaKeyEvent {
+ char Key;
+ uiExtKey ExtKey;
+ uiModifiers Modifier;
+
+ uiModifiers Modifiers;
+
+ // additional things
+ int Scancode; // bit0-7: scancode, bit8: ext flag
+
+ int Up;
+ int Repeat;
+};
+
+typedef struct uiFontButton uiFontButton;
+#define uiFontButton(this) ((uiFontButton *) (this))
+// TODO document this returns a new font
+_UI_EXTERN uiDrawTextFont *uiFontButtonFont(uiFontButton *b);
+// TOOD SetFont, mechanics
+_UI_EXTERN void uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *, void *), void *data);
+_UI_EXTERN uiFontButton *uiNewFontButton(void);
+
+typedef struct uiColorButton uiColorButton;
+#define uiColorButton(this) ((uiColorButton *) (this))
+_UI_EXTERN void uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a);
+_UI_EXTERN void uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a);
+_UI_EXTERN void uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *, void *), void *data);
+_UI_EXTERN uiColorButton *uiNewColorButton(void);
+
+typedef struct uiForm uiForm;
+#define uiForm(this) ((uiForm *) (this))
+_UI_EXTERN void uiFormAppend(uiForm *f, const char *label, uiControl *c, int stretchy);
+_UI_EXTERN void uiFormDelete(uiForm *f, int index);
+_UI_EXTERN int uiFormPadded(uiForm *f);
+_UI_EXTERN void uiFormSetPadded(uiForm *f, int padded);
+_UI_EXTERN uiForm *uiNewForm(void);
+
+_UI_ENUM(uiAlign) {
+ uiAlignFill,
+ uiAlignStart,
+ uiAlignCenter,
+ uiAlignEnd,
+};
+
+_UI_ENUM(uiAt) {
+ uiAtLeading,
+ uiAtTop,
+ uiAtTrailing,
+ uiAtBottom,
+};
+
+typedef struct uiGrid uiGrid;
+#define uiGrid(this) ((uiGrid *) (this))
+_UI_EXTERN void uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign);
+_UI_EXTERN void uiGridInsertAt(uiGrid *g, uiControl *c, uiControl *existing, uiAt at, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign);
+_UI_EXTERN int uiGridPadded(uiGrid *g);
+_UI_EXTERN void uiGridSetPadded(uiGrid *g, int padded);
+_UI_EXTERN uiGrid *uiNewGrid(void);
+
+
+// misc.
+
+_UI_EXTERN char* uiKeyName(int scancode);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/libui_sdl/libui/unix/stddialogs.c b/src/libui_sdl/libui/unix/stddialogs.c
new file mode 100644
index 0000000..10c598d
--- /dev/null
+++ b/src/libui_sdl/libui/unix/stddialogs.c
@@ -0,0 +1,126 @@
+// 26 june 2015
+#include "uipriv_unix.h"
+
+// LONGTERM figure out why, and describe, that this is the desired behavior
+// LONGTERM also point out that font and color buttons also work like this
+
+#define windowWindow(w) ((w)?(GTK_WINDOW(uiControlHandle(uiControl(w)))):NULL)
+
+static char *filedialog(GtkWindow *parent, GtkFileChooserAction mode, const gchar *confirm, const char* filter, const char* initpath)
+{
+ GtkWidget *fcd;
+ GtkFileChooser *fc;
+ gint response;
+ char *filename;
+
+ fcd = gtk_file_chooser_dialog_new(NULL, parent, mode,
+ "_Cancel", GTK_RESPONSE_CANCEL,
+ confirm, GTK_RESPONSE_ACCEPT,
+ NULL);
+ fc = GTK_FILE_CHOOSER(fcd);
+
+ // filters
+ {
+ gchar _filter[256];
+ gchar* fp = &_filter[0]; int s = 0;
+ gchar* fname;
+ for (int i = 0; i < 255; i++)
+ {
+ if (filter[i] == '|' || filter[i] == '\0')
+ {
+ _filter[i] = '\0';
+ if (s & 1)
+ {
+ GtkFileFilter* filter = gtk_file_filter_new();
+ gtk_file_filter_set_name(filter, fname);
+
+ for (gchar* j = fp; ; j++)
+ {
+ if (*j == ';')
+ {
+ *j = '\0';
+ gtk_file_filter_add_pattern(filter, fp);
+ fp = j+1;
+ }
+ else if (*j == '\0')
+ {
+ gtk_file_filter_add_pattern(filter, fp);
+ break;
+ }
+ }
+
+ gtk_file_chooser_add_filter(fc, filter);
+ }
+ else
+ {
+ fname = fp;
+ }
+ fp = &_filter[i+1];
+ s++;
+ if (s >= 8) break;
+ if (filter[i] == '\0') break;
+ }
+ else
+ _filter[i] = filter[i];
+ }
+ }
+
+ gtk_file_chooser_set_local_only(fc, FALSE);
+ gtk_file_chooser_set_select_multiple(fc, FALSE);
+ gtk_file_chooser_set_show_hidden(fc, TRUE);
+ gtk_file_chooser_set_do_overwrite_confirmation(fc, TRUE);
+ gtk_file_chooser_set_create_folders(fc, TRUE);
+ if (initpath && strlen(initpath)>0)
+ gtk_file_chooser_set_current_folder(fc, initpath);
+
+ response = gtk_dialog_run(GTK_DIALOG(fcd));
+ if (response != GTK_RESPONSE_ACCEPT) {
+ gtk_widget_destroy(fcd);
+ return NULL;
+ }
+ filename = uiUnixStrdupText(gtk_file_chooser_get_filename(fc));
+ gtk_widget_destroy(fcd);
+ return filename;
+}
+
+char *uiOpenFile(uiWindow *parent, const char* filter, const char* initpath)
+{
+ return filedialog(windowWindow(parent), GTK_FILE_CHOOSER_ACTION_OPEN, "_Open", filter, initpath);
+}
+
+char *uiSaveFile(uiWindow *parent, const char* filter, const char* initpath)
+{
+ return filedialog(windowWindow(parent), GTK_FILE_CHOOSER_ACTION_SAVE, "_Save", filter, initpath);
+}
+
+static int msgbox(GtkWindow *parent, const char *title, const char *description, GtkMessageType type, GtkButtonsType buttons)
+{
+ GtkWidget *md;
+
+ md = gtk_message_dialog_new(parent, GTK_DIALOG_MODAL,
+ type, buttons,
+ "%s", title);
+ gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(md), "%s", description);
+ int result = gtk_dialog_run(GTK_DIALOG(md));
+ gtk_widget_destroy(md);
+
+ return result;
+}
+
+void uiMsgBox(uiWindow *parent, const char *title, const char *description)
+{
+ msgbox(windowWindow(parent), title, description, GTK_MESSAGE_OTHER, GTK_BUTTONS_OK);
+}
+
+void uiMsgBoxError(uiWindow *parent, const char *title, const char *description)
+{
+ msgbox(windowWindow(parent), title, description, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK);
+}
+
+int uiMsgBoxConfirm(uiWindow * parent, const char *title, const char *description)
+{
+ int result =
+ msgbox(windowWindow(parent), title, description, GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL);
+
+ return result == GTK_RESPONSE_OK;
+} \ No newline at end of file
diff --git a/src/libui_sdl/libui/windows/stddialogs.cpp b/src/libui_sdl/libui/windows/stddialogs.cpp
new file mode 100644
index 0000000..7537015
--- /dev/null
+++ b/src/libui_sdl/libui/windows/stddialogs.cpp
@@ -0,0 +1,180 @@
+// 22 may 2015
+#include "uipriv_windows.hpp"
+
+// TODO document all this is what we want
+// TODO do the same for font and color buttons
+
+// notes:
+// - FOS_SUPPORTSTREAMABLEITEMS doesn't seem to be supported on windows vista, or at least not with the flags we use
+// - even with FOS_NOVALIDATE the dialogs will reject invalid filenames (at least on Vista, anyway)
+// - lack of FOS_NOREADONLYRETURN doesn't seem to matter on Windows 7
+
+// TODO
+// - http://blogs.msdn.com/b/wpfsdk/archive/2006/10/26/uncommon-dialogs--font-chooser-and-color-picker-dialogs.aspx
+// - when a dialog is active, tab navigation in other windows stops working
+// - when adding uiOpenFolder(), use IFileDialog as well - https://msdn.microsoft.com/en-us/library/windows/desktop/bb762115%28v=vs.85%29.aspx
+
+#define windowHWND(w) (w ? (HWND) uiControlHandle(uiControl(w)) : NULL)
+
+char *commonItemDialog(HWND parent, REFCLSID clsid, REFIID iid, const char* filter, const char* initpath, FILEOPENDIALOGOPTIONS optsadd)
+{
+ IFileDialog *d = NULL;
+ FILEOPENDIALOGOPTIONS opts;
+ IShellItem *result = NULL;
+ WCHAR *wname = NULL;
+ char *name = NULL;
+ HRESULT hr;
+
+ hr = CoCreateInstance(clsid,
+ NULL, CLSCTX_INPROC_SERVER,
+ iid, (LPVOID *) (&d));
+ if (hr != S_OK) {
+ logHRESULT(L"error creating common item dialog", hr);
+ // always return NULL on error
+ goto out;
+ }
+ hr = d->GetOptions(&opts);
+ if (hr != S_OK) {
+ logHRESULT(L"error getting current options", hr);
+ goto out;
+ }
+ opts |= optsadd;
+ // the other platforms don't check read-only; we won't either
+ opts &= ~FOS_NOREADONLYRETURN;
+ hr = d->SetOptions(opts);
+ if (hr != S_OK) {
+ logHRESULT(L"error setting options", hr);
+ goto out;
+ }
+
+ // filters
+ {
+ COMDLG_FILTERSPEC filterspec[8];
+ wchar_t _filter[256];
+ wchar_t* fp = &_filter[0]; int s = 0;
+ wchar_t* fname;
+ for (int i = 0; i < 255; i++)
+ {
+ if (filter[i] == '|' || filter[i] == '\0')
+ {
+ _filter[i] = '\0';
+ if (s & 1)
+ {
+ filterspec[s>>1].pszName = fname;
+ filterspec[s>>1].pszSpec = fp;
+ }
+ else
+ {
+ fname = fp;
+ }
+ fp = &_filter[i+1];
+ s++;
+ if (s >= 8) break;
+ if (filter[i] == '\0') break;
+ }
+ else
+ _filter[i] = filter[i];
+ }
+ d->SetFileTypes(s>>1, filterspec);
+ }
+
+ hr = d->Show(parent);
+ if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED))
+ // cancelled; return NULL like we have ready
+ goto out;
+ if (hr != S_OK) {
+ logHRESULT(L"error showing dialog", hr);
+ goto out;
+ }
+ hr = d->GetResult(&result);
+ if (hr != S_OK) {
+ logHRESULT(L"error getting dialog result", hr);
+ goto out;
+ }
+ hr = result->GetDisplayName(SIGDN_FILESYSPATH, &wname);
+ if (hr != S_OK) {
+ logHRESULT(L"error getting filename", hr);
+ goto out;
+ }
+ name = toUTF8(wname);
+
+out:
+ if (wname != NULL)
+ CoTaskMemFree(wname);
+ if (result != NULL)
+ result->Release();
+ if (d != NULL)
+ d->Release();
+ return name;
+}
+
+char *uiOpenFile(uiWindow *parent, const char* filter, const char* initpath)
+{
+ char *res;
+
+ disableAllWindowsExcept(parent);
+ res = commonItemDialog(windowHWND(parent),
+ CLSID_FileOpenDialog, IID_IFileOpenDialog,
+ filter, initpath,
+ FOS_NOCHANGEDIR | FOS_FORCEFILESYSTEM | FOS_NOVALIDATE | FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_SHAREAWARE | FOS_NOTESTFILECREATE | FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE);
+ enableAllWindowsExcept(parent);
+ return res;
+}
+
+char *uiSaveFile(uiWindow *parent, const char* filter, const char* initpath)
+{
+ char *res;
+
+ disableAllWindowsExcept(parent);
+ res = commonItemDialog(windowHWND(parent),
+ CLSID_FileSaveDialog, IID_IFileSaveDialog,
+ filter, initpath,
+ FOS_OVERWRITEPROMPT | FOS_NOCHANGEDIR | FOS_FORCEFILESYSTEM | FOS_NOVALIDATE | FOS_SHAREAWARE | FOS_NOTESTFILECREATE | FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE);
+ enableAllWindowsExcept(parent);
+ return res;
+}
+
+// TODO switch to TaskDialogIndirect()?
+
+static int msgbox(HWND parent, const char *title, const char *description, TASKDIALOG_COMMON_BUTTON_FLAGS buttons, PCWSTR icon)
+{
+ WCHAR *wtitle, *wdescription;
+ HRESULT hr;
+
+ wtitle = toUTF16(title);
+ wdescription = toUTF16(description);
+
+ int result;
+ hr = TaskDialog(parent, NULL, NULL, wtitle, wdescription, buttons, icon, &result);
+ if (hr != S_OK)
+ logHRESULT(L"error showing task dialog", hr);
+
+ uiFree(wdescription);
+ uiFree(wtitle);
+
+ return result;
+}
+
+void uiMsgBox(uiWindow *parent, const char *title, const char *description)
+{
+ disableAllWindowsExcept(parent);
+ msgbox(windowHWND(parent), title, description, TDCBF_OK_BUTTON, NULL);
+ enableAllWindowsExcept(parent);
+}
+
+void uiMsgBoxError(uiWindow *parent, const char *title, const char *description)
+{
+ disableAllWindowsExcept(parent);
+ msgbox(windowHWND(parent), title, description, TDCBF_OK_BUTTON, TD_ERROR_ICON);
+ enableAllWindowsExcept(parent);
+}
+
+int uiMsgBoxConfirm(uiWindow * parent, const char *title, const char *description)
+{
+ disableAllWindowsExcept(parent);
+ int result =
+ msgbox(windowHWND(parent), title, description, TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON, TD_WARNING_ICON);
+ enableAllWindowsExcept(parent);
+
+ return result == IDOK;
+} \ No newline at end of file
diff --git a/src/libui_sdl/main.cpp b/src/libui_sdl/main.cpp
new file mode 100644
index 0000000..0066668
--- /dev/null
+++ b/src/libui_sdl/main.cpp
@@ -0,0 +1,3061 @@
+/*
+ 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>
+
+#ifndef __WIN32__
+#include <glib.h>
+#endif
+
+#include <SDL2/SDL.h>
+#include "libui/ui.h"
+
+#include "../OpenGLSupport.h"
+#include "main_shaders.h"
+
+#include "../types.h"
+#include "../version.h"
+#include "PlatformConfig.h"
+
+#include "DlgEmuSettings.h"
+#include "DlgInputConfig.h"
+#include "DlgVideoSettings.h"
+#include "DlgAudioSettings.h"
+#include "DlgWifiSettings.h"
+
+#include "../NDS.h"
+#include "../GBACart.h"
+#include "../GPU.h"
+#include "../SPU.h"
+#include "../Wifi.h"
+#include "../Platform.h"
+#include "../Config.h"
+#include "../ARMJIT.h"
+
+#include "../Savestate.h"
+
+#include "OSD.h"
+
+#ifdef MELONCAP
+#include "MelonCap.h"
+#endif // MELONCAP
+
+
+// savestate slot mapping
+// 1-8: regular slots (quick access)
+// '9': load/save arbitrary file
+const int kSavestateNum[9] = {1, 2, 3, 4, 5, 6, 7, 8, 0};
+
+const int kScreenSize[4] = {1, 2, 3, 4};
+const int kScreenRot[4] = {0, 1, 2, 3};
+const int kScreenGap[6] = {0, 1, 8, 64, 90, 128};
+const int kScreenLayout[3] = {0, 1, 2};
+const int kScreenSizing[4] = {0, 1, 2, 3};
+
+
+char* EmuDirectory;
+
+
+uiWindow* MainWindow;
+uiArea* MainDrawArea;
+uiAreaHandler MainDrawAreaHandler;
+
+const u32 kGLVersions[] = {uiGLVersion(3,2), uiGLVersion(3,1), 0};
+uiGLContext* GLContext;
+
+int WindowWidth, WindowHeight;
+
+uiMenuItem* MenuItem_SaveState;
+uiMenuItem* MenuItem_LoadState;
+uiMenuItem* MenuItem_UndoStateLoad;
+
+uiMenuItem* MenuItem_SaveStateSlot[9];
+uiMenuItem* MenuItem_LoadStateSlot[9];
+
+uiMenuItem* MenuItem_Pause;
+uiMenuItem* MenuItem_Reset;
+uiMenuItem* MenuItem_Stop;
+
+uiMenuItem* MenuItem_SavestateSRAMReloc;
+
+uiMenuItem* MenuItem_ScreenRot[4];
+uiMenuItem* MenuItem_ScreenGap[6];
+uiMenuItem* MenuItem_ScreenLayout[3];
+uiMenuItem* MenuItem_ScreenSizing[4];
+
+uiMenuItem* MenuItem_ScreenFilter;
+uiMenuItem* MenuItem_LimitFPS;
+uiMenuItem* MenuItem_AudioSync;
+uiMenuItem* MenuItem_ShowOSD;
+
+SDL_Thread* EmuThread;
+int EmuRunning;
+volatile int EmuStatus;
+
+bool RunningSomething;
+char ROMPath[2][1024];
+char SRAMPath[2][1024];
+char PrevSRAMPath[2][1024]; // for savestate 'undo load'
+
+bool SavestateLoaded;
+
+bool Screen_UseGL;
+
+bool ScreenDrawInited = false;
+uiDrawBitmap* ScreenBitmap[2] = {NULL,NULL};
+
+GLuint GL_ScreenShader[3];
+GLuint GL_ScreenShaderAccel[3];
+GLuint GL_ScreenShaderOSD[3];
+struct
+{
+ float uScreenSize[2];
+ u32 u3DScale;
+ u32 uFilterMode;
+
+} GL_ShaderConfig;
+GLuint GL_ShaderConfigUBO;
+GLuint GL_ScreenVertexArrayID, GL_ScreenVertexBufferID;
+float GL_ScreenVertices[2 * 3*2 * 4]; // position/texcoord
+GLuint GL_ScreenTexture;
+bool GL_ScreenSizeDirty;
+
+int GL_3DScale;
+
+bool GL_VSyncStatus;
+
+int ScreenGap = 0;
+int ScreenLayout = 0;
+int ScreenSizing = 0;
+int ScreenRotation = 0;
+
+int MainScreenPos[3];
+int AutoScreenSizing;
+
+uiRect TopScreenRect;
+uiRect BottomScreenRect;
+uiDrawMatrix TopScreenTrans;
+uiDrawMatrix BottomScreenTrans;
+
+bool Touching = false;
+
+u32 KeyInputMask, JoyInputMask;
+u32 KeyHotkeyMask, JoyHotkeyMask;
+u32 HotkeyMask, LastHotkeyMask;
+u32 HotkeyPress, HotkeyRelease;
+
+#define HotkeyDown(hk) (HotkeyMask & (1<<(hk)))
+#define HotkeyPressed(hk) (HotkeyPress & (1<<(hk)))
+#define HotkeyReleased(hk) (HotkeyRelease & (1<<(hk)))
+
+bool LidStatus;
+
+int JoystickID;
+SDL_Joystick* Joystick;
+
+int AudioFreq;
+float AudioSampleFrac;
+SDL_AudioDeviceID AudioDevice, MicDevice;
+
+SDL_cond* AudioSync;
+SDL_mutex* AudioSyncLock;
+
+u32 MicBufferLength = 2048;
+s16 MicBuffer[2048];
+u32 MicBufferReadPos, MicBufferWritePos;
+
+u32 MicWavLength;
+s16* MicWavBuffer;
+
+void SetupScreenRects(int width, int height);
+
+void TogglePause(void* blarg);
+void Reset(void* blarg);
+
+void SetupSRAMPath(int slot);
+
+void SaveState(int slot);
+void LoadState(int slot);
+void UndoStateLoad();
+void GetSavestateName(int slot, char* filename, int len);
+
+void CreateMainWindow(bool opengl);
+void DestroyMainWindow();
+void RecreateMainWindow(bool opengl);
+
+
+
+bool GLScreen_InitShader(GLuint* shader, const char* fs)
+{
+ if (!OpenGL_BuildShaderProgram(kScreenVS, fs, shader, "ScreenShader"))
+ return false;
+
+ glBindAttribLocation(shader[2], 0, "vPosition");
+ glBindAttribLocation(shader[2], 1, "vTexcoord");
+ glBindFragDataLocation(shader[2], 0, "oColor");
+
+ if (!OpenGL_LinkShaderProgram(shader))
+ return false;
+
+ GLuint uni_id;
+
+ uni_id = glGetUniformBlockIndex(shader[2], "uConfig");
+ glUniformBlockBinding(shader[2], uni_id, 16);
+
+ glUseProgram(shader[2]);
+ uni_id = glGetUniformLocation(shader[2], "ScreenTex");
+ glUniform1i(uni_id, 0);
+ uni_id = glGetUniformLocation(shader[2], "_3DTex");
+ glUniform1i(uni_id, 1);
+
+ return true;
+}
+
+bool GLScreen_InitOSDShader(GLuint* shader)
+{
+ if (!OpenGL_BuildShaderProgram(kScreenVS_OSD, kScreenFS_OSD, shader, "ScreenShaderOSD"))
+ return false;
+
+ glBindAttribLocation(shader[2], 0, "vPosition");
+ glBindFragDataLocation(shader[2], 0, "oColor");
+
+ if (!OpenGL_LinkShaderProgram(shader))
+ return false;
+
+ GLuint uni_id;
+
+ uni_id = glGetUniformBlockIndex(shader[2], "uConfig");
+ glUniformBlockBinding(shader[2], uni_id, 16);
+
+ glUseProgram(shader[2]);
+ uni_id = glGetUniformLocation(shader[2], "OSDTex");
+ glUniform1i(uni_id, 0);
+
+ return true;
+}
+
+bool GLScreen_Init()
+{
+ GL_VSyncStatus = Config::ScreenVSync;
+
+ // TODO: consider using epoxy?
+ if (!OpenGL_Init())
+ return false;
+
+ const GLubyte* renderer = glGetString(GL_RENDERER); // get renderer string
+ const GLubyte* version = glGetString(GL_VERSION); // version as a string
+ printf("OpenGL: renderer: %s\n", renderer);
+ printf("OpenGL: version: %s\n", version);
+
+ if (!GLScreen_InitShader(GL_ScreenShader, kScreenFS))
+ return false;
+ if (!GLScreen_InitShader(GL_ScreenShaderAccel, kScreenFS_Accel))
+ return false;
+ if (!GLScreen_InitOSDShader(GL_ScreenShaderOSD))
+ return false;
+
+ memset(&GL_ShaderConfig, 0, sizeof(GL_ShaderConfig));
+
+ glGenBuffers(1, &GL_ShaderConfigUBO);
+ glBindBuffer(GL_UNIFORM_BUFFER, GL_ShaderConfigUBO);
+ glBufferData(GL_UNIFORM_BUFFER, sizeof(GL_ShaderConfig), &GL_ShaderConfig, GL_STATIC_DRAW);
+ glBindBufferBase(GL_UNIFORM_BUFFER, 16, GL_ShaderConfigUBO);
+
+ glGenBuffers(1, &GL_ScreenVertexBufferID);
+ glBindBuffer(GL_ARRAY_BUFFER, GL_ScreenVertexBufferID);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(GL_ScreenVertices), NULL, GL_STATIC_DRAW);
+
+ glGenVertexArrays(1, &GL_ScreenVertexArrayID);
+ glBindVertexArray(GL_ScreenVertexArrayID);
+ glEnableVertexAttribArray(0); // position
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(0));
+ glEnableVertexAttribArray(1); // texcoord
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(2*4));
+
+ glGenTextures(1, &GL_ScreenTexture);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, GL_ScreenTexture);
+ 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_RGBA8UI, 256*3 + 1, 192*2, 0, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, NULL);
+
+ GL_ScreenSizeDirty = true;
+
+ return true;
+}
+
+void GLScreen_DeInit()
+{
+ glDeleteTextures(1, &GL_ScreenTexture);
+
+ glDeleteVertexArrays(1, &GL_ScreenVertexArrayID);
+ glDeleteBuffers(1, &GL_ScreenVertexBufferID);
+
+ OpenGL_DeleteShaderProgram(GL_ScreenShader);
+ OpenGL_DeleteShaderProgram(GL_ScreenShaderAccel);
+ OpenGL_DeleteShaderProgram(GL_ScreenShaderOSD);
+}
+
+void GLScreen_DrawScreen()
+{
+ bool vsync = Config::ScreenVSync && !HotkeyDown(HK_FastForward);
+ if (vsync != GL_VSyncStatus)
+ {
+ GL_VSyncStatus = vsync;
+ uiGLSetVSync(vsync);
+ }
+
+ float scale = uiGLGetFramebufferScale(GLContext);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, uiGLGetFramebuffer(GLContext));
+
+ if (GL_ScreenSizeDirty)
+ {
+ GL_ScreenSizeDirty = false;
+
+ GL_ShaderConfig.uScreenSize[0] = WindowWidth;
+ GL_ShaderConfig.uScreenSize[1] = WindowHeight;
+ GL_ShaderConfig.u3DScale = GL_3DScale;
+
+ glBindBuffer(GL_UNIFORM_BUFFER, GL_ShaderConfigUBO);
+ void* unibuf = glMapBuffer(GL_UNIFORM_BUFFER, GL_WRITE_ONLY);
+ if (unibuf) memcpy(unibuf, &GL_ShaderConfig, sizeof(GL_ShaderConfig));
+ glUnmapBuffer(GL_UNIFORM_BUFFER);
+
+ float scwidth, scheight;
+
+ float x0, y0, x1, y1;
+ float s0, s1, s2, s3;
+ float t0, t1, t2, t3;
+
+#define SETVERTEX(i, x, y, s, t) \
+ GL_ScreenVertices[4*(i) + 0] = x; \
+ GL_ScreenVertices[4*(i) + 1] = y; \
+ GL_ScreenVertices[4*(i) + 2] = s; \
+ GL_ScreenVertices[4*(i) + 3] = t;
+
+ x0 = TopScreenRect.X;
+ y0 = TopScreenRect.Y;
+ x1 = TopScreenRect.X + TopScreenRect.Width;
+ y1 = TopScreenRect.Y + TopScreenRect.Height;
+
+ scwidth = 256;
+ scheight = 192;
+
+ switch (ScreenRotation)
+ {
+ case 0:
+ s0 = 0; t0 = 0;
+ s1 = scwidth; t1 = 0;
+ s2 = 0; t2 = scheight;
+ s3 = scwidth; t3 = scheight;
+ break;
+
+ case 1:
+ s0 = 0; t0 = scheight;
+ s1 = 0; t1 = 0;
+ s2 = scwidth; t2 = scheight;
+ s3 = scwidth; t3 = 0;
+ break;
+
+ case 2:
+ s0 = scwidth; t0 = scheight;
+ s1 = 0; t1 = scheight;
+ s2 = scwidth; t2 = 0;
+ s3 = 0; t3 = 0;
+ break;
+
+ case 3:
+ s0 = scwidth; t0 = 0;
+ s1 = scwidth; t1 = scheight;
+ s2 = 0; t2 = 0;
+ s3 = 0; t3 = scheight;
+ break;
+ }
+
+ SETVERTEX(0, x0, y0, s0, t0);
+ SETVERTEX(1, x1, y1, s3, t3);
+ SETVERTEX(2, x1, y0, s1, t1);
+ SETVERTEX(3, x0, y0, s0, t0);
+ SETVERTEX(4, x0, y1, s2, t2);
+ SETVERTEX(5, x1, y1, s3, t3);
+
+ x0 = BottomScreenRect.X;
+ y0 = BottomScreenRect.Y;
+ x1 = BottomScreenRect.X + BottomScreenRect.Width;
+ y1 = BottomScreenRect.Y + BottomScreenRect.Height;
+
+ scwidth = 256;
+ scheight = 192;
+
+ switch (ScreenRotation)
+ {
+ case 0:
+ s0 = 0; t0 = 192;
+ s1 = scwidth; t1 = 192;
+ s2 = 0; t2 = 192+scheight;
+ s3 = scwidth; t3 = 192+scheight;
+ break;
+
+ case 1:
+ s0 = 0; t0 = 192+scheight;
+ s1 = 0; t1 = 192;
+ s2 = scwidth; t2 = 192+scheight;
+ s3 = scwidth; t3 = 192;
+ break;
+
+ case 2:
+ s0 = scwidth; t0 = 192+scheight;
+ s1 = 0; t1 = 192+scheight;
+ s2 = scwidth; t2 = 192;
+ s3 = 0; t3 = 192;
+ break;
+
+ case 3:
+ s0 = scwidth; t0 = 192;
+ s1 = scwidth; t1 = 192+scheight;
+ s2 = 0; t2 = 192;
+ s3 = 0; t3 = 192+scheight;
+ break;
+ }
+
+ SETVERTEX(6, x0, y0, s0, t0);
+ SETVERTEX(7, x1, y1, s3, t3);
+ SETVERTEX(8, x1, y0, s1, t1);
+ SETVERTEX(9, x0, y0, s0, t0);
+ SETVERTEX(10, x0, y1, s2, t2);
+ SETVERTEX(11, x1, y1, s3, t3);
+
+#undef SETVERTEX
+
+ glBindBuffer(GL_ARRAY_BUFFER, GL_ScreenVertexBufferID);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GL_ScreenVertices), GL_ScreenVertices);
+ }
+
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_STENCIL_TEST);
+ glDisable(GL_BLEND);
+ glColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+
+ glViewport(0, 0, WindowWidth*scale, WindowHeight*scale);
+
+ if (GPU3D::Renderer == 0)
+ OpenGL_UseShaderProgram(GL_ScreenShader);
+ else
+ OpenGL_UseShaderProgram(GL_ScreenShaderAccel);
+
+ glClearColor(0, 0, 0, 1);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ if (RunningSomething)
+ {
+ int frontbuf = GPU::FrontBuffer;
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, GL_ScreenTexture);
+
+ if (GPU::Framebuffer[frontbuf][0] && GPU::Framebuffer[frontbuf][1])
+ {
+ if (GPU3D::Renderer == 0)
+ {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 192, GL_RGBA_INTEGER,
+ GL_UNSIGNED_BYTE, GPU::Framebuffer[frontbuf][0]);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256, 192, GL_RGBA_INTEGER,
+ GL_UNSIGNED_BYTE, GPU::Framebuffer[frontbuf][1]);
+ }
+ else
+ {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256*3 + 1, 192, GL_RGBA_INTEGER,
+ GL_UNSIGNED_BYTE, GPU::Framebuffer[frontbuf][0]);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256*3 + 1, 192, GL_RGBA_INTEGER,
+ GL_UNSIGNED_BYTE, GPU::Framebuffer[frontbuf][1]);
+ }
+ }
+
+ glActiveTexture(GL_TEXTURE1);
+ if (GPU3D::Renderer != 0)
+ GPU3D::GLRenderer::SetupAccelFrame();
+
+ glBindBuffer(GL_ARRAY_BUFFER, GL_ScreenVertexBufferID);
+ glBindVertexArray(GL_ScreenVertexArrayID);
+ glDrawArrays(GL_TRIANGLES, 0, 4*3);
+ }
+
+ OpenGL_UseShaderProgram(GL_ScreenShaderOSD);
+ OSD::Update(true, NULL);
+
+ glFlush();
+ uiGLSwapBuffers(GLContext);
+}
+
+void MicLoadWav(char* name)
+{
+ SDL_AudioSpec format;
+ memset(&format, 0, sizeof(SDL_AudioSpec));
+
+ if (MicWavBuffer) delete[] MicWavBuffer;
+ MicWavBuffer = NULL;
+ MicWavLength = 0;
+
+ u8* buf;
+ u32 len;
+ if (!SDL_LoadWAV(name, &format, &buf, &len))
+ return;
+
+ const u64 dstfreq = 44100;
+
+ if (format.format == AUDIO_S16 || format.format == AUDIO_U16)
+ {
+ int srcinc = format.channels;
+ len /= (2 * srcinc);
+
+ MicWavLength = (len * dstfreq) / format.freq;
+ if (MicWavLength < 735) MicWavLength = 735;
+ MicWavBuffer = new s16[MicWavLength];
+
+ float res_incr = len / (float)MicWavLength;
+ float res_timer = 0;
+ int res_pos = 0;
+
+ for (int i = 0; i < MicWavLength; i++)
+ {
+ u16 val = ((u16*)buf)[res_pos];
+ if (SDL_AUDIO_ISUNSIGNED(format.format)) val ^= 0x8000;
+
+ MicWavBuffer[i] = val;
+
+ res_timer += res_incr;
+ while (res_timer >= 1.0)
+ {
+ res_timer -= 1.0;
+ res_pos += srcinc;
+ }
+ }
+ }
+ else if (format.format == AUDIO_S8 || format.format == AUDIO_U8)
+ {
+ int srcinc = format.channels;
+ len /= srcinc;
+
+ MicWavLength = (len * dstfreq) / format.freq;
+ if (MicWavLength < 735) MicWavLength = 735;
+ MicWavBuffer = new s16[MicWavLength];
+
+ float res_incr = len / (float)MicWavLength;
+ float res_timer = 0;
+ int res_pos = 0;
+
+ for (int i = 0; i < MicWavLength; i++)
+ {
+ u16 val = buf[res_pos] << 8;
+ if (SDL_AUDIO_ISUNSIGNED(format.format)) val ^= 0x8000;
+
+ MicWavBuffer[i] = val;
+
+ res_timer += res_incr;
+ while (res_timer >= 1.0)
+ {
+ res_timer -= 1.0;
+ res_pos += srcinc;
+ }
+ }
+ }
+ else
+ printf("bad WAV format %08X\n", format.format);
+
+ SDL_FreeWAV(buf);
+}
+
+void AudioCallback(void* data, Uint8* stream, int len)
+{
+ len /= (sizeof(s16) * 2);
+
+ // resample incoming audio to match the output sample rate
+
+ float f_len_in = (len * 32823.6328125) / (float)AudioFreq;
+ f_len_in += AudioSampleFrac;
+ int len_in = (int)floor(f_len_in);
+ AudioSampleFrac = f_len_in - len_in;
+
+ s16 buf_in[1024*2];
+ s16* buf_out = (s16*)stream;
+
+ int num_in;
+ int num_out = len;
+
+ 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;
+ }
+
+ float res_incr = num_in / (float)num_out;
+ float res_timer = 0;
+ int res_pos = 0;
+
+ int volume = Config::AudioVolume;
+
+ for (int i = 0; i < len; i++)
+ {
+ buf_out[i*2 ] = (buf_in[res_pos*2 ] * volume) >> 8;
+ buf_out[i*2+1] = (buf_in[res_pos*2+1] * volume) >> 8;
+
+ /*s16 s_l = buf_in[res_pos*2 ];
+ s16 s_r = buf_in[res_pos*2+1];
+
+ float a = res_timer;
+ float b = 1.0 - a;
+ s_l = (s_l * a) + (buf_in[(res_pos-1)*2 ] * b);
+ s_r = (s_r * a) + (buf_in[(res_pos-1)*2+1] * b);
+
+ buf_out[i*2 ] = (s_l * volume) >> 8;
+ buf_out[i*2+1] = (s_r * volume) >> 8;*/
+
+ res_timer += res_incr;
+ while (res_timer >= 1.0)
+ {
+ res_timer -= 1.0;
+ res_pos++;
+ }
+ }
+}
+
+void MicCallback(void* data, Uint8* stream, int len)
+{
+ if (Config::MicInputType != 1) return;
+
+ s16* input = (s16*)stream;
+ len /= sizeof(s16);
+
+ if ((MicBufferWritePos + len) > MicBufferLength)
+ {
+ u32 len1 = MicBufferLength - MicBufferWritePos;
+ memcpy(&MicBuffer[MicBufferWritePos], &input[0], len1*sizeof(s16));
+ memcpy(&MicBuffer[0], &input[len1], (len - len1)*sizeof(s16));
+ MicBufferWritePos = len - len1;
+ }
+ else
+ {
+ memcpy(&MicBuffer[MicBufferWritePos], input, len*sizeof(s16));
+ MicBufferWritePos += len;
+ }
+}
+
+void FeedMicInput()
+{
+ int type = Config::MicInputType;
+ bool cmd = HotkeyDown(HK_Mic);
+
+ if ((type != 1 && !cmd) ||
+ (type == 1 && MicBufferLength == 0) ||
+ (type == 3 && MicWavBuffer == NULL))
+ {
+ type = 0;
+ MicBufferReadPos = 0;
+ }
+
+ switch (type)
+ {
+ case 0: // no mic
+ NDS::MicInputFrame(NULL, 0);
+ break;
+
+ case 1: // host mic
+ if ((MicBufferReadPos + 735) > MicBufferLength)
+ {
+ s16 tmp[735];
+ u32 len1 = MicBufferLength - MicBufferReadPos;
+ memcpy(&tmp[0], &MicBuffer[MicBufferReadPos], len1*sizeof(s16));
+ memcpy(&tmp[len1], &MicBuffer[0], (735 - len1)*sizeof(s16));
+
+ NDS::MicInputFrame(tmp, 735);
+ MicBufferReadPos = 735 - len1;
+ }
+ else
+ {
+ NDS::MicInputFrame(&MicBuffer[MicBufferReadPos], 735);
+ MicBufferReadPos += 735;
+ }
+ break;
+
+ case 2: // white noise
+ {
+ s16 tmp[735];
+ for (int i = 0; i < 735; i++) tmp[i] = rand() & 0xFFFF;
+ NDS::MicInputFrame(tmp, 735);
+ }
+ break;
+
+ case 3: // WAV
+ if ((MicBufferReadPos + 735) > MicWavLength)
+ {
+ s16 tmp[735];
+ u32 len1 = MicWavLength - MicBufferReadPos;
+ memcpy(&tmp[0], &MicWavBuffer[MicBufferReadPos], len1*sizeof(s16));
+ memcpy(&tmp[len1], &MicWavBuffer[0], (735 - len1)*sizeof(s16));
+
+ NDS::MicInputFrame(tmp, 735);
+ MicBufferReadPos = 735 - len1;
+ }
+ else
+ {
+ NDS::MicInputFrame(&MicWavBuffer[MicBufferReadPos], 735);
+ MicBufferReadPos += 735;
+ }
+ break;
+ }
+}
+
+void OpenJoystick()
+{
+ if (Joystick) SDL_JoystickClose(Joystick);
+
+ int num = SDL_NumJoysticks();
+ if (num < 1)
+ {
+ Joystick = NULL;
+ return;
+ }
+
+ if (JoystickID >= num)
+ JoystickID = 0;
+
+ Joystick = SDL_JoystickOpen(JoystickID);
+}
+
+bool JoystickButtonDown(int val)
+{
+ if (val == -1) return false;
+
+ bool hasbtn = ((val & 0xFFFF) != 0xFFFF);
+
+ if (hasbtn)
+ {
+ if (val & 0x100)
+ {
+ int hatnum = (val >> 4) & 0xF;
+ int hatdir = val & 0xF;
+ Uint8 hatval = SDL_JoystickGetHat(Joystick, hatnum);
+
+ bool pressed = false;
+ if (hatdir == 0x1) pressed = (hatval & SDL_HAT_UP);
+ else if (hatdir == 0x4) pressed = (hatval & SDL_HAT_DOWN);
+ else if (hatdir == 0x2) pressed = (hatval & SDL_HAT_RIGHT);
+ else if (hatdir == 0x8) pressed = (hatval & SDL_HAT_LEFT);
+
+ if (pressed) return true;
+ }
+ else
+ {
+ int btnnum = val & 0xFFFF;
+ Uint8 btnval = SDL_JoystickGetButton(Joystick, btnnum);
+
+ if (btnval) return true;
+ }
+ }
+
+ if (val & 0x10000)
+ {
+ int axisnum = (val >> 24) & 0xF;
+ int axisdir = (val >> 20) & 0xF;
+ Sint16 axisval = SDL_JoystickGetAxis(Joystick, axisnum);
+
+ switch (axisdir)
+ {
+ case 0: // positive
+ if (axisval > 16384) return true;
+ break;
+
+ case 1: // negative
+ if (axisval < -16384) return true;
+ break;
+
+ case 2: // trigger
+ if (axisval > 0) return true;
+ break;
+ }
+ }
+
+ return false;
+}
+
+void ProcessInput()
+{
+ SDL_JoystickUpdate();
+
+ if (Joystick)
+ {
+ if (!SDL_JoystickGetAttached(Joystick))
+ {
+ SDL_JoystickClose(Joystick);
+ Joystick = NULL;
+ }
+ }
+ if (!Joystick && (SDL_NumJoysticks() > 0))
+ {
+ JoystickID = Config::JoystickID;
+ OpenJoystick();
+ }
+
+ JoyInputMask = 0xFFF;
+ for (int i = 0; i < 12; i++)
+ if (JoystickButtonDown(Config::JoyMapping[i]))
+ JoyInputMask &= ~(1<<i);
+
+ JoyHotkeyMask = 0;
+ for (int i = 0; i < HK_MAX; i++)
+ if (JoystickButtonDown(Config::HKJoyMapping[i]))
+ JoyHotkeyMask |= (1<<i);
+
+ HotkeyMask = KeyHotkeyMask | JoyHotkeyMask;
+ HotkeyPress = HotkeyMask & ~LastHotkeyMask;
+ HotkeyRelease = LastHotkeyMask & ~HotkeyMask;
+ LastHotkeyMask = HotkeyMask;
+}
+
+bool JoyButtonPressed(int btnid, int njoybuttons, Uint8* joybuttons, Uint32 hat)
+{
+ if (btnid < 0) return false;
+
+ hat &= ~(hat >> 4);
+
+ bool pressed = false;
+ if (btnid == 0x101) // up
+ pressed = (hat & SDL_HAT_UP);
+ else if (btnid == 0x104) // down
+ pressed = (hat & SDL_HAT_DOWN);
+ else if (btnid == 0x102) // right
+ pressed = (hat & SDL_HAT_RIGHT);
+ else if (btnid == 0x108) // left
+ pressed = (hat & SDL_HAT_LEFT);
+ else if (btnid < njoybuttons)
+ pressed = (joybuttons[btnid] & ~(joybuttons[btnid] >> 1)) & 0x01;
+
+ return pressed;
+}
+
+bool JoyButtonHeld(int btnid, int njoybuttons, Uint8* joybuttons, Uint32 hat)
+{
+ if (btnid < 0) return false;
+
+ bool pressed = false;
+ if (btnid == 0x101) // up
+ pressed = (hat & SDL_HAT_UP);
+ else if (btnid == 0x104) // down
+ pressed = (hat & SDL_HAT_DOWN);
+ else if (btnid == 0x102) // right
+ pressed = (hat & SDL_HAT_RIGHT);
+ else if (btnid == 0x108) // left
+ pressed = (hat & SDL_HAT_LEFT);
+ else if (btnid < njoybuttons)
+ pressed = joybuttons[btnid] & 0x01;
+
+ return pressed;
+}
+
+void UpdateWindowTitle(void* data)
+{
+ if (EmuStatus == 0) return;
+ void** dataarray = (void**)data;
+ SDL_LockMutex((SDL_mutex*)dataarray[1]);
+ uiWindowSetTitle(MainWindow, (const char*)dataarray[0]);
+ SDL_UnlockMutex((SDL_mutex*)dataarray[1]);
+}
+
+void UpdateFPSLimit(void* data)
+{
+ uiMenuItemSetChecked(MenuItem_LimitFPS, Config::LimitFPS==1);
+}
+
+int EmuThreadFunc(void* burp)
+{
+ 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);
+ }
+
+ Touching = false;
+ KeyInputMask = 0xFFF;
+ JoyInputMask = 0xFFF;
+ KeyHotkeyMask = 0;
+ JoyHotkeyMask = 0;
+ HotkeyMask = 0;
+ LastHotkeyMask = 0;
+ LidStatus = false;
+
+ u32 nframes = 0;
+ u32 starttick = SDL_GetTicks();
+ u32 lasttick = starttick;
+ u32 lastmeasuretick = lasttick;
+ u32 fpslimitcount = 0;
+ u64 perfcount = SDL_GetPerformanceCounter();
+ u64 perffreq = SDL_GetPerformanceFrequency();
+ float samplesleft = 0;
+ u32 nsamples = 0;
+
+ char melontitle[100];
+ SDL_mutex* titlemutex = SDL_CreateMutex();
+ void* titledata[2] = {melontitle, titlemutex};
+
+ while (EmuRunning != 0)
+ {
+ ProcessInput();
+
+ if (HotkeyPressed(HK_FastForwardToggle))
+ {
+ Config::LimitFPS = !Config::LimitFPS;
+ uiQueueMain(UpdateFPSLimit, NULL);
+ }
+ // TODO: similar hotkeys for video/audio sync?
+
+ if (HotkeyPressed(HK_Pause)) uiQueueMain(TogglePause, NULL);
+ if (HotkeyPressed(HK_Reset)) uiQueueMain(Reset, NULL);
+
+ if (GBACart::CartInserted && GBACart::HasSolarSensor)
+ {
+ if (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 (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(KeyInputMask & JoyInputMask);
+
+ if (HotkeyPressed(HK_Lid))
+ {
+ LidStatus = !LidStatus;
+ NDS::SetLidClosed(LidStatus);
+ OSD::AddMessage(0, LidStatus ? "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);
+
+ bool fastforward = HotkeyDown(HK_FastForward);
+
+ if (Config::AudioSync && !fastforward)
+ {
+ SDL_LockMutex(AudioSyncLock);
+ while (SPU::GetOutputSize() > 1024)
+ {
+ int ret = SDL_CondWaitTimeout(AudioSync, AudioSyncLock, 500);
+ if (ret == SDL_MUTEX_TIMEDOUT) break;
+ }
+ SDL_UnlockMutex(AudioSyncLock);
+ }
+ else
+ {
+ // ensure the audio FIFO doesn't overflow
+ //SPU::TrimOutput();
+ }
+
+ 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;
+ nsamples = 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;
+
+ SDL_LockMutex(titlemutex);
+ sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget);
+ SDL_UnlockMutex(titlemutex);
+ uiQueueMain(UpdateWindowTitle, titledata);
+ }
+ }
+ 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);
+ }
+
+ if (Screen_UseGL) uiGLMakeContextCurrent(NULL);
+
+ EmuStatus = EmuRunning;
+
+ SDL_Delay(100);
+ }
+ }
+
+ EmuStatus = 0;
+
+ SDL_DestroyMutex(titlemutex);
+
+ 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);
+
+ return 44203;
+}
+
+void StopEmuThread()
+{
+ EmuRunning = 0;
+ SDL_WaitThread(EmuThread, NULL);
+}
+
+
+void OnAreaDraw(uiAreaHandler* handler, uiArea* area, uiAreaDrawParams* params)
+{
+ if (!ScreenDrawInited)
+ {
+ if (ScreenBitmap[0]) uiDrawFreeBitmap(ScreenBitmap[0]);
+ if (ScreenBitmap[1]) uiDrawFreeBitmap(ScreenBitmap[1]);
+
+ ScreenDrawInited = true;
+ ScreenBitmap[0] = uiDrawNewBitmap(params->Context, 256, 192, 0);
+ ScreenBitmap[1] = uiDrawNewBitmap(params->Context, 256, 192, 0);
+ }
+
+ int frontbuf = GPU::FrontBuffer;
+ if (!ScreenBitmap[0] || !ScreenBitmap[1]) return;
+ if (!GPU::Framebuffer[frontbuf][0] || !GPU::Framebuffer[frontbuf][1]) return;
+
+ uiRect top = {0, 0, 256, 192};
+ uiRect bot = {0, 0, 256, 192};
+
+ uiDrawBitmapUpdate(ScreenBitmap[0], GPU::Framebuffer[frontbuf][0]);
+ uiDrawBitmapUpdate(ScreenBitmap[1], GPU::Framebuffer[frontbuf][1]);
+
+ uiDrawSave(params->Context);
+ uiDrawTransform(params->Context, &TopScreenTrans);
+ uiDrawBitmapDraw(params->Context, ScreenBitmap[0], &top, &TopScreenRect, Config::ScreenFilter==1);
+ uiDrawRestore(params->Context);
+
+ uiDrawSave(params->Context);
+ uiDrawTransform(params->Context, &BottomScreenTrans);
+ uiDrawBitmapDraw(params->Context, ScreenBitmap[1], &bot, &BottomScreenRect, Config::ScreenFilter==1);
+ uiDrawRestore(params->Context);
+
+ OSD::Update(false, params);
+}
+
+void OnAreaMouseEvent(uiAreaHandler* handler, uiArea* area, uiAreaMouseEvent* evt)
+{
+ int x = (int)evt->X;
+ int y = (int)evt->Y;
+
+ if (Touching && (evt->Up == 1))
+ {
+ Touching = false;
+ NDS::ReleaseKey(16+6);
+ NDS::ReleaseScreen();
+ }
+ else if (!Touching && (evt->Down == 1) &&
+ (x >= BottomScreenRect.X) && (y >= BottomScreenRect.Y) &&
+ (x < (BottomScreenRect.X+BottomScreenRect.Width)) && (y < (BottomScreenRect.Y+BottomScreenRect.Height)))
+ {
+ Touching = true;
+ NDS::PressKey(16+6);
+ }
+
+ if (Touching)
+ {
+ x -= BottomScreenRect.X;
+ y -= BottomScreenRect.Y;
+
+ if (ScreenRotation == 0 || ScreenRotation == 2)
+ {
+ if (BottomScreenRect.Width != 256)
+ x = (x * 256) / BottomScreenRect.Width;
+ if (BottomScreenRect.Height != 192)
+ y = (y * 192) / BottomScreenRect.Height;
+
+ if (ScreenRotation == 2)
+ {
+ x = 255 - x;
+ y = 191 - y;
+ }
+ }
+ else
+ {
+ if (BottomScreenRect.Width != 192)
+ x = (x * 192) / BottomScreenRect.Width;
+ if (BottomScreenRect.Height != 256)
+ y = (y * 256) / BottomScreenRect.Height;
+
+ if (ScreenRotation == 1)
+ {
+ int tmp = x;
+ x = y;
+ y = 191 - tmp;
+ }
+ else
+ {
+ int tmp = x;
+ x = 255 - y;
+ y = tmp;
+ }
+ }
+
+ // clamp
+ if (x < 0) x = 0;
+ else if (x > 255) x = 255;
+ if (y < 0) y = 0;
+ else if (y > 191) y = 191;
+
+ // TODO: take advantage of possible extra precision when possible? (scaled window for example)
+ NDS::TouchScreen(x, y);
+ }
+}
+
+void OnAreaMouseCrossed(uiAreaHandler* handler, uiArea* area, int left)
+{
+}
+
+void OnAreaDragBroken(uiAreaHandler* handler, uiArea* area)
+{
+}
+
+bool EventMatchesKey(uiAreaKeyEvent* evt, int val, bool checkmod)
+{
+ if (val == -1) return false;
+
+ int key = val & 0xFFFF;
+ int mod = val >> 16;
+ return evt->Scancode == key && (!checkmod || evt->Modifiers == mod);
+}
+
+int OnAreaKeyEvent(uiAreaHandler* handler, uiArea* area, uiAreaKeyEvent* evt)
+{
+ // TODO: release all keys if the window loses focus? or somehow global key input?
+ if (evt->Scancode == 0x38) // ALT
+ return 0;
+ if (evt->Modifiers == 0x2) // ALT+key
+ return 0;
+
+ if (evt->Up)
+ {
+ for (int i = 0; i < 12; i++)
+ if (EventMatchesKey(evt, Config::KeyMapping[i], false))
+ KeyInputMask |= (1<<i);
+
+ for (int i = 0; i < HK_MAX; i++)
+ if (EventMatchesKey(evt, Config::HKKeyMapping[i], true))
+ KeyHotkeyMask &= ~(1<<i);
+ }
+ else if (!evt->Repeat)
+ {
+ // TODO, eventually: make savestate keys configurable?
+ // F keys: 3B-44, 57-58 | SHIFT: mod. 0x4
+ if (evt->Scancode >= 0x3B && evt->Scancode <= 0x42) // F1-F8, quick savestate
+ {
+ if (evt->Modifiers == 0x4) SaveState(1 + (evt->Scancode - 0x3B));
+ else if (evt->Modifiers == 0x0) LoadState(1 + (evt->Scancode - 0x3B));
+ }
+ else if (evt->Scancode == 0x43) // F9, savestate from/to file
+ {
+ if (evt->Modifiers == 0x4) SaveState(0);
+ else if (evt->Modifiers == 0x0) LoadState(0);
+ }
+ else if (evt->Scancode == 0x58) // F12, undo savestate
+ {
+ if (evt->Modifiers == 0x0) UndoStateLoad();
+ }
+
+ for (int i = 0; i < 12; i++)
+ if (EventMatchesKey(evt, Config::KeyMapping[i], false))
+ KeyInputMask &= ~(1<<i);
+
+ for (int i = 0; i < HK_MAX; i++)
+ if (EventMatchesKey(evt, Config::HKKeyMapping[i], true))
+ KeyHotkeyMask |= (1<<i);
+
+ // REMOVE ME
+ //if (evt->Scancode == 0x57) // F11
+ // NDS::debug(0);
+ }
+
+ return 1;
+}
+
+void SetupScreenRects(int width, int height)
+{
+ bool horizontal = false;
+ bool sideways = false;
+
+ if (ScreenRotation == 1 || ScreenRotation == 3)
+ sideways = true;
+
+ if (ScreenLayout == 2) horizontal = true;
+ else if (ScreenLayout == 0)
+ {
+ if (sideways)
+ horizontal = true;
+ }
+
+ int sizemode;
+ if (ScreenSizing == 3)
+ sizemode = AutoScreenSizing;
+ else
+ sizemode = ScreenSizing;
+
+ int screenW, screenH, gap;
+ if (sideways)
+ {
+ screenW = 192;
+ screenH = 256;
+ }
+ else
+ {
+ screenW = 256;
+ screenH = 192;
+ }
+
+ gap = ScreenGap;
+
+ uiRect *topscreen, *bottomscreen;
+ if (ScreenRotation == 1 || ScreenRotation == 2)
+ {
+ topscreen = &BottomScreenRect;
+ bottomscreen = &TopScreenRect;
+ }
+ else
+ {
+ topscreen = &TopScreenRect;
+ bottomscreen = &BottomScreenRect;
+ }
+
+ if (horizontal)
+ {
+ // side-by-side
+
+ int heightreq;
+ int startX = 0;
+
+ width -= gap;
+
+ if (sizemode == 0) // even
+ {
+ heightreq = (width * screenH) / (screenW*2);
+ if (heightreq > height)
+ {
+ int newwidth = (height * width) / heightreq;
+ startX = (width - newwidth) / 2;
+ heightreq = height;
+ width = newwidth;
+ }
+ }
+ else // emph. top/bottom
+ {
+ heightreq = ((width - screenW) * screenH) / screenW;
+ if (heightreq > height)
+ {
+ int newwidth = ((height * (width - screenW)) / heightreq) + screenW;
+ startX = (width - newwidth) / 2;
+ heightreq = height;
+ width = newwidth;
+ }
+ }
+
+ if (sizemode == 2)
+ {
+ topscreen->Width = screenW;
+ topscreen->Height = screenH;
+ }
+ else
+ {
+ topscreen->Width = (sizemode==0) ? (width / 2) : (width - screenW);
+ topscreen->Height = heightreq;
+ }
+ topscreen->X = startX;
+ topscreen->Y = ((height - heightreq) / 2) + (heightreq - topscreen->Height);
+
+ bottomscreen->X = topscreen->X + topscreen->Width + gap;
+
+ if (sizemode == 1)
+ {
+ bottomscreen->Width = screenW;
+ bottomscreen->Height = screenH;
+ }
+ else
+ {
+ bottomscreen->Width = width - topscreen->Width;
+ bottomscreen->Height = heightreq;
+ }
+ bottomscreen->Y = ((height - heightreq) / 2) + (heightreq - bottomscreen->Height);
+ }
+ else
+ {
+ // top then bottom
+
+ int widthreq;
+ int startY = 0;
+
+ height -= gap;
+
+ if (sizemode == 0) // even
+ {
+ widthreq = (height * screenW) / (screenH*2);
+ if (widthreq > width)
+ {
+ int newheight = (width * height) / widthreq;
+ startY = (height - newheight) / 2;
+ widthreq = width;
+ height = newheight;
+ }
+ }
+ else // emph. top/bottom
+ {
+ widthreq = ((height - screenH) * screenW) / screenH;
+ if (widthreq > width)
+ {
+ int newheight = ((width * (height - screenH)) / widthreq) + screenH;
+ startY = (height - newheight) / 2;
+ widthreq = width;
+ height = newheight;
+ }
+ }
+
+ if (sizemode == 2)
+ {
+ topscreen->Width = screenW;
+ topscreen->Height = screenH;
+ }
+ else
+ {
+ topscreen->Width = widthreq;
+ topscreen->Height = (sizemode==0) ? (height / 2) : (height - screenH);
+ }
+ topscreen->Y = startY;
+ topscreen->X = (width - topscreen->Width) / 2;
+
+ bottomscreen->Y = topscreen->Y + topscreen->Height + gap;
+
+ if (sizemode == 1)
+ {
+ bottomscreen->Width = screenW;
+ bottomscreen->Height = screenH;
+ }
+ else
+ {
+ bottomscreen->Width = widthreq;
+ bottomscreen->Height = height - topscreen->Height;
+ }
+ bottomscreen->X = (width - bottomscreen->Width) / 2;
+ }
+
+ // setup matrices for potential rotation
+
+ uiDrawMatrixSetIdentity(&TopScreenTrans);
+ uiDrawMatrixSetIdentity(&BottomScreenTrans);
+
+ switch (ScreenRotation)
+ {
+ case 1: // 90°
+ {
+ uiDrawMatrixTranslate(&TopScreenTrans, -TopScreenRect.X, -TopScreenRect.Y);
+ uiDrawMatrixRotate(&TopScreenTrans, 0, 0, M_PI/2.0f);
+ uiDrawMatrixScale(&TopScreenTrans, 0, 0,
+ TopScreenRect.Width/(double)TopScreenRect.Height,
+ TopScreenRect.Height/(double)TopScreenRect.Width);
+ uiDrawMatrixTranslate(&TopScreenTrans, TopScreenRect.X+TopScreenRect.Width, TopScreenRect.Y);
+
+ uiDrawMatrixTranslate(&BottomScreenTrans, -BottomScreenRect.X, -BottomScreenRect.Y);
+ uiDrawMatrixRotate(&BottomScreenTrans, 0, 0, M_PI/2.0f);
+ uiDrawMatrixScale(&BottomScreenTrans, 0, 0,
+ BottomScreenRect.Width/(double)BottomScreenRect.Height,
+ BottomScreenRect.Height/(double)BottomScreenRect.Width);
+ uiDrawMatrixTranslate(&BottomScreenTrans, BottomScreenRect.X+BottomScreenRect.Width, BottomScreenRect.Y);
+ }
+ break;
+
+ case 2: // 180°
+ {
+ uiDrawMatrixTranslate(&TopScreenTrans, -TopScreenRect.X, -TopScreenRect.Y);
+ uiDrawMatrixRotate(&TopScreenTrans, 0, 0, M_PI);
+ uiDrawMatrixTranslate(&TopScreenTrans, TopScreenRect.X+TopScreenRect.Width, TopScreenRect.Y+TopScreenRect.Height);
+
+ uiDrawMatrixTranslate(&BottomScreenTrans, -BottomScreenRect.X, -BottomScreenRect.Y);
+ uiDrawMatrixRotate(&BottomScreenTrans, 0, 0, M_PI);
+ uiDrawMatrixTranslate(&BottomScreenTrans, BottomScreenRect.X+BottomScreenRect.Width, BottomScreenRect.Y+BottomScreenRect.Height);
+ }
+ break;
+
+ case 3: // 270°
+ {
+ uiDrawMatrixTranslate(&TopScreenTrans, -TopScreenRect.X, -TopScreenRect.Y);
+ uiDrawMatrixRotate(&TopScreenTrans, 0, 0, -M_PI/2.0f);
+ uiDrawMatrixScale(&TopScreenTrans, 0, 0,
+ TopScreenRect.Width/(double)TopScreenRect.Height,
+ TopScreenRect.Height/(double)TopScreenRect.Width);
+ uiDrawMatrixTranslate(&TopScreenTrans, TopScreenRect.X, TopScreenRect.Y+TopScreenRect.Height);
+
+ uiDrawMatrixTranslate(&BottomScreenTrans, -BottomScreenRect.X, -BottomScreenRect.Y);
+ uiDrawMatrixRotate(&BottomScreenTrans, 0, 0, -M_PI/2.0f);
+ uiDrawMatrixScale(&BottomScreenTrans, 0, 0,
+ BottomScreenRect.Width/(double)BottomScreenRect.Height,
+ BottomScreenRect.Height/(double)BottomScreenRect.Width);
+ uiDrawMatrixTranslate(&BottomScreenTrans, BottomScreenRect.X, BottomScreenRect.Y+BottomScreenRect.Height);
+ }
+ break;
+ }
+
+ GL_ScreenSizeDirty = true;
+}
+
+void SetMinSize(int w, int h)
+{
+ int cw, ch;
+ uiWindowContentSize(MainWindow, &cw, &ch);
+
+ uiControlSetMinSize(uiControl(MainDrawArea), w, h);
+ if ((cw < w) || (ch < h))
+ {
+ if (cw < w) cw = w;
+ if (ch < h) ch = h;
+ uiWindowSetContentSize(MainWindow, cw, ch);
+ }
+}
+
+void OnAreaResize(uiAreaHandler* handler, uiArea* area, int width, int height)
+{
+ SetupScreenRects(width, height);
+
+ // TODO:
+ // should those be the size of the uiArea, or the size of the window client area?
+ // for now the uiArea fills the whole window anyway
+ // but... we never know, I guess
+ WindowWidth = width;
+ WindowHeight = height;
+
+ int ismax = uiWindowMaximized(MainWindow);
+ int ismin = uiWindowMinimized(MainWindow);
+
+ Config::WindowMaximized = ismax;
+ if (!ismax && !ismin)
+ {
+ Config::WindowWidth = width;
+ Config::WindowHeight = height;
+ }
+
+ OSD::WindowResized(Screen_UseGL);
+}
+
+
+void Run()
+{
+ EmuRunning = 1;
+ RunningSomething = true;
+
+ SPU::InitOutput();
+ AudioSampleFrac = 0;
+ SDL_PauseAudioDevice(AudioDevice, 0);
+ SDL_PauseAudioDevice(MicDevice, 0);
+
+ uiMenuItemEnable(MenuItem_SaveState);
+ uiMenuItemEnable(MenuItem_LoadState);
+
+ if (SavestateLoaded)
+ uiMenuItemEnable(MenuItem_UndoStateLoad);
+ else
+ uiMenuItemDisable(MenuItem_UndoStateLoad);
+
+ for (int i = 0; i < 8; i++)
+ {
+ char ssfile[1024];
+ GetSavestateName(i+1, ssfile, 1024);
+ if (Platform::FileExists(ssfile)) uiMenuItemEnable(MenuItem_LoadStateSlot[i]);
+ else uiMenuItemDisable(MenuItem_LoadStateSlot[i]);
+ }
+
+ for (int i = 0; i < 9; i++) uiMenuItemEnable(MenuItem_SaveStateSlot[i]);
+ uiMenuItemEnable(MenuItem_LoadStateSlot[8]);
+
+ uiMenuItemEnable(MenuItem_Pause);
+ uiMenuItemEnable(MenuItem_Reset);
+ uiMenuItemEnable(MenuItem_Stop);
+ uiMenuItemSetChecked(MenuItem_Pause, 0);
+}
+
+void TogglePause(void* blarg)
+{
+ if (!RunningSomething) return;
+
+ if (EmuRunning == 1)
+ {
+ // enable pause
+ EmuRunning = 2;
+ uiMenuItemSetChecked(MenuItem_Pause, 1);
+
+ SPU::DrainOutput();
+ SDL_PauseAudioDevice(AudioDevice, 1);
+ SDL_PauseAudioDevice(MicDevice, 1);
+
+ OSD::AddMessage(0, "Paused");
+ }
+ else
+ {
+ // disable pause
+ EmuRunning = 1;
+ uiMenuItemSetChecked(MenuItem_Pause, 0);
+
+ SPU::InitOutput();
+ AudioSampleFrac = 0;
+ SDL_PauseAudioDevice(AudioDevice, 0);
+ SDL_PauseAudioDevice(MicDevice, 0);
+
+ OSD::AddMessage(0, "Resumed");
+ }
+}
+
+void Reset(void* blarg)
+{
+ if (!RunningSomething) return;
+
+ EmuRunning = 2;
+ while (EmuStatus != 2);
+
+ SavestateLoaded = false;
+ uiMenuItemDisable(MenuItem_UndoStateLoad);
+
+ if (ROMPath[0][0] == '\0')
+ NDS::LoadBIOS();
+ else
+ {
+ SetupSRAMPath(0);
+ NDS::LoadROM(ROMPath[0], SRAMPath[0], Config::DirectBoot);
+ }
+
+ if (ROMPath[1][0] != '\0')
+ {
+ SetupSRAMPath(1);
+ NDS::LoadGBAROM(ROMPath[1], SRAMPath[1]);
+ }
+
+ Run();
+
+ OSD::AddMessage(0, "Reset");
+}
+
+void Stop(bool internal)
+{
+ EmuRunning = 2;
+ if (!internal) // if shutting down from the UI thread, wait till the emu thread has stopped
+ while (EmuStatus != 2);
+ RunningSomething = false;
+
+ // eject any inserted GBA cartridge
+ GBACart::Eject();
+ ROMPath[1][0] = '\0';
+
+ uiWindowSetTitle(MainWindow, "melonDS " MELONDS_VERSION);
+
+ for (int i = 0; i < 9; i++) uiMenuItemDisable(MenuItem_SaveStateSlot[i]);
+ for (int i = 0; i < 9; i++) uiMenuItemDisable(MenuItem_LoadStateSlot[i]);
+ uiMenuItemDisable(MenuItem_UndoStateLoad);
+
+ uiMenuItemDisable(MenuItem_Pause);
+ uiMenuItemDisable(MenuItem_Reset);
+ uiMenuItemDisable(MenuItem_Stop);
+ uiMenuItemSetChecked(MenuItem_Pause, 0);
+
+ uiAreaQueueRedrawAll(MainDrawArea);
+
+ SPU::DrainOutput();
+ SDL_PauseAudioDevice(AudioDevice, 1);
+ SDL_PauseAudioDevice(MicDevice, 1);
+
+ OSD::AddMessage(0xFFC040, "Shutdown");
+}
+
+void SetupSRAMPath(int slot)
+{
+ strncpy(SRAMPath[slot], ROMPath[slot], 1023);
+ SRAMPath[slot][1023] = '\0';
+ strncpy(SRAMPath[slot] + strlen(ROMPath[slot]) - 3, "sav", 3);
+}
+
+void TryLoadROM(char* file, int slot, int prevstatus)
+{
+ char oldpath[1024];
+ char oldsram[1024];
+ strncpy(oldpath, ROMPath[slot], 1024);
+ strncpy(oldsram, SRAMPath[slot], 1024);
+
+ strncpy(ROMPath[slot], file, 1023);
+ ROMPath[slot][1023] = '\0';
+
+ SetupSRAMPath(0);
+ SetupSRAMPath(1);
+
+ if (slot == 0 && NDS::LoadROM(ROMPath[slot], SRAMPath[slot], Config::DirectBoot))
+ {
+ SavestateLoaded = false;
+ uiMenuItemDisable(MenuItem_UndoStateLoad);
+
+ // Reload the inserted GBA cartridge (if any)
+ if (ROMPath[1][0] != '\0') NDS::LoadGBAROM(ROMPath[1], SRAMPath[1]);
+
+ strncpy(PrevSRAMPath[slot], SRAMPath[slot], 1024); // safety
+ Run();
+ }
+ else if (slot == 1 && NDS::LoadGBAROM(ROMPath[slot], SRAMPath[slot]))
+ {
+ SavestateLoaded = false;
+ uiMenuItemDisable(MenuItem_UndoStateLoad);
+
+ strncpy(PrevSRAMPath[slot], SRAMPath[slot], 1024); // safety
+ if (RunningSomething) Run(); // do not start just from a GBA cart
+ }
+ else
+ {
+ uiMsgBoxError(MainWindow,
+ "Failed to load the ROM",
+ "Make sure the file can be accessed and isn't opened in another application.");
+
+ strncpy(ROMPath[slot], oldpath, 1024);
+ strncpy(SRAMPath[slot], oldsram, 1024);
+ EmuRunning = prevstatus;
+ }
+}
+
+
+// SAVESTATE TODO
+// * configurable paths. not everyone wants their ROM directory to be polluted, I guess.
+
+void GetSavestateName(int slot, char* filename, int len)
+{
+ int pos;
+
+ if (ROMPath[0][0] == '\0') // running firmware, no ROM
+ {
+ strcpy(filename, "firmware");
+ pos = 8;
+ }
+ else
+ {
+ int l = strlen(ROMPath[0]);
+ pos = l;
+ while (ROMPath[0][pos] != '.' && pos > 0) pos--;
+ if (pos == 0) pos = l;
+
+ // avoid buffer overflow. shoddy
+ if (pos > len-5) pos = len-5;
+
+ strncpy(&filename[0], ROMPath[0], pos);
+ }
+ strcpy(&filename[pos], ".ml");
+ filename[pos+3] = '0'+slot;
+ filename[pos+4] = '\0';
+}
+
+void LoadState(int slot)
+{
+ int prevstatus = EmuRunning;
+ EmuRunning = 2;
+ while (EmuStatus != 2);
+
+ char filename[1024];
+
+ if (slot > 0)
+ {
+ GetSavestateName(slot, filename, 1024);
+ }
+ else
+ {
+ char* file = uiOpenFile(MainWindow, "melonDS savestate (any)|*.ml1;*.ml2;*.ml3;*.ml4;*.ml5;*.ml6;*.ml7;*.ml8;*.mln", Config::LastROMFolder);
+ if (!file)
+ {
+ EmuRunning = prevstatus;
+ return;
+ }
+
+ strncpy(filename, file, 1023);
+ filename[1023] = '\0';
+ uiFreeText(file);
+ }
+
+ 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);
+
+ EmuRunning = prevstatus;
+ return;
+ }
+
+ u32 oldGBACartCRC = GBACart::CartCRC;
+
+ // backup
+ Savestate* backup = new Savestate("timewarp.mln", true);
+ NDS::DoSavestate(backup);
+ delete backup;
+
+ bool failed = false;
+
+ Savestate* state = new Savestate(filename, false);
+ if (state->Error)
+ {
+ delete state;
+
+ uiMsgBoxError(MainWindow, "Error", "Could not load savestate file.");
+
+ // current state might be crapoed, so restore from sane backup
+ state = new Savestate("timewarp.mln", false);
+ failed = true;
+ }
+
+ NDS::DoSavestate(state);
+ delete state;
+
+ if (!failed)
+ {
+ if (Config::SavestateRelocSRAM && ROMPath[0][0]!='\0')
+ {
+ strncpy(PrevSRAMPath[0], SRAMPath[0], 1024);
+
+ strncpy(SRAMPath[0], filename, 1019);
+ int len = strlen(SRAMPath[0]);
+ strcpy(&SRAMPath[0][len], ".sav");
+ SRAMPath[0][len+4] = '\0';
+
+ NDS::RelocateSave(SRAMPath[0], false);
+ }
+
+ bool loadedPartialGBAROM = false;
+
+ // in case we have a GBA cart inserted, and the GBA ROM changes
+ // due to having loaded a save state, we do not want to reload
+ // the previous cartridge on reset, or commit writes to any
+ // loaded save file. therefore, their paths are "nulled".
+ if (GBACart::CartInserted && GBACart::CartCRC != oldGBACartCRC)
+ {
+ ROMPath[1][0] = '\0';
+ SRAMPath[1][0] = '\0';
+ loadedPartialGBAROM = true;
+ }
+
+ char msg[64];
+ if (slot > 0) sprintf(msg, "State loaded from slot %d%s",
+ slot, loadedPartialGBAROM ? " (GBA ROM header only)" : "");
+ else sprintf(msg, "State loaded from file%s",
+ loadedPartialGBAROM ? " (GBA ROM header only)" : "");
+ OSD::AddMessage(0, msg);
+
+ SavestateLoaded = true;
+ uiMenuItemEnable(MenuItem_UndoStateLoad);
+ }
+
+ EmuRunning = prevstatus;
+}
+
+void SaveState(int slot)
+{
+ int prevstatus = EmuRunning;
+ EmuRunning = 2;
+ while (EmuStatus != 2);
+
+ char filename[1024];
+
+ if (slot > 0)
+ {
+ GetSavestateName(slot, filename, 1024);
+ }
+ else
+ {
+ char* file = uiSaveFile(MainWindow, "melonDS savestate (*.mln)|*.mln", Config::LastROMFolder);
+ if (!file)
+ {
+ EmuRunning = prevstatus;
+ return;
+ }
+
+ strncpy(filename, file, 1023);
+ filename[1023] = '\0';
+ uiFreeText(file);
+ }
+
+ Savestate* state = new Savestate(filename, true);
+ if (state->Error)
+ {
+ delete state;
+
+ uiMsgBoxError(MainWindow, "Error", "Could not save state.");
+ }
+ else
+ {
+ NDS::DoSavestate(state);
+ delete state;
+
+ if (slot > 0)
+ uiMenuItemEnable(MenuItem_LoadStateSlot[slot-1]);
+
+ if (Config::SavestateRelocSRAM && ROMPath[0][0]!='\0')
+ {
+ strncpy(SRAMPath[0], filename, 1019);
+ int len = strlen(SRAMPath[0]);
+ strcpy(&SRAMPath[0][len], ".sav");
+ SRAMPath[0][len+4] = '\0';
+
+ NDS::RelocateSave(SRAMPath[0], true);
+ }
+ }
+
+ char msg[64];
+ if (slot > 0) sprintf(msg, "State saved to slot %d", slot);
+ else sprintf(msg, "State saved to file");
+ OSD::AddMessage(0, msg);
+
+ EmuRunning = prevstatus;
+}
+
+void UndoStateLoad()
+{
+ if (!SavestateLoaded) return;
+
+ int prevstatus = EmuRunning;
+ EmuRunning = 2;
+ while (EmuStatus != 2);
+
+ // pray that this works
+ // what do we do if it doesn't???
+ // but it should work.
+ Savestate* backup = new Savestate("timewarp.mln", false);
+ NDS::DoSavestate(backup);
+ delete backup;
+
+ if (ROMPath[0][0]!='\0')
+ {
+ strncpy(SRAMPath[0], PrevSRAMPath[0], 1024);
+ NDS::RelocateSave(SRAMPath[0], false);
+ }
+
+ OSD::AddMessage(0, "State load undone");
+
+ EmuRunning = prevstatus;
+}
+
+
+void CloseAllDialogs()
+{
+ DlgAudioSettings::Close();
+ DlgEmuSettings::Close();
+ DlgInputConfig::Close(0);
+ DlgInputConfig::Close(1);
+ DlgVideoSettings::Close();
+ DlgWifiSettings::Close();
+}
+
+
+int OnCloseWindow(uiWindow* window, void* blarg)
+{
+ EmuRunning = 3;
+ while (EmuStatus != 3);
+
+ CloseAllDialogs();
+ StopEmuThread();
+ uiQuit();
+ return 1;
+}
+
+void OnDropFile(uiWindow* window, char* file, void* blarg)
+{
+ char* ext = &file[strlen(file)-3];
+ int prevstatus = EmuRunning;
+
+ if (!strcasecmp(ext, "nds") || !strcasecmp(ext, "srl"))
+ {
+ if (RunningSomething)
+ {
+ EmuRunning = 2;
+ while (EmuStatus != 2);
+ }
+
+ TryLoadROM(file, 0, prevstatus);
+ }
+ else if (!strcasecmp(ext, "gba"))
+ {
+ TryLoadROM(file, 1, prevstatus);
+ }
+}
+
+void OnGetFocus(uiWindow* window, void* blarg)
+{
+ uiControlSetFocus(uiControl(MainDrawArea));
+}
+
+void OnLoseFocus(uiWindow* window, void* blarg)
+{
+ // TODO: shit here?
+}
+
+void OnCloseByMenu(uiMenuItem* item, uiWindow* window, void* blarg)
+{
+ EmuRunning = 3;
+ while (EmuStatus != 3);
+
+ CloseAllDialogs();
+ StopEmuThread();
+ DestroyMainWindow();
+ uiQuit();
+}
+
+void OnOpenFile(uiMenuItem* item, uiWindow* window, void* blarg)
+{
+ int prevstatus = EmuRunning;
+ EmuRunning = 2;
+ while (EmuStatus != 2);
+
+ char* file = uiOpenFile(window, "DS ROM (*.nds)|*.nds;*.srl|GBA ROM (*.gba)|*.gba|Any file|*.*", Config::LastROMFolder);
+ if (!file)
+ {
+ EmuRunning = prevstatus;
+ return;
+ }
+
+ 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];
+
+ if (!strcasecmp(ext, "gba"))
+ {
+ TryLoadROM(file, 1, prevstatus);
+ }
+ else
+ {
+ TryLoadROM(file, 0, prevstatus);
+ }
+
+ uiFreeText(file);
+}
+
+void OnSaveState(uiMenuItem* item, uiWindow* window, void* param)
+{
+ int slot = *(int*)param;
+ SaveState(slot);
+}
+
+void OnLoadState(uiMenuItem* item, uiWindow* window, void* param)
+{
+ int slot = *(int*)param;
+ LoadState(slot);
+}
+
+void OnUndoStateLoad(uiMenuItem* item, uiWindow* window, void* param)
+{
+ UndoStateLoad();
+}
+
+void OnRun(uiMenuItem* item, uiWindow* window, void* blarg)
+{
+ if (!RunningSomething)
+ {
+ ROMPath[0][0] = '\0';
+ NDS::LoadBIOS();
+
+ if (ROMPath[1][0] != '\0')
+ {
+ SetupSRAMPath(1);
+ NDS::LoadGBAROM(ROMPath[1], SRAMPath[1]);
+ }
+ }
+
+ Run();
+}
+
+void OnPause(uiMenuItem* item, uiWindow* window, void* blarg)
+{
+ TogglePause(NULL);
+}
+
+void OnReset(uiMenuItem* item, uiWindow* window, void* blarg)
+{
+ Reset(NULL);
+}
+
+void OnStop(uiMenuItem* item, uiWindow* window, void* blarg)
+{
+ if (!RunningSomething) return;
+
+ Stop(false);
+}
+
+void OnOpenEmuSettings(uiMenuItem* item, uiWindow* window, void* blarg)
+{
+ DlgEmuSettings::Open();
+}
+
+void OnOpenInputConfig(uiMenuItem* item, uiWindow* window, void* blarg)
+{
+ DlgInputConfig::Open(0);
+}
+
+void OnOpenHotkeyConfig(uiMenuItem* item, uiWindow* window, void* blarg)
+{
+ DlgInputConfig::Open(1);
+}
+
+void OnOpenVideoSettings(uiMenuItem* item, uiWindow* window, void* blarg)
+{
+ DlgVideoSettings::Open();
+}
+
+void OnOpenAudioSettings(uiMenuItem* item, uiWindow* window, void* blarg)
+{
+ DlgAudioSettings::Open();
+}
+
+void OnOpenWifiSettings(uiMenuItem* item, uiWindow* window, void* blarg)
+{
+ DlgWifiSettings::Open();
+}
+
+
+void OnSetSavestateSRAMReloc(uiMenuItem* item, uiWindow* window, void* param)
+{
+ Config::SavestateRelocSRAM = uiMenuItemChecked(item) ? 1:0;
+}
+
+
+void EnsureProperMinSize()
+{
+ bool isHori = (ScreenRotation == 1 || ScreenRotation == 3);
+
+ int w0 = 256;
+ int h0 = 192;
+ int w1 = 256;
+ int h1 = 192;
+
+ if (ScreenLayout == 0) // natural
+ {
+ if (isHori)
+ SetMinSize(h0+ScreenGap+h1, std::max(w0,w1));
+ else
+ SetMinSize(std::max(w0,w1), h0+ScreenGap+h1);
+ }
+ else if (ScreenLayout == 1) // vertical
+ {
+ if (isHori)
+ SetMinSize(std::max(h0,h1), w0+ScreenGap+w1);
+ else
+ SetMinSize(std::max(w0,w1), h0+ScreenGap+h1);
+ }
+ else // horizontal
+ {
+ if (isHori)
+ SetMinSize(h0+ScreenGap+h1, std::max(w0,w1));
+ else
+ SetMinSize(w0+ScreenGap+w1, std::max(h0,h1));
+ }
+}
+
+void OnSetScreenSize(uiMenuItem* item, uiWindow* window, void* param)
+{
+ int factor = *(int*)param;
+ bool isHori = (ScreenRotation == 1 || ScreenRotation == 3);
+
+ int w = 256*factor;
+ int h = 192*factor;
+
+ // FIXME
+
+ if (ScreenLayout == 0) // natural
+ {
+ if (isHori)
+ uiWindowSetContentSize(window, (h*2)+ScreenGap, w);
+ else
+ uiWindowSetContentSize(window, w, (h*2)+ScreenGap);
+ }
+ else if (ScreenLayout == 1) // vertical
+ {
+ if (isHori)
+ uiWindowSetContentSize(window, h, (w*2)+ScreenGap);
+ else
+ uiWindowSetContentSize(window, w, (h*2)+ScreenGap);
+ }
+ else // horizontal
+ {
+ if (isHori)
+ uiWindowSetContentSize(window, (h*2)+ScreenGap, w);
+ else
+ uiWindowSetContentSize(window, (w*2)+ScreenGap, h);
+ }
+}
+
+void OnSetScreenRotation(uiMenuItem* item, uiWindow* window, void* param)
+{
+ int rot = *(int*)param;
+
+ int oldrot = ScreenRotation;
+ ScreenRotation = rot;
+
+ int w, h;
+ uiWindowContentSize(window, &w, &h);
+
+ bool isHori = (rot == 1 || rot == 3);
+ bool wasHori = (oldrot == 1 || oldrot == 3);
+
+ EnsureProperMinSize();
+
+ if (ScreenLayout == 0) // natural
+ {
+ if (isHori ^ wasHori)
+ {
+ int blarg = h;
+ h = w;
+ w = blarg;
+
+ uiWindowSetContentSize(window, w, h);
+ }
+ }
+
+ SetupScreenRects(w, h);
+
+ for (int i = 0; i < 4; i++)
+ uiMenuItemSetChecked(MenuItem_ScreenRot[i], i==ScreenRotation);
+}
+
+void OnSetScreenGap(uiMenuItem* item, uiWindow* window, void* param)
+{
+ int gap = *(int*)param;
+
+ //int oldgap = ScreenGap;
+ ScreenGap = gap;
+
+ EnsureProperMinSize();
+ SetupScreenRects(WindowWidth, WindowHeight);
+
+ for (int i = 0; i < 6; i++)
+ uiMenuItemSetChecked(MenuItem_ScreenGap[i], kScreenGap[i]==ScreenGap);
+}
+
+void OnSetScreenLayout(uiMenuItem* item, uiWindow* window, void* param)
+{
+ int layout = *(int*)param;
+ ScreenLayout = layout;
+
+ EnsureProperMinSize();
+ SetupScreenRects(WindowWidth, WindowHeight);
+
+ for (int i = 0; i < 3; i++)
+ uiMenuItemSetChecked(MenuItem_ScreenLayout[i], i==ScreenLayout);
+}
+
+void OnSetScreenSizing(uiMenuItem* item, uiWindow* window, void* param)
+{
+ int sizing = *(int*)param;
+ ScreenSizing = sizing;
+
+ SetupScreenRects(WindowWidth, WindowHeight);
+
+ for (int i = 0; i < 4; i++)
+ uiMenuItemSetChecked(MenuItem_ScreenSizing[i], i==ScreenSizing);
+}
+
+void OnSetScreenFiltering(uiMenuItem* item, uiWindow* window, void* blarg)
+{
+ int chk = uiMenuItemChecked(item);
+ if (chk != 0) Config::ScreenFilter = 1;
+ else Config::ScreenFilter = 0;
+}
+
+void OnSetLimitFPS(uiMenuItem* item, uiWindow* window, void* blarg)
+{
+ int chk = uiMenuItemChecked(item);
+ if (chk != 0) Config::LimitFPS = true;
+ else Config::LimitFPS = false;
+}
+
+void OnSetAudioSync(uiMenuItem* item, uiWindow* window, void* blarg)
+{
+ int chk = uiMenuItemChecked(item);
+ if (chk != 0) Config::AudioSync = true;
+ else Config::AudioSync = false;
+}
+
+void OnSetShowOSD(uiMenuItem* item, uiWindow* window, void* blarg)
+{
+ int chk = uiMenuItemChecked(item);
+ if (chk != 0) Config::ShowOSD = true;
+ else Config::ShowOSD = false;
+}
+
+void ApplyNewSettings(int type)
+{
+#ifdef JIT_ENABLED
+ if (type == 4)
+ {
+ Reset(NULL);
+ return;
+ }
+#endif
+
+ if (!RunningSomething)
+ {
+ if (type == 1) return;
+ }
+
+ int prevstatus = EmuRunning;
+ EmuRunning = 3;
+ while (EmuStatus != 3);
+
+ if (type == 0) // 3D renderer settings
+ {
+ if (Screen_UseGL) uiGLMakeContextCurrent(GLContext);
+ GPU3D::UpdateRendererConfig();
+ if (Screen_UseGL) uiGLMakeContextCurrent(NULL);
+
+ GL_3DScale = Config::GL_ScaleFactor; // dorp
+ GL_ScreenSizeDirty = true;
+ }
+ else if (type == 1) // wifi settings
+ {
+ if (Wifi::MPInited)
+ {
+ Platform::MP_DeInit();
+ Platform::MP_Init();
+ }
+
+ Platform::LAN_DeInit();
+ Platform::LAN_Init();
+ }
+ else if (type == 2) // video output method
+ {
+ bool usegl = Config::ScreenUseGL || (Config::_3DRenderer != 0);
+ if (usegl != Screen_UseGL)
+ {
+ if (Screen_UseGL) uiGLMakeContextCurrent(GLContext);
+ GPU3D::DeInitRenderer();
+ OSD::DeInit(Screen_UseGL);
+ if (Screen_UseGL) uiGLMakeContextCurrent(NULL);
+
+ Screen_UseGL = usegl;
+ RecreateMainWindow(usegl);
+
+ if (Screen_UseGL) uiGLMakeContextCurrent(GLContext);
+ GPU3D::InitRenderer(Screen_UseGL);
+ if (Screen_UseGL) uiGLMakeContextCurrent(NULL);
+ }
+ }
+ else if (type == 3) // 3D renderer
+ {
+ if (Screen_UseGL) uiGLMakeContextCurrent(GLContext);
+ GPU3D::DeInitRenderer();
+ GPU3D::InitRenderer(Screen_UseGL);
+ if (Screen_UseGL) uiGLMakeContextCurrent(NULL);
+ }
+ EmuRunning = prevstatus;
+}
+
+
+void CreateMainWindowMenu()
+{
+ uiMenu* menu;
+ uiMenuItem* menuitem;
+
+ menu = uiNewMenu("File");
+ menuitem = uiMenuAppendItem(menu, "Open ROM...");
+ uiMenuItemOnClicked(menuitem, OnOpenFile, NULL);
+ uiMenuAppendSeparator(menu);
+ {
+ uiMenu* submenu = uiNewMenu("Save state");
+
+ for (int i = 0; i < 9; i++)
+ {
+ char name[32];
+ if (i < 8)
+ sprintf(name, "%d\tShift+F%d", kSavestateNum[i], kSavestateNum[i]);
+ else
+ strcpy(name, "File...\tShift+F9");
+
+ uiMenuItem* ssitem = uiMenuAppendItem(submenu, name);
+ uiMenuItemOnClicked(ssitem, OnSaveState, (void*)&kSavestateNum[i]);
+
+ MenuItem_SaveStateSlot[i] = ssitem;
+ }
+
+ MenuItem_SaveState = uiMenuAppendSubmenu(menu, submenu);
+ }
+ {
+ uiMenu* submenu = uiNewMenu("Load state");
+
+ for (int i = 0; i < 9; i++)
+ {
+ char name[32];
+ if (i < 8)
+ sprintf(name, "%d\tF%d", kSavestateNum[i], kSavestateNum[i]);
+ else
+ strcpy(name, "File...\tF9");
+
+ uiMenuItem* ssitem = uiMenuAppendItem(submenu, name);
+ uiMenuItemOnClicked(ssitem, OnLoadState, (void*)&kSavestateNum[i]);
+
+ MenuItem_LoadStateSlot[i] = ssitem;
+ }
+
+ MenuItem_LoadState = uiMenuAppendSubmenu(menu, submenu);
+ }
+ menuitem = uiMenuAppendItem(menu, "Undo state load\tF12");
+ uiMenuItemOnClicked(menuitem, OnUndoStateLoad, NULL);
+ MenuItem_UndoStateLoad = menuitem;
+ uiMenuAppendSeparator(menu);
+ menuitem = uiMenuAppendItem(menu, "Quit");
+ uiMenuItemOnClicked(menuitem, OnCloseByMenu, NULL);
+
+ menu = uiNewMenu("System");
+ menuitem = uiMenuAppendItem(menu, "Run");
+ uiMenuItemOnClicked(menuitem, OnRun, NULL);
+ menuitem = uiMenuAppendCheckItem(menu, "Pause");
+ uiMenuItemOnClicked(menuitem, OnPause, NULL);
+ MenuItem_Pause = menuitem;
+ uiMenuAppendSeparator(menu);
+ menuitem = uiMenuAppendItem(menu, "Reset");
+ uiMenuItemOnClicked(menuitem, OnReset, NULL);
+ MenuItem_Reset = menuitem;
+ menuitem = uiMenuAppendItem(menu, "Stop");
+ uiMenuItemOnClicked(menuitem, OnStop, NULL);
+ MenuItem_Stop = menuitem;
+
+ menu = uiNewMenu("Config");
+ {
+ menuitem = uiMenuAppendItem(menu, "Emu settings");
+ uiMenuItemOnClicked(menuitem, OnOpenEmuSettings, NULL);
+ menuitem = uiMenuAppendItem(menu, "Input config");
+ uiMenuItemOnClicked(menuitem, OnOpenInputConfig, NULL);
+ menuitem = uiMenuAppendItem(menu, "Hotkey config");
+ uiMenuItemOnClicked(menuitem, OnOpenHotkeyConfig, NULL);
+ menuitem = uiMenuAppendItem(menu, "Video settings");
+ uiMenuItemOnClicked(menuitem, OnOpenVideoSettings, NULL);
+ menuitem = uiMenuAppendItem(menu, "Audio settings");
+ uiMenuItemOnClicked(menuitem, OnOpenAudioSettings, NULL);
+ menuitem = uiMenuAppendItem(menu, "Wifi settings");
+ uiMenuItemOnClicked(menuitem, OnOpenWifiSettings, NULL);
+ }
+ uiMenuAppendSeparator(menu);
+ {
+ uiMenu* submenu = uiNewMenu("Savestate settings");
+
+ MenuItem_SavestateSRAMReloc = uiMenuAppendCheckItem(submenu, "Separate savefiles");
+ uiMenuItemOnClicked(MenuItem_SavestateSRAMReloc, OnSetSavestateSRAMReloc, NULL);
+
+ uiMenuAppendSubmenu(menu, submenu);
+ }
+ uiMenuAppendSeparator(menu);
+ {
+ uiMenu* submenu = uiNewMenu("Screen size");
+
+ for (int i = 0; i < 4; i++)
+ {
+ char name[32];
+ sprintf(name, "%dx", kScreenSize[i]);
+ uiMenuItem* item = uiMenuAppendItem(submenu, name);
+ uiMenuItemOnClicked(item, OnSetScreenSize, (void*)&kScreenSize[i]);
+ }
+
+ uiMenuAppendSubmenu(menu, submenu);
+ }
+ {
+ uiMenu* submenu = uiNewMenu("Screen rotation");
+
+ for (int i = 0; i < 4; i++)
+ {
+ char name[32];
+ sprintf(name, "%d", kScreenRot[i]*90);
+ MenuItem_ScreenRot[i] = uiMenuAppendCheckItem(submenu, name);
+ uiMenuItemOnClicked(MenuItem_ScreenRot[i], OnSetScreenRotation, (void*)&kScreenRot[i]);
+ }
+
+ uiMenuAppendSubmenu(menu, submenu);
+ }
+ {
+ uiMenu* submenu = uiNewMenu("Mid-screen gap");
+
+ //for (int i = 0; kScreenGap[i] != -1; i++)
+ for (int i = 0; i < 6; i++)
+ {
+ char name[32];
+ sprintf(name, "%d pixels", kScreenGap[i]);
+ MenuItem_ScreenGap[i] = uiMenuAppendCheckItem(submenu, name);
+ uiMenuItemOnClicked(MenuItem_ScreenGap[i], OnSetScreenGap, (void*)&kScreenGap[i]);
+ }
+
+ uiMenuAppendSubmenu(menu, submenu);
+ }
+ {
+ uiMenu* submenu = uiNewMenu("Screen layout");
+
+ MenuItem_ScreenLayout[0] = uiMenuAppendCheckItem(submenu, "Natural");
+ uiMenuItemOnClicked(MenuItem_ScreenLayout[0], OnSetScreenLayout, (void*)&kScreenLayout[0]);
+ MenuItem_ScreenLayout[1] = uiMenuAppendCheckItem(submenu, "Vertical");
+ uiMenuItemOnClicked(MenuItem_ScreenLayout[1], OnSetScreenLayout, (void*)&kScreenLayout[1]);
+ MenuItem_ScreenLayout[2] = uiMenuAppendCheckItem(submenu, "Horizontal");
+ uiMenuItemOnClicked(MenuItem_ScreenLayout[2], OnSetScreenLayout, (void*)&kScreenLayout[2]);
+
+ uiMenuAppendSubmenu(menu, submenu);
+ }
+ {
+ uiMenu* submenu = uiNewMenu("Screen sizing");
+
+ MenuItem_ScreenSizing[0] = uiMenuAppendCheckItem(submenu, "Even");
+ uiMenuItemOnClicked(MenuItem_ScreenSizing[0], OnSetScreenSizing, (void*)&kScreenSizing[0]);
+ MenuItem_ScreenSizing[1] = uiMenuAppendCheckItem(submenu, "Emphasize top");
+ uiMenuItemOnClicked(MenuItem_ScreenSizing[1], OnSetScreenSizing, (void*)&kScreenSizing[1]);
+ MenuItem_ScreenSizing[2] = uiMenuAppendCheckItem(submenu, "Emphasize bottom");
+ uiMenuItemOnClicked(MenuItem_ScreenSizing[2], OnSetScreenSizing, (void*)&kScreenSizing[2]);
+ MenuItem_ScreenSizing[3] = uiMenuAppendCheckItem(submenu, "Auto");
+ uiMenuItemOnClicked(MenuItem_ScreenSizing[3], OnSetScreenSizing, (void*)&kScreenSizing[3]);
+
+ uiMenuAppendSubmenu(menu, submenu);
+ }
+
+ MenuItem_ScreenFilter = uiMenuAppendCheckItem(menu, "Screen filtering");
+ uiMenuItemOnClicked(MenuItem_ScreenFilter, OnSetScreenFiltering, NULL);
+
+ MenuItem_ShowOSD = uiMenuAppendCheckItem(menu, "Show OSD");
+ uiMenuItemOnClicked(MenuItem_ShowOSD, OnSetShowOSD, NULL);
+
+ uiMenuAppendSeparator(menu);
+
+ MenuItem_LimitFPS = uiMenuAppendCheckItem(menu, "Limit framerate");
+ uiMenuItemOnClicked(MenuItem_LimitFPS, OnSetLimitFPS, NULL);
+
+ MenuItem_AudioSync = uiMenuAppendCheckItem(menu, "Audio sync");
+ uiMenuItemOnClicked(MenuItem_AudioSync, OnSetAudioSync, NULL);
+}
+
+void CreateMainWindow(bool opengl)
+{
+ MainWindow = uiNewWindow("melonDS " MELONDS_VERSION,
+ WindowWidth, WindowHeight,
+ Config::WindowMaximized, 1, 1);
+ uiWindowOnClosing(MainWindow, OnCloseWindow, NULL);
+
+ uiWindowSetDropTarget(MainWindow, 1);
+ uiWindowOnDropFile(MainWindow, OnDropFile, NULL);
+
+ uiWindowOnGetFocus(MainWindow, OnGetFocus, NULL);
+ uiWindowOnLoseFocus(MainWindow, OnLoseFocus, NULL);
+
+ ScreenDrawInited = false;
+ bool opengl_good = opengl;
+
+ if (!opengl) MainDrawArea = uiNewArea(&MainDrawAreaHandler);
+ else MainDrawArea = uiNewGLArea(&MainDrawAreaHandler, kGLVersions);
+
+ uiWindowSetChild(MainWindow, uiControl(MainDrawArea));
+ uiControlSetMinSize(uiControl(MainDrawArea), 256, 384);
+ uiAreaSetBackgroundColor(MainDrawArea, 0, 0, 0);
+
+ uiControlShow(uiControl(MainWindow));
+ uiControlSetFocus(uiControl(MainDrawArea));
+
+ if (opengl_good)
+ {
+ GLContext = uiAreaGetGLContext(MainDrawArea);
+ if (!GLContext) opengl_good = false;
+ }
+ if (opengl_good)
+ {
+ uiGLMakeContextCurrent(GLContext);
+ uiGLSetVSync(Config::ScreenVSync);
+ if (!GLScreen_Init()) opengl_good = false;
+ if (opengl_good)
+ {
+ OpenGL_UseShaderProgram(GL_ScreenShaderOSD);
+ OSD::Init(true);
+ }
+ uiGLMakeContextCurrent(NULL);
+ }
+
+ if (opengl && !opengl_good)
+ {
+ printf("OpenGL: initialization failed\n");
+ RecreateMainWindow(false);
+ Screen_UseGL = false;
+ }
+
+ if (!opengl) OSD::Init(false);
+}
+
+void DestroyMainWindow()
+{
+ uiControlDestroy(uiControl(MainWindow));
+
+ if (ScreenBitmap[0]) uiDrawFreeBitmap(ScreenBitmap[0]);
+ if (ScreenBitmap[1]) uiDrawFreeBitmap(ScreenBitmap[1]);
+
+ ScreenBitmap[0] = NULL;
+ ScreenBitmap[1] = NULL;
+}
+
+void RecreateMainWindow(bool opengl)
+{
+ int winX, winY, maxi;
+ uiWindowPosition(MainWindow, &winX, &winY);
+ maxi = uiWindowMaximized(MainWindow);
+ DestroyMainWindow();
+ CreateMainWindow(opengl);
+ uiWindowSetPosition(MainWindow, winX, winY);
+ uiWindowSetMaximized(MainWindow, maxi);
+}
+
+
+int main(int argc, char** argv)
+{
+ srand(time(NULL));
+
+ printf("melonDS " MELONDS_VERSION "\n");
+ printf(MELONDS_URL "\n");
+
+#if defined(__WIN32__) || defined(UNIX_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)
+ {
+ EmuDirectory = new char[len+1];
+ strncpy(EmuDirectory, argv[0], len);
+ EmuDirectory[len] = '\0';
+ }
+ else
+ {
+ EmuDirectory = new char[2];
+ strcpy(EmuDirectory, ".");
+ }
+ }
+ else
+ {
+ EmuDirectory = new char[2];
+ strcpy(EmuDirectory, ".");
+ }
+#else
+ const char* confdir = g_get_user_config_dir();
+ const char* confname = "/melonDS";
+ EmuDirectory = new char[strlen(confdir) + strlen(confname) + 1];
+ strcat(EmuDirectory, confdir);
+ strcat(EmuDirectory, confname);
+#endif
+
+ // 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)
+ {
+ printf("SDL shat itself :(\n");
+ return 1;
+ }
+
+ SDL_JoystickEventState(SDL_ENABLE);
+
+ uiInitOptions ui_opt;
+ memset(&ui_opt, 0, sizeof(uiInitOptions));
+ const char* ui_err = uiInit(&ui_opt);
+ if (ui_err != NULL)
+ {
+ printf("libui shat itself :( %s\n", ui_err);
+ uiFreeInitError(ui_err);
+ return 1;
+ }
+
+ Config::Load();
+
+ if (Config::AudioVolume < 0) Config::AudioVolume = 0;
+ else if (Config::AudioVolume > 256) Config::AudioVolume = 256;
+
+ if (!Platform::LocalFileExists("bios7.bin") ||
+ !Platform::LocalFileExists("bios9.bin") ||
+ !Platform::LocalFileExists("firmware.bin"))
+ {
+#if defined(__WIN32__) || defined(UNIX_PORTABLE)
+ const char* locationName = "the directory you run melonDS from";
+#else
+ char* locationName = EmuDirectory;
+#endif
+ char msgboxtext[512];
+ sprintf(msgboxtext,
+ "One or more of the following required files don't exist or couldn't be accessed:\n\n"
+ "bios7.bin -- ARM7 BIOS\n"
+ "bios9.bin -- ARM9 BIOS\n"
+ "firmware.bin -- firmware image\n\n"
+ "Dump the files from your DS and place them in %s.\n"
+ "Make sure that the files can be accessed.",
+ locationName
+ );
+
+ uiMsgBoxError(NULL, "BIOS/Firmware not found", msgboxtext);
+
+ uiUninit();
+ SDL_Quit();
+ return 0;
+ }
+ if (!Platform::LocalFileExists("firmware.bin.bak"))
+ {
+ // verify the firmware
+ //
+ // there are dumps of an old hacked firmware floating around on the internet
+ // and those are problematic
+ // the hack predates WFC, and, due to this, any game that alters the WFC
+ // access point data will brick that firmware due to it having critical
+ // data in the same area. it has the same problem on hardware.
+ //
+ // but this should help stop users from reporting that issue over and over
+ // again, when the issue is not from melonDS but from their firmware dump.
+ //
+ // I don't know about all the firmware hacks in existence, but the one I
+ // looked at has 0x180 bytes from the header repeated at 0x3FC80, but
+ // bytes 0x0C-0x14 are different.
+
+ FILE* f = Platform::OpenLocalFile("firmware.bin", "rb");
+ u8 chk1[0x180], chk2[0x180];
+
+ fseek(f, 0, SEEK_SET);
+ fread(chk1, 1, 0x180, f);
+ fseek(f, -0x380, SEEK_END);
+ fread(chk2, 1, 0x180, f);
+
+ memset(&chk1[0x0C], 0, 8);
+ memset(&chk2[0x0C], 0, 8);
+
+ fclose(f);
+
+ if (!memcmp(chk1, chk2, 0x180))
+ {
+ uiMsgBoxError(NULL,
+ "Problematic firmware dump",
+ "You are using an old hacked firmware dump.\n"
+ "Firmware boot will stop working if you run any game that alters WFC settings.\n\n"
+ "Note that the issue is not from melonDS, it would also happen on an actual DS.");
+ }
+ }
+ {
+ 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);
+ }
+ }
+
+ CreateMainWindowMenu();
+
+ MainDrawAreaHandler.Draw = OnAreaDraw;
+ MainDrawAreaHandler.MouseEvent = OnAreaMouseEvent;
+ MainDrawAreaHandler.MouseCrossed = OnAreaMouseCrossed;
+ MainDrawAreaHandler.DragBroken = OnAreaDragBroken;
+ MainDrawAreaHandler.KeyEvent = OnAreaKeyEvent;
+ MainDrawAreaHandler.Resize = OnAreaResize;
+
+ WindowWidth = Config::WindowWidth;
+ WindowHeight = Config::WindowHeight;
+
+ Screen_UseGL = Config::ScreenUseGL || (Config::_3DRenderer != 0);
+
+ GL_3DScale = Config::GL_ScaleFactor;
+ if (GL_3DScale < 1) GL_3DScale = 1;
+ else if (GL_3DScale > 8) GL_3DScale = 8;
+
+ CreateMainWindow(Screen_UseGL);
+
+ ScreenRotation = Config::ScreenRotation;
+ ScreenGap = Config::ScreenGap;
+ ScreenLayout = Config::ScreenLayout;
+ ScreenSizing = Config::ScreenSizing;
+
+#define SANITIZE(var, min, max) if ((var < min) || (var > max)) var = 0;
+ SANITIZE(ScreenRotation, 0, 3);
+ SANITIZE(ScreenLayout, 0, 2);
+ SANITIZE(ScreenSizing, 0, 3);
+#undef SANITIZE
+
+ for (int i = 0; i < 9; i++) uiMenuItemDisable(MenuItem_SaveStateSlot[i]);
+ for (int i = 0; i < 9; i++) uiMenuItemDisable(MenuItem_LoadStateSlot[i]);
+ uiMenuItemDisable(MenuItem_UndoStateLoad);
+
+ uiMenuItemDisable(MenuItem_Pause);
+ uiMenuItemDisable(MenuItem_Reset);
+ uiMenuItemDisable(MenuItem_Stop);
+
+ uiMenuItemSetChecked(MenuItem_SavestateSRAMReloc, Config::SavestateRelocSRAM?1:0);
+
+ uiMenuItemSetChecked(MenuItem_ScreenRot[ScreenRotation], 1);
+ uiMenuItemSetChecked(MenuItem_ScreenLayout[ScreenLayout], 1);
+ uiMenuItemSetChecked(MenuItem_ScreenSizing[ScreenSizing], 1);
+
+ for (int i = 0; i < 6; i++)
+ {
+ if (ScreenGap == kScreenGap[i])
+ uiMenuItemSetChecked(MenuItem_ScreenGap[i], 1);
+ }
+
+ OnSetScreenRotation(MenuItem_ScreenRot[ScreenRotation], MainWindow, (void*)&kScreenRot[ScreenRotation]);
+
+ uiMenuItemSetChecked(MenuItem_ScreenFilter, Config::ScreenFilter==1);
+ uiMenuItemSetChecked(MenuItem_LimitFPS, Config::LimitFPS==1);
+ uiMenuItemSetChecked(MenuItem_AudioSync, Config::AudioSync==1);
+ uiMenuItemSetChecked(MenuItem_ShowOSD, Config::ShowOSD==1);
+
+#ifdef MELONCAP
+ MelonCap::Init();
+#endif // MELONCAP
+
+ 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);
+ }
+
+ memset(&whatIwant, 0, sizeof(SDL_AudioSpec));
+ whatIwant.freq = 44100;
+ whatIwant.format = AUDIO_S16LSB;
+ whatIwant.channels = 1;
+ whatIwant.samples = 1024;
+ whatIwant.callback = MicCallback;
+ MicDevice = SDL_OpenAudioDevice(NULL, 1, &whatIwant, &whatIget, 0);
+ if (!MicDevice)
+ {
+ printf("Mic init failed: %s\n", SDL_GetError());
+ MicBufferLength = 0;
+ }
+ else
+ {
+ SDL_PauseAudioDevice(MicDevice, 1);
+ }
+
+ memset(MicBuffer, 0, sizeof(MicBuffer));
+ MicBufferReadPos = 0;
+ MicBufferWritePos = 0;
+
+ MicWavBuffer = NULL;
+ if (Config::MicInputType == 3) MicLoadWav(Config::MicWavPath);
+
+ JoystickID = Config::JoystickID;
+ Joystick = NULL;
+ OpenJoystick();
+
+ EmuRunning = 2;
+ RunningSomething = false;
+ EmuThread = SDL_CreateThread(EmuThreadFunc, "melonDS magic", NULL);
+
+ if (argc > 1)
+ {
+ char* file = argv[1];
+ char* ext = &file[strlen(file)-3];
+
+ if (!strcasecmp(ext, "nds") || !strcasecmp(ext, "srl"))
+ {
+ strncpy(ROMPath[0], file, 1023);
+ ROMPath[0][1023] = '\0';
+
+ SetupSRAMPath(0);
+
+ if (NDS::LoadROM(ROMPath[0], SRAMPath[0], Config::DirectBoot))
+ Run();
+ }
+
+ if (argc > 2)
+ {
+ file = argv[2];
+ ext = &file[strlen(file)-3];
+
+ if (!strcasecmp(ext, "gba"))
+ {
+ strncpy(ROMPath[1], file, 1023);
+ ROMPath[1][1023] = '\0';
+
+ SetupSRAMPath(1);
+
+ NDS::LoadGBAROM(ROMPath[1], SRAMPath[1]);
+ }
+ }
+ }
+
+ uiMain();
+
+ if (Joystick) SDL_JoystickClose(Joystick);
+ if (AudioDevice) SDL_CloseAudioDevice(AudioDevice);
+ if (MicDevice) SDL_CloseAudioDevice(MicDevice);
+
+ SDL_DestroyCond(AudioSync);
+ SDL_DestroyMutex(AudioSyncLock);
+
+ if (MicWavBuffer) delete[] MicWavBuffer;
+
+#ifdef MELONCAP
+ MelonCap::DeInit();
+#endif // MELONCAP
+
+ if (ScreenBitmap[0]) uiDrawFreeBitmap(ScreenBitmap[0]);
+ if (ScreenBitmap[1]) uiDrawFreeBitmap(ScreenBitmap[1]);
+
+ Config::ScreenRotation = ScreenRotation;
+ Config::ScreenGap = ScreenGap;
+ Config::ScreenLayout = ScreenLayout;
+ Config::ScreenSizing = ScreenSizing;
+
+ Config::Save();
+
+ uiUninit();
+ SDL_Quit();
+ delete[] EmuDirectory;
+ return 0;
+}
+
+#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++)
+ {
+ int len = WideCharToMultiByte(CP_UTF8, 0, argv_w[i], -1, NULL, 0, NULL, NULL);
+ if (len < 1) return NULL;
+ 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 (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