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 @@ class BaseVideoFilter 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 @@ class NsfLoader { 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 @@ class NsfeLoader : public NsfLoader 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,22 +72,42 @@ void SaveStateManager::SaveState(int stateIndex, bool displayMessage) if(file) { _lastIndex = stateIndex; + SaveState(file); + file.close(); - Console::Pause(); + if(displayMessage) { + MessageManager::DisplayMessage("SaveStates", "SaveStateSaved", std::to_string(stateIndex)); + } + } +} - uint32_t emuVersion = EmulationSettings::GetMesenVersion(); - file.write("MST", 3); - file.write((char*)&emuVersion, sizeof(emuVersion)); - file.write((char*)&SaveStateManager::FileFormatVersion, sizeof(uint32_t)); +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; + } - Console::SaveState(file); + 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(); - file.close(); - if(displayMessage) { - MessageManager::DisplayMessage("SaveStates", "SaveStateSaved", std::to_string(stateIndex)); - } + return true; } + + return false; } bool SaveStateManager::LoadState(int 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 @@ class SaveStateManager 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 @@ class VideoDecoder 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 @@ public static string MovieFolder } } + 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 00000000..f5cbfc06 Binary files /dev/null and b/GUI.NET/Dependencies/PixelFont.ttf differ 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 @@ private void InitializeComponent() 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 @@ private void InitializeComponent() 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 @@ private void InitializeComponent() 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 @@ private void InitializeComponent() // 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 @@ private void InitializeComponent() 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 @@ private void InitializeComponent() 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 @@ private void InitializeComponent() this.mnuPause, this.mnuReset, this.mnuPowerCycle, + this.toolStripMenuItem24, + this.mnuEjectCartridge, this.sepFdsDisk, this.mnuSwitchDiskSide, this.mnuSelectDisk, @@ -373,6 +422,19 @@ private void InitializeComponent() 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 @@ private void InitializeComponent() 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 @@ private void InitializeComponent() 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.ComponentModel; using System.Diagnostics; using System.Drawing; +using System.Drawing.Text; using System.IO; using System.Linq; using System.Net; @@ -44,6 +45,8 @@ public partial class frmMain : BaseForm, IMessageFilter private bool _noVideo = false; private bool _noInput = false; + private PrivateFontCollection _fonts = new PrivateFontCollection(); + public frmMain(string[] args) { InitializeComponent(); @@ -56,6 +59,9 @@ public frmMain(string[] args) 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 @@ private void UpdateViewerSize() } } + 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 @@ private void _notifListener_OnNotification(InteropEmu.NotificationEventArgs e) break; case InteropEmu.ConsoleNotificationType.GameStopped: + this._currentGame = null; CheatInfo.ClearCheats(); + this.BeginInvoke((MethodInvoker)(() => { + ctrlRecentGames.Initialize(); + })); break; case InteropEmu.ConsoleNotificationType.ResolutionChanged: @@ -606,7 +626,7 @@ private void LoadROM(string filename, bool autoLoadPatches = false, int archiveF MesenMsgBox.Show("FileNotFound", MessageBoxButtons.OK, MessageBoxIcon.Error, filename); } } - + private void UpdateFocusFlag() { bool hasFocus = false; @@ -631,9 +651,9 @@ private void UpdateMenus() 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 @@ private void mnuPowerCycle_Click(object sender, EventArgs e) 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 12dfc3eb..00000000 Binary files a/GUI.NET/Icon.bmp and /dev/null differ 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 @@ public class InteropEmu [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 @@ public enum EmulationFlags : UInt64 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 @@ internal static System.Drawing.Icon MesenIcon { } } + /// + /// 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 @@ public static bool ExtractResources() } 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 @@ class InteropNotificationListener : public INotificationListener }; 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 @@ class FolderUtilities 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,22 +1,34 @@ #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 { + 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); - file.write((char*)pngData, pngSize); + if(file.good()) { + file << stream.rdbuf(); + } file.close(); - mz_free(pngData); return true; } + return false; } void PNGHelper::ReadPNG(string filename, vector &pngData, uint32_t &pngWidth, uint32_t &pngHeight) 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 @@ class PNGHelper 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)