diff --git a/Core/BarcodeBattlerReader.h b/Core/BarcodeBattlerReader.h index cf9d0e46..789ee9e7 100644 --- a/Core/BarcodeBattlerReader.h +++ b/Core/BarcodeBattlerReader.h @@ -85,7 +85,7 @@ public: uint8_t ReadRAM(uint16_t addr) override { if(addr == 0x4017) { - int32_t elapsedCycles = CPU::GetCycleCount() - _insertCycle; + int32_t elapsedCycles = CPU::GetElapsedCycles(_insertCycle); constexpr uint32_t cyclesPerBit = CPU::ClockRateNtsc / 1200; uint32_t streamPosition = elapsedCycles / cyclesPerBit; diff --git a/Core/BaseControlDevice.h b/Core/BaseControlDevice.h index ea00e844..42caa60f 100644 --- a/Core/BaseControlDevice.h +++ b/Core/BaseControlDevice.h @@ -42,7 +42,8 @@ public: static const uint8_t ExpDevicePort = 4; static const uint8_t ConsoleInputPort = 5; static const uint8_t MapperInputPort = 6; - static const uint8_t PortCount = MapperInputPort + 1; + static const uint8_t ExpDevicePort2 = 7; + static const uint8_t PortCount = ExpDevicePort2 + 1; BaseControlDevice(uint8_t port, KeyMappingSet keyMappingSet = KeyMappingSet()); virtual ~BaseControlDevice(); diff --git a/Core/CPU.h b/Core/CPU.h index 1c06218e..7db384ab 100644 --- a/Core/CPU.h +++ b/Core/CPU.h @@ -785,7 +785,18 @@ public: static const uint32_t ClockRateDendy = 1773448; CPU(MemoryManager *memoryManager); + static int32_t GetCycleCount() { return CPU::Instance->_cycleCount; } + + static int32_t GetElapsedCycles(int32_t prevCycleCount) + { + if(prevCycleCount > Instance->_cycleCount) { + return 0xFFFFFFFF - prevCycleCount + Instance->_cycleCount + 1; + } else { + return Instance->_cycleCount - prevCycleCount; + } + } + static void SetNMIFlag() { CPU::Instance->_state.NMIFlag = true; } static void ClearNMIFlag() { CPU::Instance->_state.NMIFlag = false; } static void SetIRQMask(uint8_t mask) { CPU::Instance->_irqMask = mask; } diff --git a/Core/Console.cpp b/Core/Console.cpp index 5167b89e..32e427aa 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -33,6 +33,7 @@ #include "SystemActionManager.h" #include "FdsSystemActionManager.h" #include "VsSystemActionManager.h" +#include "FamilyBasicDataRecorder.h" #include "IBarcodeReader.h" #include "IBattery.h" #include "KeyManager.h" @@ -784,6 +785,10 @@ ConsoleFeatures Console::GetAvailableFeatures() if(std::dynamic_pointer_cast(_controlManager->GetControlDevice(BaseControlDevice::ExpDevicePort))) { features = (ConsoleFeatures)((int)features | (int)ConsoleFeatures::BarcodeReader); } + + if(std::dynamic_pointer_cast(_controlManager->GetControlDevice(BaseControlDevice::ExpDevicePort2))) { + features = (ConsoleFeatures)((int)features | (int)ConsoleFeatures::TapeRecorder); + } } return features; } @@ -799,4 +804,44 @@ void Console::InputBarcode(uint64_t barcode, uint32_t digitCount) if(barcodeReader) { barcodeReader->InputBarcode(barcode, digitCount); } +} + +void Console::LoadTapeFile(string filepath) +{ + shared_ptr dataRecorder = std::dynamic_pointer_cast(_controlManager->GetControlDevice(BaseControlDevice::ExpDevicePort2)); + if(dataRecorder) { + Console::Pause(); + dataRecorder->LoadFromFile(filepath); + Console::Resume(); + } +} + +void Console::StartRecordingTapeFile(string filepath) +{ + shared_ptr dataRecorder = std::dynamic_pointer_cast(_controlManager->GetControlDevice(BaseControlDevice::ExpDevicePort2)); + if(dataRecorder) { + Console::Pause(); + dataRecorder->StartRecording(filepath); + Console::Resume(); + } +} + +void Console::StopRecordingTapeFile() +{ + shared_ptr dataRecorder = std::dynamic_pointer_cast(_controlManager->GetControlDevice(BaseControlDevice::ExpDevicePort2)); + if(dataRecorder) { + Console::Pause(); + dataRecorder->StopRecording(); + Console::Resume(); + } +} + +bool Console::IsRecordingTapeFile() +{ + shared_ptr dataRecorder = std::dynamic_pointer_cast(_controlManager->GetControlDevice(BaseControlDevice::ExpDevicePort2)); + if(dataRecorder) { + return dataRecorder->IsRecording(); + } + + return false; } \ No newline at end of file diff --git a/Core/Console.h b/Core/Console.h index 2bf9e143..9b9a488d 100644 --- a/Core/Console.h +++ b/Core/Console.h @@ -88,6 +88,11 @@ class Console ConsoleFeatures GetAvailableFeatures(); void InputBarcode(uint64_t barcode, uint32_t digitCount); + void LoadTapeFile(string filepath); + void StartRecordingTapeFile(string filepath); + void StopRecordingTapeFile(); + bool IsRecordingTapeFile(); + static std::thread::id GetEmulationThreadId(); static void RequestReset(); diff --git a/Core/ControlManager.cpp b/Core/ControlManager.cpp index dc1b54ab..b6eb7a83 100644 --- a/Core/ControlManager.cpp +++ b/Core/ControlManager.cpp @@ -97,11 +97,13 @@ vector ControlManager::GetPortStates() shared_ptr ControlManager::GetControlDevice(uint8_t port) { - auto lock = _deviceLock.AcquireSafe(); + if(_instance) { + auto lock = _deviceLock.AcquireSafe(); - auto result = std::find_if(_instance->_controlDevices.begin(), _instance->_controlDevices.end(), [port](const shared_ptr control) { return control->GetPort() == port; }); - if(result != _instance->_controlDevices.end()) { - return *result; + auto result = std::find_if(_instance->_controlDevices.begin(), _instance->_controlDevices.end(), [port](const shared_ptr control) { return control->GetPort() == port; }); + if(result != _instance->_controlDevices.end()) { + return *result; + } } return nullptr; } @@ -204,6 +206,11 @@ void ControlManager::UpdateControlDevices() if(_mapperControlDevice) { ControlManager::RegisterControlDevice(_mapperControlDevice); } + + if(std::dynamic_pointer_cast(expDevice)) { + //Automatically connect the data recorder if the keyboard is connected + ControlManager::RegisterControlDevice(shared_ptr(new FamilyBasicDataRecorder())); + } } uint8_t ControlManager::GetOpenBusMask(uint8_t port) diff --git a/Core/DatachBarcodeReader.h b/Core/DatachBarcodeReader.h index 19a0a522..92ef64d5 100644 --- a/Core/DatachBarcodeReader.h +++ b/Core/DatachBarcodeReader.h @@ -58,7 +58,7 @@ public: uint8_t GetOutput() { - int32_t elapsedCycles = CPU::GetCycleCount() - _insertCycle; + int32_t elapsedCycles = CPU::GetElapsedCycles(_insertCycle); int32_t bitNumber = elapsedCycles / 1000; if(bitNumber < (int32_t)_data.size()) { return _data[bitNumber]; diff --git a/Core/FamilyBasicDataRecorder.h b/Core/FamilyBasicDataRecorder.h index 98602c97..8973521f 100644 --- a/Core/FamilyBasicDataRecorder.h +++ b/Core/FamilyBasicDataRecorder.h @@ -1,50 +1,133 @@ #pragma once #include "stdafx.h" -#include +#include "../Utilities/Base64.h" #include "BaseControlDevice.h" #include "CPU.h" -//TODO: Integration with UI class FamilyBasicDataRecorder : public BaseControlDevice { private: static const uint32_t SamplingRate = 88; - vector _saveData; + vector _data; + vector _fileData; bool _enabled = false; - int32_t _lastCycle = -1; - int32_t _readStartCycle = -1; + bool _isPlaying = false; + int32_t _cycle = -1; + + bool _isRecording = false; + string _recordFilePath; protected: void StreamState(bool saving) override { BaseControlDevice::StreamState(saving); - Stream(_enabled); + + uint32_t dataSize = _data.size(); + Stream(_enabled, _isPlaying, _cycle, dataSize); + + if(!saving) { + _data.resize(dataSize); + } + + ArrayInfo data{ _data.data(), _data.size() }; + Stream(data); + + if(!saving && _isRecording) { + StopRecording(); + } + } + + bool IsRawString() + { + return true; } public: - FamilyBasicDataRecorder() : BaseControlDevice(BaseControlDevice::ExpDevicePort) + FamilyBasicDataRecorder() : BaseControlDevice(BaseControlDevice::ExpDevicePort2) { } + ~FamilyBasicDataRecorder() + { + if(_isRecording) { + StopRecording(); + } + } + + void InternalSetStateFromInput() + { + if(_fileData.size() > 0) { + SetTextState(Base64::Encode(_fileData)); + _fileData.clear(); + } + } + + void OnAfterSetState() + { + if(_state.State.size() > 0) { + _data = Base64::Decode(GetTextState()); + _cycle = CPU::GetCycleCount(); + _isPlaying = true; + _isRecording = false; + } + } + + void LoadFromFile(VirtualFile file) + { + if(file.IsValid()) { + vector fileData; + file.ReadFile(fileData); + _fileData = fileData; + } + } + + bool IsRecording() + { + return _isRecording; + } + + void StartRecording(string filePath) + { + _isPlaying = false; + _recordFilePath = filePath; + _data.clear(); + _cycle = CPU::GetCycleCount(); + _isRecording = true; + } + + void StopRecording() + { + _isRecording = false; + + vector fileData; + + int bitPos = 0; + uint8_t currentByte = 0; + for(uint8_t bitValue : _data) { + currentByte |= (bitValue & 0x01) << bitPos; + bitPos = (bitPos + 1) % 8; + if(bitPos == 0) { + fileData.push_back(currentByte); + currentByte = 0; + } + } + + ofstream out(_recordFilePath, ios::binary); + if(out) { + out.write((char*)fileData.data(), fileData.size()); + } + } + uint8_t ReadRAM(uint16_t addr) override { - if(addr == 0x4016) { - if(EmulationSettings::CheckFlag(EmulationFlags::ShowFrameCounter)) { - if(_readStartCycle == -1) { - _readStartCycle = CPU::GetCycleCount(); - } + if(addr == 0x4016 && _isPlaying) { + int32_t readPos = CPU::GetElapsedCycles(_cycle) / FamilyBasicDataRecorder::SamplingRate; - int readPos = (CPU::GetCycleCount() - _readStartCycle) / FamilyBasicDataRecorder::SamplingRate; - - if((int32_t)_saveData.size() > readPos) { - uint8_t value = (_saveData[readPos] & 0x01) << 1; - return _enabled ? value : 0; - } + if((int32_t)_data.size() > readPos / 8) { + uint8_t value = ((_data[readPos / 8] >> (readPos % 8)) & 0x01) << 1; + return _enabled ? value : 0; } else { - if(!EmulationSettings::CheckFlag(EmulationFlags::ShowFPS)) { - _lastCycle = -1; - _readStartCycle = -1; - } + _isPlaying = false; } } @@ -55,19 +138,10 @@ public: { _enabled = (value & 0x04) != 0; - if(EmulationSettings::CheckFlag(EmulationFlags::ShowFPS)) { - if(_lastCycle == -1) { - _saveData.clear(); - _lastCycle = CPU::GetCycleCount() - 88; - } - while(_lastCycle < CPU::GetCycleCount()) { - _saveData.push_back(value & 0x01); - _lastCycle += 88; - } - } else { - if(!EmulationSettings::CheckFlag(EmulationFlags::ShowFrameCounter)) { - _lastCycle = -1; - _readStartCycle = -1; + if(_isRecording) { + while(CPU::GetElapsedCycles(_cycle) > FamilyBasicDataRecorder::SamplingRate) { + _data.push_back(value & 0x01); + _cycle += 88; } } } diff --git a/Core/FceuxMovie.cpp b/Core/FceuxMovie.cpp index 3e6879ca..94d84c97 100644 --- a/Core/FceuxMovie.cpp +++ b/Core/FceuxMovie.cpp @@ -2,30 +2,11 @@ #include #include "../Utilities/StringUtilities.h" #include "../Utilities/HexUtilities.h" +#include "../Utilities/Base64.h" #include "ControlManager.h" #include "FceuxMovie.h" #include "Console.h" -vector FceuxMovie::Base64Decode(string in) -{ - vector out; - - vector T(256, -1); - for(int i = 0; i<64; i++) T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i; - - int val = 0, valb = -8; - for(uint8_t c : in) { - if(T[c] == -1) break; - val = (val << 6) + T[c]; - valb += 6; - if(valb >= 0) { - out.push_back(val >> valb); - valb -= 8; - } - } - return out; -} - bool FceuxMovie::InitializeData(stringstream &filestream) { bool result = false; @@ -39,7 +20,7 @@ bool FceuxMovie::InitializeData(stringstream &filestream) string line; std::getline(filestream, line); if(line.compare(0, 19, "romChecksum base64:", 19) == 0) { - vector md5array = Base64Decode(line.substr(19, line.size() - 20)); + vector md5array = Base64::Decode(line.substr(19, line.size() - 20)); HashInfo hashInfo; hashInfo.PrgChrMd5Hash = HexUtilities::ToHex(md5array); if(Console::LoadROM("", hashInfo)) { diff --git a/Core/FceuxMovie.h b/Core/FceuxMovie.h index 37cb54c8..3a77040d 100644 --- a/Core/FceuxMovie.h +++ b/Core/FceuxMovie.h @@ -7,7 +7,6 @@ class FceuxMovie : public BizhawkMovie { private: - vector Base64Decode(string in); bool InitializeData(stringstream &filestream); public: diff --git a/Core/Types.h b/Core/Types.h index b87d09d9..a6f924fe 100644 --- a/Core/Types.h +++ b/Core/Types.h @@ -250,4 +250,5 @@ enum class ConsoleFeatures Nsf = 2, VsSystem = 4, BarcodeReader = 8, + TapeRecorder = 16, }; diff --git a/GUI.NET/Dependencies/resources.ca.xml b/GUI.NET/Dependencies/resources.ca.xml index 50ff6574..5111a70c 100644 --- a/GUI.NET/Dependencies/resources.ca.xml +++ b/GUI.NET/Dependencies/resources.ca.xml @@ -20,6 +20,10 @@ Insereix moneda (1) Insereix moneda (2) Input Barcode + Tape Recorder + Load from file... + Record to file... + Stop recording Opcions Velocitat Normal (100%) @@ -615,6 +619,7 @@ Fitxers de proves (*.mtp)|*.mtp|Tots els fitxers (*.*)|*.* Tots els formats suportats (*.cht, *.xml)|*.cht;*.xml Partides guardades de Mesen (*.mst)|*.mst|Tots els fitxers (*.*)|*.* + Family Basic Tape files (*.fbt)|*.fbt|Tots els fitxers (*.*)|*.* Carrega des d'un fitxer... Desa al fitxer... diff --git a/GUI.NET/Dependencies/resources.en.xml b/GUI.NET/Dependencies/resources.en.xml index 477b394b..84b50c5b 100644 --- a/GUI.NET/Dependencies/resources.en.xml +++ b/GUI.NET/Dependencies/resources.en.xml @@ -11,6 +11,7 @@ Test files (*.mtp)|*.mtp|All (*.*)|*.* All supported formats (*.cht, *.xml)|*.cht;*.xml Mesen Savestates (*.mst)|*.mst|All files (*.*)|*.* + Family Basic Tape files (*.fbt)|*.fbt|All Files (*.*)|*.* Load from file... Save to file... diff --git a/GUI.NET/Dependencies/resources.es.xml b/GUI.NET/Dependencies/resources.es.xml index ae83bb96..82cdbb0f 100644 --- a/GUI.NET/Dependencies/resources.es.xml +++ b/GUI.NET/Dependencies/resources.es.xml @@ -20,6 +20,10 @@ Insertar moneda (1) Insertar moneda (2) Input Barcode + Tape Recorder + Load from file... + Record to file... + Stop recording Opciones Velocidad Normal (100%) @@ -634,6 +638,7 @@ Archivos de test (*.mtp)|*.mtp|Todos los archivos (*.*)|*.* Todos los formatos soportados (*.cht, *.xml)|*.cht;*.xml Partidas de juegos de Mesen (*.mst)|*.mst|Todos los archivos (*.*)|*.* + Family Basic Tape files (*.fbt)|*.fbt|Todos los archivos (*.*)|*.* Cargar desde archivo... Guardar en archivo... diff --git a/GUI.NET/Dependencies/resources.fr.xml b/GUI.NET/Dependencies/resources.fr.xml index 4d0919e4..3b9dd458 100644 --- a/GUI.NET/Dependencies/resources.fr.xml +++ b/GUI.NET/Dependencies/resources.fr.xml @@ -20,6 +20,10 @@ Insérer une pièce (1) Insérer une pièce (2) Entrer un code-barres + Enregistreur de données + Charger un fichier... + Enregistrer dans un fichier... + Arrêter l'enregistrement Options Vitesse Normale (100%) @@ -647,6 +651,7 @@ Fichiers de test (*.mtp)|*.mtp|Tous les fichiers (*.*)|*.* Tous les formats supportés (*.cht, *.xml)|*.cht;*.xml Sauvegardes d'états Mesen (*.mst)|*.mst|Tous les fichiers (*.*)|*.* + Fichiers de cassettes Family Basic (*.fbt)|*.fbt|Tous les fichiers (*.*)|*.* Charger à partir d'un fichier... Sauvegarder dans un fichier... diff --git a/GUI.NET/Dependencies/resources.ja.xml b/GUI.NET/Dependencies/resources.ja.xml index 9fa6fbfb..80c32b12 100644 --- a/GUI.NET/Dependencies/resources.ja.xml +++ b/GUI.NET/Dependencies/resources.ja.xml @@ -19,7 +19,11 @@ VSゲームの設定 インサートコイン 1 インサートコイン 2 - バーコードを入力 + バーコードを入力 + テープレコーダー + ファイルから再生する + ファイルに録音する + 録音を停止する 設定 速度 通常 (100%) @@ -631,6 +635,7 @@ テストファイル (*.mtp)|*.mtp|すべてのファイル (*.*)|*.* 対応するすべてのファイル (*.cht, *.xml)|*.cht;*.xml Mesenのクイックセーブデータ (*.mst)|*.mst|すべてのファイル (*.*)|*.* + Family Basicテープファイル (*.fbt)|*.fbt|すべてのファイル (*.*)|*.* ファイルからロードする… ファイルに保存する… diff --git a/GUI.NET/Dependencies/resources.pt.xml b/GUI.NET/Dependencies/resources.pt.xml index 3ae56871..37ae53f9 100644 --- a/GUI.NET/Dependencies/resources.pt.xml +++ b/GUI.NET/Dependencies/resources.pt.xml @@ -20,6 +20,10 @@ Inserir moeda (1) Inserir moeda (2) Input Barcode + Tape Recorder + Load from file... + Record to file... + Stop recording Opções Velocidade Normal (100%) @@ -632,6 +636,7 @@ Arquivos de teste (*.mtp)|*.mtp|Todos os arquivos (*.*)|*.* Todos os formatos suportados (*.cht, *.xml)|*.cht;*.xml Estados salvos do Mesen (*.mst)|*.mst|Todos os arquivos (*.*)|*.* + Family Basic Tape files (*.fbt)|*.fbt|Todos os arquivos (*.*)|*.* Carregar de um arquivo... Salvar para arquivo... diff --git a/GUI.NET/Dependencies/resources.ru.xml b/GUI.NET/Dependencies/resources.ru.xml index d365948e..97ac0312 100644 --- a/GUI.NET/Dependencies/resources.ru.xml +++ b/GUI.NET/Dependencies/resources.ru.xml @@ -20,6 +20,10 @@ Вставить монету в слот 1 Вставить монету в слот 2 Input Barcode + Tape Recorder + Load from file... + Record to file... + Stop recording Опции Скорость эмуляции Нормальная (100%) @@ -636,6 +640,7 @@ Test files (*.mtp)|*.mtp|All (*.*)|*.* Все поддерживаемые форматы (*.cht, *.xml)|*.cht;*.xml Mesen Savestates (*.mst)|*.mst|All Files (*.*)|*.* + Family Basic Tape files (*.fbt)|*.fbt|All Files (*.*)|*.* Load from file... Save to file... diff --git a/GUI.NET/Dependencies/resources.uk.xml b/GUI.NET/Dependencies/resources.uk.xml index e1d98cc0..585b9474 100644 --- a/GUI.NET/Dependencies/resources.uk.xml +++ b/GUI.NET/Dependencies/resources.uk.xml @@ -20,6 +20,10 @@ Вставити монету в слот 1 Вставити монету в слот 2 Input Barcode + Tape Recorder + Load from file... + Record to file... + Stop recording Опції Швидкість емуляції Нормальна (100%) @@ -636,6 +640,7 @@ Test files (*.mtp)|*.mtp|All (*.*)|*.* Всі підтримувані формати (*.cht, *.xml)|*.cht;*.xml Mesen Savestates (*.mst)|*.mst|All Files (*.*)|*.* + Family Basic Tape files (*.fbt)|*.fbt|All Files (*.*)|*.* Завантажитти з файлу... Зберегти в файл... diff --git a/GUI.NET/Forms/frmMain.Designer.cs b/GUI.NET/Forms/frmMain.Designer.cs index f4ae98a1..09c7540e 100644 --- a/GUI.NET/Forms/frmMain.Designer.cs +++ b/GUI.NET/Forms/frmMain.Designer.cs @@ -66,6 +66,10 @@ namespace Mesen.GUI.Forms this.mnuInsertCoin2 = new System.Windows.Forms.ToolStripMenuItem(); this.sepBarcode = new System.Windows.Forms.ToolStripSeparator(); this.mnuInputBarcode = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuTapeRecorder = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuLoadTapeFile = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuStartRecordTapeFile = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuStopRecordTapeFile = new System.Windows.Forms.ToolStripMenuItem(); this.mnuOptions = new System.Windows.Forms.ToolStripMenuItem(); this.mnuEmulationSpeed = new System.Windows.Forms.ToolStripMenuItem(); this.mnuEmuSpeedNormal = new System.Windows.Forms.ToolStripMenuItem(); @@ -403,10 +407,12 @@ namespace Mesen.GUI.Forms this.mnuInsertCoin1, this.mnuInsertCoin2, this.sepBarcode, - this.mnuInputBarcode}); + this.mnuInputBarcode, + this.mnuTapeRecorder}); this.mnuGame.Name = "mnuGame"; this.mnuGame.Size = new System.Drawing.Size(50, 20); this.mnuGame.Text = "Game"; + this.mnuGame.DropDownOpening += new System.EventHandler(this.mnuGame_DropDownOpening); // // mnuPause // @@ -507,12 +513,47 @@ namespace Mesen.GUI.Forms // // mnuInputBarcode // - this.mnuInputBarcode.Image = global::Mesen.GUI.Properties.Resources.CheatCode; + this.mnuInputBarcode.Image = global::Mesen.GUI.Properties.Resources.Barcode; this.mnuInputBarcode.Name = "mnuInputBarcode"; this.mnuInputBarcode.Size = new System.Drawing.Size(182, 22); this.mnuInputBarcode.Text = "Input Barcode..."; this.mnuInputBarcode.Visible = false; // + // mnuTapeRecorder + // + this.mnuTapeRecorder.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.mnuLoadTapeFile, + this.mnuStartRecordTapeFile, + this.mnuStopRecordTapeFile}); + this.mnuTapeRecorder.Image = global::Mesen.GUI.Properties.Resources.Tape; + this.mnuTapeRecorder.Name = "mnuTapeRecorder"; + this.mnuTapeRecorder.Size = new System.Drawing.Size(182, 22); + this.mnuTapeRecorder.Text = "Tape Recorder"; + // + // mnuLoadTapeFile + // + this.mnuLoadTapeFile.Image = global::Mesen.GUI.Properties.Resources.Import; + this.mnuLoadTapeFile.Name = "mnuLoadTapeFile"; + this.mnuLoadTapeFile.Size = new System.Drawing.Size(179, 22); + this.mnuLoadTapeFile.Text = "Load from file..."; + this.mnuLoadTapeFile.Click += new System.EventHandler(this.mnuLoadTapeFile_Click); + // + // mnuStartRecordTapeFile + // + this.mnuStartRecordTapeFile.Image = global::Mesen.GUI.Properties.Resources.Export; + this.mnuStartRecordTapeFile.Name = "mnuStartRecordTapeFile"; + this.mnuStartRecordTapeFile.Size = new System.Drawing.Size(179, 22); + this.mnuStartRecordTapeFile.Text = "Record to file..."; + this.mnuStartRecordTapeFile.Click += new System.EventHandler(this.mnuStartRecordTapeFile_Click); + // + // mnuStopRecordTapeFile + // + this.mnuStopRecordTapeFile.Image = global::Mesen.GUI.Properties.Resources.Stop; + this.mnuStopRecordTapeFile.Name = "mnuStopRecordTapeFile"; + this.mnuStopRecordTapeFile.Size = new System.Drawing.Size(179, 22); + this.mnuStopRecordTapeFile.Text = "Stop recording"; + this.mnuStopRecordTapeFile.Click += new System.EventHandler(this.mnuStopRecordTapeFile_Click); + // // mnuOptions // this.mnuOptions.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -1759,6 +1800,10 @@ namespace Mesen.GUI.Forms private System.Windows.Forms.ToolStripSeparator sepBarcode; private System.Windows.Forms.ToolStripMenuItem mnuInputBarcode; private System.Windows.Forms.ToolStripMenuItem mnuNetPlayPlayer5; + private System.Windows.Forms.ToolStripMenuItem mnuTapeRecorder; + private System.Windows.Forms.ToolStripMenuItem mnuLoadTapeFile; + private System.Windows.Forms.ToolStripMenuItem mnuStartRecordTapeFile; + private System.Windows.Forms.ToolStripMenuItem mnuStopRecordTapeFile; } } diff --git a/GUI.NET/Forms/frmMain.Game.cs b/GUI.NET/Forms/frmMain.Game.cs index 78badace..18da396e 100644 --- a/GUI.NET/Forms/frmMain.Game.cs +++ b/GUI.NET/Forms/frmMain.Game.cs @@ -11,57 +11,52 @@ namespace Mesen.GUI.Forms { public partial class frmMain { + private void mnuGame_DropDownOpening(object sender, EventArgs e) + { + InitializeVsSystemMenu(); + InitializeFdsDiskMenu(); + + bool hasBarcodeReader = InteropEmu.GetAvailableFeatures().HasFlag(ConsoleFeatures.BarcodeReader); + mnuInputBarcode.Visible = hasBarcodeReader; + + bool hasTapeRecorder = InteropEmu.GetAvailableFeatures().HasFlag(ConsoleFeatures.TapeRecorder); + mnuTapeRecorder.Visible = hasTapeRecorder; + + sepBarcode.Visible = hasBarcodeReader || hasTapeRecorder; + } + private void InitializeFdsDiskMenu() { - if(this.InvokeRequired) { - this.BeginInvoke((MethodInvoker)(() => this.InitializeFdsDiskMenu())); - } else { - UInt32 sideCount = InteropEmu.FdsGetSideCount(); + UInt32 sideCount = InteropEmu.FdsGetSideCount(); - mnuSelectDisk.DropDownItems.Clear(); + mnuSelectDisk.DropDownItems.Clear(); - if(sideCount > 0) { - for(UInt32 i = 0; i < sideCount; i++) { - UInt32 diskNumber = i; - ToolStripItem item = mnuSelectDisk.DropDownItems.Add(ResourceHelper.GetMessage("FdsDiskSide", (diskNumber/2+1).ToString(), (diskNumber % 2 == 0 ? "A" : "B"))); - item.Click += (object sender, EventArgs args) => { - InteropEmu.FdsInsertDisk(diskNumber); - }; - } - sepFdsDisk.Visible = true; - mnuSelectDisk.Visible = true; - mnuEjectDisk.Visible = true; - mnuSwitchDiskSide.Visible = sideCount > 1; - } else { - sepFdsDisk.Visible = false; - mnuSelectDisk.Visible = false; - mnuEjectDisk.Visible = false; - mnuSwitchDiskSide.Visible = false; + if(sideCount > 0) { + for(UInt32 i = 0; i < sideCount; i++) { + UInt32 diskNumber = i; + ToolStripItem item = mnuSelectDisk.DropDownItems.Add(ResourceHelper.GetMessage("FdsDiskSide", (diskNumber/2+1).ToString(), (diskNumber % 2 == 0 ? "A" : "B"))); + item.Click += (object sender, EventArgs args) => { + InteropEmu.FdsInsertDisk(diskNumber); + }; } + sepFdsDisk.Visible = true; + mnuSelectDisk.Visible = true; + mnuEjectDisk.Visible = true; + mnuSwitchDiskSide.Visible = sideCount > 1; + } else { + sepFdsDisk.Visible = false; + mnuSelectDisk.Visible = false; + mnuEjectDisk.Visible = false; + mnuSwitchDiskSide.Visible = false; } } private void InitializeVsSystemMenu() { - if(this.InvokeRequired) { - this.BeginInvoke((MethodInvoker)(() => InitializeVsSystemMenu())); - } else { - sepVsSystem.Visible = InteropEmu.IsVsSystem(); - mnuInsertCoin1.Visible = InteropEmu.IsVsSystem(); - mnuInsertCoin2.Visible = InteropEmu.IsVsSystem(); - mnuVsGameConfig.Visible = InteropEmu.IsVsSystem(); - } - } - - private void InitializeBarcodeReaderMenu() - { - if(this.InvokeRequired) { - this.BeginInvoke((MethodInvoker)(() => InitializeBarcodeReaderMenu())); - } else { - bool hasBarcodeReader = InteropEmu.GetAvailableFeatures().HasFlag(ConsoleFeatures.BarcodeReader); - sepBarcode.Visible = hasBarcodeReader; - mnuInputBarcode.Visible = hasBarcodeReader; - } + sepVsSystem.Visible = InteropEmu.IsVsSystem(); + mnuInsertCoin1.Visible = InteropEmu.IsVsSystem(); + mnuInsertCoin2.Visible = InteropEmu.IsVsSystem(); + mnuVsGameConfig.Visible = InteropEmu.IsVsSystem(); } private void ShowVsGameConfig() @@ -78,5 +73,34 @@ namespace Mesen.GUI.Forms { ShowVsGameConfig(); } + + private void mnuLoadTapeFile_Click(object sender, EventArgs e) + { + using(OpenFileDialog ofd = new OpenFileDialog()) { + ofd.SetFilter(ResourceHelper.GetMessage("FilterTapeFiles")); + ofd.InitialDirectory = ConfigManager.SaveFolder; + ofd.FileName = InteropEmu.GetRomInfo().GetRomName() + ".fbt"; + if(ofd.ShowDialog() == DialogResult.OK) { + InteropEmu.LoadTapeFile(ofd.FileName); + } + } + } + + private void mnuStartRecordTapeFile_Click(object sender, EventArgs e) + { + using(SaveFileDialog sfd = new SaveFileDialog()) { + sfd.SetFilter(ResourceHelper.GetMessage("FilterTapeFiles")); + sfd.InitialDirectory = ConfigManager.SaveFolder; + sfd.FileName = InteropEmu.GetRomInfo().GetRomName() + ".fbt"; + if(sfd.ShowDialog() == DialogResult.OK) { + InteropEmu.StartRecordingTapeFile(sfd.FileName); + } + } + } + + private void mnuStopRecordTapeFile_Click(object sender, EventArgs e) + { + InteropEmu.StopRecordingTapeFile(); + } } } diff --git a/GUI.NET/Forms/frmMain.Options.cs b/GUI.NET/Forms/frmMain.Options.cs index c0bf51f4..b4d52271 100644 --- a/GUI.NET/Forms/frmMain.Options.cs +++ b/GUI.NET/Forms/frmMain.Options.cs @@ -122,7 +122,6 @@ namespace Mesen.GUI.Forms ResourceHelper.ApplyResources(this); UpdateMenus(); UpdateRecentFiles(); - InitializeFdsDiskMenu(); InitializeNsfMode(true); ctrlRecentGames.UpdateGameInfo(); } else { diff --git a/GUI.NET/Forms/frmMain.cs b/GUI.NET/Forms/frmMain.cs index 3d398599..fce3175e 100644 --- a/GUI.NET/Forms/frmMain.cs +++ b/GUI.NET/Forms/frmMain.cs @@ -156,10 +156,7 @@ namespace Mesen.GUI.Forms _overrideWindowSize = true; } - InitializeVsSystemMenu(); - InitializeFdsDiskMenu(); InitializeEmulationSpeedMenu(); - InitializeBarcodeReaderMenu(); UpdateVideoSettings(); @@ -426,9 +423,6 @@ namespace Mesen.GUI.Forms _currentGame = InteropEmu.GetRomInfo().GetRomName(); InteropEmu.SetNesModel(ConfigManager.Config.Region); InitializeNsfMode(false, true); - InitializeFdsDiskMenu(); - InitializeVsSystemMenu(); - InitializeBarcodeReaderMenu(); CheatInfo.ApplyCheats(); VsConfigInfo.ApplyConfig(); UpdateStateMenu(mnuSaveState, true); @@ -874,6 +868,12 @@ namespace Mesen.GUI.Forms mnuTestRecordTest.Enabled = !netPlay && !moviePlaying && !movieRecording; mnuTestRecordFrom.Enabled = (mnuTestRecordStart.Enabled || mnuTestRecordNow.Enabled || mnuTestRecordMovie.Enabled || mnuTestRecordTest.Enabled); + bool tapeRecording = InteropEmu.IsRecordingTapeFile(); + mnuTapeRecorder.Enabled = !isNetPlayClient; + mnuLoadTapeFile.Enabled = !isNetPlayClient; + mnuStartRecordTapeFile.Enabled = !tapeRecording && !isNetPlayClient; + mnuStopRecordTapeFile.Enabled = tapeRecording; + mnuDebugger.Visible = !devMode; mnuHdPackEditor.Enabled = !netPlay && running; diff --git a/GUI.NET/GUI.NET.csproj b/GUI.NET/GUI.NET.csproj index 29ce70b1..916af245 100644 --- a/GUI.NET/GUI.NET.csproj +++ b/GUI.NET/GUI.NET.csproj @@ -861,6 +861,7 @@ + @@ -1275,6 +1276,7 @@ + diff --git a/GUI.NET/InteropEmu.cs b/GUI.NET/InteropEmu.cs index 7e6cf13b..26775975 100644 --- a/GUI.NET/InteropEmu.cs +++ b/GUI.NET/InteropEmu.cs @@ -140,6 +140,11 @@ namespace Mesen.GUI [DllImport(DLLPath)] public static extern void InputBarcode(UInt64 barcode, Int32 digitCount); + [DllImport(DLLPath)] public static extern void LoadTapeFile([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string filepath); + [DllImport(DLLPath)] public static extern void StartRecordingTapeFile([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string filepath); + [DllImport(DLLPath)] public static extern void StopRecordingTapeFile(); + [DllImport(DLLPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool IsRecordingTapeFile(); + [DllImport(DLLPath)] public static extern void SetCheats([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]InteropCheatInfo[] cheats, UInt32 length); [DllImport(DLLPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool CheckFlag(EmulationFlags flag); @@ -1836,6 +1841,7 @@ namespace Mesen.GUI Nsf = 2, VsSystem = 4, BarcodeReader = 8, + TapeRecorder = 16, } public enum ScreenRotation diff --git a/GUI.NET/Properties/Resources.Designer.cs b/GUI.NET/Properties/Resources.Designer.cs index c3c44a0d..f0762e8a 100644 --- a/GUI.NET/Properties/Resources.Designer.cs +++ b/GUI.NET/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace Mesen.GUI.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { @@ -100,6 +100,16 @@ namespace Mesen.GUI.Properties { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Barcode { + get { + object obj = ResourceManager.GetObject("Barcode", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -790,6 +800,16 @@ namespace Mesen.GUI.Properties { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Tape { + get { + object obj = ResourceManager.GetObject("Tape", 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 c0523a14..7341357a 100644 --- a/GUI.NET/Properties/Resources.resx +++ b/GUI.NET/Properties/Resources.resx @@ -358,4 +358,10 @@ ..\Resources\DownArrowWin10.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\Barcode.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\Tape.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/Resources/Barcode.png b/GUI.NET/Resources/Barcode.png new file mode 100644 index 00000000..064f2ec7 Binary files /dev/null and b/GUI.NET/Resources/Barcode.png differ diff --git a/GUI.NET/Resources/Tape.png b/GUI.NET/Resources/Tape.png new file mode 100644 index 00000000..89d20f44 Binary files /dev/null and b/GUI.NET/Resources/Tape.png differ diff --git a/InteropDLL/ConsoleWrapper.cpp b/InteropDLL/ConsoleWrapper.cpp index 2b76821c..49f96fe5 100644 --- a/InteropDLL/ConsoleWrapper.cpp +++ b/InteropDLL/ConsoleWrapper.cpp @@ -460,6 +460,11 @@ namespace InteropEmu { DllExport void __stdcall InputBarcode(uint64_t barCode, int32_t digitCount) { Console::GetInstance()->InputBarcode(barCode, digitCount); } + DllExport void __stdcall LoadTapeFile(char *filepath) { Console::GetInstance()->LoadTapeFile(filepath); } + DllExport void __stdcall StartRecordingTapeFile(char *filepath) { Console::GetInstance()->StartRecordingTapeFile(filepath); } + DllExport void __stdcall StopRecordingTapeFile() { Console::GetInstance()->StopRecordingTapeFile(); } + DllExport bool __stdcall IsRecordingTapeFile() { return Console::GetInstance()->IsRecordingTapeFile(); } + DllExport ConsoleFeatures __stdcall GetAvailableFeatures() { return Console::GetInstance()->GetAvailableFeatures(); } //NSF functions diff --git a/Utilities/Base64.h b/Utilities/Base64.h new file mode 100644 index 00000000..eb6c6ec5 --- /dev/null +++ b/Utilities/Base64.h @@ -0,0 +1,44 @@ +#pragma once +#include "stdafx.h" + +class Base64 +{ +public: + static string Encode(const vector data) + { + std::string out; + + int val = 0, valb = -6; + for(uint8_t c : data) { + val = (val << 8) + c; + valb += 8; + while(valb >= 0) { + out.push_back("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(val >> valb) & 0x3F]); + valb -= 6; + } + } + if(valb>-6) out.push_back("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[((val << 8) >> (valb + 8)) & 0x3F]); + while(out.size() % 4) out.push_back('='); + return out; + } + + static vector Decode(string in) + { + vector out; + + vector T(256, -1); + for(int i = 0; i < 64; i++) T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i; + + int val = 0, valb = -8; + for(uint8_t c : in) { + if(T[c] == -1) break; + val = (val << 6) + T[c]; + valb += 6; + if(valb >= 0) { + out.push_back(val >> valb); + valb -= 8; + } + } + return out; + } +}; diff --git a/Utilities/Utilities.vcxproj b/Utilities/Utilities.vcxproj index a897ea64..304a9bcc 100644 --- a/Utilities/Utilities.vcxproj +++ b/Utilities/Utilities.vcxproj @@ -323,6 +323,7 @@ + diff --git a/Utilities/Utilities.vcxproj.filters b/Utilities/Utilities.vcxproj.filters index cf34db86..29e0c7c7 100644 --- a/Utilities/Utilities.vcxproj.filters +++ b/Utilities/Utilities.vcxproj.filters @@ -161,6 +161,9 @@ Header Files + + Header Files +