From a412b453cf646cb6c303659b033cac04b27dfbe0 Mon Sep 17 00:00:00 2001 From: Souryo Date: Sat, 6 May 2017 15:27:48 -0400 Subject: [PATCH] UI: Add visual list of recently played games (click to resume) --- Core/BaseVideoFilter.cpp | 22 ++- Core/BaseVideoFilter.h | 1 + Core/Console.cpp | 22 ++- Core/EmulationSettings.h | 2 + Core/NsfLoader.h | 1 + Core/NsfeLoader.h | 2 + Core/RomData.h | 1 + Core/SaveStateManager.cpp | 131 +++++++++---- Core/SaveStateManager.h | 5 + Core/VideoDecoder.cpp | 9 + Core/VideoDecoder.h | 1 + GUI.NET/Config/ConfigManager.cs | 12 ++ GUI.NET/Controls/ctrlRecentGames.Designer.cs | 175 +++++++++++++++++ GUI.NET/Controls/ctrlRecentGames.cs | 189 +++++++++++++++++++ GUI.NET/Controls/ctrlRecentGames.resx | 120 ++++++++++++ GUI.NET/Dependencies/PixelFont.ttf | Bin 0 -> 81192 bytes GUI.NET/Dependencies/resources.es.xml | 1 + GUI.NET/Dependencies/resources.fr.xml | 1 + GUI.NET/Dependencies/resources.ja.xml | 1 + GUI.NET/Dependencies/resources.pt.xml | 1 + GUI.NET/Dependencies/resources.ru.xml | 1 + GUI.NET/Dependencies/resources.uk.xml | 1 + GUI.NET/Forms/frmMain.Designer.cs | 129 ++++++++----- GUI.NET/Forms/frmMain.cs | 33 +++- GUI.NET/GUI.NET.csproj | 13 ++ GUI.NET/Icon.bmp | Bin 5174 -> 0 bytes GUI.NET/InteropEmu.cs | 3 + GUI.NET/Properties/Resources.Designer.cs | 10 + GUI.NET/Properties/Resources.resx | 3 + GUI.NET/ResourceManager.cs | 2 +- InteropDLL/ConsoleWrapper.cpp | 1 + PGOHelper/PGOHelper.cpp | 2 + TestHelper/TestHelper.cpp | 4 +- Utilities/FolderUtilities.cpp | 7 + Utilities/FolderUtilities.h | 1 + Utilities/PNGHelper.cpp | 24 ++- Utilities/PNGHelper.h | 1 + Utilities/miniz.cpp | 6 +- 38 files changed, 833 insertions(+), 105 deletions(-) create mode 100644 GUI.NET/Controls/ctrlRecentGames.Designer.cs create mode 100644 GUI.NET/Controls/ctrlRecentGames.cs create mode 100644 GUI.NET/Controls/ctrlRecentGames.resx create mode 100644 GUI.NET/Dependencies/PixelFont.ttf delete mode 100644 GUI.NET/Icon.bmp diff --git a/Core/BaseVideoFilter.cpp b/Core/BaseVideoFilter.cpp index bf177506..9d03d85d 100644 --- a/Core/BaseVideoFilter.cpp +++ b/Core/BaseVideoFilter.cpp @@ -60,10 +60,8 @@ uint8_t* BaseVideoFilter::GetOutputBuffer() return _outputBuffer; } -void BaseVideoFilter::TakeScreenshot() +void BaseVideoFilter::TakeScreenshot(string filename, std::stringstream *stream) { - string romFilename = FolderUtilities::GetFilename(Console::GetRomName(), false); - uint32_t* frameBuffer = nullptr; { auto lock = _frameLock.AcquireSafe(); @@ -75,10 +73,23 @@ void BaseVideoFilter::TakeScreenshot() } //ARGB -> ABGR - for(uint32_t i = 0; i < _bufferSize/GetFrameInfo().BitsPerPixel; i++) { + for(uint32_t i = 0; i < _bufferSize / GetFrameInfo().BitsPerPixel; i++) { frameBuffer[i] = 0xFF000000 | (frameBuffer[i] & 0xFF00) | ((frameBuffer[i] & 0xFF0000) >> 16) | ((frameBuffer[i] & 0xFF) << 16); } + if(!filename.empty()) { + PNGHelper::WritePNG(filename, (uint8_t*)frameBuffer, GetFrameInfo().Width, GetFrameInfo().Height); + } else { + PNGHelper::WritePNG(*stream, (uint8_t*)frameBuffer, GetFrameInfo().Width, GetFrameInfo().Height); + } + + delete[] frameBuffer; +} + +void BaseVideoFilter::TakeScreenshot() +{ + string romFilename = FolderUtilities::GetFilename(Console::GetRomName(), false); + int counter = 0; string baseFilename = FolderUtilities::CombinePath(FolderUtilities::GetScreenshotFolder(), romFilename); string ssFilename; @@ -97,8 +108,7 @@ void BaseVideoFilter::TakeScreenshot() counter++; } - PNGHelper::WritePNG(ssFilename, (uint8_t*)frameBuffer, GetFrameInfo().Width, GetFrameInfo().Height); - delete[] frameBuffer; + TakeScreenshot(ssFilename); MessageManager::DisplayMessage("ScreenshotSaved", FolderUtilities::GetFilename(ssFilename, true)); } diff --git a/Core/BaseVideoFilter.h b/Core/BaseVideoFilter.h index 415021b5..c5b6158d 100644 --- a/Core/BaseVideoFilter.h +++ b/Core/BaseVideoFilter.h @@ -28,6 +28,7 @@ public: uint8_t* GetOutputBuffer(); void SendFrame(uint16_t *ppuOutputBuffer); void TakeScreenshot(); + void TakeScreenshot(string filename, std::stringstream *stream = nullptr); virtual FrameInfo GetFrameInfo() = 0; }; \ No newline at end of file diff --git a/Core/Console.cpp b/Core/Console.cpp index f9b9f07a..47e7a468 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -24,6 +24,7 @@ #include "ShortcutKeyHandler.h" #include "MovieManager.h" #include "RewindManager.h" +#include "SaveStateManager.h" shared_ptr Console::Instance(new Console()); @@ -54,9 +55,12 @@ bool Console::Initialize(string romFilename, stringstream *filestream, string pa { SoundMixer::StopAudio(); - if(_mapper) { + if(!_romFilepath.empty() && _mapper) { //Ensure we save any battery file before loading a new game _mapper->SaveBattery(); + + //Save current game state before loading another one + SaveStateManager::SaveRecentGame(_mapper->GetRomName(), _romFilepath, _patchFilename, _archiveFileIndex); } MessageManager::SendNotification(ConsoleNotificationType::GameStopped); @@ -336,11 +340,13 @@ void Console::Run() VideoDecoder::GetInstance()->StartThread(); PlatformUtilities::DisableScreensaver(); - + + bool crashed = false; while(true) { try { _cpu->Exec(); } catch(const std::runtime_error &ex) { + crashed = true; MessageManager::DisplayMessage("Error", "GameCrash", ex.what()); break; } @@ -421,6 +427,13 @@ void Console::Run() } } } + + if(!crashed) { + SaveStateManager::SaveRecentGame(_mapper->GetRomName(), _romFilepath, _patchFilename, _archiveFileIndex); + } + + MessageManager::SendNotification(ConsoleNotificationType::GameStopped); + _rewindManager.reset(); SoundMixer::StopAudio(); MovieManager::Stop(); @@ -433,6 +446,11 @@ void Console::Run() _initialized = false; _romFilepath = ""; + _mapper.reset(); + _ppu.reset(); + _cpu.reset(); + _memoryManager.reset(); + _controlManager.reset(); _stopLock.Release(); _runLock.Release(); diff --git a/Core/EmulationSettings.h b/Core/EmulationSettings.h index 135daf6c..4c6e0489 100644 --- a/Core/EmulationSettings.h +++ b/Core/EmulationSettings.h @@ -56,6 +56,8 @@ enum EmulationFlags : uint64_t DisplayMovieIcons = 0x10000000000, HidePauseOverlay = 0x20000000000, + + ConsoleMode = 0x8000000000000000, }; enum class AudioChannel diff --git a/Core/NsfLoader.h b/Core/NsfLoader.h index a45dd604..b5b864c8 100644 --- a/Core/NsfLoader.h +++ b/Core/NsfLoader.h @@ -27,6 +27,7 @@ protected: { NsfHeader &header = romData.NsfInfo; + romData.Format = RomFormat::Nsf; romData.MapperID = MapperFactory::NsfMapperID; if(header.LoadAddress < 0x6000 || header.TotalSongs == 0) { diff --git a/Core/NsfeLoader.h b/Core/NsfeLoader.h index 291e801f..43404cdd 100644 --- a/Core/NsfeLoader.h +++ b/Core/NsfeLoader.h @@ -185,6 +185,8 @@ public: InitHeader(header); + romData.Format = RomFormat::Nsf; + uint8_t* data = romFile.data() + 4; uint8_t* endOfData = romFile.data() + romFile.size(); diff --git a/Core/RomData.h b/Core/RomData.h index f3bc0b85..b9dfec71 100644 --- a/Core/RomData.h +++ b/Core/RomData.h @@ -289,6 +289,7 @@ enum class RomFormat iNes = 1, Unif = 2, Fds = 3, + Nsf = 4, }; struct RomData diff --git a/Core/SaveStateManager.cpp b/Core/SaveStateManager.cpp index 48d302f4..a350c794 100644 --- a/Core/SaveStateManager.cpp +++ b/Core/SaveStateManager.cpp @@ -1,10 +1,12 @@ #include "stdafx.h" - +#include "../Utilities/FolderUtilities.h" +#include "../Utilities/ZipWriter.h" +#include "../Utilities/ZipReader.h" #include "SaveStateManager.h" #include "MessageManager.h" #include "Console.h" -#include "../Utilities/FolderUtilities.h" #include "EmulationSettings.h" +#include "VideoDecoder.h" const uint32_t SaveStateManager::FileFormatVersion; atomic SaveStateManager::_lastIndex(1); @@ -50,6 +52,19 @@ bool SaveStateManager::LoadState() return LoadState(_lastIndex); } +void SaveStateManager::SaveState(ostream &stream) +{ + Console::Pause(); + + uint32_t emuVersion = EmulationSettings::GetMesenVersion(); + stream.write("MST", 3); + stream.write((char*)&emuVersion, sizeof(emuVersion)); + stream.write((char*)&SaveStateManager::FileFormatVersion, sizeof(uint32_t)); + + Console::SaveState(stream); + Console::Resume(); +} + void SaveStateManager::SaveState(int stateIndex, bool displayMessage) { string filepath = SaveStateManager::GetStateFilepath(stateIndex); @@ -57,17 +72,8 @@ void SaveStateManager::SaveState(int stateIndex, bool displayMessage) if(file) { _lastIndex = stateIndex; - - Console::Pause(); - - uint32_t emuVersion = EmulationSettings::GetMesenVersion(); - file.write("MST", 3); - file.write((char*)&emuVersion, sizeof(emuVersion)); - file.write((char*)&SaveStateManager::FileFormatVersion, sizeof(uint32_t)); - - Console::SaveState(file); - Console::Resume(); - file.close(); + SaveState(file); + file.close(); if(displayMessage) { MessageManager::DisplayMessage("SaveStates", "SaveStateSaved", std::to_string(stateIndex)); @@ -75,6 +81,35 @@ void SaveStateManager::SaveState(int stateIndex, bool displayMessage) } } +bool SaveStateManager::LoadState(istream &stream) +{ + char header[3]; + stream.read(header, 3); + if(memcmp(header, "MST", 3) == 0) { + uint32_t emuVersion, fileFormatVersion; + + stream.read((char*)&emuVersion, sizeof(emuVersion)); + if(emuVersion > EmulationSettings::GetMesenVersion()) { + MessageManager::DisplayMessage("SaveStates", "SaveStateNewerVersion"); + return false; + } + + stream.read((char*)&fileFormatVersion, sizeof(fileFormatVersion)); + if(fileFormatVersion != SaveStateManager::FileFormatVersion) { + MessageManager::DisplayMessage("SaveStates", "SaveStateIncompatibleVersion"); // , std::to_string(stateIndex)); + return false; + } + + Console::Pause(); + Console::LoadState(stream); + Console::Resume(); + + return true; + } + + return false; +} + bool SaveStateManager::LoadState(int stateIndex) { string filepath = SaveStateManager::GetStateFilepath(stateIndex); @@ -82,29 +117,8 @@ bool SaveStateManager::LoadState(int stateIndex) bool result = false; if(file) { - char header[3]; - file.read(header, 3); - if(memcmp(header, "MST", 3) == 0) { - uint32_t emuVersion, fileFormatVersion; - - file.read((char*)&emuVersion, sizeof(emuVersion)); - if(emuVersion > EmulationSettings::GetMesenVersion()) { - MessageManager::DisplayMessage("SaveStates", "SaveStateNewerVersion"); - return false; - } - - file.read((char*)&fileFormatVersion, sizeof(fileFormatVersion)); - if(fileFormatVersion != SaveStateManager::FileFormatVersion) { - MessageManager::DisplayMessage("SaveStates", "SaveStateIncompatibleVersion", std::to_string(stateIndex)); - return false; - } - + if(LoadState(file)) { _lastIndex = stateIndex; - - Console::Pause(); - Console::LoadState(file); - Console::Resume(); - MessageManager::DisplayMessage("SaveStates", "SaveStateLoaded", std::to_string(stateIndex)); result = true; } else { @@ -118,4 +132,51 @@ bool SaveStateManager::LoadState(int stateIndex) } return result; +} + +void SaveStateManager::SaveRecentGame(string romName, string romPath, string patchPath, int32_t archiveFileIndex) +{ + if(!EmulationSettings::CheckFlag(EmulationFlags::ConsoleMode) && Console::GetRomFormat() != RomFormat::Nsf) { + string filename = FolderUtilities::GetFilename(Console::GetRomName(), false) + ".rgd"; + ZipWriter writer(FolderUtilities::CombinePath(FolderUtilities::GetRecentGamesFolder(), filename)); + + std::stringstream pngStream; + VideoDecoder::GetInstance()->TakeScreenshot(pngStream); + writer.AddFile(pngStream, "Screenshot.png"); + + std::stringstream stateStream; + SaveStateManager::SaveState(stateStream); + writer.AddFile(stateStream, "Savestate.mst"); + + std::stringstream romInfoStream; + romInfoStream << romName << std::endl; + romInfoStream << romPath << std::endl; + romInfoStream << patchPath << std::endl; + romInfoStream << std::to_string(archiveFileIndex) << std::endl; + writer.AddFile(romInfoStream, "RomInfo.txt"); + } +} + +void SaveStateManager::LoadRecentGame(string filename) +{ + ZipReader reader; + reader.LoadArchive(filename); + + std::stringstream romInfoStream = reader.GetStream("RomInfo.txt"); + std::stringstream stateStream = reader.GetStream("Savestate.mst"); + + string romName, romPath, patchPath, archiveIndex; + std::getline(romInfoStream, romName); + std::getline(romInfoStream, romPath); + std::getline(romInfoStream, patchPath); + std::getline(romInfoStream, archiveIndex); + + Console::Pause(); + try { + Console::LoadROM(romPath, nullptr, std::stoi(archiveIndex.c_str()), patchPath); + SaveStateManager::LoadState(stateStream); + } catch(std::exception ex) { + Console::GetInstance()->Stop(); + } + Console::Resume(); } \ No newline at end of file diff --git a/Core/SaveStateManager.h b/Core/SaveStateManager.h index b15da653..e25ff3ae 100644 --- a/Core/SaveStateManager.h +++ b/Core/SaveStateManager.h @@ -18,9 +18,14 @@ public: static void SaveState(); static bool LoadState(); + static void SaveState(ostream &stream); static void SaveState(int stateIndex, bool displayMessage = true); + static bool LoadState(istream &stream); static bool LoadState(int stateIndex); + static void SaveRecentGame(string romName, string romPath, string patchPath, int32_t archiveFileIndex); + static void LoadRecentGame(string filename); + static void MoveToNextSlot(); static void MoveToPreviousSlot(); }; \ No newline at end of file diff --git a/Core/VideoDecoder.cpp b/Core/VideoDecoder.cpp index 4114d23b..751e992d 100644 --- a/Core/VideoDecoder.cpp +++ b/Core/VideoDecoder.cpp @@ -204,6 +204,8 @@ void VideoDecoder::StopThread() _decodeThread.reset(); + _hdScreenTiles = nullptr; + UpdateVideoFilter(); if(_ppuOutputBuffer != nullptr) { //Clear whole screen for(uint32_t i = 0; i < PPU::PixelCount; i++) { @@ -226,3 +228,10 @@ void VideoDecoder::TakeScreenshot() _videoFilter->TakeScreenshot(); } } + +void VideoDecoder::TakeScreenshot(std::stringstream &stream) +{ + if(_videoFilter) { + _videoFilter->TakeScreenshot("", &stream); + } +} diff --git a/Core/VideoDecoder.h b/Core/VideoDecoder.h index 6fe00dd2..5dbf8899 100644 --- a/Core/VideoDecoder.h +++ b/Core/VideoDecoder.h @@ -54,6 +54,7 @@ public: void DecodeFrame(); void TakeScreenshot(); + void TakeScreenshot(std::stringstream &stream); uint32_t GetFrameCount(); diff --git a/GUI.NET/Config/ConfigManager.cs b/GUI.NET/Config/ConfigManager.cs index 91ff3eb1..0e63f6ed 100644 --- a/GUI.NET/Config/ConfigManager.cs +++ b/GUI.NET/Config/ConfigManager.cs @@ -160,6 +160,18 @@ namespace Mesen.GUI.Config } } + public static string RecentGamesFolder + { + get + { + string recentGamesPath = Path.Combine(ConfigManager.HomeFolder, "RecentGames"); + if(!Directory.Exists(recentGamesPath)) { + Directory.CreateDirectory(recentGamesPath); + } + return recentGamesPath; + } + } + public static string WaveFolder { get diff --git a/GUI.NET/Controls/ctrlRecentGames.Designer.cs b/GUI.NET/Controls/ctrlRecentGames.Designer.cs new file mode 100644 index 00000000..704ebc05 --- /dev/null +++ b/GUI.NET/Controls/ctrlRecentGames.Designer.cs @@ -0,0 +1,175 @@ +namespace Mesen.GUI.Controls +{ + partial class ctrlRecentGames + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if(disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.tlpPreviousState = new Mesen.GUI.Controls.DBTableLayoutPanel(); + this.pnlPreviousState = new System.Windows.Forms.Panel(); + this.picPreviousState = new Mesen.GUI.Controls.GamePreviewBox(); + this.lblGameName = new System.Windows.Forms.Label(); + this.lblSaveDate = new System.Windows.Forms.Label(); + this.picNextGame = new System.Windows.Forms.PictureBox(); + this.picPrevGame = new System.Windows.Forms.PictureBox(); + this.tlpPreviousState.SuspendLayout(); + this.pnlPreviousState.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.picPreviousState)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.picNextGame)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.picPrevGame)).BeginInit(); + this.SuspendLayout(); + // + // tlpPreviousState + // + this.tlpPreviousState.BackColor = System.Drawing.Color.Black; + this.tlpPreviousState.ColumnCount = 3; + this.tlpPreviousState.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tlpPreviousState.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tlpPreviousState.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tlpPreviousState.Controls.Add(this.pnlPreviousState, 1, 1); + this.tlpPreviousState.Controls.Add(this.lblGameName, 1, 2); + this.tlpPreviousState.Controls.Add(this.lblSaveDate, 1, 3); + this.tlpPreviousState.Controls.Add(this.picNextGame, 2, 1); + this.tlpPreviousState.Controls.Add(this.picPrevGame, 0, 1); + this.tlpPreviousState.Dock = System.Windows.Forms.DockStyle.Fill; + this.tlpPreviousState.Location = new System.Drawing.Point(0, 0); + this.tlpPreviousState.Name = "tlpPreviousState"; + this.tlpPreviousState.RowCount = 6; + this.tlpPreviousState.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 10F)); + this.tlpPreviousState.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tlpPreviousState.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tlpPreviousState.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tlpPreviousState.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 5F)); + this.tlpPreviousState.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tlpPreviousState.Size = new System.Drawing.Size(272, 107); + this.tlpPreviousState.TabIndex = 9; + // + // pnlPreviousState + // + this.pnlPreviousState.Anchor = System.Windows.Forms.AnchorStyles.None; + this.pnlPreviousState.BackColor = System.Drawing.Color.Gray; + this.pnlPreviousState.Controls.Add(this.picPreviousState); + this.pnlPreviousState.Location = new System.Drawing.Point(113, 13); + this.pnlPreviousState.Name = "pnlPreviousState"; + this.pnlPreviousState.Padding = new System.Windows.Forms.Padding(2); + this.pnlPreviousState.Size = new System.Drawing.Size(46, 46); + this.pnlPreviousState.TabIndex = 8; + // + // picPreviousState + // + this.picPreviousState.BackColor = System.Drawing.Color.Black; + this.picPreviousState.Cursor = System.Windows.Forms.Cursors.Hand; + this.picPreviousState.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + this.picPreviousState.Location = new System.Drawing.Point(2, 2); + this.picPreviousState.Margin = new System.Windows.Forms.Padding(0); + this.picPreviousState.Name = "picPreviousState"; + this.picPreviousState.Size = new System.Drawing.Size(42, 42); + this.picPreviousState.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.picPreviousState.TabIndex = 7; + this.picPreviousState.TabStop = false; + this.picPreviousState.Click += new System.EventHandler(this.picPreviousState_Click); + this.picPreviousState.MouseEnter += new System.EventHandler(this.picPreviousState_MouseEnter); + this.picPreviousState.MouseLeave += new System.EventHandler(this.picPreviousState_MouseLeave); + // + // lblGameName + // + this.lblGameName.AutoEllipsis = true; + this.lblGameName.BackColor = System.Drawing.Color.Transparent; + this.lblGameName.Dock = System.Windows.Forms.DockStyle.Fill; + this.lblGameName.ForeColor = System.Drawing.Color.White; + this.lblGameName.Location = new System.Drawing.Point(36, 62); + this.lblGameName.Name = "lblGameName"; + this.lblGameName.Size = new System.Drawing.Size(200, 16); + this.lblGameName.TabIndex = 9; + this.lblGameName.Text = "Game Name"; + this.lblGameName.TextAlign = System.Drawing.ContentAlignment.TopCenter; + // + // lblSaveDate + // + this.lblSaveDate.Anchor = System.Windows.Forms.AnchorStyles.Top; + this.lblSaveDate.AutoSize = true; + this.lblSaveDate.BackColor = System.Drawing.Color.Transparent; + this.lblSaveDate.ForeColor = System.Drawing.Color.White; + this.lblSaveDate.Location = new System.Drawing.Point(121, 78); + this.lblSaveDate.Name = "lblSaveDate"; + this.lblSaveDate.Size = new System.Drawing.Size(30, 13); + this.lblSaveDate.TabIndex = 10; + this.lblSaveDate.Text = "Date"; + // + // picNextGame + // + this.picNextGame.Cursor = System.Windows.Forms.Cursors.Hand; + this.picNextGame.Dock = System.Windows.Forms.DockStyle.Right; + this.picNextGame.Image = global::Mesen.GUI.Properties.Resources.Play; + this.picNextGame.Location = new System.Drawing.Point(242, 13); + this.picNextGame.Name = "picNextGame"; + this.picNextGame.Size = new System.Drawing.Size(27, 46); + this.picNextGame.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage; + this.picNextGame.TabIndex = 11; + this.picNextGame.TabStop = false; + this.picNextGame.MouseDown += new System.Windows.Forms.MouseEventHandler(this.picNextGame_MouseDown); + // + // picPrevGame + // + this.picPrevGame.Cursor = System.Windows.Forms.Cursors.Hand; + this.picPrevGame.Dock = System.Windows.Forms.DockStyle.Left; + this.picPrevGame.Image = global::Mesen.GUI.Properties.Resources.Play; + this.picPrevGame.Location = new System.Drawing.Point(3, 13); + this.picPrevGame.Name = "picPrevGame"; + this.picPrevGame.Size = new System.Drawing.Size(27, 46); + this.picPrevGame.SizeMode = System.Windows.Forms.PictureBoxSizeMode.CenterImage; + this.picPrevGame.TabIndex = 12; + this.picPrevGame.TabStop = false; + this.picPrevGame.MouseDown += new System.Windows.Forms.MouseEventHandler(this.picPrevGame_MouseDown); + // + // ctrlRecentGames + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.Transparent; + this.Controls.Add(this.tlpPreviousState); + this.Name = "ctrlRecentGames"; + this.Size = new System.Drawing.Size(272, 107); + this.tlpPreviousState.ResumeLayout(false); + this.tlpPreviousState.PerformLayout(); + this.pnlPreviousState.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.picPreviousState)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.picNextGame)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.picPrevGame)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private DBTableLayoutPanel tlpPreviousState; + private System.Windows.Forms.Panel pnlPreviousState; + private GamePreviewBox picPreviousState; + private System.Windows.Forms.Label lblGameName; + private System.Windows.Forms.Label lblSaveDate; + private System.Windows.Forms.PictureBox picNextGame; + private System.Windows.Forms.PictureBox picPrevGame; + } +} diff --git a/GUI.NET/Controls/ctrlRecentGames.cs b/GUI.NET/Controls/ctrlRecentGames.cs new file mode 100644 index 00000000..e2246b22 --- /dev/null +++ b/GUI.NET/Controls/ctrlRecentGames.cs @@ -0,0 +1,189 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.IO; +using Mesen.GUI.Config; +using System.Drawing.Text; +using System.IO.Compression; + +namespace Mesen.GUI.Controls +{ + public partial class ctrlRecentGames : UserControl + { + private int _currentIndex = 0; + private List _recentGames = new List(); + private PrivateFontCollection _fonts = new PrivateFontCollection(); + + private class RecentGameInfo + { + public string FileName { get; set; } + public string RomName { get; set; } + public string RomPath { get; set; } + public DateTime Timestamp { get; set; } + public Image Screenshot { get; set; } + } + + public ctrlRecentGames() + { + InitializeComponent(); + + DoubleBuffered = true; + + bool designMode = (LicenseManager.UsageMode == LicenseUsageMode.Designtime); + if(!designMode) { + _fonts.AddFontFile(Path.Combine(ConfigManager.HomeFolder, "Resources", "PixelFont.ttf")); + lblGameName.Font = new Font(_fonts.Families[0], 10); + lblSaveDate.Font = new Font(_fonts.Families[0], 10); + + picPrevGame.Image.RotateFlip(RotateFlipType.RotateNoneFlipX); + Initialize(); + } + } + + protected override void OnVisibleChanged(EventArgs e) + { + if(_recentGames.Count == 0) { + this.Visible = false; + } + base.OnVisibleChanged(e); + } + + public void Initialize() + { + _recentGames = new List(); + _currentIndex = 0; + + foreach(string file in Directory.GetFiles(ConfigManager.RecentGamesFolder, "*.rgd")) { + try { + RecentGameInfo info = new RecentGameInfo(); + ZipArchive zip = new ZipArchive(new MemoryStream(File.ReadAllBytes(file))); + + Stream stream = zip.GetEntry("Screenshot.png").Open(); + info.Screenshot = Image.FromStream(stream); + + using(StreamReader sr = new StreamReader(zip.GetEntry("RomInfo.txt").Open())) { + info.RomName = sr.ReadLine(); + info.RomPath = sr.ReadLine(); + } + + info.Timestamp = new FileInfo(file).LastWriteTime; + info.FileName = file; + + if(File.Exists(info.RomPath)) { + _recentGames.Add(info); + } + } catch { } + } + + _recentGames = _recentGames.OrderBy((info) => info.Timestamp).Reverse().ToList(); + + if(_recentGames.Count > 5) { + _recentGames.RemoveRange(5, _recentGames.Count - 5); + } + + picPrevGame.Visible = _recentGames.Count > 1; + picNextGame.Visible = _recentGames.Count > 1; + + if(_recentGames.Count == 0) { + this.Visible = false; + } else { + UpdateGameInfo(); + } + } + + private void UpdateGameInfo() + { + if(_currentIndex < _recentGames.Count) { + lblGameName.Text = Path.GetFileNameWithoutExtension(_recentGames[_currentIndex].RomName); + lblSaveDate.Text = _recentGames[_currentIndex].Timestamp.ToString(); + picPreviousState.Image = _recentGames[_currentIndex].Screenshot; + UpdateSize(); + } + } + + private void UpdateSize() + { + tlpPreviousState.Visible = false; + Size maxSize = new Size(this.Size.Width - 120, this.Size.Height - 50); + + double xRatio = (double)picPreviousState.Image.Width / maxSize.Width; + double yRatio = (double)picPreviousState.Image.Height / maxSize.Height; + double ratio = Math.Max(xRatio, yRatio); + + Size newSize = new Size((int)(picPreviousState.Image.Width / ratio), (int)(picPreviousState.Image.Height / ratio)); + picPreviousState.Size = newSize; + pnlPreviousState.Size = new Size(newSize.Width+4, newSize.Height+4); + tlpPreviousState.Visible = true; + } + + protected override void OnResize(EventArgs e) + { + if(picPreviousState.Image != null) { + UpdateSize(); + } + base.OnResize(e); + } + + private void picPreviousState_MouseEnter(object sender, EventArgs e) + { + pnlPreviousState.BackColor = Color.LightBlue; + } + + private void picPreviousState_MouseLeave(object sender, EventArgs e) + { + pnlPreviousState.BackColor = Color.Gray; + } + + private void picPreviousState_Click(object sender, EventArgs e) + { + InteropEmu.LoadRecentGame(_recentGames[_currentIndex].FileName); + } + + private void picNextGame_MouseDown(object sender, MouseEventArgs e) + { + _currentIndex = (_currentIndex + 1) % _recentGames.Count; + UpdateGameInfo(); + } + + private void picPrevGame_MouseDown(object sender, MouseEventArgs e) + { + if(_currentIndex == 0) { + _currentIndex = _recentGames.Count - 1; + } else { + _currentIndex--; + } + UpdateGameInfo(); + } + } + + public class DBTableLayoutPanel : TableLayoutPanel + { + public DBTableLayoutPanel() + { + DoubleBuffered = true; + } + } + + public class GamePreviewBox : PictureBox + { + public System.Drawing.Drawing2D.InterpolationMode InterpolationMode { get; set; } + + public GamePreviewBox() + { + DoubleBuffered = true; + InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Default; + } + + protected override void OnPaint(PaintEventArgs pe) + { + pe.Graphics.InterpolationMode = InterpolationMode; + base.OnPaint(pe); + } + } +} diff --git a/GUI.NET/Controls/ctrlRecentGames.resx b/GUI.NET/Controls/ctrlRecentGames.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/GUI.NET/Controls/ctrlRecentGames.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/GUI.NET/Dependencies/PixelFont.ttf b/GUI.NET/Dependencies/PixelFont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f5cbfc06fd489f01566cfaa1a41f5c260368d7b9 GIT binary patch literal 81192 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 literal 0 HcmV?d00001 diff --git a/GUI.NET/Dependencies/resources.es.xml b/GUI.NET/Dependencies/resources.es.xml index 037330ff..79c97303 100644 --- a/GUI.NET/Dependencies/resources.es.xml +++ b/GUI.NET/Dependencies/resources.es.xml @@ -12,6 +12,7 @@ Pausa Reset Power Cycle + Eject Cartridge Cambiar la cara del disco Elegir el disco Expulsar el disco diff --git a/GUI.NET/Dependencies/resources.fr.xml b/GUI.NET/Dependencies/resources.fr.xml index 020a5719..dac76bc0 100644 --- a/GUI.NET/Dependencies/resources.fr.xml +++ b/GUI.NET/Dependencies/resources.fr.xml @@ -12,6 +12,7 @@ Pause Reset Arrêt && redémarrage + Éjecter le jeu Changer le disque de côté Choisir le disque Éjecter le disque diff --git a/GUI.NET/Dependencies/resources.ja.xml b/GUI.NET/Dependencies/resources.ja.xml index c2d4aecf..79c2789b 100644 --- a/GUI.NET/Dependencies/resources.ja.xml +++ b/GUI.NET/Dependencies/resources.ja.xml @@ -12,6 +12,7 @@ ポーズ リセット 停止と再起動 + ゲームを取り出す A面B面切り替え ディスク選択 ディスクを取り出す diff --git a/GUI.NET/Dependencies/resources.pt.xml b/GUI.NET/Dependencies/resources.pt.xml index db8290ee..480c2666 100644 --- a/GUI.NET/Dependencies/resources.pt.xml +++ b/GUI.NET/Dependencies/resources.pt.xml @@ -12,6 +12,7 @@ Pausar Resetar Power Cycle + Eject Cartridge Trocar o lado do disco Selecionar o disco Ejetar o disco diff --git a/GUI.NET/Dependencies/resources.ru.xml b/GUI.NET/Dependencies/resources.ru.xml index 414b84bd..44bddcb3 100644 --- a/GUI.NET/Dependencies/resources.ru.xml +++ b/GUI.NET/Dependencies/resources.ru.xml @@ -12,6 +12,7 @@ Пауза Сброс Power Cycle + Eject Cartridge Сменить сторону диска Выбрать диск Извлечь диск diff --git a/GUI.NET/Dependencies/resources.uk.xml b/GUI.NET/Dependencies/resources.uk.xml index 2deea1ae..b4fc3f04 100644 --- a/GUI.NET/Dependencies/resources.uk.xml +++ b/GUI.NET/Dependencies/resources.uk.xml @@ -12,6 +12,7 @@ Пауза Скидання Цикл включення + Eject Cartridge Змінити сторону диска Вибрати диск Вилучити диск diff --git a/GUI.NET/Forms/frmMain.Designer.cs b/GUI.NET/Forms/frmMain.Designer.cs index 320bcf79..45b3575c 100644 --- a/GUI.NET/Forms/frmMain.Designer.cs +++ b/GUI.NET/Forms/frmMain.Designer.cs @@ -34,6 +34,10 @@ namespace Mesen.GUI.Forms this.menuTimer = new System.Windows.Forms.Timer(this.components); this.panelRenderer = new System.Windows.Forms.Panel(); this.ctrlLoading = new Mesen.GUI.Controls.ctrlLoadingRom(); + this.panelInfo = new System.Windows.Forms.Panel(); + this.picIcon = new System.Windows.Forms.PictureBox(); + this.lblVersion = new System.Windows.Forms.Label(); + this.ctrlRecentGames = new Mesen.GUI.Controls.ctrlRecentGames(); this.ctrlNsfPlayer = new Mesen.GUI.Controls.ctrlNsfPlayer(); this.ctrlRenderer = new Mesen.GUI.Controls.ctrlRenderer(); this.menuStrip = new System.Windows.Forms.MenuStrip(); @@ -50,6 +54,8 @@ namespace Mesen.GUI.Forms this.mnuPause = new System.Windows.Forms.ToolStripMenuItem(); this.mnuReset = new System.Windows.Forms.ToolStripMenuItem(); this.mnuPowerCycle = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem24 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuEjectCartridge = new System.Windows.Forms.ToolStripMenuItem(); this.sepFdsDisk = new System.Windows.Forms.ToolStripSeparator(); this.mnuSwitchDiskSide = new System.Windows.Forms.ToolStripMenuItem(); this.mnuSelectDisk = new System.Windows.Forms.ToolStripMenuItem(); @@ -180,13 +186,10 @@ namespace Mesen.GUI.Forms this.toolStripMenuItem5 = new System.Windows.Forms.ToolStripSeparator(); this.mnuHelpWindow = new System.Windows.Forms.ToolStripMenuItem(); this.mnuAbout = new System.Windows.Forms.ToolStripMenuItem(); - this.picIcon = new System.Windows.Forms.PictureBox(); - this.panelInfo = new System.Windows.Forms.Panel(); - this.lblVersion = new System.Windows.Forms.Label(); this.panelRenderer.SuspendLayout(); - this.menuStrip.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.picIcon)).BeginInit(); this.panelInfo.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.picIcon)).BeginInit(); + this.menuStrip.SuspendLayout(); this.SuspendLayout(); // // menuTimer @@ -196,14 +199,15 @@ namespace Mesen.GUI.Forms // panelRenderer // this.panelRenderer.BackColor = System.Drawing.Color.Black; - this.panelRenderer.Controls.Add(this.panelInfo); this.panelRenderer.Controls.Add(this.ctrlLoading); + this.panelRenderer.Controls.Add(this.panelInfo); + this.panelRenderer.Controls.Add(this.ctrlRecentGames); this.panelRenderer.Controls.Add(this.ctrlNsfPlayer); this.panelRenderer.Controls.Add(this.ctrlRenderer); this.panelRenderer.Dock = System.Windows.Forms.DockStyle.Fill; this.panelRenderer.Location = new System.Drawing.Point(0, 24); this.panelRenderer.Name = "panelRenderer"; - this.panelRenderer.Size = new System.Drawing.Size(360, 239); + this.panelRenderer.Size = new System.Drawing.Size(430, 309); this.panelRenderer.TabIndex = 2; this.panelRenderer.Click += new System.EventHandler(this.panelRenderer_Click); this.panelRenderer.DoubleClick += new System.EventHandler(this.ctrlRenderer_DoubleClick); @@ -215,17 +219,60 @@ namespace Mesen.GUI.Forms this.ctrlLoading.Dock = System.Windows.Forms.DockStyle.Fill; this.ctrlLoading.Location = new System.Drawing.Point(0, 0); this.ctrlLoading.Name = "ctrlLoading"; - this.ctrlLoading.Size = new System.Drawing.Size(360, 239); + this.ctrlLoading.Size = new System.Drawing.Size(430, 309); this.ctrlLoading.TabIndex = 4; this.ctrlLoading.Visible = false; // + // panelInfo + // + this.panelInfo.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.panelInfo.BackColor = System.Drawing.Color.Transparent; + this.panelInfo.Controls.Add(this.picIcon); + this.panelInfo.Controls.Add(this.lblVersion); + this.panelInfo.Location = new System.Drawing.Point(359, 283); + this.panelInfo.Name = "panelInfo"; + this.panelInfo.Size = new System.Drawing.Size(71, 26); + this.panelInfo.TabIndex = 6; + // + // picIcon + // + this.picIcon.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.picIcon.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.picIcon.Image = global::Mesen.GUI.Properties.Resources.MesenIconSmall; + this.picIcon.Location = new System.Drawing.Point(50, 5); + this.picIcon.Name = "picIcon"; + this.picIcon.Size = new System.Drawing.Size(18, 18); + this.picIcon.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize; + this.picIcon.TabIndex = 5; + this.picIcon.TabStop = false; + // + // lblVersion + // + this.lblVersion.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.lblVersion.AutoSize = true; + this.lblVersion.BackColor = System.Drawing.Color.Transparent; + this.lblVersion.ForeColor = System.Drawing.Color.White; + this.lblVersion.Location = new System.Drawing.Point(4, 7); + this.lblVersion.Name = "lblVersion"; + this.lblVersion.Size = new System.Drawing.Size(0, 13); + this.lblVersion.TabIndex = 6; + // + // ctrlRecentGames + // + this.ctrlRecentGames.BackColor = System.Drawing.Color.Transparent; + this.ctrlRecentGames.Dock = System.Windows.Forms.DockStyle.Top; + this.ctrlRecentGames.Location = new System.Drawing.Point(0, 0); + this.ctrlRecentGames.Name = "ctrlRecentGames"; + this.ctrlRecentGames.Size = new System.Drawing.Size(430, 309); + this.ctrlRecentGames.TabIndex = 7; + // // ctrlNsfPlayer // this.ctrlNsfPlayer.BackColor = System.Drawing.Color.Black; this.ctrlNsfPlayer.Dock = System.Windows.Forms.DockStyle.Fill; this.ctrlNsfPlayer.Location = new System.Drawing.Point(0, 0); this.ctrlNsfPlayer.Name = "ctrlNsfPlayer"; - this.ctrlNsfPlayer.Size = new System.Drawing.Size(360, 239); + this.ctrlNsfPlayer.Size = new System.Drawing.Size(430, 309); this.ctrlNsfPlayer.TabIndex = 2; this.ctrlNsfPlayer.Visible = false; // @@ -252,7 +299,7 @@ namespace Mesen.GUI.Forms this.mnuHelp}); this.menuStrip.Location = new System.Drawing.Point(0, 0); this.menuStrip.Name = "menuStrip"; - this.menuStrip.Size = new System.Drawing.Size(360, 24); + this.menuStrip.Size = new System.Drawing.Size(430, 24); this.menuStrip.TabIndex = 0; this.menuStrip.Text = "menuStrip1"; this.menuStrip.VisibleChanged += new System.EventHandler(this.menuStrip_VisibleChanged); @@ -331,6 +378,8 @@ namespace Mesen.GUI.Forms this.mnuPause, this.mnuReset, this.mnuPowerCycle, + this.toolStripMenuItem24, + this.mnuEjectCartridge, this.sepFdsDisk, this.mnuSwitchDiskSide, this.mnuSelectDisk, @@ -373,6 +422,19 @@ namespace Mesen.GUI.Forms this.mnuPowerCycle.Text = "Power Cycle"; this.mnuPowerCycle.Click += new System.EventHandler(this.mnuPowerCycle_Click); // + // toolStripMenuItem24 + // + this.toolStripMenuItem24.Name = "toolStripMenuItem24"; + this.toolStripMenuItem24.Size = new System.Drawing.Size(197, 6); + // + // mnuEjectCartridge + // + this.mnuEjectCartridge.Image = global::Mesen.GUI.Properties.Resources.Eject; + this.mnuEjectCartridge.Name = "mnuEjectCartridge"; + this.mnuEjectCartridge.Size = new System.Drawing.Size(200, 22); + this.mnuEjectCartridge.Text = "Eject Cartridge"; + this.mnuEjectCartridge.Click += new System.EventHandler(this.mnuEjectCartridge_Click); + // // sepFdsDisk // this.sepFdsDisk.Name = "sepFdsDisk"; @@ -1421,62 +1483,28 @@ namespace Mesen.GUI.Forms this.mnuAbout.Text = "About"; this.mnuAbout.Click += new System.EventHandler(this.mnuAbout_Click); // - // picIcon - // - this.picIcon.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.picIcon.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.picIcon.Image = global::Mesen.GUI.Properties.Resources.MesenLogo; - this.picIcon.Location = new System.Drawing.Point(32, 6); - this.picIcon.Name = "picIcon"; - this.picIcon.Size = new System.Drawing.Size(34, 34); - this.picIcon.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage; - this.picIcon.TabIndex = 5; - this.picIcon.TabStop = false; - // - // panelInfo - // - this.panelInfo.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.panelInfo.BackColor = System.Drawing.Color.Transparent; - this.panelInfo.Controls.Add(this.picIcon); - this.panelInfo.Controls.Add(this.lblVersion); - this.panelInfo.Location = new System.Drawing.Point(291, 196); - this.panelInfo.Name = "panelInfo"; - this.panelInfo.Size = new System.Drawing.Size(69, 43); - this.panelInfo.TabIndex = 6; - // - // lblVersion - // - this.lblVersion.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.lblVersion.AutoSize = true; - this.lblVersion.BackColor = System.Drawing.Color.Transparent; - this.lblVersion.ForeColor = System.Drawing.Color.White; - this.lblVersion.Location = new System.Drawing.Point(0, 26); - this.lblVersion.Name = "lblVersion"; - this.lblVersion.Size = new System.Drawing.Size(0, 13); - this.lblVersion.TabIndex = 6; - // // frmMain // this.AllowDrop = true; this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.BackColor = System.Drawing.Color.Black; - this.ClientSize = new System.Drawing.Size(360, 263); + this.ClientSize = new System.Drawing.Size(430, 333); this.Controls.Add(this.panelRenderer); this.Controls.Add(this.menuStrip); this.MainMenuStrip = this.menuStrip; - this.MinimumSize = new System.Drawing.Size(320, 280); + this.MinimumSize = new System.Drawing.Size(340, 300); this.Name = "frmMain"; this.Text = "Mesen"; this.DragDrop += new System.Windows.Forms.DragEventHandler(this.frmMain_DragDrop); this.DragEnter += new System.Windows.Forms.DragEventHandler(this.frmMain_DragEnter); this.Resize += new System.EventHandler(this.frmMain_Resize); this.panelRenderer.ResumeLayout(false); - this.menuStrip.ResumeLayout(false); - this.menuStrip.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.picIcon)).EndInit(); this.panelInfo.ResumeLayout(false); this.panelInfo.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.picIcon)).EndInit(); + this.menuStrip.ResumeLayout(false); + this.menuStrip.PerformLayout(); this.ResumeLayout(false); this.PerformLayout(); @@ -1636,6 +1664,9 @@ namespace Mesen.GUI.Forms private System.Windows.Forms.PictureBox picIcon; private System.Windows.Forms.Panel panelInfo; private System.Windows.Forms.Label lblVersion; + private Controls.ctrlRecentGames ctrlRecentGames; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem24; + private System.Windows.Forms.ToolStripMenuItem mnuEjectCartridge; } } diff --git a/GUI.NET/Forms/frmMain.cs b/GUI.NET/Forms/frmMain.cs index 7c4bda21..bd4b9320 100644 --- a/GUI.NET/Forms/frmMain.cs +++ b/GUI.NET/Forms/frmMain.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Drawing; +using System.Drawing.Text; using System.IO; using System.Linq; using System.Net; @@ -44,6 +45,8 @@ namespace Mesen.GUI.Forms private bool _noVideo = false; private bool _noInput = false; + private PrivateFontCollection _fonts = new PrivateFontCollection(); + public frmMain(string[] args) { InitializeComponent(); @@ -56,6 +59,9 @@ namespace Mesen.GUI.Forms Version currentVersion = new Version(InteropEmu.GetMesenVersion()); lblVersion.Text = currentVersion.ToString(); + _fonts.AddFontFile(Path.Combine(ConfigManager.HomeFolder, "Resources", "PixelFont.ttf")); + lblVersion.Font = new Font(_fonts.Families[0], 11); + _commandLineArgs = args; Application.AddMessageFilter(this); @@ -356,6 +362,16 @@ namespace Mesen.GUI.Forms } } + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + if(this.ClientSize.Height < 400) { + ctrlRecentGames.Height = this.ClientSize.Height - 125 + Math.Min(50, (400 - this.ClientSize.Height)); + } else { + ctrlRecentGames.Height = this.ClientSize.Height - 125; + } + } + private void frmMain_Resize(object sender, EventArgs e) { if(this.WindowState != FormWindowState.Minimized) { @@ -459,7 +475,11 @@ namespace Mesen.GUI.Forms break; case InteropEmu.ConsoleNotificationType.GameStopped: + this._currentGame = null; CheatInfo.ClearCheats(); + this.BeginInvoke((MethodInvoker)(() => { + ctrlRecentGames.Initialize(); + })); break; case InteropEmu.ConsoleNotificationType.ResolutionChanged: @@ -606,7 +626,7 @@ namespace Mesen.GUI.Forms MesenMsgBox.Show("FileNotFound", MessageBoxButtons.OK, MessageBoxIcon.Error, filename); } } - + private void UpdateFocusFlag() { bool hasFocus = false; @@ -631,9 +651,9 @@ namespace Mesen.GUI.Forms if(this.InvokeRequired) { this.BeginInvoke((MethodInvoker)(() => this.UpdateMenus())); } else { - if(_emuThread != null) { - panelInfo.Visible = false; - } + panelInfo.Visible = _emuThread == null; + ctrlRecentGames.Visible = _emuThread == null; + mnuEjectCartridge.Enabled = _emuThread != null; ctrlLoading.Visible = (_romLoadCounter > 0); @@ -920,6 +940,11 @@ namespace Mesen.GUI.Forms InteropEmu.PowerCycle(); } + private void mnuEjectCartridge_Click(object sender, EventArgs e) + { + InteropEmu.Stop(); + } + private void mnuShowFPS_Click(object sender, EventArgs e) { UpdateEmulationFlags(); diff --git a/GUI.NET/GUI.NET.csproj b/GUI.NET/GUI.NET.csproj index 162d1667..9319bfc8 100644 --- a/GUI.NET/GUI.NET.csproj +++ b/GUI.NET/GUI.NET.csproj @@ -247,6 +247,12 @@ ctrlNsfPlayer.cs + + UserControl + + + ctrlRecentGames.cs + UserControl @@ -672,6 +678,7 @@ + ctrlHorizontalTrackbar.cs @@ -681,6 +688,9 @@ ctrlNsfPlayer.cs + + ctrlRecentGames.cs + ctrlRiskyOption.cs @@ -882,6 +892,9 @@ Always + + Always + SettingsSingleFileGenerator Settings.Designer.cs diff --git a/GUI.NET/Icon.bmp b/GUI.NET/Icon.bmp deleted file mode 100644 index 12dfc3ebbf5711e74bc9de18c64fd0418c313bc5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5174 zcmeH~d2|)!8Hewfh-eYPVryNiw1~D^TuNIPu$TlvG{qu|B?*`iz#>uwr3G8fqQpQ( z${Ml~2!%k%0tq1-B`d^iN|Kulkc71)1R^^L%j22lo4GgK6wl!w|L}LtoHO&i@ALca zyx;pyPKLcYxSvhaV0W8spv`7`(YPdAPd#9>*@}AWmrb&1m*fLBUyyv^CSP##1@}qN zV=~% z56*&z--7|O;gLBoa1K2B0X+60JU$l&&4VZB!!sYjv!B58!SF&b3=IK~1>hA1!^2=? zIE-2ZuSI}&1dLe>Z!Cs!OJMv`n6MOlR)FtH@QZ{=kuZ4`1jfMB7?>6d)8k-9Jj_gh zS!>|^wJKACIXRiln>Uk^l0s@~DrsqHq^GB|b?a6#GBU`_%w*fP zZDeI-v3>h?va_?v$;lx%HA16a_3WTIWXeum7hp;WMEE|^Rz=~X0 znFrB3AZ{nb?}UUxNGyg8rLb`~Y}yUUWsv$Mq?SWk1!Pu3)>n}IHRRPmek~Lf6i`@L zNKsJ{#l^*xl$20fTFS0nyC^FwqrAMFii!#V7 zcIxZvX=rFbQ55#>-OIjx``EvKKaGuzG&MD`vknUEP@+Jo0%iN4yb&rIp{fb0nxVQG z_8f%zBe1^(np&Z`xtRk84sh_`K@J@{#Noq-IdbF(M~@!m*s)`@w6xIL+RE|c$7yS8 z-1E?>UFl`B`c zauu##y^1QE&Gx;!cu1hXe_&|%($tFXHgU~YMh3m^;UsVW z#RaZBT<~=(eZ8Dmp+AQgUSn|$UmP|{^^B^hT1CgA;FGG{Py@ zwI*zJaB$?)y40*Yd45`LI#|3t%(E*nYV1TmzlmdC?YizUdW$eH`QiyqSzdoxlu{{@ zs?)#lf5kccZzCr7t*<%8-&|_4LJcWFPQ?jr&>{xE*&*gRB||{Ys`}zj4x0pM20EYY zaB6;8&Xl9apOsSLJ$!l#O&@dZ|GrdaP?5;5L5 z5YO=uC6?=Et5Pd8-ab}XdMz>2N<*yQVwS>8gSzp?YOpMO8@H>> z=rRj&)-p4t(C8O$SvHCsXvJQ3rYw!Hz8~TGs;4ZE(e6C0uTy{pbX9=0i5?mw%Td~0 zkQv!&LgPctlQrbI(@YD}mZD@?i5%@QGR$rU%-Szh6lH-R4LO`a*hr7jk$V4D>$9Y5 zoRRvv`e+x`H8jrLMo}8nz83Rq$}YaW`n&oGQkR$GPO7fqiv%!5i%$}dhKkIMbZLA| zy|tPpGF|iAtjzivegC=lx%GLeR;(Kbx!nIVk@b6>x>=Tg zs^8t7V}vq{0=neIi2q%CX-T?UmY>&o=@(xa1`~AySNudHW%X?a^ha4hx%) zUO_+Y=}mE*s=PAwjKn?$-ZNL0mERZ-(x9*P@IozlxH^%Bmmzf`6h(=-Ll9Hd{>kno z9tY%W@dkZ@M<-V%IkmsP-n@UiMdbJ2qxIh$@htWGL^H6v`~(DOXTSTmW_I5VM*qKP zF1fw1TXTpIQ)J33LJNHKGc)(rSTR5Lr_Dv_&RJS3Z}TxpO+H%V|K|xSvFE>3Lw){g z91Ns;-YHWfTY}8L0i%MnZ}Ylmj9ydqqmQ-UW6W>e5X)GSdOsVjo@GPk@`Vc)EL@(c z)@|4OkBEa-b=MjV7rdg(>fz`h^?hn){Fi!)qU?_T>p-*Ung-+vc&z`Owk{EOYuqhfDI2QK)OGb>ocLi5zh70tFR#l=Qm+|Cn`&~{U#-~uhp*$% zbEw{dGsMvVHo`9Z$+sk6(XKE-+Irctugxzx(7*oIKng y_``?)H6aJN=G7P35%b=(A-{Y2slh|0&yLfl!iBo!zoUNOzhj}g(T)E1E`JML+~s2c diff --git a/GUI.NET/InteropEmu.cs b/GUI.NET/InteropEmu.cs index 5b4b561f..626c3d94 100644 --- a/GUI.NET/InteropEmu.cs +++ b/GUI.NET/InteropEmu.cs @@ -28,6 +28,7 @@ namespace Mesen.GUI [DllImport(DLLPath)] public static extern void LoadROM([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string filename, Int32 archiveFileIndex, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string patchFile); [DllImport(DLLPath)] public static extern void AddKnownGameFolder([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string folder); + [DllImport(DLLPath)] public static extern void LoadRecentGame([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string filepath); [DllImport(DLLPath, EntryPoint = "GetArchiveRomList")] private static extern IntPtr GetArchiveRomListWrapper([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string filename); public static List GetArchiveRomList(string filename) { return new List(PtrToStringUtf8(InteropEmu.GetArchiveRomListWrapper(filename)).Split(new string[] { "[!|!]" }, StringSplitOptions.RemoveEmptyEntries)); } @@ -994,6 +995,8 @@ namespace Mesen.GUI DisplayMovieIcons = 0x10000000000, HidePauseOverlay = 0x20000000000, + + ConsoleMode = 0x8000000000000000, } [Flags] diff --git a/GUI.NET/Properties/Resources.Designer.cs b/GUI.NET/Properties/Resources.Designer.cs index 9cce7fd9..ae967f5d 100644 --- a/GUI.NET/Properties/Resources.Designer.cs +++ b/GUI.NET/Properties/Resources.Designer.cs @@ -400,6 +400,16 @@ namespace Mesen.GUI.Properties { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap MesenIconSmall { + get { + object obj = ResourceManager.GetObject("MesenIconSmall", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/GUI.NET/Properties/Resources.resx b/GUI.NET/Properties/Resources.resx index 3b66eefe..1816f915 100644 --- a/GUI.NET/Properties/Resources.resx +++ b/GUI.NET/Properties/Resources.resx @@ -295,4 +295,7 @@ ..\Resources\CommandLine.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\MesenIconSmall.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + \ No newline at end of file diff --git a/GUI.NET/ResourceManager.cs b/GUI.NET/ResourceManager.cs index 09ee3013..1154c876 100644 --- a/GUI.NET/ResourceManager.cs +++ b/GUI.NET/ResourceManager.cs @@ -98,7 +98,7 @@ namespace Mesen.GUI } else if(entry.Name.StartsWith("Google.Apis") || entry.Name == "BouncyCastle.Crypto.dll" || entry.Name == "Zlib.Portable.dll" || entry.Name == "Newtonsoft.Json.dll") { string outputFilename = Path.Combine(ConfigManager.HomeFolder, "GoogleDrive", entry.Name); ExtractFile(entry, outputFilename); - } else if(entry.Name == "Font.24.spritefont" || entry.Name == "Font.64.spritefont" || entry.Name == "LICENSE.txt") { + } else if(entry.Name == "Font.24.spritefont" || entry.Name == "Font.64.spritefont" || entry.Name == "LICENSE.txt" || entry.Name == "PixelFont.ttf") { string outputFilename = Path.Combine(ConfigManager.HomeFolder, "Resources", entry.Name); ExtractFile(entry, outputFilename); } else if(entry.Name == "DroidSansMono.ttf" && Program.IsMono) { diff --git a/InteropDLL/ConsoleWrapper.cpp b/InteropDLL/ConsoleWrapper.cpp index adced91a..6e5f847c 100644 --- a/InteropDLL/ConsoleWrapper.cpp +++ b/InteropDLL/ConsoleWrapper.cpp @@ -115,6 +115,7 @@ namespace InteropEmu { DllExport void __stdcall LoadROM(char* filename, int32_t archiveFileIndex, char* patchFile) { Console::LoadROM(filename, nullptr, archiveFileIndex, patchFile); } DllExport void __stdcall AddKnownGameFolder(char* folder) { FolderUtilities::AddKnownGameFolder(folder); } + DllExport void __stdcall LoadRecentGame(char* filepath) { SaveStateManager::LoadRecentGame(filepath); } DllExport const char* __stdcall GetArchiveRomList(char* filename) { std::ostringstream out; diff --git a/PGOHelper/PGOHelper.cpp b/PGOHelper/PGOHelper.cpp index 8e538b50..2e252d55 100644 --- a/PGOHelper/PGOHelper.cpp +++ b/PGOHelper/PGOHelper.cpp @@ -35,6 +35,7 @@ enum class VideoFilterType extern "C" { + void __stdcall SetFlags(uint64_t flags); void __stdcall SetVideoFilter(VideoFilterType filter); void __stdcall InitializeEmu(char* homeFolder, void*, void*, bool, bool, bool); void __stdcall LoadROM(const char* filename, int32_t archiveFileIndex, char* patchFile); @@ -64,6 +65,7 @@ int main(int argc, char* argv[]) "..\\..\\Games\\Dragon Warrior IV (USA).nes" }; + SetFlags(0x8000000000000000); //EmulationFlags::ConsoleMode InitializeEmu("C:\\Windows\\Temp\\Mesen", nullptr, nullptr, false, false, false); LoadROM(testRoms[0], -1, ""); std::cout << "Running: " << testRoms[0] << std::endl; diff --git a/TestHelper/TestHelper.cpp b/TestHelper/TestHelper.cpp index 1df6dbdc..d253e768 100644 --- a/TestHelper/TestHelper.cpp +++ b/TestHelper/TestHelper.cpp @@ -45,6 +45,7 @@ public: }; extern "C" { + void __stdcall SetFlags(uint64_t flags); void __stdcall InitializeEmu(const char* homeFolder, void*, void*, bool, bool, bool); void __stdcall SetControllerType(uint32_t port, ControllerType type); int __stdcall RunAutomaticTest(char* filename); @@ -187,7 +188,7 @@ int main(int argc, char* argv[]) std::cout << "------------" << std::endl; std::cout << "Failed tests" << std::endl; std::cout << "------------" << std::endl; - for(int i = 0; i < failedTests.size(); i++) { + for(int i = 0; i < (int)failedTests.size(); i++) { std::cout << failedTests[i] << " (" << std::to_string(failedTestErrorCode[i]) << ")" << std::endl; } std::cout << std::endl << std::to_string(failedTests.size()) << " tests failed." << std::endl; @@ -201,6 +202,7 @@ int main(int argc, char* argv[]) char* testFilename = argv[2]; RegisterNotificationCallback((NotificationListenerCallback)OnNotificationReceived); + SetFlags(0x8000000000000000); //EmulationFlags::ConsoleMode InitializeEmu(mesenFolder.c_str(), nullptr, nullptr, false, false, false); SetControllerType(0, ControllerType::StandardController); SetControllerType(1, ControllerType::StandardController); diff --git a/Utilities/FolderUtilities.cpp b/Utilities/FolderUtilities.cpp index c192badc..6f614264 100644 --- a/Utilities/FolderUtilities.cpp +++ b/Utilities/FolderUtilities.cpp @@ -91,6 +91,13 @@ string FolderUtilities::GetScreenshotFolder() return folder; } +string FolderUtilities::GetRecentGamesFolder() +{ + string folder = CombinePath(GetHomeFolder(), "RecentGames"); + CreateFolder(folder); + return folder; +} + void FolderUtilities::CreateFolder(string folder) { fs::create_directory(fs::u8path(folder)); diff --git a/Utilities/FolderUtilities.h b/Utilities/FolderUtilities.h index ba9a0095..6d1b1f9c 100644 --- a/Utilities/FolderUtilities.h +++ b/Utilities/FolderUtilities.h @@ -21,6 +21,7 @@ public: static string GetScreenshotFolder(); static string GetHdPackFolder(); static string GetDebuggerFolder(); + static string GetRecentGamesFolder(); static vector GetFolders(string rootFolder); static vector GetFilesInFolder(string rootFolder, string mask, bool recursive); diff --git a/Utilities/PNGHelper.cpp b/Utilities/PNGHelper.cpp index a28f6435..e0d29e0e 100644 --- a/Utilities/PNGHelper.cpp +++ b/Utilities/PNGHelper.cpp @@ -1,24 +1,36 @@ #include "stdafx.h" - +#include #include "PNGHelper.h" #include "miniz.h" -bool PNGHelper::WritePNG(string filename, uint8_t* buffer, uint32_t xSize, uint32_t ySize, uint32_t bitsPerPixel) +bool PNGHelper::WritePNG(std::stringstream &stream, uint8_t* buffer, uint32_t xSize, uint32_t ySize, uint32_t bitsPerPixel) { size_t pngSize = 0; - void *pngData = tdefl_write_image_to_png_file_in_memory_ex(buffer, xSize, ySize, bitsPerPixel/8, &pngSize, MZ_DEFAULT_LEVEL, MZ_FALSE); + void *pngData = tdefl_write_image_to_png_file_in_memory_ex(buffer, xSize, ySize, bitsPerPixel / 8, &pngSize, MZ_DEFAULT_LEVEL, MZ_FALSE); if(!pngData) { std::cout << "tdefl_write_image_to_png_file_in_memory_ex() failed!" << std::endl; return false; } else { - ofstream file(filename, std::ios::out | std::ios::binary); - file.write((char*)pngData, pngSize); - file.close(); + stream.write((char*)pngData, pngSize); mz_free(pngData); return true; } } +bool PNGHelper::WritePNG(string filename, uint8_t* buffer, uint32_t xSize, uint32_t ySize, uint32_t bitsPerPixel) +{ + std::stringstream stream; + if(WritePNG(stream, buffer, xSize, ySize, bitsPerPixel)) { + ofstream file(filename, std::ios::out | std::ios::binary); + if(file.good()) { + file << stream.rdbuf(); + } + file.close(); + return true; + } + return false; +} + void PNGHelper::ReadPNG(string filename, vector &pngData, uint32_t &pngWidth, uint32_t &pngHeight) { unsigned long width; diff --git a/Utilities/PNGHelper.h b/Utilities/PNGHelper.h index e9ecbf57..6faffef8 100644 --- a/Utilities/PNGHelper.h +++ b/Utilities/PNGHelper.h @@ -7,6 +7,7 @@ private: static int DecodePNG(vector& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32 = true); public: + static bool WritePNG(std::stringstream &stream, uint8_t* buffer, uint32_t xSize, uint32_t ySize, uint32_t bitsPerPixel = 32); static bool WritePNG(string filename, uint8_t* buffer, uint32_t xSize, uint32_t ySize, uint32_t bitsPerPixel = 32); static void ReadPNG(string filename, vector &pngData, uint32_t &pngWidth, uint32_t &pngHeight); }; \ No newline at end of file diff --git a/Utilities/miniz.cpp b/Utilities/miniz.cpp index 14937045..38637da8 100644 --- a/Utilities/miniz.cpp +++ b/Utilities/miniz.cpp @@ -2082,7 +2082,11 @@ void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, static FILE *mz_fopen(const char *pFilename, const char *pMode) { FILE* pFile = NULL; - fopen_s(&pFile, pFilename, pMode); + #ifdef _MSC_VER + _wfopen_s(&pFile, utf8::utf8::decode(pFilename).c_str(), utf8::utf8::decode(pMode).c_str()); + #else + fopen_s(&pFile, pFilename, pMode); + #endif return pFile; } static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream)