1561 lines
41 KiB
C#
1561 lines
41 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Data;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
|
|
using BizHawk.Common;
|
|
using BizHawk.Common.IOExtensions;
|
|
using BizHawk.Client.EmuHawk;
|
|
using BizHawk.Bizware.BizwareGL;
|
|
using BizHawk.Emulation.Common;
|
|
using BizHawk.Emulation.Common.IEmulatorExtensions;
|
|
using BizHawk.Emulation.Cores.Nintendo.NES;
|
|
using BizHawk.Client.Common;
|
|
using BizHawk.Client.MultiHawk.ToolExtensions;
|
|
|
|
namespace BizHawk.Client.MultiHawk
|
|
{
|
|
public partial class Mainform : Form
|
|
{
|
|
static Mainform()
|
|
{
|
|
// If this isnt here, then our assemblyresolving hacks wont work due to the check for MainForm.INTERIM
|
|
// its.. weird. dont ask.
|
|
}
|
|
|
|
public Mainform(string[] args)
|
|
{
|
|
GLManager.CreateInstance(GlobalWin.IGL_GL);
|
|
|
|
InitializeComponent();
|
|
_throttle = new BizHawk.Client.EmuHawk.Throttle();
|
|
_inputManager = new InputManager(this);
|
|
Global.Config = ConfigService.Load<Config>(PathManager.DefaultIniPath);
|
|
Global.Config.DispFixAspectRatio = false; // TODO: don't hardcode this
|
|
Global.Config.ResolveDefaults();
|
|
GlobalWin.MainForm = this;
|
|
|
|
Global.ControllerInputCoalescer = new ControllerInputCoalescer();
|
|
Global.FirmwareManager = new FirmwareManager();
|
|
Global.MovieSession = new MovieSession
|
|
{
|
|
Movie = MovieService.DefaultInstance,
|
|
MovieControllerAdapter = MovieService.DefaultInstance.LogGeneratorInstance().MovieControllerAdapter,
|
|
MessageCallback = AddMessage,
|
|
|
|
AskYesNoCallback = StateErrorAskUser,
|
|
PauseCallback = PauseEmulator,
|
|
ModeChangedCallback = SetMainformMovieInfo
|
|
};
|
|
|
|
new AutoResetEvent(false);
|
|
// TODO
|
|
//Icon = Properties.Resources.logo;
|
|
Global.Game = GameInfo.NullInstance;
|
|
|
|
// In order to allow late construction of this database, we hook up a delegate here to dearchive the data and provide it on demand
|
|
// we could background thread this later instead if we wanted to be real clever
|
|
NES.BootGodDB.GetDatabaseBytes = () =>
|
|
{
|
|
string xmlPath = Path.Combine(PathManager.GetExeDirectoryAbsolute(), "gamedb", "NesCarts.xml");
|
|
string x7zPath = Path.Combine(PathManager.GetExeDirectoryAbsolute(), "gamedb", "NesCarts.7z");
|
|
bool loadXml = File.Exists(xmlPath);
|
|
using (var NesCartFile = new HawkFile(loadXml ? xmlPath : x7zPath))
|
|
{
|
|
if (!loadXml) { NesCartFile.BindFirst(); }
|
|
return NesCartFile
|
|
.GetStream()
|
|
.ReadAllBytes();
|
|
}
|
|
};
|
|
|
|
Database.LoadDatabase(Path.Combine(PathManager.GetExeDirectoryAbsolute(), "gamedb", "gamedb.txt"));
|
|
|
|
Input.Initialize(this.Handle);
|
|
InitControls();
|
|
|
|
// TODO
|
|
//CoreFileProvider.SyncCoreCommInputSignals();
|
|
|
|
Global.ActiveController = new Controller(NullController.Instance.Definition);
|
|
Global.AutoFireController = AutofireNullControls;
|
|
Global.AutofireStickyXORAdapter.SetOnOffPatternFromConfig();
|
|
|
|
Closing += (o, e) =>
|
|
{
|
|
Global.MovieSession.Movie.Stop();
|
|
|
|
foreach (var ew in EmulatorWindows.ToList())
|
|
{
|
|
ew.ShutDown();
|
|
}
|
|
|
|
SaveConfig();
|
|
};
|
|
|
|
if (Global.Config.MainWndx != -1 && Global.Config.MainWndy != -1 && Global.Config.SaveWindowPosition)
|
|
{
|
|
Location = new Point(Global.Config.MainWndx, Global.Config.MainWndy);
|
|
}
|
|
}
|
|
|
|
// TODO: make this an actual property, set it when loading a Rom, and pass it dialogs, etc
|
|
// This is a quick hack to reduce the dependency on Global.Emulator
|
|
public IEmulator Emulator
|
|
{
|
|
get { return Global.Emulator; }
|
|
set { Global.Emulator = value; }
|
|
}
|
|
|
|
private static bool StateErrorAskUser(string title, string message)
|
|
{
|
|
var result = MessageBox.Show(
|
|
message,
|
|
title,
|
|
MessageBoxButtons.YesNo,
|
|
MessageBoxIcon.Question
|
|
);
|
|
|
|
return result == DialogResult.Yes;
|
|
}
|
|
|
|
private void Mainform_Load(object sender, EventArgs e)
|
|
{
|
|
SetMainformMovieInfo();
|
|
|
|
if (Global.Config.RecentRomSessions.AutoLoad)
|
|
{
|
|
LoadRomSessionFromRecent(Global.Config.RecentRomSessions.MostRecent);
|
|
|
|
if (Global.Config.RecentMovies.AutoLoad && !Global.Config.RecentMovies.Empty)
|
|
{
|
|
LoadMoviesFromRecent(Global.Config.RecentMovies.MostRecent);
|
|
}
|
|
}
|
|
|
|
if (Global.Config.SaveWindowPosition && Global.Config.MainWidth > 0 && Global.Config.MainHeight > 0)
|
|
{
|
|
this.Size = new Size(Global.Config.MainWidth, Global.Config.MainHeight);
|
|
|
|
}
|
|
}
|
|
|
|
public EmulatorWindowList EmulatorWindows = new EmulatorWindowList();
|
|
private AutofireController AutofireNullControls;
|
|
private bool _exit;
|
|
|
|
protected override void OnClosed(EventArgs e)
|
|
{
|
|
_exit = true;
|
|
base.OnClosed(e);
|
|
}
|
|
|
|
private void SaveConfig()
|
|
{
|
|
if (Global.Config.SaveWindowPosition)
|
|
{
|
|
if (Global.Config.MainWndx != -32000) // When minimized location is -32000, don't save this into the config file!
|
|
{
|
|
Global.Config.MainWndx = Location.X;
|
|
}
|
|
|
|
if (Global.Config.MainWndy != -32000)
|
|
{
|
|
Global.Config.MainWndy = Location.Y;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Global.Config.MainWndx = -1;
|
|
Global.Config.MainWndy = -1;
|
|
}
|
|
|
|
Global.Config.MainWidth = this.Width;
|
|
Global.Config.MainHeight = this.Height;
|
|
|
|
ConfigService.Save(PathManager.DefaultIniPath, Global.Config);
|
|
}
|
|
|
|
private void InitControls()
|
|
{
|
|
var controls = new Controller(
|
|
new ControllerDefinition
|
|
{
|
|
Name = "Emulator Frontend Controls",
|
|
BoolButtons = Global.Config.HotkeyBindings.Select(x => x.DisplayName).ToList()
|
|
});
|
|
|
|
foreach (var b in Global.Config.HotkeyBindings)
|
|
{
|
|
controls.BindMulti(b.DisplayName, b.Bindings);
|
|
}
|
|
|
|
Global.ClientControls = controls;
|
|
AutofireNullControls = new AutofireController(NullController.Instance.Definition, Emulator);
|
|
}
|
|
|
|
private void OpenRomMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
var ofd = new OpenFileDialog
|
|
{
|
|
InitialDirectory = PathManager.GetExeDirectoryAbsolute()
|
|
};
|
|
|
|
ofd.Filter = FormatFilter(
|
|
"Rom Files", "*.nes;*.fds;*.sms;*.gg;*.sg;*.pce;*.sgx;*.bin;*.smd;*.rom;*.a26;*.a78;*.lnx;*.m3u;*.cue;*.ccd;*.exe;*.gb;*.gbc;*.gba;*.gen;*.md;*.col;.int;*.smc;*.sfc;*.prg;*.d64;*.g64;*.crt;*.sgb;*.xml;*.z64;*.v64;*.n64;*.ws;*.wsc;%ARCH%",
|
|
"Music Files", "*.psf;*.sid",
|
|
"Disc Images", "*.cue;*.ccd;*.m3u",
|
|
"NES", "*.nes;*.fds;%ARCH%",
|
|
"Super NES", "*.smc;*.sfc;*.xml;%ARCH%",
|
|
"Master System", "*.sms;*.gg;*.sg;%ARCH%",
|
|
"PC Engine", "*.pce;*.sgx;*.cue;*.ccd;%ARCH%",
|
|
"TI-83", "*.rom;%ARCH%",
|
|
"Archive Files", "%ARCH%",
|
|
"Savestate", "*.state",
|
|
"Atari 2600", "*.a26;*.bin;%ARCH%",
|
|
"Atari 7800", "*.a78;*.bin;%ARCH%",
|
|
"Atari Lynx", "*.lnx;%ARCH%",
|
|
"Genesis", "*.gen;*.smd;*.bin;*.md;*.cue;*.ccd;%ARCH%",
|
|
"Gameboy", "*.gb;*.gbc;*.sgb;%ARCH%",
|
|
"Gameboy Advance", "*.gba;%ARCH%",
|
|
"Colecovision", "*.col;%ARCH%",
|
|
"Intellivision (very experimental)", "*.int;*.bin;*.rom;%ARCH%",
|
|
"PSX Executables (experimental)", "*.exe",
|
|
"PSF Playstation Sound File", "*.psf;*.minipsf",
|
|
"Commodore 64 (experimental)", "*.prg; *.d64, *.g64; *.crt;%ARCH%",
|
|
"SID Commodore 64 Music File", "*.sid;%ARCH%",
|
|
"Nintendo 64", "*.z64;*.v64;*.n64",
|
|
"WonderSwan", "*.ws;*.wsc;%ARCH%",
|
|
"All Files", "*.*");
|
|
|
|
var result = ofd.ShowDialog();
|
|
if (result != DialogResult.OK)
|
|
{
|
|
return;
|
|
}
|
|
|
|
LoadRom(ofd.FileName);
|
|
}
|
|
|
|
private readonly InputManager _inputManager;
|
|
|
|
private bool ReloadRom(EmulatorWindow ew)
|
|
{
|
|
bool deterministic = ew.Emulator.DeterministicEmulation;
|
|
string path = ew.CurrentRomPath;
|
|
|
|
var loader = new RomLoader
|
|
{
|
|
ChooseArchive = LoadArhiveChooser,
|
|
ChoosePlatform = ChoosePlatformForRom,
|
|
Deterministic = deterministic,
|
|
MessageCallback = AddMessage
|
|
};
|
|
|
|
|
|
loader.OnLoadError += ShowLoadError;
|
|
loader.OnLoadSettings += CoreSettings;
|
|
loader.OnLoadSyncSettings += CoreSyncSettings;
|
|
|
|
var nextComm = new CoreComm(ShowMessageCoreComm, AddMessage);
|
|
|
|
var result = loader.LoadRom(path, nextComm);
|
|
|
|
if (result)
|
|
{
|
|
ew.SaveRam();
|
|
ew.Emulator.Dispose();
|
|
ew.Emulator = loader.LoadedEmulator;
|
|
ew.CoreComm = nextComm;
|
|
|
|
_inputManager.SyncControls();
|
|
|
|
if (EmulatorWindows.First() == ew)
|
|
{
|
|
Emulator = ew.Emulator;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private string StripArchivePath(string path)
|
|
{
|
|
if (path.Contains("|"))
|
|
{
|
|
return path.Split('|').Last();
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
private bool LoadRom(string path)
|
|
{
|
|
bool deterministic = true;
|
|
|
|
var loader = new RomLoader
|
|
{
|
|
ChooseArchive = LoadArhiveChooser,
|
|
ChoosePlatform = ChoosePlatformForRom,
|
|
Deterministic = deterministic,
|
|
MessageCallback = AddMessage
|
|
};
|
|
|
|
|
|
loader.OnLoadError += ShowLoadError;
|
|
loader.OnLoadSettings += CoreSettings;
|
|
loader.OnLoadSyncSettings += CoreSyncSettings;
|
|
|
|
var nextComm = new CoreComm(ShowMessageCoreComm, AddMessage);
|
|
nextComm.CoreFileProvider = new CoreFileProvider(s => MessageBox.Show(s));
|
|
|
|
var result = loader.LoadRom(path, nextComm);
|
|
|
|
if (result)
|
|
{
|
|
var ew = new EmulatorWindow(this)
|
|
{
|
|
TopLevel = false,
|
|
Text = Path.GetFileNameWithoutExtension(StripArchivePath(path)),
|
|
Emulator = loader.LoadedEmulator,
|
|
GL = new Bizware.BizwareGL.Drivers.OpenTK.IGL_TK(2,0,false),
|
|
//GL = new Bizware.BizwareGL.Drivers.SlimDX.IGL_SlimDX9(),
|
|
GLManager = GLManager.Instance,
|
|
Game = loader.Game,
|
|
CurrentRomPath = loader.CanonicalFullPath
|
|
};
|
|
|
|
nextComm.ReleaseGLContext = (o) => GlobalWin.GLManager.ReleaseGLContext(o);
|
|
nextComm.RequestGLContext = (major, minor, forward) => GlobalWin.GLManager.CreateGLContext(major, minor, forward);
|
|
nextComm.ActivateGLContext = (gl) => GlobalWin.GLManager.Activate((GLManager.ContextRef)gl);
|
|
nextComm.DeactivateGLContext = () => GlobalWin.GLManager.Deactivate();
|
|
|
|
ew.CoreComm = nextComm;
|
|
ew.Init();
|
|
|
|
if (EmulatorWindows.Any())
|
|
{
|
|
// Attempt to open the window is a smart location
|
|
var last = EmulatorWindows.Last();
|
|
|
|
int x = last.Location.X + last.Width + 5;
|
|
int y = last.Location.Y;
|
|
if (x + (last.Width / 2) > Width) // If it will go too far off screen
|
|
{
|
|
y += last.Height + 5;
|
|
x = EmulatorWindows.First().Location.X;
|
|
}
|
|
|
|
ew.Location = new Point(x, y);
|
|
}
|
|
|
|
|
|
EmulatorWindows.Add(ew);
|
|
|
|
WorkspacePanel.Controls.Add(ew);
|
|
ew.Show();
|
|
|
|
Global.Config.RecentRoms.Add(loader.CanonicalFullPath);
|
|
|
|
if (EmulatorWindows.Count == 1)
|
|
{
|
|
Emulator = ew.Emulator;
|
|
ViewSubMenu.Enabled = true;
|
|
}
|
|
|
|
_inputManager.SyncControls();
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Controls whether the app generates input events. should be turned off for most modal dialogs
|
|
/// </summary>
|
|
public bool AllowInput
|
|
{
|
|
get
|
|
{
|
|
// the main form gets input
|
|
if (ActiveForm == this)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// modals that need to capture input for binding purposes get input, of course
|
|
if (ActiveForm is BizHawk.Client.EmuHawk.HotkeyConfig
|
|
|| ActiveForm is BizHawk.Client.EmuHawk.ControllerConfig
|
|
//|| ActiveForm is TAStudio
|
|
//|| ActiveForm is VirtualpadTool
|
|
)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static string FormatFilter(params string[] args)
|
|
{
|
|
var sb = new StringBuilder();
|
|
if (args.Length % 2 != 0)
|
|
{
|
|
throw new ArgumentException();
|
|
}
|
|
|
|
var num = args.Length / 2;
|
|
for (int i = 0; i < num; i++)
|
|
{
|
|
sb.AppendFormat("{0} ({1})|{1}", args[i * 2], args[i * 2 + 1]);
|
|
if (i != num - 1)
|
|
{
|
|
sb.Append('|');
|
|
}
|
|
}
|
|
|
|
var str = sb.ToString().Replace("%ARCH%", "*.zip;*.rar;*.7z;*.gz");
|
|
str = str.Replace(";", "; ");
|
|
return str;
|
|
}
|
|
|
|
public void AddMessage(string message)
|
|
{
|
|
StatusBarMessageLabel.Text = message;
|
|
}
|
|
|
|
private string ChoosePlatformForRom(RomGame rom)
|
|
{
|
|
// TODO
|
|
return null;
|
|
}
|
|
|
|
private int? LoadArhiveChooser(HawkFile file)
|
|
{
|
|
var ac = new BizHawk.Client.EmuHawk.ArchiveChooser(file);
|
|
if (ac.ShowDialog(this) == DialogResult.OK)
|
|
{
|
|
return ac.SelectedMemberIndex;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private void ShowLoadError(object sender, RomLoader.RomErrorArgs e)
|
|
{
|
|
string title = "load error";
|
|
if (e.AttemptedCoreLoad != null)
|
|
{
|
|
title = e.AttemptedCoreLoad + " load error";
|
|
}
|
|
|
|
MessageBox.Show(this, e.Message, title, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
}
|
|
|
|
private static void CoreSettings(object sender, RomLoader.SettingsLoadArgs e)
|
|
{
|
|
e.Settings = Global.Config.GetCoreSettings(e.Core);
|
|
}
|
|
|
|
private void CoreSyncSettings(object sender, RomLoader.SettingsLoadArgs e)
|
|
{
|
|
if (Global.MovieSession.QueuedMovie != null)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(Global.MovieSession.QueuedMovie.SyncSettingsJson))
|
|
{
|
|
e.Settings = ConfigService.LoadWithType(Global.MovieSession.QueuedMovie.SyncSettingsJson);
|
|
}
|
|
else
|
|
{
|
|
MessageBox.Show(
|
|
"No sync settings found, using currently configured settings for this core.",
|
|
"No sync settings found",
|
|
MessageBoxButtons.OK,
|
|
MessageBoxIcon.Warning
|
|
);
|
|
|
|
e.Settings = Global.Config.GetCoreSyncSettings(e.Core);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e.Settings = Global.Config.GetCoreSyncSettings(e.Core);
|
|
}
|
|
|
|
}
|
|
|
|
public void FlagNeedsReboot()
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
private void ShowMessageCoreComm(string message)
|
|
{
|
|
MessageBox.Show(this, message, "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
}
|
|
|
|
private static void CheckMessages()
|
|
{
|
|
Application.DoEvents();
|
|
if (ActiveForm != null)
|
|
{
|
|
BizHawk.Client.EmuHawk.ScreenSaver.ResetTimerPeriodically();
|
|
}
|
|
}
|
|
|
|
// sends an alt+mnemonic combination
|
|
private void SendAltKeyChar(char c)
|
|
{
|
|
typeof(ToolStrip).InvokeMember("ProcessMnemonicInternal", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.InvokeMethod | System.Reflection.BindingFlags.Instance, null, MainformMenu, new object[] { c });
|
|
}
|
|
|
|
// sends a simulation of a plain alt key keystroke
|
|
private void SendPlainAltKey(int lparam)
|
|
{
|
|
var m = new Message { WParam = new IntPtr(0xF100), LParam = new IntPtr(lparam), Msg = 0x0112, HWnd = Handle };
|
|
base.WndProc(ref m);
|
|
}
|
|
|
|
public void ProcessInput()
|
|
{
|
|
ControllerInputCoalescer conInput = Global.ControllerInputCoalescer as ControllerInputCoalescer;
|
|
|
|
for (; ; )
|
|
{
|
|
|
|
// loop through all available events
|
|
var ie = Input.Instance.DequeueEvent();
|
|
if (ie == null) { break; }
|
|
|
|
// look for hotkey bindings for this key
|
|
var triggers = Global.ClientControls.SearchBindings(ie.LogicalButton.ToString());
|
|
if (triggers.Count == 0)
|
|
{
|
|
// Maybe it is a system alt-key which hasnt been overridden
|
|
if (ie.EventType == Input.InputEventType.Press)
|
|
{
|
|
if (ie.LogicalButton.Alt && ie.LogicalButton.Button.Length == 1)
|
|
{
|
|
var c = ie.LogicalButton.Button.ToLower()[0];
|
|
if ((c >= 'a' && c <= 'z') || c == ' ')
|
|
{
|
|
SendAltKeyChar(c);
|
|
}
|
|
}
|
|
if (ie.LogicalButton.Alt && ie.LogicalButton.Button == "Space")
|
|
{
|
|
SendPlainAltKey(32);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool handled;
|
|
switch (Global.Config.Input_Hotkey_OverrideOptions)
|
|
{
|
|
default:
|
|
case 0: // Both allowed
|
|
conInput.Receive(ie);
|
|
|
|
handled = false;
|
|
if (ie.EventType == Input.InputEventType.Press)
|
|
{
|
|
handled = triggers.Aggregate(handled, (current, trigger) => current | CheckHotkey(trigger));
|
|
}
|
|
|
|
// hotkeys which arent handled as actions get coalesced as pollable virtual client buttons
|
|
if (!handled)
|
|
{
|
|
GlobalWin.HotkeyCoalescer.Receive(ie);
|
|
}
|
|
|
|
break;
|
|
case 1: // Input overrides Hokeys
|
|
conInput.Receive(ie);
|
|
if (!Global.ActiveController.HasBinding(ie.LogicalButton.ToString()))
|
|
{
|
|
handled = false;
|
|
if (ie.EventType == Input.InputEventType.Press)
|
|
{
|
|
handled = triggers.Aggregate(handled, (current, trigger) => current | CheckHotkey(trigger));
|
|
}
|
|
|
|
// hotkeys which arent handled as actions get coalesced as pollable virtual client buttons
|
|
if (!handled)
|
|
{
|
|
GlobalWin.HotkeyCoalescer.Receive(ie);
|
|
}
|
|
}
|
|
break;
|
|
case 2: // Hotkeys override Input
|
|
handled = false;
|
|
if (ie.EventType == Input.InputEventType.Press)
|
|
{
|
|
handled = triggers.Aggregate(handled, (current, trigger) => current | CheckHotkey(trigger));
|
|
}
|
|
|
|
// hotkeys which arent handled as actions get coalesced as pollable virtual client buttons
|
|
if (!handled)
|
|
{
|
|
GlobalWin.HotkeyCoalescer.Receive(ie);
|
|
conInput.Receive(ie);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ProgramRunLoop()
|
|
{
|
|
CheckMessages();
|
|
|
|
for (; ; )
|
|
{
|
|
Input.Instance.Update();
|
|
|
|
// handle events and dispatch as a hotkey action, or a hotkey button, or an input button
|
|
ProcessInput();
|
|
Global.ClientControls.LatchFromPhysical(GlobalWin.HotkeyCoalescer);
|
|
|
|
Global.ActiveController.LatchFromPhysical(Global.ControllerInputCoalescer);
|
|
|
|
// TODO
|
|
//Global.ActiveController.ApplyAxisConstraints(
|
|
// (Emulator is N64 && Global.Config.N64UseCircularAnalogConstraint) ? "Natural Circle" : null);
|
|
|
|
Global.ActiveController.OR_FromLogical(Global.ClickyVirtualPadController);
|
|
Global.AutoFireController.LatchFromPhysical(Global.ControllerInputCoalescer);
|
|
|
|
if (Global.ClientControls["Autohold"])
|
|
{
|
|
Global.StickyXORAdapter.MassToggleStickyState(Global.ActiveController.PressedButtons);
|
|
Global.AutofireStickyXORAdapter.MassToggleStickyState(Global.AutoFireController.PressedButtons);
|
|
}
|
|
else if (Global.ClientControls["Autofire"])
|
|
{
|
|
Global.AutofireStickyXORAdapter.MassToggleStickyState(Global.ActiveController.PressedButtons);
|
|
}
|
|
|
|
// autohold/autofire must not be affected by the following inputs
|
|
Global.ActiveController.Overrides(Global.LuaAndAdaptor);
|
|
|
|
if (EmulatorWindows.Any())
|
|
{
|
|
StepRunLoop_Core();
|
|
StepRunLoop_Throttle();
|
|
|
|
foreach (var window in EmulatorWindows)
|
|
{
|
|
window.Render();
|
|
}
|
|
}
|
|
|
|
CheckMessages();
|
|
|
|
if (_exit)
|
|
{
|
|
break;
|
|
}
|
|
|
|
Thread.Sleep(0);
|
|
}
|
|
|
|
Shutdown();
|
|
}
|
|
|
|
private void Shutdown()
|
|
{
|
|
//TODO
|
|
//if (_currAviWriter != null)
|
|
//{
|
|
// _currAviWriter.CloseFile();
|
|
// _currAviWriter = null;
|
|
//}
|
|
}
|
|
|
|
public void LoadQuickSave(string quickSlotName)
|
|
{
|
|
try
|
|
{
|
|
foreach (var window in EmulatorWindows)
|
|
{
|
|
window.LoadQuickSave(quickSlotName);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
MessageBox.Show("Could not load " + quickSlotName);
|
|
}
|
|
}
|
|
|
|
public void SaveQuickSave(string quickSlotName)
|
|
{
|
|
try
|
|
{
|
|
foreach (var window in EmulatorWindows)
|
|
{
|
|
window.SaveQuickSave(quickSlotName);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
MessageBox.Show("Could not save " + quickSlotName);
|
|
}
|
|
}
|
|
|
|
private void SelectSlot(int num)
|
|
{
|
|
Global.Config.SaveSlot = num;
|
|
UpdateStatusSlots();
|
|
}
|
|
|
|
private void UpdateStatusSlots()
|
|
{
|
|
// TODO
|
|
SaveSlotSelectedMessage();
|
|
UpdateAfterFrameChanged();
|
|
}
|
|
|
|
private void SaveSlotSelectedMessage()
|
|
{
|
|
AddMessage("Slot " + Global.Config.SaveSlot + " selected.");
|
|
}
|
|
|
|
public void TogglePause()
|
|
{
|
|
EmulatorPaused ^= true;
|
|
//SetPauseStatusbarIcon(); // TODO
|
|
}
|
|
private bool CheckHotkey(string trigger)
|
|
{
|
|
switch (trigger)
|
|
{
|
|
default:
|
|
return false;
|
|
|
|
case "Pause":
|
|
TogglePause();
|
|
break;
|
|
case "Reboot Core":
|
|
RebootCoresMenuItem_Click(null, null);
|
|
break;
|
|
case "Quick Load":
|
|
LoadQuickSave("QuickSave" + Global.Config.SaveSlot);
|
|
break;
|
|
case "Quick Save":
|
|
SaveQuickSave("QuickSave" + Global.Config.SaveSlot);
|
|
break;
|
|
|
|
// Save States
|
|
case "Save State 0":
|
|
SaveQuickSave("QuickSave0");
|
|
Global.Config.SaveSlot = 0;
|
|
UpdateStatusSlots();
|
|
break;
|
|
case "Save State 1":
|
|
SaveQuickSave("QuickSave1");
|
|
Global.Config.SaveSlot = 1;
|
|
UpdateStatusSlots();
|
|
break;
|
|
case "Save State 2":
|
|
SaveQuickSave("QuickSave2");
|
|
Global.Config.SaveSlot = 2;
|
|
UpdateStatusSlots();
|
|
break;
|
|
case "Save State 3":
|
|
SaveQuickSave("QuickSave3");
|
|
Global.Config.SaveSlot = 3;
|
|
UpdateStatusSlots();
|
|
break;
|
|
case "Save State 4":
|
|
SaveQuickSave("QuickSave4");
|
|
Global.Config.SaveSlot = 4;
|
|
UpdateStatusSlots();
|
|
break;
|
|
case "Save State 5":
|
|
SaveQuickSave("QuickSave5");
|
|
Global.Config.SaveSlot = 5;
|
|
UpdateStatusSlots();
|
|
break;
|
|
case "Save State 6":
|
|
SaveQuickSave("QuickSave6");
|
|
Global.Config.SaveSlot = 6;
|
|
UpdateStatusSlots();
|
|
break;
|
|
case "Save State 7":
|
|
SaveQuickSave("QuickSave7");
|
|
Global.Config.SaveSlot = 7;
|
|
UpdateStatusSlots();
|
|
break;
|
|
case "Save State 8":
|
|
SaveQuickSave("QuickSave8");
|
|
Global.Config.SaveSlot = 8;
|
|
UpdateStatusSlots();
|
|
break;
|
|
case "Save State 9":
|
|
SaveQuickSave("QuickSave9");
|
|
Global.Config.SaveSlot = 9;
|
|
//UpdateStatusSlots();
|
|
break;
|
|
case "Load State 0":
|
|
LoadQuickSave("QuickSave0");
|
|
Global.Config.SaveSlot = 0;
|
|
UpdateStatusSlots();
|
|
break;
|
|
case "Load State 1":
|
|
LoadQuickSave("QuickSave1");
|
|
Global.Config.SaveSlot = 1;
|
|
UpdateStatusSlots();
|
|
break;
|
|
case "Load State 2":
|
|
LoadQuickSave("QuickSave2");
|
|
Global.Config.SaveSlot = 2;
|
|
UpdateStatusSlots();
|
|
break;
|
|
case "Load State 3":
|
|
LoadQuickSave("QuickSave3");
|
|
Global.Config.SaveSlot = 3;
|
|
UpdateStatusSlots();
|
|
break;
|
|
case "Load State 4":
|
|
LoadQuickSave("QuickSave4");
|
|
Global.Config.SaveSlot = 4;
|
|
UpdateStatusSlots();
|
|
break;
|
|
case "Load State 5":
|
|
LoadQuickSave("QuickSave5");
|
|
Global.Config.SaveSlot = 5;
|
|
UpdateStatusSlots();
|
|
break;
|
|
case "Load State 6":
|
|
LoadQuickSave("QuickSave6");
|
|
Global.Config.SaveSlot = 6;
|
|
UpdateStatusSlots();
|
|
break;
|
|
case "Load State 7":
|
|
LoadQuickSave("QuickSave7");
|
|
Global.Config.SaveSlot = 7;
|
|
break;
|
|
case "Load State 8":
|
|
LoadQuickSave("QuickSave8");
|
|
Global.Config.SaveSlot = 8;
|
|
UpdateStatusSlots();
|
|
break;
|
|
case "Load State 9":
|
|
LoadQuickSave("QuickSave9");
|
|
Global.Config.SaveSlot = 9;
|
|
UpdateStatusSlots();
|
|
break;
|
|
|
|
case "Select State 0":
|
|
SelectSlot(0);
|
|
break;
|
|
case "Select State 1":
|
|
SelectSlot(1);
|
|
break;
|
|
case "Select State 2":
|
|
SelectSlot(2);
|
|
break;
|
|
case "Select State 3":
|
|
SelectSlot(3);
|
|
break;
|
|
case "Select State 4":
|
|
SelectSlot(4);
|
|
break;
|
|
case "Select State 5":
|
|
SelectSlot(5);
|
|
break;
|
|
case "Select State 6":
|
|
SelectSlot(6);
|
|
break;
|
|
case "Select State 7":
|
|
SelectSlot(7);
|
|
break;
|
|
case "Select State 8":
|
|
SelectSlot(8);
|
|
break;
|
|
case "Select State 9":
|
|
SelectSlot(9);
|
|
break;
|
|
|
|
// Movie
|
|
case "Stop Movie":
|
|
StopMovieMenuItem_Click(null, null);
|
|
break;
|
|
case "Toggle read-only":
|
|
ToggleReadonlyMenuItem_Click(null, null);
|
|
break;
|
|
// TODO
|
|
//case "Play from beginning":
|
|
// RestartMovie();
|
|
// break;
|
|
// TODO
|
|
//case "Save Movie":
|
|
// SaveMovie();
|
|
// break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool PressFrameAdvance = false;
|
|
public bool PressRewind = false;
|
|
public bool FastForward = false;
|
|
public bool TurboFastForward = false;
|
|
public bool EmulatorPaused = true;
|
|
private readonly BizHawk.Client.EmuHawk.Throttle _throttle;
|
|
//private bool _unthrottled; // TODO
|
|
private bool _runloopFrameAdvance;
|
|
private bool _runloopFrameProgress;
|
|
private long _frameAdvanceTimestamp;
|
|
private bool _runloopLastFf;
|
|
private int _runloopFps;
|
|
private long _runloopSecond;
|
|
private int _runloopLastFps;
|
|
|
|
public bool IsTurboing
|
|
{
|
|
get
|
|
{
|
|
return Global.ClientControls["Turbo"];
|
|
}
|
|
}
|
|
|
|
private void SyncThrottle()
|
|
{
|
|
// "unthrottled" = throttle was turned off with "Toggle Throttle" hotkey
|
|
// "turbo" = throttle is off due to the "Turbo" hotkey being held
|
|
// They are basically the same thing but one is a toggle and the other requires a
|
|
// hotkey to be held. There is however slightly different behavior in that turbo
|
|
// skips outputting the audio. There's also a third way which is when no throttle
|
|
// method is selected, but the clock throttle determines that by itself and
|
|
// everything appears normal here.
|
|
|
|
var fastForward = Global.ClientControls["Fast Forward"] || FastForward;
|
|
var turbo = IsTurboing;
|
|
|
|
int speedPercent = fastForward ? Global.Config.SpeedPercentAlternate : Global.Config.SpeedPercent;
|
|
|
|
Global.DisableSecondaryThrottling = /*_unthrottled || TODO */ turbo || fastForward;
|
|
|
|
// realtime throttle is never going to be so exact that using a double here is wrong
|
|
_throttle.SetCoreFps(EmulatorWindows.Master.Emulator.VsyncRate());
|
|
_throttle.signal_paused = EmulatorPaused;
|
|
_throttle.signal_unthrottle = /*_unthrottled || TODO */ turbo;
|
|
_throttle.signal_overrideSecondaryThrottle = fastForward && (Global.Config.SoundThrottle || Global.Config.VSyncThrottle || Global.Config.VSync);
|
|
_throttle.SetSpeedPercent(speedPercent);
|
|
}
|
|
|
|
private void StepRunLoop_Throttle()
|
|
{
|
|
SyncThrottle();
|
|
_throttle.signal_frameAdvance = _runloopFrameAdvance;
|
|
_throttle.signal_continuousFrameAdvancing = _runloopFrameProgress;
|
|
|
|
_throttle.Step(true, -1);
|
|
}
|
|
|
|
public void PauseEmulator()
|
|
{
|
|
EmulatorPaused = true;
|
|
//SetPauseStatusbarIcon(); // TODO
|
|
}
|
|
|
|
public void UnpauseEmulator()
|
|
{
|
|
EmulatorPaused = false;
|
|
//SetPauseStatusbarIcon(); // TODO
|
|
}
|
|
|
|
private void StepRunLoop_Core()
|
|
{
|
|
var runFrame = false;
|
|
_runloopFrameAdvance = false;
|
|
var currentTimestamp = Stopwatch.GetTimestamp();
|
|
double frameAdvanceTimestampDeltaMs = (double)(currentTimestamp - _frameAdvanceTimestamp) / Stopwatch.Frequency * 1000.0;
|
|
bool frameProgressTimeElapsed = frameAdvanceTimestampDeltaMs >= Global.Config.FrameProgressDelayMs;
|
|
|
|
if (Global.ClientControls["Frame Advance"])
|
|
{
|
|
// handle the initial trigger of a frame advance
|
|
if (_frameAdvanceTimestamp == 0)
|
|
{
|
|
PauseEmulator();
|
|
runFrame = true;
|
|
_runloopFrameAdvance = true;
|
|
_frameAdvanceTimestamp = currentTimestamp;
|
|
}
|
|
else
|
|
{
|
|
// handle the timed transition from countdown to FrameProgress
|
|
if (frameProgressTimeElapsed)
|
|
{
|
|
runFrame = true;
|
|
_runloopFrameProgress = true;
|
|
UnpauseEmulator();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// handle release of frame advance: do we need to deactivate FrameProgress?
|
|
if (_runloopFrameProgress)
|
|
{
|
|
_runloopFrameProgress = false;
|
|
PauseEmulator();
|
|
}
|
|
|
|
_frameAdvanceTimestamp = 0;
|
|
}
|
|
|
|
if (!EmulatorPaused)
|
|
{
|
|
runFrame = true;
|
|
}
|
|
|
|
if (runFrame)
|
|
{
|
|
var isFastForwarding = Global.ClientControls["Fast Forward"] || IsTurboing;
|
|
var updateFpsString = _runloopLastFf != isFastForwarding;
|
|
_runloopLastFf = isFastForwarding;
|
|
_runloopFps++;
|
|
|
|
if ((double)(currentTimestamp - _runloopSecond) / Stopwatch.Frequency >= 1.0)
|
|
{
|
|
_runloopLastFps = _runloopFps;
|
|
_runloopSecond = currentTimestamp;
|
|
_runloopFps = 0;
|
|
updateFpsString = true;
|
|
}
|
|
|
|
if (updateFpsString)
|
|
{
|
|
var fps_string = _runloopLastFps + " fps";
|
|
if (IsTurboing)
|
|
{
|
|
fps_string += " >>>>";
|
|
}
|
|
else if (isFastForwarding)
|
|
{
|
|
fps_string += " >>";
|
|
}
|
|
|
|
//GlobalWin.OSD.FPS = fps_string; // TODO
|
|
}
|
|
|
|
Global.MovieSession.HandleMovieOnFrameLoop();
|
|
|
|
foreach (var window in EmulatorWindows)
|
|
{
|
|
window.FrameAdvance();
|
|
}
|
|
|
|
PressFrameAdvance = false;
|
|
|
|
UpdateAfterFrameChanged();
|
|
}
|
|
}
|
|
|
|
private void UpdateAfterFrameChanged()
|
|
{
|
|
if (EmulatorWindows.Any())
|
|
{
|
|
string frame = EmulatorWindows.Master.Emulator.Frame.ToString();
|
|
|
|
if (Global.MovieSession.Movie.IsActive)
|
|
{
|
|
if (Global.MovieSession.Movie.IsFinished)
|
|
{
|
|
frame += string.Format(" / {0} (finished)", Global.MovieSession.Movie.FrameCount);
|
|
}
|
|
else if (Global.MovieSession.Movie.IsPlaying)
|
|
{
|
|
frame += string.Format(" / {0}", Global.MovieSession.Movie.FrameCount);
|
|
}
|
|
}
|
|
|
|
FameStatusBarLabel.Text = frame;
|
|
}
|
|
}
|
|
|
|
private void FileSubMenu_DropDownOpened(object sender, EventArgs e)
|
|
{
|
|
SaveSessionMenuItem.Enabled = !string.IsNullOrWhiteSpace(EmulatorWindows.SessionName);
|
|
SaveSessionAsMenuItem.Enabled = EmulatorWindows.Any();
|
|
}
|
|
|
|
private void LoadRomFromRecent(string rom)
|
|
{
|
|
if (!LoadRom(rom))
|
|
{
|
|
Global.Config.RecentRoms.HandleLoadError(rom);
|
|
}
|
|
}
|
|
|
|
private void MovieSubMenu_DropDownOpened(object sender, EventArgs e)
|
|
{
|
|
PlayMovieMenuItem.Enabled =
|
|
RecordMovieMenuItem.Enabled =
|
|
EmulatorWindows.Any();
|
|
|
|
StopMovieMenuItem.Enabled =
|
|
RestartMovieMenuItem.Enabled =
|
|
Global.MovieSession.Movie.IsActive;
|
|
}
|
|
|
|
private void RecordMovieMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
new RecordMovie(Emulator).ShowDialog();
|
|
UpdateMainText();
|
|
UpdateAfterFrameChanged();
|
|
}
|
|
|
|
private void PlayMovieMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
new PlayMovie().ShowDialog();
|
|
UpdateMainText();
|
|
UpdateAfterFrameChanged();
|
|
}
|
|
|
|
private void StopMovieMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
Global.MovieSession.StopMovie(true);
|
|
SetMainformMovieInfo();
|
|
UpdateMainText();
|
|
UpdateAfterFrameChanged();
|
|
//UpdateStatusSlots(); // TODO
|
|
}
|
|
|
|
private void ToggleReadonlyMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
Global.MovieSession.ReadOnly ^= true;
|
|
if (Global.MovieSession.ReadOnly)
|
|
{
|
|
AddMessage("Movie is now Read-only");
|
|
}
|
|
else
|
|
{
|
|
AddMessage("Movie is now read+write");
|
|
}
|
|
}
|
|
|
|
private void LoadMoviesFromRecent(string path)
|
|
{
|
|
if (File.Exists(path))
|
|
{
|
|
var movie = MovieService.Get(path);
|
|
Global.MovieSession.ReadOnly = true;
|
|
StartNewMovie(movie, false);
|
|
}
|
|
else
|
|
{
|
|
Global.Config.RecentMovies.HandleLoadError(path);
|
|
}
|
|
}
|
|
|
|
public void SetMainformMovieInfo()
|
|
{
|
|
if (Global.MovieSession.Movie.IsPlaying)
|
|
{
|
|
PlayRecordStatusButton.Image = Properties.Resources.Play;
|
|
PlayRecordStatusButton.ToolTipText = "Movie is in playback mode";
|
|
PlayRecordStatusButton.Visible = true;
|
|
}
|
|
else if (Global.MovieSession.Movie.IsRecording)
|
|
{
|
|
PlayRecordStatusButton.Image = Properties.Resources.RecordHS;
|
|
PlayRecordStatusButton.ToolTipText = "Movie is in record mode";
|
|
PlayRecordStatusButton.Visible = true;
|
|
}
|
|
else if (!Global.MovieSession.Movie.IsActive)
|
|
{
|
|
PlayRecordStatusButton.Image = Properties.Resources.Blank;
|
|
PlayRecordStatusButton.ToolTipText = "No movie is active";
|
|
PlayRecordStatusButton.Visible = false;
|
|
}
|
|
}
|
|
|
|
public bool StartNewMovie(IMovie movie, bool record)
|
|
{
|
|
if (movie.IsActive)
|
|
{
|
|
movie.Save();
|
|
}
|
|
|
|
try
|
|
{
|
|
Global.MovieSession.QueueNewMovie(movie, record, Emulator);
|
|
}
|
|
catch (MoviePlatformMismatchException ex)
|
|
{
|
|
MessageBox.Show(this, ex.Message, "Movie/Platform Mismatch", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
return false;
|
|
}
|
|
|
|
RebootCoresMenuItem_Click(null, null);
|
|
|
|
Emulator = EmulatorWindows.Master.Emulator;
|
|
|
|
if (Global.MovieSession.PreviousNES_InQuickNES.HasValue)
|
|
{
|
|
Global.Config.NES_InQuickNES = Global.MovieSession.PreviousNES_InQuickNES.Value;
|
|
Global.MovieSession.PreviousNES_InQuickNES = null;
|
|
}
|
|
|
|
if (Global.MovieSession.PreviousSNES_InSnes9x.HasValue)
|
|
{
|
|
Global.Config.SNES_InSnes9x = Global.MovieSession.PreviousSNES_InSnes9x.Value;
|
|
Global.MovieSession.PreviousSNES_InSnes9x = null;
|
|
}
|
|
|
|
if (Global.MovieSession.PreviousGBA_UsemGBA.HasValue)
|
|
{
|
|
Global.Config.GBA_UsemGBA = Global.MovieSession.PreviousGBA_UsemGBA.Value;
|
|
Global.MovieSession.PreviousGBA_UsemGBA = null;
|
|
}
|
|
|
|
Global.Config.RecentMovies.Add(movie.Filename);
|
|
|
|
if (EmulatorWindows.Master.Emulator.HasSavestates() && movie.StartsFromSavestate)
|
|
{
|
|
if (movie.TextSavestate != null)
|
|
{
|
|
EmulatorWindows.Master.Emulator.AsStatable().LoadStateText(new StringReader(movie.TextSavestate));
|
|
}
|
|
else
|
|
{
|
|
EmulatorWindows.Master.Emulator.AsStatable().LoadStateBinary(new BinaryReader(new MemoryStream(movie.BinarySavestate, false)));
|
|
}
|
|
|
|
foreach (var ew in EmulatorWindows)
|
|
{
|
|
ew.Emulator.ResetCounters();
|
|
}
|
|
}
|
|
|
|
Global.MovieSession.RunQueuedMovie(record);
|
|
|
|
SetMainformMovieInfo();
|
|
UpdateAfterFrameChanged();
|
|
return true;
|
|
}
|
|
|
|
private void hotkeyConfigToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
if (new BizHawk.Client.EmuHawk.HotkeyConfig().ShowDialog() == DialogResult.OK)
|
|
{
|
|
InitControls();
|
|
_inputManager.SyncControls();
|
|
}
|
|
}
|
|
|
|
private void controllerConfigToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
var controller = new BizHawk.Client.EmuHawk.ControllerConfig(EmulatorWindows.Master.Emulator.ControllerDefinition);
|
|
if (controller.ShowDialog() == DialogResult.OK)
|
|
{
|
|
InitControls();
|
|
_inputManager.SyncControls();
|
|
}
|
|
}
|
|
|
|
public void EmulatorWindowClosed(EmulatorWindow ew)
|
|
{
|
|
EmulatorWindows.Remove(ew);
|
|
WorkspacePanel.Controls.Remove(ew);
|
|
|
|
if (ew.Emulator == Emulator)
|
|
{
|
|
if (EmulatorWindows.Any())
|
|
{
|
|
Emulator = EmulatorWindows.Master.Emulator;
|
|
}
|
|
else
|
|
{
|
|
Emulator = null;
|
|
ViewSubMenu.Enabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ExitMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
Close();
|
|
}
|
|
|
|
private void saveConfigToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
SaveConfig();
|
|
AddMessage("Saved settings");
|
|
}
|
|
|
|
private void RebootCoresMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
foreach (var ew in EmulatorWindows)
|
|
{
|
|
ReloadRom(ew);
|
|
}
|
|
|
|
AddMessage("Rebooted all cores");
|
|
}
|
|
|
|
private void SaveSessionMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(EmulatorWindows.SessionName))
|
|
{
|
|
File.WriteAllText(EmulatorWindows.SessionName, EmulatorWindows.SessionJson);
|
|
AddMessage("Session saved.");
|
|
}
|
|
}
|
|
|
|
private void SaveSessionAsMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
if (EmulatorWindows.Any())
|
|
{
|
|
var file = GetSaveFileFromUser();
|
|
if (file != null)
|
|
{
|
|
EmulatorWindows.SessionName = file.FullName;
|
|
Global.Config.RecentRomSessions.Add(file.FullName);
|
|
SaveSessionMenuItem_Click(sender, e);
|
|
UpdateMainText();
|
|
}
|
|
}
|
|
}
|
|
|
|
private FileInfo GetSaveFileFromUser()
|
|
{
|
|
var sfd = new SaveFileDialog();
|
|
if (!string.IsNullOrWhiteSpace(EmulatorWindows.SessionName))
|
|
{
|
|
sfd.FileName = Path.GetFileNameWithoutExtension(EmulatorWindows.SessionName);
|
|
sfd.InitialDirectory = Path.GetDirectoryName(EmulatorWindows.SessionName);
|
|
}
|
|
else if (EmulatorWindows.Master != null)
|
|
{
|
|
sfd.FileName = PathManager.FilesystemSafeName(EmulatorWindows.Master.Game);
|
|
sfd.InitialDirectory = PathManager.GetRomsPath("Global");
|
|
}
|
|
else
|
|
{
|
|
sfd.FileName = "NULL";
|
|
sfd.InitialDirectory = PathManager.GetRomsPath("Global");
|
|
}
|
|
|
|
sfd.Filter = "Rom Session Files (*.romses)|*.romses|All Files|*.*";
|
|
sfd.RestoreDirectory = true;
|
|
var result = sfd.ShowDialog();
|
|
if (result != DialogResult.OK)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return new FileInfo(sfd.FileName);
|
|
}
|
|
|
|
private void OpenSessionMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
var file = GetFileFromUser("Rom Session Files (*.romses)|*.romses|All Files|*.*");
|
|
if (file != null)
|
|
{
|
|
NewSessionMenuItem_Click(null, null);
|
|
var json = File.ReadAllText(file.FullName);
|
|
EmulatorWindows.SessionName = file.FullName;
|
|
LoadRomSession(EmulatorWindowList.FromJson(json));
|
|
Global.Config.RecentRomSessions.Add(file.FullName);
|
|
UpdateMainText();
|
|
}
|
|
}
|
|
|
|
private void UpdateMainText()
|
|
{
|
|
string text = "MultiHawk";
|
|
|
|
if (!string.IsNullOrWhiteSpace(EmulatorWindows.SessionName))
|
|
{
|
|
text += " - " + Path.GetFileNameWithoutExtension(EmulatorWindows.SessionName);
|
|
}
|
|
|
|
if (Global.MovieSession.Movie.IsActive)
|
|
{
|
|
text += " - " + Path.GetFileNameWithoutExtension(Global.MovieSession.Movie.Filename);
|
|
}
|
|
|
|
Text = text;
|
|
}
|
|
|
|
private static FileInfo GetFileFromUser(string filter)
|
|
{
|
|
var ofd = new OpenFileDialog
|
|
{
|
|
InitialDirectory = PathManager.GetRomsPath("Global"),
|
|
Filter = filter,
|
|
RestoreDirectory = true
|
|
};
|
|
|
|
if (!Directory.Exists(ofd.InitialDirectory))
|
|
{
|
|
Directory.CreateDirectory(ofd.InitialDirectory);
|
|
}
|
|
|
|
var result = ofd.ShowDialog();
|
|
return result == DialogResult.OK ? new FileInfo(ofd.FileName) : null;
|
|
}
|
|
|
|
private void CloseAllWindows()
|
|
{
|
|
foreach (var ew in EmulatorWindows.ToList())
|
|
{
|
|
ew.Close();
|
|
}
|
|
|
|
EmulatorWindows.Clear();
|
|
}
|
|
|
|
private void NewSessionMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
foreach (var ew in EmulatorWindows.ToList())
|
|
{
|
|
ew.Close();
|
|
}
|
|
|
|
EmulatorWindows.Clear();
|
|
UpdateMainText();
|
|
}
|
|
|
|
private void LoadRomSession(IEnumerable<EmulatorWindowList.RomSessionEntry> entries)
|
|
{
|
|
foreach (var entry in entries)
|
|
{
|
|
LoadRom(entry.RomName);
|
|
EmulatorWindows.Last().Location = new Point(entry.Wndx, entry.Wndy);
|
|
UpdateMainText();
|
|
}
|
|
}
|
|
|
|
private void LoadRomSessionFromRecent(string path)
|
|
{
|
|
var file = new FileInfo(path);
|
|
if (file.Exists)
|
|
{
|
|
NewSessionMenuItem_Click(null, null);
|
|
var json = File.ReadAllText(file.FullName);
|
|
EmulatorWindows.SessionName = file.FullName;
|
|
LoadRomSession(EmulatorWindowList.FromJson(json));
|
|
Global.Config.RecentRomSessions.Add(file.FullName);
|
|
UpdateMainText();
|
|
}
|
|
else
|
|
{
|
|
Global.Config.RecentRomSessions.HandleLoadError(path);
|
|
}
|
|
}
|
|
|
|
private void RecentSessionSubMenu_DropDownOpened(object sender, EventArgs e)
|
|
{
|
|
RecentSessionSubMenu.DropDownItems.Clear();
|
|
RecentSessionSubMenu.DropDownItems.AddRange(
|
|
Global.Config.RecentRomSessions.RecentMenu(LoadRomSessionFromRecent, autoload: true));
|
|
}
|
|
|
|
private void RecentRomSubMenu_DropDownOpened(object sender, EventArgs e)
|
|
{
|
|
RecentRomSubMenu.DropDownItems.Clear();
|
|
RecentRomSubMenu.DropDownItems.AddRange(
|
|
Global.Config.RecentRoms.RecentMenu(LoadRomFromRecent, autoload: false));
|
|
}
|
|
|
|
private void ViewSubMenu_DropDownOpened(object sender, EventArgs e)
|
|
{
|
|
_1xMenuItem.Checked = Global.Config.TargetZoomFactors[Emulator.SystemId] == 1;
|
|
_2xMenuItem.Checked = Global.Config.TargetZoomFactors[Emulator.SystemId] == 2;
|
|
_3xMenuItem.Checked = Global.Config.TargetZoomFactors[Emulator.SystemId] == 3;
|
|
_4xMenuItem.Checked = Global.Config.TargetZoomFactors[Emulator.SystemId] == 4;
|
|
}
|
|
|
|
private void _1xMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
Global.Config.TargetZoomFactors[Emulator.SystemId] = 1;
|
|
ReRenderAllWindows();
|
|
}
|
|
|
|
private void _2xMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
Global.Config.TargetZoomFactors[Emulator.SystemId] = 2;
|
|
ReRenderAllWindows();
|
|
}
|
|
|
|
private void _3xMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
Global.Config.TargetZoomFactors[Emulator.SystemId] = 3;
|
|
ReRenderAllWindows();
|
|
}
|
|
|
|
private void _4xMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
Global.Config.TargetZoomFactors[Emulator.SystemId] = 4;
|
|
ReRenderAllWindows();
|
|
}
|
|
|
|
private void ReRenderAllWindows()
|
|
{
|
|
foreach (var ew in EmulatorWindows)
|
|
{
|
|
ew.FrameBufferResized();
|
|
ew.Render();
|
|
}
|
|
}
|
|
|
|
private void LoadLastMovieMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
LoadMoviesFromRecent(Global.Config.RecentMovies.MostRecent);
|
|
}
|
|
|
|
private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
|
|
{
|
|
LoadLastMovieContextMenuItem.Visible = !Global.Config.RecentMovies.Empty;
|
|
PlayMovieContextMenuItem.Visible =
|
|
RecordMovieContextMenuItem.Visible =
|
|
!Global.MovieSession.Movie.IsActive;
|
|
|
|
StopMovieContextMenuItem.Visible =
|
|
RestartMovieContextMenuItem.Visible =
|
|
Global.MovieSession.Movie.IsActive;
|
|
|
|
}
|
|
|
|
private void RecentMovieSubMenu_DropDownOpened(object sender, EventArgs e)
|
|
{
|
|
RecentMovieSubMenu.DropDownItems.Clear();
|
|
RecentMovieSubMenu.DropDownItems.AddRange(
|
|
Global.Config.RecentMovies.RecentMenu(LoadMoviesFromRecent, autoload: true));
|
|
}
|
|
|
|
private void RestartMovieMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
if (Global.MovieSession.Movie.IsActive)
|
|
{
|
|
StartNewMovie(Global.MovieSession.Movie, false);
|
|
AddMessage("Replaying movie file in read-only mode");
|
|
}
|
|
}
|
|
}
|
|
}
|